@masterxinxin/lark-coding-agent-bridge 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lark Channel Bridge contributors
4
+ Copyright (c) 2026 @masterxinxin/lark-coding-agent-bridge contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,481 @@
1
- # Temporary Holding Version
1
+ # @masterxinxin/lark-coding-agent-bridge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Private fork of [zarazhangrui/lark-coding-agent-bridge](https://github.com/zarazhangrui/lark-coding-agent-bridge) (upstream npm package: `lark-channel-bridge`).** The fork's reason for existing is to add an **opencode** adapter alongside the upstream's Claude Code and Codex adapters. Most documentation below is upstream's; fork-specific notes are flagged inline.
4
+
5
+ A lightweight bot that bridges Feishu / Lark messenger with your local Claude Code, Codex, or opencode CLI. Run one command, scan a QR code to bind a PersonalAgent app, and talk to your local coding agent from chat.
6
+
7
+ [中文 README](./README.zh.md)
8
+
9
+ For a product walkthrough, see the [Feishu document](https://larkcommunity.feishu.cn/docx/OaRIdFIRFoLM3xxTmKwcetHqn5e).
10
+
11
+ ## What it does
12
+
13
+ - Forwards Feishu / Lark messages to local Claude Code, Codex, or opencode CLI. Send a DM directly, or `@bot` in a group.
14
+ - **Streaming card**: text replies and tool calls update on one Lark card in real time.
15
+ - **COT process messages**: optionally send a process message with agent progress text and tool calls, then send the final answer separately.
16
+ - **Session continuity**: each chat, topic, or document comment thread keeps its own session.
17
+ - **Queueing and batching**: messages sent in quick succession are handled together; messages sent during a run are queued for the next turn, while commands like `/new`, `/cd`, `/ws use`, and `/stop` can interrupt the current task.
18
+ - **Multiple workspaces**: use `/cd` to switch the current project, and `/ws` to save and reuse common project directories.
19
+ - **Images and files**: send them to the bot directly, and the bridge downloads them locally for the agent.
20
+ - **Interactive cards**: `/help`, `/ws list`, and `/status` return cards with clickable buttons.
21
+
22
+ ## What this fork adds over upstream
23
+
24
+ Upstream already covers Claude Code and Codex thoroughly. This fork primarily adds the **opencode lane** and a few cross-agent UX improvements built around it:
25
+
26
+ | Addition | Why it matters | Applies to |
27
+ | --- | --- | --- |
28
+ | **opencode adapter** | Upstream supports Claude / Codex only; this fork adds opencode as the third agent, reusing all upstream features (profiles, workspaces, permission modes, cards). | opencode profile |
29
+ | **Persistent opencode sessions** | Multiple user turns in one chat reuse the same opencode session so the agent remembers context. Without this every message would mint a new session and the agent would forget prior turns. | opencode |
30
+ | **Background-task wake-up card** | When the `oh-my-openagent` plugin's background task finishes, it can **proactively** post a fresh streaming card in Lark to continue the conversation — you don't have to ask again. | opencode + oh-my-openagent |
31
+ | **Interactive permission card** | When opencode emits `permission.asked`, the bridge surfaces it as a card with `Allow once` / `Always allow` / `Reject` buttons, with a 5-minute timeout that auto-rejects. | opencode |
32
+ | **`/new chat <name>` spawns a group** | One command creates a new Lark group with the bot as owner and registers the requester as the workbench owner. **Messages in that group don't need `@bot`** — same idea as [`lark-opencode-bridge`'s](https://github.com/fullstackjam/lark-opencode-bridge) `/spawn`. | all agents |
33
+ | **Better tool-call rendering** | Tool cards read opencode's `state.title`: `✅ read — src/foo.ts` instead of bare `✅ read`. Case-insensitive switch + camelCase fallbacks (`filePath`) + opencode-native names (`todowrite`, `ast_grep_search`). | all agents, opencode benefits most |
34
+
35
+ ### Data flow
36
+
37
+ ```mermaid
38
+ flowchart LR
39
+ User([Feishu / Lark user])
40
+ Lark[Feishu WS<br/>persistent connection]
41
+ Bridge[lark-channel-bridge<br/>local daemon]
42
+ Agent[Claude / Codex / opencode<br/>local CLI]
43
+
44
+ User -- message / image --> Lark
45
+ Lark -- pushed event --> Bridge
46
+ Bridge -- prompt / stdin --> Agent
47
+ Agent -- streaming events --> Bridge
48
+ Bridge -- streaming card --> Lark
49
+ Lark -- render card --> User
50
+ ```
51
+
52
+ Code and model execution **stay on your machine**; Lark only carries messages and renders cards.
53
+
54
+ ### Persistent opencode sessions + background-task wake-up
55
+
56
+ ```mermaid
57
+ sequenceDiagram
58
+ autonumber
59
+ participant U as User
60
+ participant B as Bridge
61
+ participant O as opencode serve
62
+ participant P as oh-my-openagent
63
+
64
+ U->>B: "look up X, take your time"
65
+ B->>O: createSession + prompt_async
66
+ O-->>B: SSE: text / tool / status:idle
67
+ B->>U: card (turn 1 done)
68
+
69
+ Note over O,P: background task running<br/>(LSP indexing / long query)
70
+
71
+ P->>O: notifyParentSession<br/>(prompt_async injects synthetic user msg)
72
+ O-->>B: SSE: new message.updated
73
+ B->>U: new card<br/>🔔 background task complete — agent continues
74
+ ```
75
+
76
+ Notes:
77
+
78
+ - Each chat runs one long-lived SSE consumer; the opencode session is reused across user turns until `/new` or `/cd` resets it.
79
+ - Wake-up detection works by tracking SSE `messageID`s — trailing re-broadcasts from the just-ended turn are deduped, so the wake-up card only fires for an actually new turn.
80
+ - The wake-up card goes through the same `processAgentStream + channel.stream` pipeline as user-initiated turns, so streaming text, tool cards, permission prompts, and the signed stop button all work identically.
81
+
82
+ ### `/new chat`: spawn a working group
83
+
84
+ Inspired by [`lark-opencode-bridge`](https://github.com/fullstackjam/lark-opencode-bridge)'s `/spawn`. One group = one session = one project context.
85
+
86
+ ```mermaid
87
+ flowchart TD
88
+ A["/new chat workbench"] --> B[bridge calls<br/>lark im.chat.create]
89
+ B --> C[new group: bot is owner<br/>requester invited]
90
+ C --> D[workbenchGroups<br/>chatId → requester openId]
91
+ D --> E[requester messages in the group<br/>**no @ needed**]
92
+ E --> F{shouldTriggerWorkbenchMessage}
93
+ F -- senderId == owner --> G[run triggers]
94
+ F -- @ another user --> H[skip]
95
+ F -- reply to non-bot msg --> H
96
+ ```
97
+
98
+ How it works:
99
+
100
+ 1. `/new chat <name>` creates the Lark group with the bridge as owner and invites the requester.
101
+ 2. `cwd` inherits from the originating chat's workspace (anything you set via `/cd` carries over).
102
+ 3. `chatId → requester openId` is persisted to `workbenchGroups` in `config.json`.
103
+ 4. Any non-`@`, non-cross-replied message from the requester in that group triggers a run; messages that `@` someone else or reply to a non-bot message are skipped.
104
+
105
+ > ⚠️ **Requires the Lark "receive all group messages" scope**: by default Lark only delivers `@bot`-mention events to the app. For "owner without `@`" to actually fire, **add the `im:message.group_msg:readonly` permission ("Receive group messages") in the Lark Developer Console** and get it approved. This is an advanced scope — without it, the bridge logic is correct but no event ever arrives. Verify by sending a non-`@` message in the workbench group and checking `~/.lark-channel/profiles/<profile>/logs/bridge-$(date +%Y%m%d).jsonl` for a matching `intake.enter` line; if there isn't one, Lark didn't push it.
106
+
107
+ ## Prerequisites
108
+
109
+ - Node.js **>= 20.12.0**
110
+ - At least one local agent installed and logged in:
111
+ - Claude Code: `claude`, see https://docs.anthropic.com/en/docs/claude-code/quickstart
112
+ - Codex CLI: `codex`, see https://developers.openai.com/codex/cli
113
+ - opencode CLI: `opencode`, see https://opencode.ai/docs/
114
+ - A Feishu / Lark **PersonalAgent** app. The first-run QR wizard can create and bind one for you.
115
+
116
+ ## Install
117
+
118
+ ```bash
119
+ npm i -g @masterxinxin/lark-coding-agent-bridge
120
+ # or
121
+ pnpm add -g @masterxinxin/lark-coding-agent-bridge
122
+ ```
123
+
124
+ The installed CLI command is still `lark-channel-bridge` for upstream compatibility.
125
+
126
+ ## First run
127
+
128
+ ```bash
129
+ lark-channel-bridge run
130
+ ```
131
+
132
+ The first run opens a QR-code wizard:
133
+
134
+ 1. A QR code renders in your terminal.
135
+ 2. Scan it with the Feishu / Lark app.
136
+ 3. Pick or create a PersonalAgent app.
137
+ 4. If prompted, choose which agent to initialize.
138
+ 5. Config is written to `~/.lark-channel/config.json`.
139
+
140
+ You do not need to choose a project directory up front. The bridge creates a profile-managed default working directory; after startup, send `/cd <path>` in Feishu / Lark to switch to a real project.
141
+
142
+ If you already have a PersonalAgent app, pass `--app-id` during initialization to skip app creation. The command prompts for the App Secret.
143
+
144
+ ```bash
145
+ lark-channel-bridge run --app-id cli_xxx
146
+ # or initialize and start the background service directly
147
+ lark-channel-bridge start --app-id cli_xxx
148
+ ```
149
+
150
+ For Lark global apps, add `--tenant lark`.
151
+
152
+ ## Background service
153
+
154
+ Use `run` for first-run setup and foreground debugging. After the bot can send and receive messages, stop the foreground process with `Ctrl-C`, then use an OS-managed service for background operation:
155
+
156
+ ```bash
157
+ lark-channel-bridge start
158
+ lark-channel-bridge status
159
+ lark-channel-bridge stop
160
+ ```
161
+
162
+ Install globally before using service commands. The daemon's launchd plist / systemd unit / Windows task records the bridge CLI path; if that path comes from an npm temp cache through `npx`, the daemon can break when the cache is cleaned. `run` is fine through `npx` as a one-shot foreground process.
163
+
164
+ Service commands install a per-profile service:
165
+
166
+ ```bash
167
+ lark-channel-bridge start [--profile <name>]
168
+ lark-channel-bridge stop [--profile <name>]
169
+ lark-channel-bridge restart [--profile <name>]
170
+ lark-channel-bridge status [--profile <name>]
171
+ lark-channel-bridge unregister [--profile <name>]
172
+ ```
173
+
174
+ Platform mapping:
175
+ - **macOS**: launchd user agent `ai.lark-channel-bridge.bot.<profile>`
176
+ - **Linux**: systemd user unit `lark-channel-bridge.bot.<profile>.service`
177
+ - **Windows**: Task Scheduler task `LarkChannelBridge.Bot.<profile>`, launched through a `.cmd` wrapper
178
+
179
+ Daemon logs are under `~/.lark-channel/profiles/<profile>/logs/daemon/`.
180
+
181
+ ### Multiple profiles: Claude, Codex, and opencode
182
+
183
+ By default, the bridge starts with the currently selected profile. Use `profile use <name>` to change it. Each profile keeps its own app credentials, sessions, working directories, and logs. Create multiple profiles only when you need to connect multiple PersonalAgent apps, or run Claude, Codex, and opencode as separate bots:
184
+
185
+ ```bash
186
+ lark-channel-bridge start --profile claude --agent claude
187
+ lark-channel-bridge start --profile codex --agent codex
188
+ lark-channel-bridge start --profile opencode --agent opencode
189
+ ```
190
+
191
+ For example, to restart only the Codex bot:
192
+
193
+ ```bash
194
+ lark-channel-bridge restart --profile codex
195
+ lark-channel-bridge status --profile codex
196
+ ```
197
+
198
+ ## Commands
199
+
200
+ ### Host CLI
201
+
202
+ ```text
203
+ lark-channel-bridge run [--profile <name>] [--agent claude|codex|opencode] [--workspace <path>] [-c <config>]
204
+ lark-channel-bridge migrate [--profile <name>] [--agent claude|codex|opencode]
205
+ lark-channel-bridge ps
206
+ lark-channel-bridge kill <id|#>
207
+ lark-channel-bridge --help
208
+ ```
209
+
210
+ `profile use <name>` changes the profile used by later default starts. Use these profile management commands when running separate Claude / Codex / opencode bots, connecting multiple PersonalAgent apps, or doing scripted deployment:
211
+
212
+ ```bash
213
+ lark-channel-bridge profile create claude --agent claude
214
+ lark-channel-bridge profile create codex --agent codex
215
+ lark-channel-bridge profile create opencode --agent opencode
216
+ lark-channel-bridge profile list
217
+ lark-channel-bridge profile use <name>
218
+ lark-channel-bridge profile remove <name>
219
+ lark-channel-bridge profile remove <name> --purge --yes
220
+ lark-channel-bridge profile export <name> [--output ./profile.json] [--force]
221
+ lark-channel-bridge profile export <name> --include-secrets --yes
222
+ ```
223
+
224
+ `profile remove` archives local state by default, including the active profile. If other profiles remain, the bridge switches to the next one; if it was the last profile, the root config is cleared so the same name can be created again. `--purge --yes` permanently deletes local state. `profile export` redacts app secrets by default; `--include-secrets --yes` includes sensitive config.
225
+
226
+ If a profile was created with the wrong agent kind, stop or unregister any matching background service first, then run `profile remove <name>` and recreate it with the intended `--agent`.
227
+
228
+ ### Slash commands inside Feishu / Lark
229
+
230
+ | Command | Effect |
231
+ |---|---|
232
+ | `/new`, `/reset` | Clear the current session |
233
+ | `/new chat [name]` | Spawn a new group with the bot as owner and the requester as workbench owner (fork addition; see "What this fork adds → `/new chat`") |
234
+ | `/cd <path>` | Switch working directory and reset the session |
235
+ | `/ws list` | List named workspaces |
236
+ | `/ws save <name>` | Save the current working directory as a named workspace |
237
+ | `/ws use <name>` | Switch to a named workspace |
238
+ | `/ws remove <name>` | Delete a named workspace |
239
+ | `/ws dir <path>` | Set the profile's default workspace directory (new projects check out here) |
240
+ | `/ws pull [project]` | Pull all (or one) project now |
241
+ | `/ws schedule on\|off\|<minutes>` | Enable / disable scheduled pulls, or set the interval (5–1440 min) |
242
+ | `/project add <url> [branch]` | Add a project manually and check it out |
243
+ | `/project rm <name>` | Remove the project record (**without** deleting the on-disk directory) |
244
+ | `/project branch <name> <branch>` | Switch a project's checkout to another branch |
245
+ | `/resume` | Resume compatible history for the same agent, working directory, and permission mode |
246
+ | `/status` | Show profile, agent, working directory, session, lark-cli identity, and run state |
247
+ | `/config` | Adjust presentation preferences, access settings, and lark-cli identity policy (workspace / git projects live in the console) |
248
+ | `/invite user @name` | Allow a user to use the bot in DMs |
249
+ | `/invite admin @name` | Add an access-control admin |
250
+ | `/invite group` | Allow the current group to use the bot |
251
+ | `/invite all group` | Allow all groups the bot has joined |
252
+ | `/remove user @name`, `/remove admin @name`, `/remove group` | Remove access entries |
253
+ | `/stop` | Stop the current run, including the card stop button |
254
+ | `/timeout [N\|off\|default]` | Set or clear the current session idle watchdog |
255
+ | `/ps` | List local bridge processes |
256
+ | `/exit <id\|#>` | Stop a bridge process |
257
+ | `/reconnect` | Force a WebSocket reconnect |
258
+ | `/doctor [description]` | Run low-sensitive diagnostics |
259
+ | `/help` | Help card |
260
+
261
+ DMs do not require an @ mention. Groups and topic groups require `@bot` by default; `@all` is ignored. Cloud-doc comments in supported document types run when the bot is mentioned. Workbench groups spawned by `/new chat` are an exception: the workbench owner does not need to `@bot` in those groups.
262
+
263
+ ## Reply Display and COT
264
+
265
+ `/config` controls the runtime model and three presentation settings:
266
+
267
+ - **Model**: Claude and Codex use curated pickers. For opencode, enter a provider-qualified id such as `openai/gpt-5`; leave it empty to follow the opencode configuration.
268
+ - **Message reply mode**: `message card` streams the final reply; `plain text` sends once after the run finishes.
269
+ - **Tool-call display**: controls whether tool blocks appear in the final card / markdown reply.
270
+ - **COT process message**: `off` sends only the final reply; `brief` first sends a COT message with agent progress text and tool summaries; `detailed` also includes tool args and truncated output.
271
+
272
+ When COT is enabled, the bridge splits the process view and final answer into two messages. The COT message is for tracing what the agent did; the final answer is still generated from the agent's raw text, without heuristic bridge-side filtering. If an agent emits final-answer text as ordinary stream text, that text can also appear in the COT process message.
273
+
274
+ ## lark-cli identity policy
275
+
276
+ Each profile uses a profile-local lark-cli directory at `~/.lark-channel/profiles/<profile>/lark-cli`. The agent process receives `LARKSUITE_CLI_CONFIG_DIR` for that directory, so personal authorization in one profile is not shared with another profile.
277
+
278
+ The default policy is `bot-only`: lark-cli uses the app/bot identity and does not access personal resources. When a user authorizes personal resources such as calendar, mail, or drive, the current profile can switch to `user-default`, which keeps app identity available and also allows the authorized user identity. Owner/admin users can inspect or change this policy in `/config`; `/status` shows the current summary as `lark-cli: app` or `lark-cli: user-ready`.
279
+
280
+ ## Working directories
281
+
282
+ Each profile may define a default working directory through `workspaces.default`. New profiles may be created with `--workspace <path>`; if omitted, the bridge creates a profile-managed default working directory.
283
+
284
+ This is a profile-field snippet. Do not replace the whole `config.json` with it; edit the matching profile's `workspaces` field.
285
+
286
+ ```json
287
+ {
288
+ "workspaces": {
289
+ "default": "/Users/me/.lark-channel-workspaces/claude/default"
290
+ }
291
+ }
292
+ ```
293
+
294
+ The bridge checks that a selected directory exists, is a directory, and is not an overly broad location such as `/`, the home root, a system directory, or a temp root. The working directory is only the current directory for an agent run. It is not a filesystem sandbox; actual file access still depends on the local agent process and its permission mode.
295
+
296
+ ### Console: workspaces and projects
297
+
298
+ The management console (the `http://127.0.0.1:<port>/?token=…` URL the bridge prints on start) has a **Workspace** tab for changing the workspace directory, managing global Git accounts, and adding/pulling projects.
299
+
300
+ - **Add from an account**: once a Git account is configured you can list its remote repositories and add several at once with a per-repo branch; you can also paste a repository URL manually.
301
+ - **Live progress**: add (clone) and pull run in the background while a progress dialog streams git's output (progress, `remote:` messages) and phases. Closing the dialog leaves the task running; reloading the page reconnects you to that task's progress view. A **Cancel task** button in the dialog stops it in place and rolls back the half-finished work.
302
+ - **Remove a project**: by default only the record is removed; the confirmation dialog offers "remove and delete the directory" to delete the local checkout too (a dirty tree / missing directory / non-git repo shows a red warning but can still be deleted).
303
+ - **Switch branch**: a project's branch is a dropdown of the repo's branches (remote heads included; it degrades to manual input when listing fails). Selecting one immediately fetches and checks out that branch, persists it, and re-syncs the index for an already-indexed project. A dirty tree refuses the switch and keeps the old branch. Scheduled/manual pulls also target the configured branch (`fetch-only` never moves the tree).
304
+ - **Scheduled pulls**: enable a profile-level schedule (interval 5–1440 min, strategy `ff-only` / `reset-hard`). The supervisor drives it, so offline profiles still pull; a locally dirty tree is skipped and recorded, never overwritten. The last scheduled run time is recovered from the pull history, so a bridge restart keeps the interval and does not pull immediately.
305
+ - **Pull history**: every pull (scheduled or manual) is appended to `profiles/<profile>/pull-history.jsonl`; the console shows recent records and notifies the owner on failures (optional).
306
+ - **CodeGraph index**: detects/installs the codegraph CLI (official script or npm), wires the codegraph MCP for local agents, indexes projects on demand (no automatic index on first clone), and re-syncs an already-indexed project after a pull that changed files. Each project row shows its index state.
307
+ - Only one add/pull task runs at a time. Task state lives in memory only. When the service shuts down, a profile is stopped/restarted, or you click **Cancel task**, the in-flight git pull / codegraph indexing is terminated together with its child processes and half-finished work is rolled back (an incomplete checkout directory, a partial `.codegraph/`). Running jobs are also recorded under `<rootDir>/running-jobs/`, so after a force-kill (`kill -9` / closing the terminal / `taskkill /F`) the next start cleans up leftover children and rolls back.
308
+
309
+ ### Console: role
310
+
311
+ The profile detail page's **Role** tab lets you write a **role** for that profile. It is a system-prompt soft constraint: appended as a `## 角色设定` section after the bridge run conventions and injected into every run for that profile (claude via `--append-system-prompt-file`, codex as a stdin prefix, opencode v1 as the per-turn `system` / v2 prepended into the text).
312
+
313
+ - It constrains identity, tone, and boundaries at the prompt layer. It is **not a hard permission** — real permissions live in the **Configuration** tab's access control.
314
+ - An online profile takes effect on the **next run** after saving; an offline profile takes effect on its **next start**.
315
+ - Saving an empty prompt clears the role.
316
+ - The Role tab also lists the profile's **MCP servers and skills** and lets you pick which apply. Phase 2a **only stores the selection — it is not enforced yet**; per-profile restriction lands in phase 2b. The catalog is read from local config files (opencode `opencode.json(c)`, claude `~/.claude.json` and the workspace `.mcp.json`, codex `config.toml`); a failed probe shows a retry hint and never affects the role itself.
317
+
318
+ ## Permission modes
319
+
320
+ The recommended user-facing profile config is `permissions.defaultAccess` and `permissions.maxAccess`. New profiles default to `full` for both values so the bridge can keep local tools, authorization flows, file writes, and other agent features fully usable. To tighten a profile, set one or both values to `workspace` or `read-only`; stricter modes can limit local tool execution, login/authorization flows, file writes, and similar capabilities.
321
+
322
+ This is a profile-field snippet. Do not replace the whole `config.json` with it; edit the matching profile's `permissions` field.
323
+
324
+ ```json
325
+ {
326
+ "permissions": {
327
+ "defaultAccess": "full",
328
+ "maxAccess": "full"
329
+ }
330
+ }
331
+ ```
332
+
333
+ Mode mapping:
334
+
335
+ | Bridge access | Claude permission mode | Codex mode |
336
+ |---|---|---|
337
+ | `full` | `bypassPermissions` | `danger-full-access` |
338
+ | `workspace` | `acceptEdits` | `workspace-write` |
339
+ | `read-only` | `plan` | `read-only` |
340
+
341
+ The legacy `sandbox` field is still readable for old configs. After the bridge saves the profile, it migrates that setting to canonical `permissions`.
342
+
343
+ ## Data directories
344
+
345
+ | Path | Content |
346
+ |---|---|
347
+ | `~/.lark-channel/config.json` | Root config with profiles and active profile |
348
+ | `~/.lark-channel/active-profile` | Last selected profile |
349
+ | `~/.lark-channel/git-accounts.json` | Global Git accounts (tokens live in the host keystore) |
350
+ | `~/.lark-channel/secrets.enc` | Host-level encrypted secrets (Git account tokens) |
351
+ | `~/.lark-channel/.keystore.salt` | Host keystore salt |
352
+ | `~/.lark-channel/profiles/<profile>/sessions.json` | Session state |
353
+ | `~/.lark-channel/profiles/<profile>/sessions.json.catalog.json` | Agent-aware session catalog |
354
+ | `~/.lark-channel/profiles/<profile>/workspaces.json` | Current and named workspace bindings |
355
+ | `~/.lark-channel/profiles/<profile>/projects.json` | Workspace git projects + pull schedule |
356
+ | `~/.lark-channel/profiles/<profile>/pull-history.jsonl` | Recent pull-execution records |
357
+ | `~/.lark-channel/profiles/<profile>/secrets.enc` | Profile-local encrypted secrets |
358
+ | `~/.lark-channel/profiles/<profile>/lark-cli/` | Profile-local lark-cli directory |
359
+ | `~/.lark-channel/profiles/<profile>/media/` | Attachment cache |
360
+ | `~/.lark-channel/profiles/<profile>/logs/` | Structured run logs |
361
+ | `~/.lark-channel/registry/processes.json` | Local process registry |
362
+ | `~/.lark-channel/registry/locks/` | Profile and app locks |
363
+
364
+ Set `LARK_CHANNEL_HOME=/path/to/state` to move all local bridge state. `LARK_CHANNEL_LOG_DAYS` overrides log retention.
365
+
366
+ ## Access control
367
+
368
+ **Chat access is private by default: out of the box, only *you* can use the bot in DMs and groups.** "You" = whoever created / owns the Feishu app (the person who scanned the QR to set it up). The bot figures out who the app owner is automatically from Feishu, so **solo chat use needs zero configuration** — you can DM it and `@`-mention it in any group, and everyone else's chat messages are silently ignored (no "permission denied" reply, which would only confirm the bot exists). Cloud-doc comments are document-scoped; see below.
369
+
370
+ To let other people or groups in, add them to one of three lists:
371
+
372
+ | List | Controls | Add | Remove |
373
+ |------|----------|-----|--------|
374
+ | **Allowed users** | who can DM the bot | `/invite user @them` | `/remove user @them` |
375
+ | **Allowed chats** | which groups the bot answers in (for **everyone** in them) | `/invite group` (current group) / `/invite all group` (every group the bot is in) | `/remove group` (current group) |
376
+ | **Admins** | who can change settings, and use the bot in any group | `/invite admin @them` | `/remove admin @them` |
377
+
378
+ > `/invite` and `/remove` can only be run by **you (the creator) and admins**. The `@` in the command points at the *target person* (not the bot) — the bot resolves the mention to their identity, so you never deal with raw IDs.
379
+
380
+ ### Two identities that bypass everything
381
+
382
+ - **You (the creator)**: subject to no list at all — DMs, any group, every command. You **can never lock yourself out**: even if the lists get messed up, DM the bot and send `/config` to get back in. Transfer the app's ownership in the Feishu console and the bot follows the new owner automatically.
383
+ - **Admins**: can DM, run management commands like `/config`, and **bypass the allowed-chats list** — the bot answers them in any group, listed or not. Good for teammates who co-maintain the bot.
384
+
385
+ ### Common setups
386
+
387
+ - **Just me** → nothing to do; this is the default.
388
+ - **Let a teammate DM the bot** → `/invite user @them`
389
+ - **Open a work group to everyone in it** → send `/invite group` inside that group
390
+ - **First-time setup, onboard every group the bot is already in** → `/invite all group` pulls them all into the list at once; trim with `/remove group` afterwards
391
+ - **Add a co-admin** → `/invite admin @them`
392
+
393
+ ### Worth knowing
394
+
395
+ - Changes take effect on the **next message** — no restart needed.
396
+ - **In groups you must `@` the bot first** (DMs don't need it). That's a separate toggle (`/config` → "require @ in groups"), independent of the lists above.
397
+ - Strangers get pure silence — no reply at all. The one exception: if someone `@`-mentions the bot in a group that hasn't been opened up, the bot posts a friendly one-liner telling them an admin can run `/invite group` to enable it.
398
+ - Cloud-doc comments are document-scoped: anyone who can comment in a supported document and mention the bot can trigger a reply.
399
+
400
+ ### Advanced: editing the config file directly
401
+
402
+ If you'd rather not do it inside Feishu, `/invite` and `/config` write the matching profile's `access` field in `~/.lark-channel/config.json`. Empty lists mean nobody from that list, not open access. This is a profile-field snippet; do not replace the whole `config.json` with it:
403
+
404
+ ```json
405
+ {
406
+ "schemaVersion": 2,
407
+ "profiles": {
408
+ "claude": {
409
+ "agentKind": "claude",
410
+ "access": {
411
+ "allowedUsers": ["ou_xxxxxxxxxxxxx"],
412
+ "allowedChats": ["oc_xxxxxxxxxxxxx"],
413
+ "admins": ["ou_xxxxxxxxxxxxx"],
414
+ "requireMentionInGroup": true
415
+ }
416
+ }
417
+ }
418
+ }
419
+ ```
420
+
421
+ `allowedUsers` / `admins` take user `open_id`s; `allowedChats` takes group `chat_id`s. The easiest way to find an ID by hand: have the person message the bot (or `@` it in the group), then check the active profile's log:
422
+
423
+ ```bash
424
+ grep '"event":"enter"' ~/.lark-channel/profiles/<profile>/logs/bridge-$(date +%Y%m%d).jsonl | tail -5
425
+ ```
426
+
427
+ Each line carries `chatId` (group / DM id) and `senderId` (user `open_id`). After a manual edit, **restart the bridge** or send `/reconnect` from an allowed admin context to apply it. For day-to-day tweaks `/invite` / `/config` are easier; direct edits are mainly for deployment scripts that pre-seed access.
428
+
429
+ ## Cloud-doc comments
430
+
431
+ Cloud-doc comments do not need a separate workspace binding or document allowlist. In supported document comments, mention the bot and the bridge replies in the same thread. Comment runs reuse the document session key and fall back to the user home directory when no document cwd was previously recorded.
432
+
433
+ ## FAQ
434
+
435
+ **The bot stays silent or the local CLI never replies.** Usually the local `claude`, `codex`, or `opencode` CLI is not logged in, or the current session points to a working directory that no longer exists. Send `/status` to inspect; `/new` often fixes it by starting a fresh session.
436
+
437
+ **The agent subprocess looks frozen (card stuck on the last frame).** The bridge supports an idle watchdog: if the agent emits nothing for N minutes, the process is killed and the card is annotated with the auto-termination reason. Disabled by default. Enable with `/config` globally, or `/timeout 10` for the current session; `/timeout off` disables it for the session; `/timeout default` clears the session override.
438
+
439
+ **The agent says it cannot see an image I sent.** Upgrade to the latest version. Releases before 0.1.0 had a filename-dedup bug.
440
+
441
+ ## Testing and CI
442
+
443
+ Local checks:
444
+
445
+ ```bash
446
+ pnpm test
447
+ pnpm typecheck
448
+ pnpm build
449
+ ```
450
+
451
+ `pnpm test` includes unit, integration, and process-level adapter tests. CI runs on macOS, Ubuntu, and Windows with `pnpm install --frozen-lockfile`, `pnpm test`, `pnpm typecheck`, and `pnpm build`.
452
+
453
+ ## Optional telemetry
454
+
455
+ By default the bridge reports **nothing**: no metrics, no logs leave your machine, and it pulls in zero telemetry dependencies. The hook below is inert unless you opt in.
456
+
457
+ To wire up your own monitoring, point an environment variable at a module that default-exports (or exports `createAdapter`) an `AdapterFactory`:
458
+
459
+ ```bash
460
+ LARK_CHANNEL_TELEMETRY_MODULE=your-telemetry-package lark-channel-bridge start
461
+ ```
462
+
463
+ That module receives every `log.*` event plus error/metric hooks and forwards them wherever you like. The interface is exported from the package root:
464
+
465
+ ```ts
466
+ import type { AdapterFactory, TelemetryAdapter, TelemetryEvent } from '@masterxinxin/lark-coding-agent-bridge';
467
+
468
+ const createAdapter: AdapterFactory = (meta) => ({
469
+ emit(event) {/* ship event */},
470
+ recordError(err, ctx) {/* ship exception */},
471
+ recordMetric(name, value, tags) {/* ship metric */},
472
+ flush(timeoutMs) {/* drain buffered events */},
473
+ });
474
+ export default createAdapter;
475
+ ```
476
+
477
+ A missing module, a bad factory, or a throwing adapter all degrade to noop — telemetry can never stop the bridge from starting or break logging.
478
+
479
+ ## License
480
+
481
+ [MIT](./LICENSE)