@clawpod/openclaw-plugin 0.1.0 → 0.2.0
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/connection.d.ts +4 -0
- package/dist/connection.js +67 -3
- package/dist/index.js +43 -4
- package/package.json +3 -6
package/dist/connection.d.ts
CHANGED
package/dist/connection.js
CHANGED
|
@@ -4,10 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createConnection = createConnection;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const node_util_1 = require("node:util");
|
|
10
12
|
const ws_1 = __importDefault(require("ws"));
|
|
13
|
+
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
11
14
|
function createConnection(config, handlers) {
|
|
12
15
|
const { logger, workspace } = handlers;
|
|
13
16
|
const conn = {
|
|
@@ -110,16 +113,22 @@ function createConnection(config, handlers) {
|
|
|
110
113
|
}
|
|
111
114
|
case 'message:new': {
|
|
112
115
|
const msg = event.message;
|
|
113
|
-
if (!conn.agentId
|
|
116
|
+
if (!conn.agentId)
|
|
114
117
|
break;
|
|
118
|
+
// Ignore own messages
|
|
119
|
+
if (msg.senderId === conn.agentId)
|
|
120
|
+
break;
|
|
121
|
+
const channelAgents = event.channelAgents;
|
|
122
|
+
msg.channelAgents = channelAgents;
|
|
115
123
|
const isMentioned = msg.content?.includes(`@${config.agentName}`);
|
|
116
124
|
const channelMeta = event.channelMeta;
|
|
117
125
|
const autoReply = channelMeta?.agentAutoReply === true;
|
|
118
126
|
if (isMentioned) {
|
|
119
|
-
logger.info(`[clawpod] @mentioned by ${msg.senderName} in channel`);
|
|
127
|
+
logger.info(`[clawpod] @mentioned by ${msg.senderName} (${msg.senderType}) in channel`);
|
|
120
128
|
conn.onMention(msg);
|
|
121
129
|
}
|
|
122
|
-
else if (autoReply) {
|
|
130
|
+
else if (autoReply && msg.senderType !== 'agent') {
|
|
131
|
+
// autoReply only for human messages to avoid echo loops
|
|
123
132
|
logger.info(`[clawpod] autoReply channel msg from ${msg.senderName}: ${msg.content?.slice(0, 60)}`);
|
|
124
133
|
conn.onMention({ ...msg, autoReply: true });
|
|
125
134
|
}
|
|
@@ -135,6 +144,10 @@ function createConnection(config, handlers) {
|
|
|
135
144
|
handleSkillsList(event, workspace || null, conn);
|
|
136
145
|
break;
|
|
137
146
|
}
|
|
147
|
+
case 'agent:add': {
|
|
148
|
+
handleAgentAdd(event, conn);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
153
|
function handleWorkspaceBrowse(event, wsPath, conn) {
|
|
@@ -263,6 +276,57 @@ function createConnection(config, handlers) {
|
|
|
263
276
|
skills,
|
|
264
277
|
}));
|
|
265
278
|
}
|
|
279
|
+
async function handleAgentAdd(event, conn) {
|
|
280
|
+
const requestId = event.requestId;
|
|
281
|
+
const agentName = event.name;
|
|
282
|
+
const agentWorkspace = event.workspace;
|
|
283
|
+
const identity = event.identity;
|
|
284
|
+
try {
|
|
285
|
+
const wsFlag = agentWorkspace ? ` --workspace ${JSON.stringify(agentWorkspace)}` : '';
|
|
286
|
+
await execAsync(`openclaw agents add ${JSON.stringify(agentName)}${wsFlag}`);
|
|
287
|
+
const identityParts = [];
|
|
288
|
+
if (identity?.name)
|
|
289
|
+
identityParts.push(`--name ${JSON.stringify(identity.name)}`);
|
|
290
|
+
if (identity?.emoji)
|
|
291
|
+
identityParts.push(`--emoji ${JSON.stringify(identity.emoji)}`);
|
|
292
|
+
if (identity?.avatar)
|
|
293
|
+
identityParts.push(`--avatar ${JSON.stringify(identity.avatar)}`);
|
|
294
|
+
if (identityParts.length > 0) {
|
|
295
|
+
await execAsync(`openclaw agents set-identity --agent ${JSON.stringify(agentName)} ${identityParts.join(' ')}`);
|
|
296
|
+
}
|
|
297
|
+
logger.info(`[clawpod] Agent added via CLI: ${agentName}`);
|
|
298
|
+
conn.ws?.send(JSON.stringify({ type: 'agent:add:result', requestId, success: true }));
|
|
299
|
+
// Re-register to refresh agent list in ClawPod
|
|
300
|
+
try {
|
|
301
|
+
const { stdout } = await execAsync('openclaw agents list --json');
|
|
302
|
+
const agentsList = JSON.parse(stdout);
|
|
303
|
+
const mapped = (Array.isArray(agentsList) ? agentsList : []).map((a) => ({
|
|
304
|
+
name: a.identity?.name || a.name || a.id || 'Agent',
|
|
305
|
+
defaultPrompt: 'OpenClaw Agent',
|
|
306
|
+
status: 'online',
|
|
307
|
+
model: 'openclaw',
|
|
308
|
+
}));
|
|
309
|
+
conn.ws?.send(JSON.stringify({
|
|
310
|
+
type: 'machine:register',
|
|
311
|
+
name: `${node_os_1.default.hostname()} (OpenClaw)`,
|
|
312
|
+
mode: 'openclaw',
|
|
313
|
+
workspacePath: workspace || process.cwd(),
|
|
314
|
+
allowedMembers: config.allowedMembers || [],
|
|
315
|
+
agents: mapped.length > 0 ? mapped : [
|
|
316
|
+
{ name: config.agentName, defaultPrompt: 'OpenClaw Agent', status: 'online', model: 'openclaw' },
|
|
317
|
+
],
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
logger.info('[clawpod] Could not refresh agent list after add');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
326
|
+
logger.error(`[clawpod] agent:add failed: ${msg}`);
|
|
327
|
+
conn.ws?.send(JSON.stringify({ type: 'agent:add:result', requestId, success: false, error: msg }));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
266
330
|
// Public API
|
|
267
331
|
return {
|
|
268
332
|
...conn,
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,22 @@ const connection_js_1 = require("./connection.js");
|
|
|
12
12
|
const connections = new Map();
|
|
13
13
|
let _pluginApi = null;
|
|
14
14
|
let pluginRuntime = null;
|
|
15
|
+
const AGENT_REPLY_MAX = 5;
|
|
16
|
+
const AGENT_REPLY_WINDOW_MS = 60_000;
|
|
17
|
+
const agentReplyTracker = new Map();
|
|
18
|
+
function isAgentReplyAllowed(channelId, senderId) {
|
|
19
|
+
const key = `${channelId}:${senderId}`;
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const entry = agentReplyTracker.get(key);
|
|
22
|
+
if (!entry || now > entry.resetAt) {
|
|
23
|
+
agentReplyTracker.set(key, { count: 1, resetAt: now + AGENT_REPLY_WINDOW_MS });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (entry.count >= AGENT_REPLY_MAX)
|
|
27
|
+
return false;
|
|
28
|
+
entry.count++;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
15
31
|
const clawpodChannel = {
|
|
16
32
|
id: 'clawpod',
|
|
17
33
|
meta: {
|
|
@@ -157,7 +173,12 @@ const clawpodChannel = {
|
|
|
157
173
|
});
|
|
158
174
|
},
|
|
159
175
|
onMention: async (msg) => {
|
|
160
|
-
|
|
176
|
+
if (msg.senderType === 'agent' &&
|
|
177
|
+
!isAgentReplyAllowed(msg.channelId, msg.senderId)) {
|
|
178
|
+
logger.info(`[clawpod:${accountId}] Rate limited agent-to-agent reply in ${msg.channelId}`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
logger.info(`[clawpod:${accountId}] @mention by ${msg.senderName} (${msg.senderType})`);
|
|
161
182
|
const clean = msg.content?.replace(`@${config.agentName}`, '').trim();
|
|
162
183
|
await handleInboundMessage({
|
|
163
184
|
cfg,
|
|
@@ -170,6 +191,7 @@ const clawpodChannel = {
|
|
|
170
191
|
messageId: msg.id,
|
|
171
192
|
isDm: false,
|
|
172
193
|
channelId: msg.channelId,
|
|
194
|
+
channelAgents: msg.channelAgents,
|
|
173
195
|
});
|
|
174
196
|
},
|
|
175
197
|
logger,
|
|
@@ -209,8 +231,18 @@ const clawpodChannel = {
|
|
|
209
231
|
},
|
|
210
232
|
};
|
|
211
233
|
exports.clawpodChannel = clawpodChannel;
|
|
234
|
+
function buildAgentRoster(channelAgents, selfName) {
|
|
235
|
+
if (!channelAgents?.length)
|
|
236
|
+
return null;
|
|
237
|
+
const others = channelAgents.filter((a) => a.name !== selfName);
|
|
238
|
+
if (!others.length)
|
|
239
|
+
return null;
|
|
240
|
+
return others
|
|
241
|
+
.map((a) => `- @${a.name}: ${a.defaultPrompt || 'No description'}`)
|
|
242
|
+
.join('\n');
|
|
243
|
+
}
|
|
212
244
|
async function handleInboundMessage(params) {
|
|
213
|
-
const { cfg, logger, conn, accountId, senderId, senderName, content, messageId, isDm, channelId, } = params;
|
|
245
|
+
const { cfg, logger, conn, accountId, senderId, senderName, content, messageId, isDm, channelId, channelAgents, } = params;
|
|
214
246
|
// Context for status isolation: dm:<senderId> or channel:<channelId>
|
|
215
247
|
const statusContext = isDm ? `dm:${senderId}` : `channel:${channelId}`;
|
|
216
248
|
try {
|
|
@@ -238,11 +270,15 @@ async function handleInboundMessage(params) {
|
|
|
238
270
|
logger.info(`[clawpod] Route fallback: ${e instanceof Error ? e.message : String(e)}`);
|
|
239
271
|
}
|
|
240
272
|
}
|
|
273
|
+
const agentRoster = buildAgentRoster(channelAgents, conn.config.agentName);
|
|
274
|
+
const bodyWithRoster = agentRoster
|
|
275
|
+
? `[Available agents in this channel:\n${agentRoster}\nYou can @mention them to delegate tasks.]\n\n${content}`
|
|
276
|
+
: content;
|
|
241
277
|
const ctx = {
|
|
242
278
|
SessionKey: route.sessionKey,
|
|
243
279
|
Channel: 'clawpod',
|
|
244
280
|
AccountId: accountId,
|
|
245
|
-
Body:
|
|
281
|
+
Body: bodyWithRoster,
|
|
246
282
|
SenderName: senderName,
|
|
247
283
|
SenderId: senderId,
|
|
248
284
|
ChatType: isDm ? 'direct' : 'group',
|
|
@@ -318,8 +354,11 @@ async function handleInboundMessage(params) {
|
|
|
318
354
|
// Use openclaw agent CLI via detached spawn
|
|
319
355
|
logger.info('[clawpod] Dispatching via openclaw agent CLI');
|
|
320
356
|
const { spawn: spawnFn } = await import('node:child_process');
|
|
357
|
+
const cliMessage = agentRoster
|
|
358
|
+
? `[Available agents in this channel:\n${agentRoster}\nYou can @mention them to delegate tasks.]\n\n${content}`
|
|
359
|
+
: content;
|
|
321
360
|
const reply = await new Promise((resolve) => {
|
|
322
|
-
const proc = spawnFn('openclaw', ['agent', '--message',
|
|
361
|
+
const proc = spawnFn('openclaw', ['agent', '--message', cliMessage, '--session-id', route.sessionKey], {
|
|
323
362
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
324
363
|
timeout: 90000,
|
|
325
364
|
detached: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawpod/openclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "ClawPod channel plugin for OpenClaw",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"openclaw": {
|
|
15
15
|
"extensions": [
|
|
16
|
-
"./
|
|
16
|
+
"./dist/index.js"
|
|
17
17
|
],
|
|
18
18
|
"channel": {
|
|
19
19
|
"id": "clawpod",
|
|
@@ -21,10 +21,7 @@
|
|
|
21
21
|
"selectionLabel": "ClawPod (Human & Agent Workspace)",
|
|
22
22
|
"docsPath": "/channels/clawpod",
|
|
23
23
|
"blurb": "Connect OpenClaw agents to ClawPod collaborative spaces.",
|
|
24
|
-
"order": 80
|
|
25
|
-
"aliases": [
|
|
26
|
-
"den"
|
|
27
|
-
]
|
|
24
|
+
"order": 80
|
|
28
25
|
}
|
|
29
26
|
},
|
|
30
27
|
"dependencies": {
|