feat(feishu): add /addwriter /removewriter /writers commands

Adds parity with Telegram and Discord for file-writer management
commands, closing the UX gap where users saw an error mentioning
/addwriter but the Feishu channel had no handler.

- New maybeHandleWriterCommand routes /addwriter, /removewriter,
  /writers from the Feishu inbound flow. Runs at step 5a — after
  checkGroupPolicy — so commands never bypass allowlist or pairing
  enforcement. Step 2a rejects slash commands in DM chats early so
  users get a clear hint without waking the agent pipeline.
- Target user is identified via reply-to (fetches parent message
  sender) or first non-bot @mention. A bare /addwriter with no
  target shows the usage hint instead of silently self-granting,
  preventing accidental privilege capture in empty-writer groups.
- Refuses to run while botOpenID is unresolved so a @mention of
  the bot itself cannot be mistaken for a human target.
- 10s context timeout on each handler bounds worst-case Lark API
  latency (parent message lookup, permission store access).
- feishu.New() gains variadic Option parameter with WithAgentStore
  and WithConfigPermStore mirroring Telegram's pattern. The gateway
  now wires pgStores.Agents and pgStores.ConfigPermissions into
  Feishu channel on startup.
- 12 new unit tests with fakeConfigPermStore and httptest Lark
  server cover DM rejection, nil-store graceful degradation, bootstrap
  via self-mention, bare-command usage hint, bot-probe race refusal,
  non-writer rejection, grant via mention, remove last-writer guard,
  empty and populated list output, reply-to target resolution via
  Lark im/v1/messages lookup, and non-command passthrough. 40 total
  tests in the feishu package, all green with -race.

Closes #818.
This commit is contained in:
viettranx committed 2026-04-11 21:45:37 +07:00
1 parent 934ab7e485
commit 20de0e332e
7 files changed
+824 -29

No files matched your search

+23
View File
@@ -397,6 +397,29 @@ When a user pastes a Lark docx (document) URL in a message, the channel automati
**Configuration**: No new config flags. Supported document type and cache tunables (8000 rune limit, 10-URL cap, 5-min TTL, 128-entry cache) are hardcoded.
### Writer Management Commands
Group chats support file-write permission management via slash commands. Permissions are scoped to the group via `group:feishu:<chatID>`. DM users who attempt these commands receive a hint that they only work in groups.
**Commands** (group-only):
| Command | Description | Requires Target | Permission |
|---------|-------------|:---:|:---:|
| `/addwriter <@user or reply>` | Grant file_writer permission to target user | Yes | Writers only |
| `/removewriter <@user or reply>` | Revoke file_writer permission from target user | Yes | Writers only |
| `/writers` | List current group writers with displayName | No | -- |
**Target specification**: Commands require explicit identification via reply-to or @mention. Bare `/addwriter` without a target is rejected — prevents accidental privilege capture.
**Bootstrap behavior**: Groups with no writers allow the first writer to grant themselves via `/addwriter @self` (explicit self-mention). This enables initial configuration without external admin intervention.
**Authorization**:
- Only existing writers can manage the writer list (enforce via `IsGroupFileWriter` check)
- Last-writer guard: If removing a writer would leave zero writers, operation is rejected with user-facing message
- Database errors are fail-open; security issues are logged as `security.writer_check_failed`
**Implementation**: Timeout of 10 seconds bounds Feishu API calls. Requires `AgentStore` and `ConfigPermissionStore` wired to the Feishu channel via constructor options.
---
## 7. Discord
+5 -12
View File
@@ -34,21 +34,14 @@ All notable changes to GoClaw Gateway are documented here. Format follows [Keep
### Fixed
#### Feishu/Lark Thread Reply Routing — Issue #818 Phase 1 (2026-04-11)
- **Thread detection**: Inbound messages with `thread_id` now properly route responses back to the same Feishu thread via `/open-apis/im/v1/messages/{id}/reply` endpoint
- **Metadata propagation**: New `feishu_reply_target_id` metadata key added to `routingMetaKeys` allowlist so all outbound messages (text, cards, files, reactions) land in the correct thread
- **Graceful fallback**: If thread root is deleted, channel falls back to regular `SendMessage()` for robustness
#### Feishu/Lark Writer Management Commands — Issue #818 Closed (2026-04-11)
- **Issue #818 resolution**: Closes UX gap where users saw `/addwriter` error messages but Feishu had no handler
- **Phase 1 — Thread reply routing**: Inbound messages with `thread_id` now properly route responses back to the same Feishu thread via `/open-apis/im/v1/messages/{id}/reply`. New `feishu_reply_target_id` metadata key included in `routingMetaKeys` allowlist. Graceful fallback to `SendMessage()` if thread root deleted
- **Phase 2 — Document auto-fetch**: Pasted Lark docx URLs auto-detected and fetched via `/open-apis/docx/v1/documents/{id}/raw_content`. Content injected as `[Lark Doc: URL]` markers. LRU cache (128 entries, 5-min TTL) + 8000-rune truncation per document. Requires `docx:document:readonly` permission + owner grant
- **Phase 3 — Writer management commands**: Added `/addwriter <@user or reply>`, `/removewriter`, `/writers` for group file-write permission control. Group-only (DMs rejected early). Requires existing writer authorization. Last-writer guard prevents removing final writer. Empty-writer groups allow bootstrap via explicit `/addwriter @self`. 10s timeout bounds Feishu API calls
### Added
#### Feishu/Lark Document URL Auto-Fetch — Issue #818 Phase 2 (2026-04-11)
- **Automatic docx fetching**: Users paste Lark docx URLs in chat; channel auto-detects and fetches raw text via `/open-apis/docx/v1/documents/{id}/raw_content`
- **Transparent context injection**: Document content embedded as `[Lark Doc: URL] ... [End of Lark Doc]` markers, visible to agent for reasoning
- **Smart caching**: LRU cache per channel instance (128 entries, 5-minute TTL) reduces redundant API calls
- **Rune-safe truncation**: 8000-rune limit per document prevents token budget overrun
- **Access control**: Requires bot app `docx:document:readonly` permission + per-document grant from owner. Access denied displays inline marker with grant instructions
- **Safeguards**: Docx-only (sheets/base/wiki deferred), max 10 URLs per message, soft-fail on errors (no message blocks)
### Testing
#### Test Coverage Improvement — Wave 1-3 (2026-04-11)