openclaw-pincer 0.2.5 → 0.2.7
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/dist/src/channel.d.ts +9 -0
- package/dist/src/channel.js +62 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/src/channel.ts +75 -6
package/dist/src/channel.d.ts
CHANGED
|
@@ -7,8 +7,11 @@ export interface PincerConfig {
|
|
|
7
7
|
baseUrl: string;
|
|
8
8
|
apiKey: string;
|
|
9
9
|
agentId: string;
|
|
10
|
+
agentName?: string;
|
|
10
11
|
rooms?: string[];
|
|
11
12
|
pollMs?: number;
|
|
13
|
+
requireMention?: boolean;
|
|
14
|
+
historyLimit?: number;
|
|
12
15
|
}
|
|
13
16
|
export declare const pincerChannel: {
|
|
14
17
|
id: string;
|
|
@@ -27,6 +30,12 @@ export declare const pincerChannel: {
|
|
|
27
30
|
reactions: boolean;
|
|
28
31
|
threads: boolean;
|
|
29
32
|
};
|
|
33
|
+
agentPrompt: {
|
|
34
|
+
messageToolHints: () => string[];
|
|
35
|
+
};
|
|
36
|
+
groups: {
|
|
37
|
+
resolveRequireMention: (params: any) => boolean;
|
|
38
|
+
};
|
|
30
39
|
config: {
|
|
31
40
|
listAccountIds: (cfg: any) => string[];
|
|
32
41
|
resolveAccount: (_cfg: any, accountId: string) => {
|
package/dist/src/channel.js
CHANGED
|
@@ -28,13 +28,41 @@ async function sendToPincerRoom(config, roomId, agentId, text) {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
async function sendToPincerDm(config, peerId, text) {
|
|
31
|
-
await pincerFetch(config.baseUrl, config.apiKey,
|
|
31
|
+
await pincerFetch(config.baseUrl, config.apiKey, "/messages/send", {
|
|
32
32
|
method: "POST",
|
|
33
|
-
body: JSON.stringify({
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
from_agent_id: config.agentId,
|
|
35
|
+
to_agent_id: peerId,
|
|
36
|
+
payload: { text },
|
|
37
|
+
}),
|
|
34
38
|
});
|
|
35
39
|
}
|
|
40
|
+
/** Check if a room message mentions this agent (by agentId or agentName). */
|
|
41
|
+
function isMentioned(text, config) {
|
|
42
|
+
if (text.includes(config.agentId))
|
|
43
|
+
return true;
|
|
44
|
+
if (config.agentName && text.includes(config.agentName))
|
|
45
|
+
return true;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/** Fetch recent room messages to use as conversation history. */
|
|
49
|
+
async function fetchRoomHistory(config, roomId, limit) {
|
|
50
|
+
try {
|
|
51
|
+
const msgs = await pincerFetch(config.baseUrl, config.apiKey, `/rooms/${roomId}/messages?limit=${limit}`);
|
|
52
|
+
return msgs.map((m) => ({
|
|
53
|
+
sender: m.sender_agent_id ?? "unknown",
|
|
54
|
+
body: m.content ?? "",
|
|
55
|
+
timestamp: m.created_at ? new Date(m.created_at).getTime() : undefined,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
36
62
|
function startRoomPoller(params) {
|
|
37
63
|
const { config, roomId, ctx, signal, pollMs } = params;
|
|
64
|
+
const requireMention = config.requireMention !== false; // default true
|
|
65
|
+
const historyLimit = config.historyLimit ?? 10;
|
|
38
66
|
let lastId = null;
|
|
39
67
|
const poll = async () => {
|
|
40
68
|
if (signal.aborted)
|
|
@@ -50,6 +78,7 @@ function startRoomPoller(params) {
|
|
|
50
78
|
}
|
|
51
79
|
const channelRuntime = ctx.channelRuntime;
|
|
52
80
|
for (const msg of msgs) {
|
|
81
|
+
// Skip own messages
|
|
53
82
|
if (msg.sender_agent_id === config.agentId) {
|
|
54
83
|
lastId = msg.id;
|
|
55
84
|
continue;
|
|
@@ -60,7 +89,14 @@ function startRoomPoller(params) {
|
|
|
60
89
|
continue;
|
|
61
90
|
}
|
|
62
91
|
const messageText = msg.content ?? "";
|
|
92
|
+
// Require mention in group rooms (default: on)
|
|
93
|
+
if (requireMention && !isMentioned(messageText, config)) {
|
|
94
|
+
lastId = msg.id;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
63
97
|
const senderId = msg.sender_agent_id ?? "unknown";
|
|
98
|
+
// Fetch recent history for context
|
|
99
|
+
const history = await fetchRoomHistory(config, roomId, historyLimit);
|
|
64
100
|
const route = channelRuntime.routing.resolveAgentRoute({
|
|
65
101
|
cfg: ctx.cfg,
|
|
66
102
|
channel: "pincer",
|
|
@@ -75,6 +111,7 @@ function startRoomPoller(params) {
|
|
|
75
111
|
SessionKey: route.sessionKey,
|
|
76
112
|
Channel: "pincer",
|
|
77
113
|
AccountId: ctx.accountId,
|
|
114
|
+
InboundHistory: history,
|
|
78
115
|
},
|
|
79
116
|
cfg: ctx.cfg,
|
|
80
117
|
dispatcherOptions: {
|
|
@@ -82,6 +119,15 @@ function startRoomPoller(params) {
|
|
|
82
119
|
await sendToPincerRoom(config, roomId, config.agentId, payload.text);
|
|
83
120
|
},
|
|
84
121
|
},
|
|
122
|
+
replyOptions: {
|
|
123
|
+
extraSystemPrompt: [
|
|
124
|
+
"You are in a Pincer agent room (group chat). Rules:",
|
|
125
|
+
"- Only respond when directly mentioned or asked a question.",
|
|
126
|
+
"- Keep responses concise and on-topic.",
|
|
127
|
+
"- Do NOT engage in idle chit-chat or filler responses.",
|
|
128
|
+
"- Do NOT respond to every message — quality over quantity.",
|
|
129
|
+
].join("\n"),
|
|
130
|
+
},
|
|
85
131
|
});
|
|
86
132
|
lastId = msg.id;
|
|
87
133
|
}
|
|
@@ -177,6 +223,18 @@ export const pincerChannel = {
|
|
|
177
223
|
reactions: false,
|
|
178
224
|
threads: false,
|
|
179
225
|
},
|
|
226
|
+
agentPrompt: {
|
|
227
|
+
messageToolHints: () => [
|
|
228
|
+
"- In Pincer rooms, only respond when @mentioned or directly asked. No idle chit-chat.",
|
|
229
|
+
"- In Pincer DMs, respond normally.",
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
groups: {
|
|
233
|
+
resolveRequireMention: (params) => {
|
|
234
|
+
const config = resolveConfig(params.cfg);
|
|
235
|
+
return config.requireMention !== false; // default true
|
|
236
|
+
},
|
|
237
|
+
},
|
|
180
238
|
config: {
|
|
181
239
|
listAccountIds: (cfg) => {
|
|
182
240
|
const config = resolveConfig(cfg);
|
|
@@ -196,14 +254,12 @@ export const pincerChannel = {
|
|
|
196
254
|
return;
|
|
197
255
|
}
|
|
198
256
|
const signal = ctx.abortSignal;
|
|
199
|
-
const pollMs = config.pollMs ??
|
|
257
|
+
const pollMs = config.pollMs ?? 15000;
|
|
200
258
|
for (const roomId of config.rooms ?? []) {
|
|
201
259
|
startRoomPoller({ config, roomId, ctx, signal, pollMs });
|
|
202
260
|
}
|
|
203
261
|
startDmPoller({ config, ctx, signal, pollMs });
|
|
204
|
-
console.log(`[pincer] Started. Monitoring ${(config.rooms ?? []).length} room(s) + DMs as agent ${config.agentId}`);
|
|
205
|
-
// Keep startAccount alive until the signal fires — OpenClaw treats immediate
|
|
206
|
-
// return as a crash and schedules auto-restart.
|
|
262
|
+
console.log(`[pincer] Started. requireMention=${config.requireMention !== false}. Monitoring ${(config.rooms ?? []).length} room(s) + DMs as agent ${config.agentId}`);
|
|
207
263
|
await new Promise((resolve) => {
|
|
208
264
|
if (signal.aborted) {
|
|
209
265
|
resolve();
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-pincer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Pincer channel plugin for OpenClaw",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
"index.ts",
|
|
39
39
|
"openclaw.plugin.json"
|
|
40
40
|
]
|
|
41
|
-
}
|
|
41
|
+
}
|
package/src/channel.ts
CHANGED
|
@@ -8,8 +8,11 @@ export interface PincerConfig {
|
|
|
8
8
|
baseUrl: string;
|
|
9
9
|
apiKey: string;
|
|
10
10
|
agentId: string;
|
|
11
|
+
agentName?: string; // display name for mention detection (e.g. "蔻儿")
|
|
11
12
|
rooms?: string[];
|
|
12
13
|
pollMs?: number;
|
|
14
|
+
requireMention?: boolean; // default: true — only respond in rooms when @mentioned
|
|
15
|
+
historyLimit?: number; // how many messages to include as context (default: 10)
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
interface PincerMessage {
|
|
@@ -65,12 +68,45 @@ async function sendToPincerDm(
|
|
|
65
68
|
peerId: string,
|
|
66
69
|
text: string
|
|
67
70
|
): Promise<void> {
|
|
68
|
-
await pincerFetch(config.baseUrl, config.apiKey,
|
|
71
|
+
await pincerFetch(config.baseUrl, config.apiKey, "/messages/send", {
|
|
69
72
|
method: "POST",
|
|
70
|
-
body: JSON.stringify({
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
from_agent_id: config.agentId,
|
|
75
|
+
to_agent_id: peerId,
|
|
76
|
+
payload: { text },
|
|
77
|
+
}),
|
|
71
78
|
});
|
|
72
79
|
}
|
|
73
80
|
|
|
81
|
+
/** Check if a room message mentions this agent (by agentId or agentName). */
|
|
82
|
+
function isMentioned(text: string, config: PincerConfig): boolean {
|
|
83
|
+
if (text.includes(config.agentId)) return true;
|
|
84
|
+
if (config.agentName && text.includes(config.agentName)) return true;
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Fetch recent room messages to use as conversation history. */
|
|
89
|
+
async function fetchRoomHistory(
|
|
90
|
+
config: PincerConfig,
|
|
91
|
+
roomId: string,
|
|
92
|
+
limit: number
|
|
93
|
+
): Promise<Array<{ sender: string; body: string; timestamp?: number }>> {
|
|
94
|
+
try {
|
|
95
|
+
const msgs: PincerMessage[] = await pincerFetch(
|
|
96
|
+
config.baseUrl,
|
|
97
|
+
config.apiKey,
|
|
98
|
+
`/rooms/${roomId}/messages?limit=${limit}`
|
|
99
|
+
);
|
|
100
|
+
return msgs.map((m) => ({
|
|
101
|
+
sender: m.sender_agent_id ?? "unknown",
|
|
102
|
+
body: m.content ?? "",
|
|
103
|
+
timestamp: m.created_at ? new Date(m.created_at).getTime() : undefined,
|
|
104
|
+
}));
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
74
110
|
function startRoomPoller(params: {
|
|
75
111
|
config: PincerConfig;
|
|
76
112
|
roomId: string;
|
|
@@ -79,6 +115,8 @@ function startRoomPoller(params: {
|
|
|
79
115
|
pollMs: number;
|
|
80
116
|
}) {
|
|
81
117
|
const { config, roomId, ctx, signal, pollMs } = params;
|
|
118
|
+
const requireMention = config.requireMention !== false; // default true
|
|
119
|
+
const historyLimit = config.historyLimit ?? 10;
|
|
82
120
|
let lastId: string | null = null;
|
|
83
121
|
|
|
84
122
|
const poll = async () => {
|
|
@@ -99,6 +137,7 @@ function startRoomPoller(params: {
|
|
|
99
137
|
|
|
100
138
|
const channelRuntime = ctx.channelRuntime;
|
|
101
139
|
for (const msg of msgs) {
|
|
140
|
+
// Skip own messages
|
|
102
141
|
if (msg.sender_agent_id === config.agentId) {
|
|
103
142
|
lastId = msg.id;
|
|
104
143
|
continue;
|
|
@@ -111,8 +150,18 @@ function startRoomPoller(params: {
|
|
|
111
150
|
}
|
|
112
151
|
|
|
113
152
|
const messageText = msg.content ?? "";
|
|
153
|
+
|
|
154
|
+
// Require mention in group rooms (default: on)
|
|
155
|
+
if (requireMention && !isMentioned(messageText, config)) {
|
|
156
|
+
lastId = msg.id;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
114
160
|
const senderId = msg.sender_agent_id ?? "unknown";
|
|
115
161
|
|
|
162
|
+
// Fetch recent history for context
|
|
163
|
+
const history = await fetchRoomHistory(config, roomId, historyLimit);
|
|
164
|
+
|
|
116
165
|
const route = channelRuntime.routing.resolveAgentRoute({
|
|
117
166
|
cfg: ctx.cfg,
|
|
118
167
|
channel: "pincer",
|
|
@@ -128,6 +177,7 @@ function startRoomPoller(params: {
|
|
|
128
177
|
SessionKey: route.sessionKey,
|
|
129
178
|
Channel: "pincer",
|
|
130
179
|
AccountId: ctx.accountId,
|
|
180
|
+
InboundHistory: history,
|
|
131
181
|
},
|
|
132
182
|
cfg: ctx.cfg,
|
|
133
183
|
dispatcherOptions: {
|
|
@@ -135,6 +185,15 @@ function startRoomPoller(params: {
|
|
|
135
185
|
await sendToPincerRoom(config, roomId, config.agentId, payload.text);
|
|
136
186
|
},
|
|
137
187
|
},
|
|
188
|
+
replyOptions: {
|
|
189
|
+
extraSystemPrompt: [
|
|
190
|
+
"You are in a Pincer agent room (group chat). Rules:",
|
|
191
|
+
"- Only respond when directly mentioned or asked a question.",
|
|
192
|
+
"- Keep responses concise and on-topic.",
|
|
193
|
+
"- Do NOT engage in idle chit-chat or filler responses.",
|
|
194
|
+
"- Do NOT respond to every message — quality over quantity.",
|
|
195
|
+
].join("\n"),
|
|
196
|
+
} as any,
|
|
138
197
|
});
|
|
139
198
|
|
|
140
199
|
lastId = msg.id;
|
|
@@ -248,6 +307,18 @@ export const pincerChannel = {
|
|
|
248
307
|
reactions: false,
|
|
249
308
|
threads: false,
|
|
250
309
|
},
|
|
310
|
+
agentPrompt: {
|
|
311
|
+
messageToolHints: () => [
|
|
312
|
+
"- In Pincer rooms, only respond when @mentioned or directly asked. No idle chit-chat.",
|
|
313
|
+
"- In Pincer DMs, respond normally.",
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
groups: {
|
|
317
|
+
resolveRequireMention: (params: any) => {
|
|
318
|
+
const config = resolveConfig(params.cfg);
|
|
319
|
+
return config.requireMention !== false; // default true
|
|
320
|
+
},
|
|
321
|
+
},
|
|
251
322
|
config: {
|
|
252
323
|
listAccountIds: (cfg: any) => {
|
|
253
324
|
const config = resolveConfig(cfg);
|
|
@@ -267,7 +338,7 @@ export const pincerChannel = {
|
|
|
267
338
|
}
|
|
268
339
|
|
|
269
340
|
const signal: AbortSignal = ctx.abortSignal;
|
|
270
|
-
const pollMs = config.pollMs ??
|
|
341
|
+
const pollMs = config.pollMs ?? 15000;
|
|
271
342
|
|
|
272
343
|
for (const roomId of config.rooms ?? []) {
|
|
273
344
|
startRoomPoller({ config, roomId, ctx, signal, pollMs });
|
|
@@ -276,11 +347,9 @@ export const pincerChannel = {
|
|
|
276
347
|
startDmPoller({ config, ctx, signal, pollMs });
|
|
277
348
|
|
|
278
349
|
console.log(
|
|
279
|
-
`[pincer] Started. Monitoring ${(config.rooms ?? []).length} room(s) + DMs as agent ${config.agentId}`
|
|
350
|
+
`[pincer] Started. requireMention=${config.requireMention !== false}. Monitoring ${(config.rooms ?? []).length} room(s) + DMs as agent ${config.agentId}`
|
|
280
351
|
);
|
|
281
352
|
|
|
282
|
-
// Keep startAccount alive until the signal fires — OpenClaw treats immediate
|
|
283
|
-
// return as a crash and schedules auto-restart.
|
|
284
353
|
await new Promise<void>((resolve) => {
|
|
285
354
|
if (signal.aborted) { resolve(); return; }
|
|
286
355
|
signal.addEventListener("abort", () => resolve(), { once: true });
|