@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.
@@ -27,6 +27,10 @@ export interface ChannelMessage {
27
27
  senderType: string;
28
28
  senderName?: string;
29
29
  content: string;
30
+ channelAgents?: {
31
+ name: string;
32
+ defaultPrompt?: string;
33
+ }[];
30
34
  }
31
35
  export interface ClawPodConnection {
32
36
  ws: WebSocket | null;
@@ -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 || msg.senderType === 'agent')
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
- logger.info(`[clawpod:${accountId}] @mention by ${msg.senderName}`);
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: content,
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', content, '--session-id', route.sessionKey], {
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.1.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
- "./src/index.ts"
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": {