@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 +81 -44
- package/LICENSE +1 -1
- package/README.fr.md +15 -15
- package/README.zhHant.md +9 -9
- package/README.zhHantHK.md +3 -3
- package/dist/src/channel.js +10 -49
- package/dist/src/channel.js.map +1 -1
- package/dist/src/client.js +1 -1
- package/dist/src/client.js.map +1 -1
- package/dist/src/runtime.js +33 -54
- package/dist/src/runtime.js.map +1 -1
- package/package.json +2 -2
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
> **Compatible with OpenClaw `^2026.5.28`** (tested against `2026.5.28`).
|
|
38
88
|
|
|
39
|
-
-
|
|
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
|
-
|
|
91
|
+
## [3.12.2] - 2026-06-03
|
|
51
92
|
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
+
> **Compatible with OpenClaw `^2026.5.28`** (tested against `2026.5.28`).
|
|
65
102
|
|
|
66
|
-
- **
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
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
|
-
|
|
109
|
+
## [3.12.0] - 2026-06-03
|
|
72
110
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- **
|
|
76
|
-
-
|
|
77
|
-
-
|
|
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
|
-
|
|
157
|
+
- Fix apiGatewayUrl lint check to skip when accounts have gateway set.
|
|
158
|
+
|
|
159
|
+
## [3.8.1] - 2026-05-20
|
|
124
160
|
|
|
125
|
-
|
|
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
|
|
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-
|
|
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 :
|
|
24
|
-
- **Démarrage automatique** — la passerelle connecte automatiquement tous les comptes de
|
|
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
|
|
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
|
|
120
|
-
| `LANSENGER_APP_SECRET` | App Secret du
|
|
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
|
|
169
|
-
| `appSecret` | App Secret du
|
|
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-
|
|
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
|
|
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-
|
|
211
|
+
### Configuration multi-robot
|
|
212
212
|
|
|
213
|
-
Pour ajouter plusieurs
|
|
213
|
+
Pour ajouter plusieurs robots, utilisez `openclaw config set` avec la structure `accounts` :
|
|
214
214
|
|
|
215
215
|
```bash
|
|
216
|
-
# Ajouter un deuxième
|
|
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 `
|
|
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
|
|
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` |
|
|
54
|
-
| `lansenger_send_format_text` |
|
|
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` |
|
|
204
|
+
| `messages.inbound.debounceMs` | 全域防抖視窗(毫秒);同一傳送者在視窗內的連續訊息會被合併 | `0`(停用) |
|
|
205
205
|
| `messages.inbound.byChannel.lansenger` | 藍信頻道專屬覆蓋(優先於全域) | — |
|
|
206
206
|
| `messages.queue.mode` | 代理處理中的佇列模式:`steer`、`followup`、`collect`、`queue`、`interrupt` | `steer`(推薦) |
|
|
207
207
|
|
package/README.zhHantHK.md
CHANGED
|
@@ -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
|
-
>
|
|
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` |
|
|
183
|
+
| `mediaLocalRoots` | 透過媒體發送本地檔案時允許的根目錄列表;空陣列預設為工作區 + `/tmp` | `[cwd, /tmp]` |
|
|
184
184
|
| `ackMessageTextZh` | 中文確認訊息文案 | `收到,正在處理...` |
|
|
185
185
|
| `ackMessageTextEn` | 英文確認訊息文案 | `Received, processing...` |
|
|
186
186
|
|
package/dist/src/channel.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|