@lansenger-pm/openclaw-lansenger-channel 3.14.3 → 3.14.5

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/CHANGELOG.md CHANGED
@@ -4,6 +4,57 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [3.14.5] - 2026-06-17
8
+
9
+ > **Compatible with OpenClaw `^2026.6.1`** (tested against `2026.6.1`).
10
+
11
+ ### Bug Fixes
12
+
13
+ - **Group chat reply routing**: Outbound adapters (`sendText`, `sendMedia`, `sendFormattedText`, `sendPayload`, `beforeDeliverPayload`) created fresh `LansengerClient` instances via `makeClient()`, which had an empty `chatTypeMap` cache. `isGroupChat()` then fell back to `chatId.startsWith("group:")` — which never matches Lansenger group IDs — causing all group replies to be sent via private-message API instead of group-message API. Fixed by using `getRunningClient() ?? makeClient()` so the cached client (with populated `chatTypeMap`) handles routing.
14
+
15
+ ## [3.14.4] - 2026-06-15
16
+
17
+ > **Compatible with OpenClaw `^2026.6.1`** (tested against `2026.6.1`).
18
+
19
+ ### Bug Fixes
20
+
21
+ - **`recoverPendingInboundContexts` duplicate restart notice**: Gateway restart-recovery sent multiple "system restart" notices to the same chat due to two issues:
22
+ 1. `startLansengerGateway` was called from both runtime extension and channel extension initialization paths, causing `recoverPendingInboundContexts` to execute twice. Added a module-level `recoveryGuard` flag for idempotency.
23
+ 2. Even within a single invocation, multiple pending contexts for the same `chatId` would each trigger a separate notice. Added `chatId` deduplication — only one notice per chat per recovery cycle.
24
+ - **`recoverPendingInboundContexts` duplicate ack revoke**: Same guard prevents the stale ack message from being revoked twice.
25
+ - **WS reconnection without exponential backoff**: `backoffIdx` was reset to 0 immediately after `new WebSocket()` instead of on actual connection (`ws.onopen`). This caused every reconnect attempt to use a fixed 2s delay and log `attempt 1` regardless of retry count. Moved `this.backoffIdx = 0` into `ws.onopen` so the `[2, 5, 10, 30, 60]` backoff array is properly utilized.
26
+
27
+ ## [3.14.3] - 2026-06-04
28
+
29
+ > **Compatible with OpenClaw `^2026.6.1`** (tested against `2026.6.1`).
30
+
31
+ ### Features
32
+
33
+ - **Gateway restart-recovery inbound context persistence**: Active inbound sessions are persisted to `~/.openclaw/lansenger-inbound-contexts.json` before `inbound.run`. On gateway restart/plugin upgrade, `recoverPendingInboundContexts` detects interrupted sessions, revokes stale ack messages, and sends a "system restart, reprocessing..." notification. Contexts older than 5 minutes are discarded.
34
+ - **User notification**: Sends a locale-aware restart notice (`zh`/`en`) to the affected chat after recovery.
35
+
36
+ ## [3.14.2] - 2026-06-04
37
+
38
+ > **Compatible with OpenClaw `^2026.6.1`** (tested against `2026.6.1`).
39
+
40
+ ### Features
41
+
42
+ - **`reply_payload_sending` fallback delivery**: Tracks active `inbound.run` sessions via `activeDeliverySessions` set. When `reply_payload_sending` fires for a session without an active `inbound.run` (e.g., after gateway restart/turn timeout), the reply is delivered directly via the Lansenger client instead of being silently dropped. Extracts `chatId` from `sessionKey` to resolve delivery target.
43
+
44
+ ## [3.14.1] - 2026-06-04
45
+
46
+ > **Compatible with OpenClaw `^2026.6.1`** (tested against `2026.6.1`).
47
+
48
+ ### Bug Fixes
49
+
50
+ - **`ackMessage` default**: Changed from `false` to `true`; `revokeAckMessage` from `true` to `false`, aligning with the `after_agent_dispatch` ack policy.
51
+ - **`pendingApprovalCards` persistence**: Migrated from in-memory `Map` to `~/.openclaw/lansenger-approval-cards.json` file-backed store, surviving gateway restarts.
52
+ - **Group ingress fallback**: Added `requireMention` + `isAtMe` check to prevent non-@ messages from triggering the bot.
53
+ - **`isPathAllowed` security hardening**: Empty `mediaLocalRoots` now defaults to `[cwd, tmpdir]` instead of allowing any path.
54
+ - **Dead code removal**: Removed unused `debounceMs` statement in `runtime.ts`.
55
+ - **`convertPxToPtDeep` → `convertPxToPtCard`**: Only transforms known style fields; skips URL/link fields to avoid corruption.
56
+ - **Documentation**: All 5 locale READMEs updated with new defaults and `mediaLocalRoots` docs.
57
+
7
58
  ## [3.14.0] - 2026-06-04
8
59
 
9
60
  > **Compatible with OpenClaw `^2026.6.1`** (tested against `2026.6.1`).
@@ -25,60 +76,43 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
25
76
  - **Test coverage**: 4 new tests via `verifyChannelMessageReceiveAckPolicyAdapterProofs` and `listDeclaredReceiveAckPolicies`, verifying all declared policies pass SDK contract proofs.
26
77
  - This replaces the previously implicit ack timing embedded in the `runtime.ts` monitor-local state with a standardized, SDK-verifiable contract.
27
78
 
28
- ## [3.13.0] - 2026-06-03
79
+ ## [3.13.1] - 2026-06-03
29
80
 
30
81
  > **Compatible with OpenClaw `^2026.5.28`** (tested against `2026.5.28`).
31
82
 
32
- ### Bug Fix
83
+ - migrate `message_sending` from `api.registerHook` to `api.on("message_sending", ...)` (OpenClaw 2026.5.28 rejects `registerHook` for typed hooks that lack a name)
33
84
 
34
- - **Missing `dist/src/setup-i18n.*` in npm package**: `setup-wizard.js` imports `./setup-i18n.js`, but the `files` field in `package.json` did not include `dist/src/setup-i18n.*`, causing `Cannot find module './setup-i18n.js'` at gateway startup. Added `dist/src/setup-i18n.*` to the `files` array.
35
- - **`message_sending` hook registration missing name**: `api.registerHook("message_sending", ...)` now fails at gateway startup with "hook registration missing name" (OpenClaw 2026.5.28 rejects `registerHook` calls for typed hooks that lack a name). Migrated `message_sending` to `api.on("message_sending", ...)` — the typed lifecycle hook API — alongside the existing `reply_payload_sending` hook.
85
+ ## [3.13.0] - 2026-06-03
36
86
 
37
- ### Outbound Hook Expansion
87
+ > **Compatible with OpenClaw `^2026.5.28`** (tested against `2026.5.28`).
38
88
 
39
- - **`reply_payload_sending` hook**: Register typed plugin hook via `api.on()` for the lansenger channel. Intercepts reply payloads before delivery enables logging, approval context injection, and payload rewrite/cancel decisions.
40
- - **`message_sending` hook**: Register typed plugin hook via `api.on()` for early-stage reply interception.
41
- - **`normalizePayload` — code-block detection**: Payloads containing `\`\`\`` code fences are now marked `_lansengerFormatText: true` even when they also carry `mediaUrl` or `presentation`. Previously, code-rich agent replies with attachments fell back to plain-text `msgType`, losing Markdown formatting. Now they always render via `formatText`.
42
- - **`beforeDeliverPayload` — approval-resolved crash recovery**: When `hint.kind === "approval-resolved"`, the delivery pipeline now proactively updates the original appCard status via `updateDynamicCard`. Because the delivery pipeline replays on gateway restart, this provides crash-recovery safety.
43
- - **`shouldSuppressLocalPayloadPrompt`**: Suppresses the local text prompt when the native approval route is active (`hint.kind === "approval-pending"` with `nativeRouteActive === true`). Prevents duplicate approval prompts.
44
- - **`pendingApprovalCards` Map**: Tracks sent approval card `messageId`s keyed by `chatId`. Enables `beforeDeliverPayload` to correlate approval-resolved payloads with the original card for status updates.
45
- - **Text chunking**: Add `textChunkLimit: 4000`, `chunkerMode: "markdown"`, and `chunker` (using SDK `chunkMarkdownTextWithMode`). Long agent replies are automatically split into multiple Lansenger messages, preserving Markdown structure across chunks.
46
- - **`resolveEffectiveTextChunkLimit`**: Uses SDK `resolveTextChunkLimit` to allow config-level override of the chunk limit.
47
- - Add `normalizePayload` and `beforeDeliverPayload` callbacks on outbound base (structural preparation for outbound hooks).
48
- - Add session-scoped delivery dedup (`sessionDeliveryTracker`) to prevent duplicate sends across turns in the same session.
89
+ - Add `dist/src/setup-i18n.*` to `files` field in package.json (fixes `Cannot find module './setup-i18n.js'` at gateway startup)
49
90
 
50
- ### OpenClaw 2026.5.27–28 Compatibility
91
+ ## [3.12.2] - 2026-06-03
51
92
 
52
- - Migrate `api.runtime.channel.turn` `api.runtime.channel.inbound` (OpenClaw removed the old `turn` runtime alias; the new `inbound` namespace provides the same `run` function with identical parameters). This is a **required** migration — the old `turn` alias no longer exists in OpenClaw 2026.5.27.
53
- - Thread canonical `sessionKey` into outbound hooks (`sendText`, `sendMedia`, `sendFormattedText`) for multi-session/multi-agent routing and dedup.
54
- - Fallback `sessionKey` now includes `accountId` for multi-bot scenarios (`agent:main:lansenger:<accountId>:<chatType>:<chatId>`).
55
- - Pin npm install spec to exact version (`@lansenger-pm/openclaw-lansenger-channel@3.13.0`) to prevent supply-chain attacks and accidental upgrades.
56
- - Bump `openclaw` devDependency from `^2026.5.20` to `^2026.5.28`.
57
- - Split changelog out of READMEs into standalone `CHANGELOG.md` (all 5 locale READMEs now reference this file).
93
+ > **Compatible with OpenClaw `^2026.5.28`** (tested against `2026.5.28`).
58
94
 
59
- ### Approval Flow Enhancement
95
+ - **Outbound Hook Expansion**: `reply_payload_sending` hook, `message_sending` hook, `normalizePayload` code-block detection, `beforeDeliverPayload` approval-resolved crash recovery, `shouldSuppressLocalPayloadPrompt`, `pendingApprovalCards` Map, text chunking (`textChunkLimit: 4000`, `chunkerMode: "markdown"`), `resolveEffectiveTextChunkLimit`, session-scoped delivery dedup (`sessionDeliveryTracker`)
96
+ - **Approval Flow Enhancement**: `approvalCapability.transport.send` stores card `messageId` in `pendingApprovalCards`; card status updates via both `transport.update` and `beforeDeliverPayload` (crash-recovery safety)
97
+ - **Setup Wizard i18n**: `createSetupTranslator()` + plugin-owned dictionary, locales `en`/`zh-CN`/`zh-TW`, `src/setup-i18n.ts`
60
98
 
61
- - `approvalCapability.transport.send` now stores card `messageId` in `pendingApprovalCards` for later correlation in `beforeDeliverPayload`.
62
- - Card status updates (pending → approved/denied) now happen via both `transport.update` (approval framework) and `beforeDeliverPayload` (delivery pipeline). The latter provides crash-recovery safety.
99
+ ## [3.12.1] - 2026-06-03
63
100
 
64
- ### Setup Wizard i18n
101
+ > **Compatible with OpenClaw `^2026.5.28`** (tested against `2026.5.28`).
65
102
 
66
- - **Locale-aware setup wizard**: Replace all bilingual hard-coded strings (`"Chinese / English"`) with `createSetupTranslator()` + plugin-owned dictionary. The wizard now displays in the user's locale (resolved from `OPENCLAW_LOCALE` `LC_ALL` `LC_MESSAGES` → `LANG`, fallback English).
67
- - Supported locales: **`en`**, **`zh-CN`**, **`zh-TW`**. French and other locales fall back to English for wizard prompts (READMEs remain 5-language).
68
- - Common strings (status labels, "Docs:" prefix, etc.) use the built-in OpenClaw catalog keys (`wizard.channels.*`). Plugin-specific strings use a local dictionary with `lt()`.
69
- - New file: `src/setup-i18n.ts` locale resolver + 40+ entry dictionary.
103
+ - **Security**: SecretRef support for appSecret, plaintext appSecret warning, SSRF protection for `sendImageUrl`, `dangerouslyAllowPrivateNetwork` config, `mediaLocalRoots` config, path validation (`isPathAllowed`)
104
+ - Pin npm install spec to exact version (`@lansenger-pm/openclaw-lansenger-channel@3.12.1`)
105
+ - Thread canonical `sessionKey` into outbound hooks for multi-session/multi-agent routing
106
+ - Fallback `sessionKey` includes `accountId` for multi-bot scenarios
107
+ - Bump `openclaw` devDependency from `^2026.5.20` to `^2026.5.28`
70
108
 
71
- ### Security
109
+ ## [3.12.0] - 2026-06-03
72
110
 
73
- - **SecretRef support for appSecret**: `resolveAccount()` now detects SecretRef objects via `coerceSecretRef()` and resolves from env vars. No need to store appSecret as plaintext in config. After running `openclaw secrets configure`, the config contains `__OPENCLAW_SECRET__({ref_id})` instead of the raw secret value, while the actual value is stored in the system credential store.
74
- - **Plaintext appSecret warning in `resolveAccount()`**: Emits `log.warn` on every gateway startup when `appSecret` is stored as a plaintext string in `openclaw.json`, advising migration to SecretRef via `openclaw secrets configure`.
75
- - **Plaintext appSecurity warning in setup wizard `finalize`**: Console output in the user's locale when plaintext appSecret is detected after config migration.
76
- - **`securityNote` in setup wizard**: A conditional note that appears only when a plaintext appSecret exists, with locale-appropriate migration instructions.
77
- - **README security advisory**: All 5 locale READMEs now include a prominent `⚠️ Security: Migrate appSecret to SecretRef storage` block.
78
- - **SSRF protection for sendImageUrl**: `assertHttpUrlTargetsPrivateNetwork()` blocks RFC1918/link-local/metadata-IP targets by default.
79
- - **`dangerouslyAllowPrivateNetwork` config**: Opt-in at top-level or per-account to allow private network image URLs. Audit finding (`lansenger/dangerously-allow-private-network`) warns when enabled.
80
- - **`mediaLocalRoots` config**: Restrict local file delivery to configured directories. Prevents agents from accessing arbitrary files outside allowed roots. Available at top-level and per-account.
81
- - **Path validation**: `isPathAllowed()` validates local file paths against `mediaLocalRoots` before delivery; blocked paths are logged and skipped.
111
+ > **Compatible with OpenClaw `^2026.5.27`** (tested against `2026.5.27`).
112
+
113
+ - **Breaking**: Migrate `api.runtime.channel.turn` `api.runtime.channel.inbound` (OpenClaw removed the old `turn` runtime alias)
114
+ - Split changelog out of READMEs into standalone `CHANGELOG.md`
115
+ - All 5 locale READMEs now reference this file
82
116
 
83
117
  ## [3.11.0] - 2026-05-26
84
118
 
@@ -120,12 +154,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
120
154
 
121
155
  > **Compatible with OpenClaw `^2026.5.20`** (tested against `2026.5.20`).
122
156
 
123
- ### Features
157
+ - Fix apiGatewayUrl lint check to skip when accounts have gateway set.
158
+
159
+ ## [3.8.1] - 2026-05-20
124
160
 
125
- - Add `security.collectWarnings` and `security.collectAuditFindings` for `openclaw doctor --lint` integration. Checks: credentials missing/incomplete, dmPolicy not pairing, apiGatewayUrl not set, group config unused.
161
+ > **Compatible with OpenClaw `^2026.5.20`** (tested against `2026.5.20`).
162
+
163
+ - Add `security.collectWarnings` and `security.collectAuditFindings` for `openclaw doctor --lint` integration.
126
164
  - Add `doctor.repairConfig` to auto-fix dmPolicy to pairing.
127
165
  - Bump `openclaw` devDependency from `^2026.5.7` to `^2026.5.20`.
128
- - Fix apiGatewayUrl lint check to skip when accounts have gateway set.
129
166
 
130
167
  ## [3.7.9] - 2026-05-18
131
168
 
@@ -388,4 +425,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
388
425
  - Multi-language READMEs (en, zhHans, zhHant, zhHantHK, fr).
389
426
  - DM security: pairing mode (default), allowlist, open, disabled.
390
427
  - Approval workflow: appCard with `headStatusInfo` for pending/approved/denied states.
391
- - Auto-start WebSocket gateway on plugin activation.
428
+ - Auto-start WebSocket gateway on plugin activation.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Lansenger OpenClaw Channel
3
+ Copyright (c) 2026 Lanxin Mobile (Beijing) Technology Co., ltd.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.fr.md CHANGED
@@ -12,7 +12,7 @@ Connecte OpenClaw à Lansenger — une plateforme de messagerie d'entreprise —
12
12
  ## Fonctionnalités
13
13
 
14
14
  - **Messagerie en temps réel** via connexion longue WebSocket
15
- - **Support multi-bot** — lier plusieurs bots Lansenger à différents agents OpenClaw
15
+ - **Support multi-robot** — lier plusieurs robots Lansenger à différents agents OpenClaw
16
16
  - **Support Markdown** utilisant le msgType `formatText` (par défaut)
17
17
  - **Fichiers/Images/Vocaux** via le msgType `text` avec upload de médias
18
18
  - **Cartes d'approbation** — workflow d'approbation interactif avec mises à jour de statut en place (en attente → approuvé/refusé)
@@ -20,8 +20,8 @@ Connecte OpenClaw à Lansenger — une plateforme de messagerie d'entreprise —
20
20
  - **Auto-routage via msgTarget** — toutes les méthodes d'envoi routent automatiquement vers les API groupe ou DM (privé) ; pas de méthodes groupe/privé séparées
21
21
  - **@Mentions** — support @tout et @utilisateurs spécifiques dans les chats de groupe
22
22
  - **Traitement des médias entrants** — téléchargement d'images/fichiers/vocaux, détection d'extension, chemins de fichiers pour l'agent
23
- - **Révocation de messages** — révoquer les messages précédemment envoyés (chatType : bot ou groupe uniquement)
24
- - **Démarrage automatique** — la passerelle connecte automatiquement tous les comptes de bots configurés au démarrage
23
+ - **Révocation de messages** — révoquer les messages précédemment envoyés (chatType : robot ou groupe uniquement)
24
+ - **Démarrage automatique** — la passerelle connecte automatiquement tous les comptes de robots configurés au démarrage
25
25
  - **Debounce entrant** — fusionner les messages consécutifs rapides du même émetteur via la config OpenClaw `messages.inbound.debounceMs`
26
26
  - **Message ack** — envoyer un message « reçu, en cours de traitement... » avant le traitement de l'agent ; auto-révoqué après la réponse ; langue auto-détectée
27
27
  - **Zéro modification du core** — mode plugin pur, `git diff HEAD` reste INTACT
@@ -102,7 +102,7 @@ openclaw gateway restart
102
102
 
103
103
  ### Premier message
104
104
 
105
- Après le redémarrage, le bot se connecte automatiquement via WebSocket. Envoyez un DM au bot — vous recevrez un code de pairage. Approuvez-le :
105
+ Après le redémarrage, le robot se connecte automatiquement via WebSocket. Envoyez un DM au robot — vous recevrez un code de pairage. Approuvez-le :
106
106
 
107
107
  ```bash
108
108
  openclaw pairing approve lansenger <code>
@@ -116,8 +116,8 @@ Ajoutez ces variables à `~/.openclaw/.env` ou à votre environnement :
116
116
 
117
117
  | Variable | Description | Exemple |
118
118
  |----------|-------------|---------|
119
- | `LANSENGER_APP_ID` | App ID du bot personnel | `your-appid` |
120
- | `LANSENGER_APP_SECRET` | App Secret du bot personnel | `ABCDEF123456...` |
119
+ | `LANSENGER_APP_ID` | App ID du robot personnel | `your-appid` |
120
+ | `LANSENGER_APP_SECRET` | App Secret du robot personnel | `ABCDEF123456...` |
121
121
  | `LANSENGER_API_GATEWAY_URL` | URL de la passerelle API Lansenger (remplacement) | `https://open.e.lanxin.cn/open/apigw` |
122
122
 
123
123
  Les identifiants peuvent aussi être fournis via la configuration `openclaw.json` (voir Configuration optionnelle ci-dessous). Les valeurs de configuration sont prioritaires ; les variables d'environnement sont utilisées comme repli lorsque la configuration n'est pas définie.
@@ -165,8 +165,8 @@ Les identifiants peuvent aussi être fournis via la configuration `openclaw.json
165
165
 
166
166
  | Champ | Description | Valeur par défaut |
167
167
  |-------|-------------|-------------------|
168
- | `appId` | App ID du bot personnel | — |
169
- | `appSecret` | App Secret du bot personnel | — |
168
+ | `appId` | App ID du robot personnel | — |
169
+ | `appSecret` | App Secret du robot personnel | — |
170
170
  | `apiGatewayUrl` | URL de la passerelle API | `https://open.e.lanxin.cn/open/apigw` |
171
171
  | `homeChannel` | Chat ID par défaut pour la livraison cron/notification | — |
172
172
  | `enabled` | Activer/désactiver le canal (défaut runtime : false sans identifiants) | `true` |
@@ -174,9 +174,9 @@ Les identifiants peuvent aussi être fournis via la configuration `openclaw.json
174
174
  | `dmPolicy` | Politique DM : `pairing`, `allowlist`, `open`, `disabled` | `pairing` |
175
175
  | `configWrites` | Autoriser Lansenger à écrire la config en réponse aux événements du canal | `true` |
176
176
  | `name` | Nom d'affichage pour ce compte | — |
177
- | `accounts` | Configuration multi-bot | — |
177
+ | `accounts` | Configuration multi-robot | — |
178
178
  | `groupPolicy` | Politique de groupe : `open` (tous les groupes), `allowlist` (groupes autorisés uniquement), `disabled` (messages de groupe désactivés) | `allowlist` |
179
- | `groupAllowFrom` | IDs de groupes autorisés à déclencher le bot | `[]` |
179
+ | `groupAllowFrom` | IDs de groupes autorisés à déclencher le robot | `[]` |
180
180
  | `groups` | Configuration par groupe (requireMention, enabled, allowFrom) | — |
181
181
  | `ackMessage` | Envoyer un message de confirmation avant le traitement de l'agent | `true` |
182
182
  | `revokeAckMessage` | Révoquer automatiquement le message ack après la réponse de l'agent. Mettre `false` pour garder le message visible (certains utilisateurs préfèrent voir le message plutôt qu'une notification « message révoqué ») | `false` |
@@ -208,12 +208,12 @@ Lorsqu'un utilisateur envoie plusieurs messages rapides consécutifs, le mécani
208
208
  - Les messages média et commandes de contrôle ne sont PAS debounceés — ils sont traités immédiatement
209
209
  - Quand le debounce est actif, les textes fusionnés sont joints par `\n` ; les chemins média sont concaténés ; les métadonnées du dernier message sont utilisées
210
210
 
211
- ### Configuration multi-bot
211
+ ### Configuration multi-robot
212
212
 
213
- Pour ajouter plusieurs bots, utilisez `openclaw config set` avec la structure `accounts` :
213
+ Pour ajouter plusieurs robots, utilisez `openclaw config set` avec la structure `accounts` :
214
214
 
215
215
  ```bash
216
- # Ajouter un deuxième bot (remplacez appid/appsecret/gateway par vos valeurs)
216
+ # Ajouter un deuxième robot (remplacez appid/appsecret/gateway par vos valeurs)
217
217
  openclaw config set channels.lansenger.accounts.your-appid-2.appId "your-appid-2"
218
218
  openclaw config set channels.lansenger.accounts.your-appid-2.appSecret "your-appsecret"
219
219
  openclaw config set channels.lansenger.accounts.your-appid-2.apiGatewayUrl "https://apigw.lx.qianxin.com"
@@ -348,7 +348,7 @@ Le plugin supporte les cartes d'approbation :
348
348
  ## Notes importantes
349
349
 
350
350
  - **Pas de chat staff** — Lansenger n'a que les chats de groupe et DM (privé) ; il n'existe pas de concept de « chat staff ».
351
- - **Révocation chatType** — uniquement `bot` ou `group` ; pas de chatType `staff`.
351
+ - **Révocation chatType** — uniquement `robot` ou `group` ; pas de chatType `staff`.
352
352
  - **Pas de sysMsg sur révocation** — l'API accepte `sysMsg` mais ne l'affiche pas.
353
353
  - **Pas de deleteMessage** — l'API retourne l'erreur 10000 ; la suppression n'est pas disponible.
354
354
  - **appArticles** — utilise le champ `summary` (pas `description`).
@@ -432,7 +432,7 @@ Voir [Documentation des appareils OpenClaw](https://docs.openclaw.ai/cli/devices
432
432
 
433
433
  ### "Le client mobile ne permet pas de voir les identifiants"
434
434
 
435
- Utilisez uniquement le **client Lansenger (desktop)**. L'application mobile n'affiche pas les identifiants du bot.
435
+ Utilisez uniquement le **client Lansenger (desktop)**. L'application mobile n'affiche pas les identifiants du robot.
436
436
 
437
437
  ### "No binding for botId"
438
438
 
package/README.zhHant.md CHANGED
@@ -22,7 +22,7 @@
22
22
  - **入站媒體處理**——下載圖片/檔案/語音,偵測副檔名,向代理提供檔案路徑
23
23
  - **訊息撤回**——撤回已傳送的訊息(chatType 僅支援 bot 與 group)
24
24
  - **自動啟動**——閘道啟動時自動連接所有已設定的機器人帳戶
25
- - **入站防抖合併**——利用 OpenClaw 的 `messages.inbound.debounceMs` 配置,合併同一發送者的連續快速訊息
25
+ - **入站防抖合併**——利用 OpenClaw 的 `messages.inbound.debounceMs` 配置,合併同一傳送者的連續快速訊息
26
26
  - **確認訊息**——在代理處理前傳送「收到,正在處理...」確認訊息,代理回覆後自動撤回,語言自動偵測
27
27
  - **零核心修改**——純插件模式,`git diff HEAD` 保持 PRISTINE
28
28
 
@@ -50,13 +50,13 @@
50
50
 
51
51
  | 工具 | 說明 |
52
52
  |------|------|
53
- | `lansenger_send_text` | 發送純文字訊息,不支援 Markdown |
54
- | `lansenger_send_format_text` | 發送 Markdown 格式文字,支援 @提及 |
55
- | `lansenger_send_file` | 發送檔案/圖片/影片/語音(工作區或外部路徑) |
56
- | `lansenger_send_image_url` | 透過 URL 發送圖片 |
57
- | `lansenger_send_link_card` | 發送富連結預覽卡片 |
58
- | `lansenger_send_app_card` | 發送互動/審批卡片 |
59
- | `lansenger_send_app_articles` | 發送多文章卡片 |
53
+ | `lansenger_send_text` | 傳送純文字訊息,不支援 Markdown |
54
+ | `lansenger_send_format_text` | 傳送 Markdown 格式文字,支援 @提及 |
55
+ | `lansenger_send_file` | 傳送檔案/圖片/影片/語音(工作區或外部路徑) |
56
+ | `lansenger_send_image_url` | 透過 URL 傳送圖片 |
57
+ | `lansenger_send_link_card` | 傳送富連結預覽卡片 |
58
+ | `lansenger_send_app_card` | 傳送互動/審批卡片 |
59
+ | `lansenger_send_app_articles` | 傳送多文章卡片 |
60
60
  | `lansenger_update_dynamic_card` | 原地更新動態卡片狀態 |
61
61
  | `lansenger_revoke_message` | 撤回已傳送的訊息 |
62
62
  | `lansenger_query_groups` | 查詢可用群組 |
@@ -201,7 +201,7 @@ openclaw pairing approve lansenger <配對碼>
201
201
 
202
202
  | 欄位 | 說明 | 預設值 |
203
203
  |------|------|--------|
204
- | `messages.inbound.debounceMs` | 全域防抖視窗(毫秒);同一發送者在視窗內的連續訊息會被合併 | `0`(停用) |
204
+ | `messages.inbound.debounceMs` | 全域防抖視窗(毫秒);同一傳送者在視窗內的連續訊息會被合併 | `0`(停用) |
205
205
  | `messages.inbound.byChannel.lansenger` | 藍信頻道專屬覆蓋(優先於全域) | — |
206
206
  | `messages.queue.mode` | 代理處理中的佇列模式:`steer`、`followup`、`collect`、`queue`、`interrupt` | `steer`(推薦) |
207
207
 
@@ -14,7 +14,7 @@
14
14
  - **即時訊息** — 透過 WebSocket 長連線實現
15
15
  - **多機械人支援** — 將多個藍信機械人綁定至不同的 OpenClaw 代理
16
16
  - **Markdown 支援** — 使用 `formatText` msgType(預設)
17
- - **檔案/圖片/語音附件** — 透過 `text` msgType 上傳媒體
17
+ - **檔案/圖片/語音附件** — 透過 `text` msgType 上載媒體
18
18
  - **審批卡片**——互動式審批流程,支援原地狀態更新(待審批 → 已通過/已拒絕)
19
19
  - **語言偵測** — 自動偵測使用者語言,提供本地化回應
20
20
  - **msgTarget 自動路由** — 所有發送方法自動路由至群組聊天或私聊(DM)API;無需分別的群組/私聊方法
@@ -83,7 +83,7 @@ openclaw gateway restart
83
83
 
84
84
  > **可選**:安裝 `lansenger-cli` 作為 CLI 替代方案:`pipx install lansenger-cli`。
85
85
 
86
- > **自訂閘道**:企業私有化部署(如奇安信)需在設定後透過 `openclaw.json` 或環境變數設定 `apiGatewayUrl` — 見[可選設定](#可選設定)。
86
+ > **自訂網關**:企業私有化部署(如奇安信)需在設定後透過 `openclaw.json` 或環境變數設定 `apiGatewayUrl` — 見[可選設定](#可選設定)。
87
87
 
88
88
  ### 開發安裝(本地連結)
89
89
 
@@ -180,7 +180,7 @@ openclaw pairing approve lansenger <配對碼>
180
180
  | `groups` | 羣級設定(requireMention、enabled、allowFrom) | — |
181
181
  | `ackMessage` | 在代理處理前發送確認訊息 | `true` |
182
182
  | `revokeAckMessage` | 代理回覆遞送後自動撤回確認訊息。設為 `false` 則保留確認訊息可見(有些使用者偏好看到確認訊息而非撤回的系統通知) | `false` |
183
- | `mediaLocalRoots` | 透過媒體傳送本地檔案時允許的根目錄列表;空陣列預設為工作區 + `/tmp` | `[cwd, /tmp]` |
183
+ | `mediaLocalRoots` | 透過媒體發送本地檔案時允許的根目錄列表;空陣列預設為工作區 + `/tmp` | `[cwd, /tmp]` |
184
184
  | `ackMessageTextZh` | 中文確認訊息文案 | `收到,正在處理...` |
185
185
  | `ackMessageTextEn` | 英文確認訊息文案 | `Received, processing...` |
186
186
 
@@ -8,7 +8,6 @@ import { coerceSecretRef } from "openclaw/plugin-sdk/secret-ref-runtime";
8
8
  import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-policy";
9
9
  import { chunkMarkdownTextWithMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
10
10
  import * as fs from "node:fs/promises";
11
- import * as fsSync from "node:fs";
12
11
  import * as os from "node:os";
13
12
  import * as path from "node:path";
14
13
  import * as crypto from "node:crypto";
@@ -16,51 +15,13 @@ import { LansengerClient, DEFAULT_API_GATEWAY_URL } from "./client.js";
16
15
  import { createMessageReceiptFromOutboundResults, createChannelMessageAdapterFromOutbound, } from "openclaw/plugin-sdk/channel-outbound";
17
16
  import { getRunningClient, getLastInboundTime, stripOpenClawUuidSuffix, gatewayStartAccount, gatewayStopAccount } from "./runtime.js";
18
17
  import { lansengerSetupWizard } from "./setup-wizard.js";
18
+ import { PersistentStore } from "./persistent-store.js";
19
19
  const log = createSubsystemLogger("lansenger");
20
20
  const LANSENGER_TEXT_CHUNK_LIMIT = 4000;
21
21
  const APPROVAL_CARD_FILE = path.join(os.homedir(), ".openclaw", "lansenger-approval-cards.json");
22
- class PersistentApprovalCardStore {
23
- data = new Map();
22
+ class PersistentApprovalCardStore extends PersistentStore {
24
23
  constructor() {
25
- this.load();
26
- }
27
- load() {
28
- try {
29
- const raw = fsSync.readFileSync(APPROVAL_CARD_FILE, "utf-8");
30
- const parsed = JSON.parse(raw);
31
- if (parsed && typeof parsed === "object") {
32
- for (const [k, v] of Object.entries(parsed)) {
33
- this.data.set(k, v);
34
- }
35
- }
36
- }
37
- catch { }
38
- }
39
- save() {
40
- try {
41
- const dir = path.dirname(APPROVAL_CARD_FILE);
42
- fsSync.mkdirSync(dir, { recursive: true });
43
- const obj = {};
44
- for (const [k, v] of this.data)
45
- obj[k] = v;
46
- fsSync.writeFileSync(APPROVAL_CARD_FILE, JSON.stringify(obj), "utf-8");
47
- }
48
- catch (e) {
49
- log.warn(`failed to persist approval cards: ${e instanceof Error ? e.message : String(e)}`);
50
- }
51
- }
52
- get(chatId) { return this.data.get(chatId); }
53
- set(chatId, info) {
54
- this.data.set(chatId, info);
55
- this.save();
56
- }
57
- delete(chatId) {
58
- this.data.delete(chatId);
59
- this.save();
60
- }
61
- clear() {
62
- this.data.clear();
63
- this.save();
24
+ super(APPROVAL_CARD_FILE, "approval cards");
64
25
  }
65
26
  }
66
27
  const pendingApprovalCards = new PersistentApprovalCardStore();
@@ -456,14 +417,14 @@ const chatPlugin = createChatChannelPlugin({
456
417
  sendText: async (ctx) => {
457
418
  const sessionKey = ctx.sessionKey;
458
419
  const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
459
- const client = makeClient(account);
420
+ const client = getRunningClient() ?? makeClient(account);
460
421
  const result = await client.sendFormatText(ctx.to, ctx.text);
461
422
  return toOutboundResult(result.messageId, sessionKey, ctx.to);
462
423
  },
463
424
  sendMedia: async (ctx) => {
464
425
  const sessionKey = ctx.sessionKey;
465
426
  const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
466
- const client = makeClient(account);
427
+ const client = getRunningClient() ?? makeClient(account);
467
428
  const caption = ctx.text ?? "";
468
429
  if (ctx.mediaUrl && /^https?:\/\//i.test(ctx.mediaUrl)) {
469
430
  const result = await client.sendImageUrl(ctx.to, ctx.mediaUrl, caption, account.dangerouslyAllowPrivateNetwork);
@@ -523,7 +484,7 @@ const chatPlugin = createChatChannelPlugin({
523
484
  const cardInfo = pendingApprovalCards.get(chatId);
524
485
  if (cardInfo?.messageId) {
525
486
  const account = resolveAccount(params.cfg, target?.accountId ?? undefined);
526
- const client = makeClient(account);
487
+ const client = getRunningClient() ?? makeClient(account);
527
488
  const status = payload?.text?.includes("✅") ? "approved" : "denied";
528
489
  try {
529
490
  await client.updateCardStatus(cardInfo.messageId, status, cardInfo.lang);
@@ -546,7 +507,7 @@ const chatPlugin = createChatChannelPlugin({
546
507
  sendFormattedText: async (ctx) => {
547
508
  const sessionKey = ctx.sessionKey;
548
509
  const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
549
- const client = makeClient(account);
510
+ const client = getRunningClient() ?? makeClient(account);
550
511
  const result = await client.sendFormatText(ctx.to, ctx.text);
551
512
  return [{ channel: "lansenger", messageId: result.messageId ?? "", sessionKey }];
552
513
  },
@@ -572,7 +533,7 @@ const chatPlugin = createChatChannelPlugin({
572
533
  const lansengerOutboundBridge = {
573
534
  sendText: async (ctx) => {
574
535
  const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
575
- const client = makeClient(account);
536
+ const client = getRunningClient() ?? makeClient(account);
576
537
  const result = await client.sendFormatText(ctx.to, ctx.text);
577
538
  const receipt = result.messageId
578
539
  ? createMessageReceiptFromOutboundResults({ results: [{ messageId: result.messageId, chatId: ctx.to }], kind: "text", sentAt: Date.now() })
@@ -581,7 +542,7 @@ const lansengerOutboundBridge = {
581
542
  },
582
543
  sendMedia: async (ctx) => {
583
544
  const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
584
- const client = makeClient(account);
545
+ const client = getRunningClient() ?? makeClient(account);
585
546
  const caption = ctx.text ?? "";
586
547
  if (ctx.mediaUrl && /^https?:\/\//i.test(ctx.mediaUrl)) {
587
548
  const result = await client.sendImageUrl(ctx.to, ctx.mediaUrl, caption, account.dangerouslyAllowPrivateNetwork);
@@ -630,7 +591,7 @@ const lansengerOutboundBridge = {
630
591
  },
631
592
  sendPayload: async (ctx) => {
632
593
  const account = resolveAccount(ctx.cfg, ctx.accountId ?? undefined);
633
- const client = makeClient(account);
594
+ const client = getRunningClient() ?? makeClient(account);
634
595
  const payload = ctx.payload;
635
596
  const hasCodeBlock = payload?.text && /```/.test(payload.text);
636
597
  if (payload?.text && !payload?.mediaUrl && !payload?.presentation) {