@ascegu/teamily 1.0.15 → 1.0.16
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/README.md +92 -46
- package/package.json +1 -1
- package/src/channel.ts +49 -6
package/README.md
CHANGED
|
@@ -1,41 +1,54 @@
|
|
|
1
1
|
# Teamily Channel Plugin for OpenClaw
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Integrates [Teamily](https://teamily.ai/) (based on OpenIM) with OpenClaw as a self-hosted team messaging channel.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @ascegu/teamily
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Or update an existing install:
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
openclaw plugins
|
|
14
|
+
openclaw plugins update teamily
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
> **Note:** The plugin update command uses the plugin ID `teamily`, not the npm package name.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
## Configuration
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
### Interactive Setup
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
24
|
openclaw channel configure teamily
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
### Server Settings
|
|
28
|
+
|
|
29
|
+
| Field | Description | Default |
|
|
30
|
+
|---------------|--------------------------|---------------------------|
|
|
31
|
+
| `platformUrl` | Teamily platform URL | `http://localhost:10002` |
|
|
32
|
+
| `apiURL` | Teamily REST API URL | `http://localhost:10002` |
|
|
33
|
+
| `wsURL` | Teamily WebSocket URL | `ws://localhost:10001` |
|
|
34
|
+
|
|
35
|
+
### Bot Account Settings
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
| Field | Required | Description |
|
|
38
|
+
|------------|----------|---------------------------------|
|
|
39
|
+
| `userID` | Yes | User ID for the bot account |
|
|
40
|
+
| `token` | Yes | User token for authentication |
|
|
41
|
+
| `nickname` | No | Display nickname |
|
|
42
|
+
| `faceURL` | No | Avatar URL |
|
|
30
43
|
|
|
31
|
-
###
|
|
44
|
+
### DM Security
|
|
32
45
|
|
|
33
|
-
|
|
46
|
+
Per-account or channel-level DM security can be configured:
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
| Field | Description |
|
|
49
|
+
|--------------------|------------------------------------------------------|
|
|
50
|
+
| `dm.policy` | DM security policy (`pairing`, `allowlist`, `open`) |
|
|
51
|
+
| `dm.allowFrom` | List of allowed sender IDs |
|
|
39
52
|
|
|
40
53
|
### Example Configuration
|
|
41
54
|
|
|
@@ -44,27 +57,29 @@ channels:
|
|
|
44
57
|
teamily:
|
|
45
58
|
enabled: true
|
|
46
59
|
server:
|
|
47
|
-
platformUrl: http://
|
|
48
|
-
apiURL: http://
|
|
49
|
-
wsURL: ws://
|
|
60
|
+
platformUrl: http://your-server:10002
|
|
61
|
+
apiURL: http://your-server:10002
|
|
62
|
+
wsURL: ws://your-server:10001
|
|
50
63
|
accounts:
|
|
51
64
|
default:
|
|
52
|
-
userID: "
|
|
53
|
-
token: "
|
|
65
|
+
userID: "bot-user-id"
|
|
66
|
+
token: "bot-token"
|
|
54
67
|
nickname: "OpenClaw Bot"
|
|
68
|
+
dm:
|
|
69
|
+
policy: open
|
|
55
70
|
```
|
|
56
71
|
|
|
72
|
+
Multiple accounts are supported under the `accounts` key.
|
|
73
|
+
|
|
57
74
|
## Usage
|
|
58
75
|
|
|
59
|
-
### Send
|
|
76
|
+
### Send Messages
|
|
60
77
|
|
|
61
78
|
```bash
|
|
79
|
+
# Send to a user
|
|
62
80
|
openclaw message send teamily:user:userID "Hello!"
|
|
63
|
-
```
|
|
64
81
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```bash
|
|
82
|
+
# Send to a group
|
|
68
83
|
openclaw message send teamily:group:groupID "Hello group!"
|
|
69
84
|
```
|
|
70
85
|
|
|
@@ -74,34 +89,65 @@ openclaw message send teamily:group:groupID "Hello group!"
|
|
|
74
89
|
openclaw message send teamily:user:userID --media /path/to/image.jpg
|
|
75
90
|
```
|
|
76
91
|
|
|
77
|
-
|
|
92
|
+
Supported media types are auto-detected by file extension:
|
|
78
93
|
|
|
79
|
-
|
|
94
|
+
| Extension | Type |
|
|
95
|
+
|------------------------------|-------|
|
|
96
|
+
| `.jpg`, `.png`, `.gif`, etc. | Image |
|
|
97
|
+
| `.mp4`, `.mov`, `.webm` | Video |
|
|
98
|
+
| `.mp3`, `.m4a`, `.wav` | Audio |
|
|
99
|
+
| `.pdf`, `.doc`, `.docx`, `.zip` | File |
|
|
80
100
|
|
|
81
|
-
|
|
101
|
+
## Group Chat Behavior
|
|
82
102
|
|
|
83
|
-
|
|
103
|
+
- The bot only **replies** to messages where it is **@-mentioned** (`@BotName`).
|
|
104
|
+
- Non-@-mention group messages are **buffered in memory** (up to 50 per group) and injected as conversation context when an @-mention triggers a reply. The buffer is cleared after each dispatch so context doesn't repeat.
|
|
105
|
+
- In direct messages, the bot always replies.
|
|
106
|
+
- Both regular groups (`sessionType=3`) and super groups (`sessionType=2`) are supported.
|
|
84
107
|
|
|
85
|
-
|
|
108
|
+
## Capabilities
|
|
86
109
|
|
|
87
|
-
|
|
110
|
+
| Feature | Supported |
|
|
111
|
+
|---------------------------|-----------|
|
|
112
|
+
| Direct messaging | Yes |
|
|
113
|
+
| Group messaging | Yes |
|
|
114
|
+
| Text messages | Yes |
|
|
115
|
+
| Media (image/video/audio/file) | Yes |
|
|
116
|
+
| @-mention gating (groups) | Yes |
|
|
117
|
+
| Group history context (50 msg buffer) | Yes |
|
|
118
|
+
| WebSocket real-time monitoring | Yes |
|
|
119
|
+
| Automatic reconnection | Yes |
|
|
120
|
+
| Connection health probes | Yes |
|
|
121
|
+
| Reactions | No |
|
|
122
|
+
| Threads | No |
|
|
123
|
+
| Polls | No |
|
|
88
124
|
|
|
89
|
-
|
|
125
|
+
## Setting Up Teamily Server
|
|
90
126
|
|
|
91
|
-
|
|
127
|
+
1. **Deploy OpenIM server** -- follow the [OpenIM Quick Start](https://docs.openim.io/guides/gettingStarted/introduction) guide.
|
|
128
|
+
2. **Create a bot user** -- use the Teamily management API to create a bot account and obtain its `userID` and `token`.
|
|
129
|
+
3. **Configure OpenClaw** -- run `openclaw channel configure teamily` and provide the server URLs and bot credentials.
|
|
92
130
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
131
|
+
## Architecture
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
index.ts Plugin entry point (registers channel with OpenClaw)
|
|
135
|
+
src/
|
|
136
|
+
channel.ts Channel plugin definition (gateway, outbound, config, security)
|
|
137
|
+
monitor.ts WebSocket monitor using @openim/client-sdk (inbound + send)
|
|
138
|
+
types.ts Shared types and constants (session types, content types, message shapes)
|
|
139
|
+
config-schema.ts Zod config schema (server, accounts, DM security)
|
|
140
|
+
accounts.ts Account resolution and listing
|
|
141
|
+
normalize.ts Target ID normalization (user:ID, group:ID)
|
|
142
|
+
probe.ts Health check via REST API
|
|
143
|
+
runtime.ts Plugin runtime store
|
|
144
|
+
send.ts REST API message/media send (fallback path)
|
|
145
|
+
```
|
|
100
146
|
|
|
101
|
-
##
|
|
147
|
+
## Compatibility
|
|
102
148
|
|
|
103
|
-
|
|
149
|
+
Designed for OpenIM API v2/v3. Requires `@openim/client-sdk` ^3.8.3.
|
|
104
150
|
|
|
105
151
|
## License
|
|
106
152
|
|
|
107
|
-
|
|
153
|
+
MIT
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyAccountNameToChannelSection,
|
|
3
3
|
buildChannelConfigSchema,
|
|
4
|
+
buildPendingHistoryContextFromMap,
|
|
5
|
+
clearHistoryEntriesIfEnabled,
|
|
4
6
|
DEFAULT_ACCOUNT_ID,
|
|
7
|
+
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
5
8
|
deleteAccountFromConfigSection,
|
|
6
9
|
normalizeAccountId,
|
|
7
10
|
PAIRING_APPROVED_MESSAGE,
|
|
11
|
+
recordPendingHistoryEntryIfEnabled,
|
|
8
12
|
setAccountEnabledInConfigSection,
|
|
9
13
|
type ChannelPlugin,
|
|
10
14
|
type ChannelOutboundContext,
|
|
11
15
|
type ChannelStatusIssue,
|
|
16
|
+
type HistoryEntry,
|
|
12
17
|
} from "openclaw/plugin-sdk";
|
|
13
18
|
import {
|
|
14
19
|
buildAccountScopedDmSecurityPolicy,
|
|
@@ -238,6 +243,8 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
238
243
|
log?.info?.(`Starting Teamily channel (account: ${accountId})`);
|
|
239
244
|
|
|
240
245
|
const MEDIA_MAX_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
246
|
+
const historyLimit = DEFAULT_GROUP_HISTORY_LIMIT; // 50 messages per group
|
|
247
|
+
const groupHistories = new Map<string, HistoryEntry[]>();
|
|
241
248
|
|
|
242
249
|
const stopFn = startTeamilyMonitoring(account, async (message) => {
|
|
243
250
|
const rt = getTeamilyRuntime();
|
|
@@ -245,17 +252,33 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
245
252
|
|
|
246
253
|
const isGroup = isGroupSession(message.sessionType);
|
|
247
254
|
const from = message.sendID;
|
|
255
|
+
const rawText = message.content?.text || "";
|
|
248
256
|
|
|
249
257
|
log?.info?.(
|
|
250
258
|
`[${accountId}] Incoming message: sessionType=${message.sessionType}, isGroup=${isGroup}, ` +
|
|
251
259
|
`from=${from}, recvID=${message.recvID}, isAtSelf=${message.isAtSelf ?? false}`,
|
|
252
260
|
);
|
|
253
261
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
262
|
+
const historyKey = isGroup ? `teamily:group:${message.recvID}` : undefined;
|
|
263
|
+
|
|
264
|
+
// In group chats, buffer non-@-mention messages for context and skip dispatch.
|
|
265
|
+
// Only @-mention messages are dispatched to the agent, with the buffer injected.
|
|
266
|
+
if (isGroup && !message.isAtSelf) {
|
|
267
|
+
if (historyKey && rawText) {
|
|
268
|
+
recordPendingHistoryEntryIfEnabled({
|
|
269
|
+
historyMap: groupHistories,
|
|
270
|
+
historyKey,
|
|
271
|
+
limit: historyLimit,
|
|
272
|
+
entry: {
|
|
273
|
+
sender: from,
|
|
274
|
+
body: rawText,
|
|
275
|
+
timestamp: message.sendTime,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
257
281
|
|
|
258
|
-
const text = message.content?.text || "";
|
|
259
282
|
const sessionKey = isGroup ? `teamily:group:${message.recvID}` : `teamily:${from}`;
|
|
260
283
|
|
|
261
284
|
let mediaUrl: string | undefined;
|
|
@@ -287,9 +310,21 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
287
310
|
}
|
|
288
311
|
}
|
|
289
312
|
|
|
313
|
+
// For group @-mention messages, prepend buffered history as context.
|
|
314
|
+
const body =
|
|
315
|
+
isGroup && historyKey
|
|
316
|
+
? buildPendingHistoryContextFromMap({
|
|
317
|
+
historyMap: groupHistories,
|
|
318
|
+
historyKey,
|
|
319
|
+
limit: historyLimit,
|
|
320
|
+
currentMessage: rawText,
|
|
321
|
+
formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
|
|
322
|
+
})
|
|
323
|
+
: rawText;
|
|
324
|
+
|
|
290
325
|
const replyTarget = isGroup ? `group:${message.recvID}` : from;
|
|
291
326
|
const msgCtx = {
|
|
292
|
-
Body:
|
|
327
|
+
Body: body,
|
|
293
328
|
From: from,
|
|
294
329
|
To: account.userID,
|
|
295
330
|
SessionKey: sessionKey,
|
|
@@ -309,7 +344,6 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
309
344
|
cfg: currentCfg,
|
|
310
345
|
dispatcherOptions: {
|
|
311
346
|
deliver: async (payload: { text?: string; body?: string }) => {
|
|
312
|
-
if (!shouldReply) return;
|
|
313
347
|
const replyText = payload?.text ?? payload?.body;
|
|
314
348
|
if (replyText) {
|
|
315
349
|
const monitor = getTeamilyMonitor(accountId);
|
|
@@ -324,6 +358,15 @@ export const teamilyPlugin: ChannelPlugin<ResolvedTeamilyAccount> = {
|
|
|
324
358
|
},
|
|
325
359
|
},
|
|
326
360
|
});
|
|
361
|
+
|
|
362
|
+
// Clear history buffer after dispatch so context doesn't repeat.
|
|
363
|
+
if (isGroup && historyKey) {
|
|
364
|
+
clearHistoryEntriesIfEnabled({
|
|
365
|
+
historyMap: groupHistories,
|
|
366
|
+
historyKey,
|
|
367
|
+
limit: historyLimit,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
327
370
|
});
|
|
328
371
|
|
|
329
372
|
// Respect abort signal from the gateway framework
|