@dhfpub/clawpool-admin 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 askie and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # OpenClaw ClawPool Admin Plugin
2
+
3
+ This plugin provides typed optional admin tools and an operator CLI for Clawpool.
4
+
5
+ It is intentionally separate from the channel transport plugin:
6
+
7
+ - `@dhfpub/clawpool`: channel transport only
8
+ - `@dhfpub/clawpool-admin`: admin tools and CLI only
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ openclaw plugins install @dhfpub/clawpool-admin
14
+ openclaw plugins enable clawpool-admin
15
+ openclaw gateway restart
16
+ ```
17
+
18
+ The admin plugin reads credentials from the configured `channels.clawpool` account. Install and configure `@dhfpub/clawpool` first.
19
+
20
+ ## Agent Tools
21
+
22
+ ### `clawpool_group`
23
+
24
+ Typed group governance tool with these actions:
25
+
26
+ - `create`
27
+ - `detail`
28
+ - `add_members`
29
+ - `remove_members`
30
+ - `update_member_role`
31
+ - `dissolve`
32
+
33
+ ### `clawpool_agent_admin`
34
+
35
+ Typed admin tool for creating API agents.
36
+
37
+ This tool only creates the remote Clawpool API agent. It does not edit local OpenClaw config.
38
+
39
+ ## Operator CLI
40
+
41
+ ```bash
42
+ openclaw clawpool-admin doctor
43
+ openclaw clawpool-admin create-agent --agent-name ops-assistant
44
+ ```
45
+
46
+ `create-agent` prints the created agent payload plus the exact `openclaw channels add` and `openclaw gateway restart` next steps.
package/dist/index.js ADDED
@@ -0,0 +1,825 @@
1
+ // index.ts
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
+
4
+ // src/agent-api-actions.ts
5
+ var AGENT_NAME_RE = /^[a-z][a-z0-9-]{2,31}$/;
6
+ function readRawParam(params, key) {
7
+ if (Object.hasOwn(params, key)) {
8
+ return params[key];
9
+ }
10
+ return void 0;
11
+ }
12
+ function readStringParam(params, key) {
13
+ const raw = readRawParam(params, key);
14
+ if (typeof raw === "string") {
15
+ return raw.trim();
16
+ }
17
+ if (typeof raw === "number" && Number.isFinite(raw)) {
18
+ return String(raw);
19
+ }
20
+ return "";
21
+ }
22
+ function readRequiredStringParam(params, key) {
23
+ const value = readStringParam(params, key);
24
+ if (!value) {
25
+ throw new Error(`Clawpool action requires ${key}.`);
26
+ }
27
+ return value;
28
+ }
29
+ function readArrayParam(params, key) {
30
+ const raw = readRawParam(params, key);
31
+ if (raw == null) {
32
+ return void 0;
33
+ }
34
+ if (!Array.isArray(raw)) {
35
+ throw new Error(`Clawpool action requires ${key} as array.`);
36
+ }
37
+ return raw;
38
+ }
39
+ function readNumericIDArray(params, key, required) {
40
+ const values = readArrayParam(params, key);
41
+ if (!values || values.length == 0) {
42
+ if (required) {
43
+ throw new Error(`Clawpool action requires non-empty ${key}.`);
44
+ }
45
+ return [];
46
+ }
47
+ const normalized = [];
48
+ for (const item of values) {
49
+ const value = typeof item === "string" ? item.trim() : typeof item === "number" && Number.isFinite(item) ? String(Math.trunc(item)) : "";
50
+ if (!/^\d+$/.test(value)) {
51
+ throw new Error(`Clawpool action ${key} must contain numeric IDs.`);
52
+ }
53
+ normalized.push(value);
54
+ }
55
+ return normalized;
56
+ }
57
+ function readIntArray(params, key) {
58
+ const values = readArrayParam(params, key);
59
+ if (!values || values.length == 0) {
60
+ return [];
61
+ }
62
+ const normalized = [];
63
+ for (const item of values) {
64
+ const num = typeof item === "number" ? item : Number(String(item ?? "").trim());
65
+ if (!Number.isInteger(num)) {
66
+ throw new Error(`Clawpool action ${key} must contain integers.`);
67
+ }
68
+ normalized.push(num);
69
+ }
70
+ return normalized;
71
+ }
72
+ function readOptionalInt(params, key) {
73
+ const raw = readRawParam(params, key);
74
+ if (raw == null) {
75
+ return void 0;
76
+ }
77
+ const num = typeof raw === "number" ? raw : Number(String(raw).trim());
78
+ if (!Number.isInteger(num)) {
79
+ throw new Error(`Clawpool action ${key} must be an integer.`);
80
+ }
81
+ return num;
82
+ }
83
+ function readRequiredInt(params, key) {
84
+ const value = readOptionalInt(params, key);
85
+ if (value == null) {
86
+ throw new Error(`Clawpool action requires ${key}.`);
87
+ }
88
+ return value;
89
+ }
90
+ function ensureMemberTypes(types) {
91
+ for (const memberType of types) {
92
+ if (memberType !== 1 && memberType !== 2) {
93
+ throw new Error("Clawpool action member_types only supports 1 (human) or 2 (agent).");
94
+ }
95
+ }
96
+ }
97
+ function ensureMemberType(memberType) {
98
+ if (memberType !== 1) {
99
+ throw new Error("Clawpool action member_type only supports 1 for role update.");
100
+ }
101
+ }
102
+ function buildGroupCreateRequest(params) {
103
+ const name = readRequiredStringParam(params, "name");
104
+ const memberIDs = readNumericIDArray(params, "memberIds", false);
105
+ const memberTypes = readIntArray(params, "memberTypes");
106
+ if (memberTypes.length > 0) {
107
+ ensureMemberTypes(memberTypes);
108
+ if (memberIDs.length == 0 || memberTypes.length !== memberIDs.length) {
109
+ throw new Error("Clawpool action memberTypes length must match memberIds.");
110
+ }
111
+ }
112
+ const body = { name };
113
+ if (memberIDs.length > 0) {
114
+ body.member_ids = memberIDs;
115
+ }
116
+ if (memberTypes.length > 0) {
117
+ body.member_types = memberTypes;
118
+ }
119
+ return {
120
+ actionName: "group_create",
121
+ method: "POST",
122
+ path: "/sessions/create_group",
123
+ body
124
+ };
125
+ }
126
+ function buildGroupMemberAddRequest(params) {
127
+ const sessionID = readRequiredStringParam(params, "sessionId");
128
+ const memberIDs = readNumericIDArray(params, "memberIds", true);
129
+ const memberTypes = readIntArray(params, "memberTypes");
130
+ if (memberTypes.length > 0) {
131
+ ensureMemberTypes(memberTypes);
132
+ if (memberTypes.length !== memberIDs.length) {
133
+ throw new Error("Clawpool action memberTypes length must match memberIds.");
134
+ }
135
+ }
136
+ const body = {
137
+ session_id: sessionID,
138
+ member_ids: memberIDs
139
+ };
140
+ if (memberTypes.length > 0) {
141
+ body.member_types = memberTypes;
142
+ }
143
+ return {
144
+ actionName: "group_member_add",
145
+ method: "POST",
146
+ path: "/sessions/members/add",
147
+ body
148
+ };
149
+ }
150
+ function buildGroupMemberRemoveRequest(params) {
151
+ const sessionID = readRequiredStringParam(params, "sessionId");
152
+ const memberIDs = readNumericIDArray(params, "memberIds", true);
153
+ const memberTypes = readIntArray(params, "memberTypes");
154
+ if (memberTypes.length > 0) {
155
+ ensureMemberTypes(memberTypes);
156
+ if (memberTypes.length !== memberIDs.length) {
157
+ throw new Error("Clawpool action memberTypes length must match memberIds.");
158
+ }
159
+ }
160
+ const body = {
161
+ session_id: sessionID,
162
+ member_ids: memberIDs
163
+ };
164
+ if (memberTypes.length > 0) {
165
+ body.member_types = memberTypes;
166
+ }
167
+ return {
168
+ actionName: "group_member_remove",
169
+ method: "POST",
170
+ path: "/sessions/members/remove",
171
+ body
172
+ };
173
+ }
174
+ function buildGroupMemberRoleUpdateRequest(params) {
175
+ const sessionID = readRequiredStringParam(params, "sessionId");
176
+ const memberID = readRequiredStringParam(params, "memberId");
177
+ if (!/^\d+$/.test(memberID)) {
178
+ throw new Error("Clawpool action memberId must be numeric.");
179
+ }
180
+ const role = readRequiredInt(params, "role");
181
+ if (role !== 1 && role !== 2) {
182
+ throw new Error("Clawpool action role only supports 1 or 2.");
183
+ }
184
+ const memberType = readOptionalInt(params, "memberType") ?? 1;
185
+ ensureMemberType(memberType);
186
+ return {
187
+ actionName: "group_member_role_update",
188
+ method: "POST",
189
+ path: "/sessions/members/role",
190
+ body: {
191
+ session_id: sessionID,
192
+ member_id: memberID,
193
+ member_type: memberType,
194
+ role
195
+ }
196
+ };
197
+ }
198
+ function buildGroupDissolveRequest(params) {
199
+ const sessionID = readRequiredStringParam(params, "sessionId");
200
+ return {
201
+ actionName: "group_dissolve",
202
+ method: "POST",
203
+ path: "/sessions/dissolve",
204
+ body: {
205
+ session_id: sessionID
206
+ }
207
+ };
208
+ }
209
+ function buildGroupDetailReadRequest(params) {
210
+ const sessionID = readRequiredStringParam(params, "sessionId");
211
+ return {
212
+ actionName: "group_detail_read",
213
+ method: "GET",
214
+ path: "/sessions/group/detail",
215
+ query: {
216
+ session_id: sessionID
217
+ }
218
+ };
219
+ }
220
+ function buildAgentAPICreateRequest(params) {
221
+ const agentName = readRequiredStringParam(params, "agentName");
222
+ if (!AGENT_NAME_RE.test(agentName)) {
223
+ throw new Error("Clawpool action agentName must match ^[a-z][a-z0-9-]{2,31}$.");
224
+ }
225
+ const avatarURL = readStringParam(params, "avatarUrl");
226
+ const body = {
227
+ agent_name: agentName
228
+ };
229
+ if (avatarURL) {
230
+ body.avatar_url = avatarURL;
231
+ }
232
+ return {
233
+ actionName: "agent_api_create",
234
+ method: "POST",
235
+ path: "/agents/create",
236
+ body
237
+ };
238
+ }
239
+ function buildAgentHTTPRequest(action, params) {
240
+ switch (action) {
241
+ case "group_create":
242
+ return buildGroupCreateRequest(params);
243
+ case "group_member_add":
244
+ return buildGroupMemberAddRequest(params);
245
+ case "group_member_remove":
246
+ return buildGroupMemberRemoveRequest(params);
247
+ case "group_member_role_update":
248
+ return buildGroupMemberRoleUpdateRequest(params);
249
+ case "group_dissolve":
250
+ return buildGroupDissolveRequest(params);
251
+ case "group_detail_read":
252
+ return buildGroupDetailReadRequest(params);
253
+ case "agent_api_create":
254
+ return buildAgentAPICreateRequest(params);
255
+ default:
256
+ throw new Error(`Clawpool action ${action} is not supported.`);
257
+ }
258
+ }
259
+
260
+ // src/agent-api-http.ts
261
+ var DEFAULT_HTTP_TIMEOUT_MS = 15e3;
262
+ function trimTrailingSlash(value) {
263
+ return value.replace(/\/+$/, "");
264
+ }
265
+ function resolveExplicitAgentAPIBase() {
266
+ const base = String(
267
+ process.env.CLAWPOOL_AGENT_API_BASE ?? process.env.AIBOT_AGENT_API_BASE ?? ""
268
+ ).trim();
269
+ if (!base) {
270
+ return "";
271
+ }
272
+ return trimTrailingSlash(base);
273
+ }
274
+ function deriveAgentAPIBaseFromWsUrl(wsUrl) {
275
+ const normalizedWsUrl = String(wsUrl ?? "").trim();
276
+ if (!normalizedWsUrl) {
277
+ throw new Error("Clawpool account wsUrl is missing");
278
+ }
279
+ let parsed;
280
+ try {
281
+ parsed = new URL(normalizedWsUrl);
282
+ } catch {
283
+ throw new Error(`Clawpool wsUrl is invalid: ${normalizedWsUrl}`);
284
+ }
285
+ const protocol = parsed.protocol === "wss:" ? "https:" : parsed.protocol === "ws:" ? "http:" : "";
286
+ if (!protocol) {
287
+ throw new Error(`Clawpool wsUrl must start with ws:// or wss://: ${normalizedWsUrl}`);
288
+ }
289
+ const marker = "/v1/agent-api/ws";
290
+ const markerIndex = parsed.pathname.indexOf(marker);
291
+ const basePath = markerIndex >= 0 ? parsed.pathname.slice(0, markerIndex) : parsed.pathname;
292
+ return trimTrailingSlash(`${protocol}//${parsed.host}${basePath}`) + "/v1/agent-api";
293
+ }
294
+ function deriveLocalAgentAPIBaseFromWsUrl(wsUrl) {
295
+ const normalizedWsUrl = String(wsUrl ?? "").trim();
296
+ if (!normalizedWsUrl) {
297
+ return "";
298
+ }
299
+ let parsed;
300
+ try {
301
+ parsed = new URL(normalizedWsUrl);
302
+ } catch {
303
+ return "";
304
+ }
305
+ const host = String(parsed.hostname ?? "").trim().toLowerCase();
306
+ const localHosts = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
307
+ if (!localHosts.has(host)) {
308
+ return "";
309
+ }
310
+ const wsPort = Number(parsed.port || (parsed.protocol === "wss:" ? 443 : 80));
311
+ if (!Number.isFinite(wsPort) || wsPort <= 0) {
312
+ return "";
313
+ }
314
+ const apiPort = wsPort % 10 === 9 ? wsPort - 9 : 27180;
315
+ const protocol = parsed.protocol === "wss:" ? "https:" : "http:";
316
+ return trimTrailingSlash(`${protocol}//${parsed.hostname}:${apiPort}`) + "/v1/agent-api";
317
+ }
318
+ function resolveAgentAPIBase(account) {
319
+ const explicit = resolveExplicitAgentAPIBase();
320
+ if (explicit) {
321
+ return explicit;
322
+ }
323
+ const local = deriveLocalAgentAPIBaseFromWsUrl(account.wsUrl);
324
+ if (local) {
325
+ return local;
326
+ }
327
+ return deriveAgentAPIBaseFromWsUrl(account.wsUrl);
328
+ }
329
+ function buildRequestURL(base, path, query) {
330
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
331
+ const url = new URL(`${trimTrailingSlash(base)}${normalizedPath}`);
332
+ if (query) {
333
+ for (const [key, value] of Object.entries(query)) {
334
+ const normalizedValue = String(value ?? "").trim();
335
+ if (!normalizedValue) {
336
+ continue;
337
+ }
338
+ url.searchParams.set(key, normalizedValue);
339
+ }
340
+ }
341
+ return url.toString();
342
+ }
343
+ function normalizeStatusCode(raw) {
344
+ const n = Number(raw);
345
+ if (Number.isFinite(n)) {
346
+ return Math.floor(n);
347
+ }
348
+ return 0;
349
+ }
350
+ function normalizeBizCode(raw) {
351
+ const n = Number(raw);
352
+ if (Number.isFinite(n)) {
353
+ return Math.floor(n);
354
+ }
355
+ return -1;
356
+ }
357
+ function normalizeMessage(raw) {
358
+ const message = String(raw ?? "").trim();
359
+ if (!message) {
360
+ return "unknown error";
361
+ }
362
+ return message;
363
+ }
364
+ function extractNetworkErrorMessage(error) {
365
+ if (error instanceof Error) {
366
+ return error.message || String(error);
367
+ }
368
+ return String(error);
369
+ }
370
+ async function callAgentAPI(params) {
371
+ const base = resolveAgentAPIBase(params.account);
372
+ const url = buildRequestURL(base, params.path, params.query);
373
+ const timeoutMs = Number.isFinite(params.timeoutMs) ? Math.max(1e3, Math.floor(params.timeoutMs)) : DEFAULT_HTTP_TIMEOUT_MS;
374
+ const controller = new AbortController();
375
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
376
+ let resp;
377
+ try {
378
+ resp = await fetch(url, {
379
+ method: params.method,
380
+ headers: {
381
+ Authorization: `Bearer ${params.account.apiKey}`,
382
+ ...params.method === "POST" ? { "Content-Type": "application/json" } : {}
383
+ },
384
+ body: params.method === "POST" ? JSON.stringify(params.body ?? {}) : void 0,
385
+ signal: controller.signal
386
+ });
387
+ } catch (error) {
388
+ clearTimeout(timer);
389
+ throw new Error(
390
+ `Clawpool ${params.actionName} network error: ${extractNetworkErrorMessage(error)}`
391
+ );
392
+ }
393
+ clearTimeout(timer);
394
+ const status = normalizeStatusCode(resp.status);
395
+ const rawBody = await resp.text();
396
+ let envelope;
397
+ try {
398
+ envelope = JSON.parse(rawBody);
399
+ } catch {
400
+ throw new Error(
401
+ `Clawpool ${params.actionName} invalid response: status=${status} body=${rawBody.slice(0, 256)}`
402
+ );
403
+ }
404
+ const bizCode = normalizeBizCode(envelope.code);
405
+ if (!resp.ok || bizCode !== 0) {
406
+ const message = normalizeMessage(envelope.msg);
407
+ throw new Error(
408
+ `Clawpool ${params.actionName} failed: status=${status} code=${bizCode} msg=${message}`
409
+ );
410
+ }
411
+ return envelope.data;
412
+ }
413
+
414
+ // src/accounts.ts
415
+ var DEFAULT_ACCOUNT_ID = "default";
416
+ function normalizeAccountId(value) {
417
+ const normalized = String(value ?? "").trim();
418
+ return normalized || DEFAULT_ACCOUNT_ID;
419
+ }
420
+ function normalizeOptionalAccountId(value) {
421
+ const normalized = String(value ?? "").trim();
422
+ return normalized || void 0;
423
+ }
424
+ function rawClawpoolConfig(cfg) {
425
+ return cfg.channels?.clawpool ?? {};
426
+ }
427
+ function listConfiguredAccountIds(cfg) {
428
+ const accounts = rawClawpoolConfig(cfg).accounts;
429
+ if (!accounts || typeof accounts !== "object") {
430
+ return [];
431
+ }
432
+ return Object.keys(accounts).filter(Boolean);
433
+ }
434
+ function normalizeNonEmpty(value) {
435
+ return String(value ?? "").trim();
436
+ }
437
+ function appendAgentIdToWsUrl(rawWsUrl, agentId) {
438
+ if (!rawWsUrl) {
439
+ return "";
440
+ }
441
+ const direct = rawWsUrl.replaceAll("{agent_id}", encodeURIComponent(agentId));
442
+ if (!agentId) {
443
+ return direct;
444
+ }
445
+ try {
446
+ const parsed = new URL(direct);
447
+ if (!parsed.searchParams.get("agent_id")) {
448
+ parsed.searchParams.set("agent_id", agentId);
449
+ }
450
+ return parsed.toString();
451
+ } catch {
452
+ if (direct.includes("agent_id=")) {
453
+ return direct;
454
+ }
455
+ return direct.includes("?") ? `${direct}&agent_id=${encodeURIComponent(agentId)}` : `${direct}?agent_id=${encodeURIComponent(agentId)}`;
456
+ }
457
+ }
458
+ function resolveWsUrl(merged, agentId) {
459
+ const envWs = normalizeNonEmpty(process.env.CLAWPOOL_WS_URL);
460
+ const cfgWs = normalizeNonEmpty(merged.wsUrl);
461
+ const ws = cfgWs || envWs;
462
+ if (ws) {
463
+ return appendAgentIdToWsUrl(ws, agentId);
464
+ }
465
+ if (!agentId) {
466
+ return "";
467
+ }
468
+ return `ws://127.0.0.1:27189/v1/agent-api/ws?agent_id=${encodeURIComponent(agentId)}`;
469
+ }
470
+ function resolveMergedAccountConfig(cfg, accountId) {
471
+ const clawpoolCfg = rawClawpoolConfig(cfg);
472
+ const { accounts: _ignoredAccounts, defaultAccount: _ignoredDefault, ...base } = clawpoolCfg;
473
+ const account = clawpoolCfg.accounts?.[accountId] ?? {};
474
+ return {
475
+ ...base,
476
+ ...account
477
+ };
478
+ }
479
+ function listClawpoolAccountIds(cfg) {
480
+ const ids = listConfiguredAccountIds(cfg);
481
+ if (ids.length === 0) {
482
+ return [DEFAULT_ACCOUNT_ID];
483
+ }
484
+ return ids.toSorted((a, b) => a.localeCompare(b));
485
+ }
486
+ function resolveDefaultClawpoolAccountId(cfg) {
487
+ const clawpoolCfg = rawClawpoolConfig(cfg);
488
+ const preferred = normalizeOptionalAccountId(clawpoolCfg.defaultAccount);
489
+ if (preferred && listClawpoolAccountIds(cfg).some((accountId) => normalizeAccountId(accountId) === preferred)) {
490
+ return preferred;
491
+ }
492
+ const ids = listClawpoolAccountIds(cfg);
493
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
494
+ return DEFAULT_ACCOUNT_ID;
495
+ }
496
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
497
+ }
498
+ function resolveClawpoolAccount(params) {
499
+ const accountId = params.accountId == null || String(params.accountId).trim() === "" ? resolveDefaultClawpoolAccountId(params.cfg) : normalizeAccountId(params.accountId);
500
+ const merged = resolveMergedAccountConfig(params.cfg, accountId);
501
+ const baseEnabled = rawClawpoolConfig(params.cfg).enabled !== false;
502
+ const accountEnabled = merged.enabled !== false;
503
+ const enabled = baseEnabled && accountEnabled;
504
+ const agentId = normalizeNonEmpty(merged.agentId || process.env.CLAWPOOL_AGENT_ID);
505
+ const apiKey = normalizeNonEmpty(merged.apiKey || process.env.CLAWPOOL_API_KEY);
506
+ const wsUrl = resolveWsUrl(merged, agentId);
507
+ const configured = Boolean(wsUrl && agentId && apiKey);
508
+ return {
509
+ accountId,
510
+ name: normalizeNonEmpty(merged.name) || void 0,
511
+ enabled,
512
+ configured,
513
+ wsUrl,
514
+ agentId,
515
+ apiKey,
516
+ config: merged
517
+ };
518
+ }
519
+ function summarizeClawpoolAccounts(cfg) {
520
+ return listClawpoolAccountIds(cfg).map((accountId) => {
521
+ const account = resolveClawpoolAccount({ cfg, accountId });
522
+ return {
523
+ accountId: account.accountId,
524
+ name: account.name ?? null,
525
+ enabled: account.enabled,
526
+ configured: account.configured,
527
+ wsUrl: account.wsUrl || null,
528
+ agentId: account.agentId || null
529
+ };
530
+ });
531
+ }
532
+
533
+ // src/agent-admin-service.ts
534
+ function buildChannelBootstrapCommand(params) {
535
+ return [
536
+ "openclaw channels add",
537
+ "--channel clawpool",
538
+ `--name ${JSON.stringify(params.channelName)}`,
539
+ `--http-url ${JSON.stringify(params.apiEndpoint)}`,
540
+ `--user-id ${JSON.stringify(params.agentId)}`,
541
+ `--token ${JSON.stringify(params.apiKey)}`
542
+ ].join(" ");
543
+ }
544
+ async function createClawpoolApiAgent(params) {
545
+ const account = resolveClawpoolAccount({
546
+ cfg: params.cfg,
547
+ accountId: params.toolParams.accountId
548
+ });
549
+ if (!account.enabled) {
550
+ throw new Error(`Clawpool account "${account.accountId}" is disabled.`);
551
+ }
552
+ if (!account.configured) {
553
+ throw new Error(`Clawpool account "${account.accountId}" is not configured.`);
554
+ }
555
+ const request = buildAgentHTTPRequest("agent_api_create", params.toolParams);
556
+ const data = await callAgentAPI({
557
+ account,
558
+ actionName: request.actionName,
559
+ method: request.method,
560
+ path: request.path,
561
+ query: request.query,
562
+ body: request.body
563
+ });
564
+ const agentName = String(data.agent_name ?? params.toolParams.agentName ?? "").trim();
565
+ const apiEndpoint = String(data.api_endpoint ?? "").trim();
566
+ const agentId = String(data.id ?? "").trim();
567
+ const apiKey = String(data.api_key ?? "").trim();
568
+ return {
569
+ ok: true,
570
+ accountId: account.accountId,
571
+ action: "create_api_agent",
572
+ data,
573
+ nextSteps: agentName && apiEndpoint && agentId && apiKey ? [
574
+ "Install and enable the channel plugin if it is not installed yet: `openclaw plugins install @dhfpub/clawpool && openclaw plugins enable clawpool`.",
575
+ `Bind the new API agent to OpenClaw with: \`${buildChannelBootstrapCommand({
576
+ channelName: `clawpool-${agentName}`,
577
+ apiEndpoint,
578
+ agentId,
579
+ apiKey
580
+ })}\``,
581
+ "Restart the gateway after adding the channel: `openclaw gateway restart`."
582
+ ] : []
583
+ };
584
+ }
585
+ function inspectClawpoolAdminConfig(cfg) {
586
+ return {
587
+ accounts: summarizeClawpoolAccounts(cfg),
588
+ defaultAccountId: resolveClawpoolAccount({ cfg }).accountId
589
+ };
590
+ }
591
+
592
+ // src/cli.ts
593
+ function registerClawpoolAdminCli(params) {
594
+ const root = params.program.command("clawpool-admin").description("Clawpool admin utilities").addHelpText(
595
+ "after",
596
+ "\nThis CLI is for operator workflows. Agent tools stay scoped to typed remote admin actions only.\n"
597
+ );
598
+ root.command("doctor").description("Show the Clawpool accounts visible from the current OpenClaw config").action(() => {
599
+ console.log(JSON.stringify(inspectClawpoolAdminConfig(params.api.config), null, 2));
600
+ });
601
+ root.command("create-agent").description("Create a Clawpool API agent and print the exact next steps for channel binding").requiredOption("--agent-name <name>", "New API agent name").option("--account-id <id>", "Configured Clawpool account id").option("--avatar-url <url>", "Optional avatar URL").action(
602
+ async (options) => {
603
+ const result = await createClawpoolApiAgent({
604
+ cfg: params.api.config,
605
+ toolParams: options
606
+ });
607
+ console.log(JSON.stringify(result, null, 2));
608
+ }
609
+ );
610
+ }
611
+
612
+ // src/json-result.ts
613
+ function jsonToolResult(payload) {
614
+ return {
615
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
616
+ details: payload
617
+ };
618
+ }
619
+
620
+ // src/agent-admin-tool.ts
621
+ var ClawpoolAgentAdminToolSchema = {
622
+ type: "object",
623
+ additionalProperties: false,
624
+ properties: {
625
+ accountId: { type: "string", minLength: 1 },
626
+ agentName: {
627
+ type: "string",
628
+ pattern: "^[a-z][a-z0-9-]{2,31}$",
629
+ description: "Lowercase API agent name."
630
+ },
631
+ avatarUrl: { type: "string", minLength: 1 }
632
+ },
633
+ required: ["agentName"]
634
+ };
635
+ function createClawpoolAgentAdminTool(api) {
636
+ return {
637
+ name: "clawpool_agent_admin",
638
+ label: "Clawpool Agent Admin",
639
+ description: "Create Clawpool API agents with typed parameters. This tool does not modify local OpenClaw channel config.",
640
+ parameters: ClawpoolAgentAdminToolSchema,
641
+ async execute(_toolCallId, params) {
642
+ try {
643
+ return jsonToolResult(
644
+ await createClawpoolApiAgent({
645
+ cfg: api.config,
646
+ toolParams: params
647
+ })
648
+ );
649
+ } catch (err) {
650
+ return jsonToolResult({
651
+ error: err instanceof Error ? err.message : String(err)
652
+ });
653
+ }
654
+ }
655
+ };
656
+ }
657
+
658
+ // src/group-service.ts
659
+ function mapGroupActionToRequestAction(action) {
660
+ switch (action) {
661
+ case "create":
662
+ return "group_create";
663
+ case "detail":
664
+ return "group_detail_read";
665
+ case "add_members":
666
+ return "group_member_add";
667
+ case "remove_members":
668
+ return "group_member_remove";
669
+ case "update_member_role":
670
+ return "group_member_role_update";
671
+ case "dissolve":
672
+ return "group_dissolve";
673
+ default:
674
+ action;
675
+ throw new Error(`Unsupported Clawpool group action: ${String(action)}`);
676
+ }
677
+ }
678
+ async function runClawpoolGroupAction(params) {
679
+ const account = resolveClawpoolAccount({
680
+ cfg: params.cfg,
681
+ accountId: params.toolParams.accountId
682
+ });
683
+ if (!account.enabled) {
684
+ throw new Error(`Clawpool account "${account.accountId}" is disabled.`);
685
+ }
686
+ if (!account.configured) {
687
+ throw new Error(`Clawpool account "${account.accountId}" is not configured.`);
688
+ }
689
+ const requestAction = mapGroupActionToRequestAction(params.toolParams.action);
690
+ const request = buildAgentHTTPRequest(requestAction, params.toolParams);
691
+ const data = await callAgentAPI({
692
+ account,
693
+ actionName: request.actionName,
694
+ method: request.method,
695
+ path: request.path,
696
+ query: request.query,
697
+ body: request.body
698
+ });
699
+ return {
700
+ ok: true,
701
+ accountId: account.accountId,
702
+ action: params.toolParams.action,
703
+ data
704
+ };
705
+ }
706
+
707
+ // src/group-tool.ts
708
+ var numericIdSchema = {
709
+ type: "string",
710
+ pattern: "^[0-9]+$"
711
+ };
712
+ var ClawpoolGroupToolSchema = {
713
+ oneOf: [
714
+ {
715
+ type: "object",
716
+ additionalProperties: false,
717
+ properties: {
718
+ action: { const: "create" },
719
+ accountId: { type: "string", minLength: 1 },
720
+ name: { type: "string", minLength: 1 },
721
+ memberIds: { type: "array", items: numericIdSchema },
722
+ memberTypes: { type: "array", items: { type: "integer", enum: [1, 2] } }
723
+ },
724
+ required: ["action", "name"]
725
+ },
726
+ {
727
+ type: "object",
728
+ additionalProperties: false,
729
+ properties: {
730
+ action: { const: "detail" },
731
+ accountId: { type: "string", minLength: 1 },
732
+ sessionId: { type: "string", minLength: 1 }
733
+ },
734
+ required: ["action", "sessionId"]
735
+ },
736
+ {
737
+ type: "object",
738
+ additionalProperties: false,
739
+ properties: {
740
+ action: { const: "add_members" },
741
+ accountId: { type: "string", minLength: 1 },
742
+ sessionId: { type: "string", minLength: 1 },
743
+ memberIds: { type: "array", items: numericIdSchema, minItems: 1 },
744
+ memberTypes: { type: "array", items: { type: "integer", enum: [1, 2] } }
745
+ },
746
+ required: ["action", "sessionId", "memberIds"]
747
+ },
748
+ {
749
+ type: "object",
750
+ additionalProperties: false,
751
+ properties: {
752
+ action: { const: "remove_members" },
753
+ accountId: { type: "string", minLength: 1 },
754
+ sessionId: { type: "string", minLength: 1 },
755
+ memberIds: { type: "array", items: numericIdSchema, minItems: 1 },
756
+ memberTypes: { type: "array", items: { type: "integer", enum: [1, 2] } }
757
+ },
758
+ required: ["action", "sessionId", "memberIds"]
759
+ },
760
+ {
761
+ type: "object",
762
+ additionalProperties: false,
763
+ properties: {
764
+ action: { const: "update_member_role" },
765
+ accountId: { type: "string", minLength: 1 },
766
+ sessionId: { type: "string", minLength: 1 },
767
+ memberId: numericIdSchema,
768
+ memberType: { type: "integer", enum: [1] },
769
+ role: { type: "integer", enum: [1, 2] }
770
+ },
771
+ required: ["action", "sessionId", "memberId", "role"]
772
+ },
773
+ {
774
+ type: "object",
775
+ additionalProperties: false,
776
+ properties: {
777
+ action: { const: "dissolve" },
778
+ accountId: { type: "string", minLength: 1 },
779
+ sessionId: { type: "string", minLength: 1 }
780
+ },
781
+ required: ["action", "sessionId"]
782
+ }
783
+ ]
784
+ };
785
+ function createClawpoolGroupTool(api) {
786
+ return {
787
+ name: "clawpool_group",
788
+ label: "Clawpool Group",
789
+ description: "Manage Clawpool groups through typed admin operations. This tool only handles group lifecycle and membership changes.",
790
+ parameters: ClawpoolGroupToolSchema,
791
+ async execute(_toolCallId, params) {
792
+ try {
793
+ return jsonToolResult(
794
+ await runClawpoolGroupAction({
795
+ cfg: api.config,
796
+ toolParams: params
797
+ })
798
+ );
799
+ } catch (err) {
800
+ return jsonToolResult({
801
+ error: err instanceof Error ? err.message : String(err)
802
+ });
803
+ }
804
+ }
805
+ };
806
+ }
807
+
808
+ // index.ts
809
+ var plugin = {
810
+ id: "clawpool-admin",
811
+ name: "Clawpool Admin",
812
+ description: "Typed optional admin tools and operator CLI for Clawpool",
813
+ configSchema: emptyPluginConfigSchema(),
814
+ register(api) {
815
+ api.registerTool(createClawpoolGroupTool(api), { optional: true });
816
+ api.registerTool(createClawpoolAgentAdminTool(api), { optional: true });
817
+ api.registerCli(({ program }) => registerClawpoolAdminCli({ api, program }), {
818
+ commands: ["clawpool-admin"]
819
+ });
820
+ }
821
+ };
822
+ var index_default = plugin;
823
+ export {
824
+ index_default as default
825
+ };
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "clawpool-admin",
3
+ "skills": ["./skills"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@dhfpub/clawpool-admin",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw admin tools plugin for ClawPool",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "keywords": [
11
+ "openclaw",
12
+ "plugin",
13
+ "tools",
14
+ "clawpool",
15
+ "dhfpub"
16
+ ],
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/askie/aibot.git",
21
+ "directory": "openclaw_plugins/clawpool-admin"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/askie/aibot/issues"
25
+ },
26
+ "homepage": "https://github.com/askie/aibot/tree/main/openclaw_plugins/clawpool-admin#readme",
27
+ "files": [
28
+ "LICENSE",
29
+ "README.md",
30
+ "openclaw.plugin.json",
31
+ "dist/index.js",
32
+ "skills"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "registry": "https://registry.npmjs.org/"
37
+ },
38
+ "scripts": {
39
+ "clean": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true });\"",
40
+ "build": "npm run clean && esbuild index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:openclaw --external:openclaw/*",
41
+ "pack:dry-run": "npm run build && npm pack --dry-run --ignore-scripts",
42
+ "prepack": "npm run build",
43
+ "test": "node --test src/*.test.ts"
44
+ },
45
+ "devDependencies": {
46
+ "esbuild": "0.27.4"
47
+ },
48
+ "openclaw": {
49
+ "extensions": [
50
+ "./dist/index.js"
51
+ ],
52
+ "install": {
53
+ "npmSpec": "@dhfpub/clawpool-admin",
54
+ "localPath": "openclaw_plugins/clawpool-admin",
55
+ "defaultChoice": "npm"
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: clawpool-agent-admin
3
+ description: Use the typed `clawpool_agent_admin` tool to create Clawpool API agents. Trigger when users ask to create a new API agent or when `/v1/agent-api/agents/create` fails with scope or permission errors.
4
+ ---
5
+
6
+ # Clawpool Agent Admin
7
+
8
+ Create a new `provider_type=3` agent through Aibot Agent API.
9
+ This skill does not install plugins, edit local config, or restart the gateway.
10
+
11
+ ## Required Input
12
+
13
+ 1. Require `agentName` from user.
14
+ 2. Accept only English lowercase + digits + hyphen.
15
+ 3. Enforce regex: `^[a-z][a-z0-9-]{2,31}$`.
16
+ 4. Reject Chinese, uppercase, underscore, and spaces.
17
+
18
+ ## Workflow
19
+
20
+ 1. Ask user for `agentName` when missing.
21
+ 2. Validate `agentName` with the regex above before any tool call.
22
+ 3. Call `clawpool_agent_admin` once with `agentName` and optional `accountId` / `avatarUrl`.
23
+ 4. Return the created agent details and the explicit next-step commands for channel binding.
24
+
25
+ ## Tool Contract
26
+
27
+ Tool: `clawpool_agent_admin`
28
+
29
+ Purpose: create an API-type agent under current owner.
30
+
31
+ Request payload:
32
+
33
+ 1. `agentName` (required, validated by regex)
34
+ 2. `avatarUrl` (optional)
35
+ 3. `accountId` (optional, when multiple Clawpool accounts are configured)
36
+
37
+ Guardrails:
38
+
39
+ 1. Treat create action as non-idempotent; never auto-retry without user confirmation.
40
+ 2. Expose `api_key` only once in success output; mask it in any later logs.
41
+ 3. Do not auto-grant extra scopes to the new agent.
42
+ 4. Do not auto-install plugins or mutate local OpenClaw config from this skill.
43
+
44
+ ## Error Handling Rules
45
+
46
+ 1. Name regex check failed: ask user to provide a valid English name.
47
+ 2. `403/20011`: missing `agent.api.create` scope, ask owner to grant this scope.
48
+ 3. `401/10001`: invalid or missing `agent_api_key`, ask to verify config/rotate key.
49
+ 4. `403/10002`: caller agent inactive or invalid provider type.
50
+ 5. `409/20002`: duplicate agent name; ask user for another name.
51
+ 6. `400/20004`: owner quota exceeded; ask owner to clean up agents.
52
+ 7. Other errors: return backend `msg` and stop automatic retries.
53
+
54
+ ## Response Style
55
+
56
+ 1. Return `created` status first.
57
+ 2. Include `agent_id`, `agent_name`, `api_endpoint`, and `api_key_hint`.
58
+ 3. Show `api_key` once, then only show `api_key_hint`.
59
+ 4. Include the exact next steps for `openclaw channels add` and `openclaw gateway restart`.
60
+
61
+ ## References
62
+
63
+ 1. Load [references/api-contract.md](references/api-contract.md) for API mapping and error matrix.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Clawpool Agent Admin"
3
+ short_description: "Use clawpool_agent_admin to create Clawpool API agents."
4
+ default_prompt: "Use this skill when users ask to create a new Clawpool API agent. Validate agentName, call clawpool_agent_admin once, and return the exact next-step commands for channel binding without editing local OpenClaw config automatically."
@@ -0,0 +1,73 @@
1
+ # API Contract
2
+
3
+ ## Purpose
4
+
5
+ Map provisioning action to Aibot Agent API HTTP route.
6
+
7
+ ## Base Rules
8
+
9
+ 1. Base path: `/v1/agent-api`
10
+ 2. Auth: `Authorization: Bearer <agent_api_key>`
11
+ 3. Caller must be `provider_type=3` and `status=active`.
12
+ 4. Route must pass scope middleware before service business checks.
13
+
14
+ ## Action Mapping (v1)
15
+
16
+ | Tool | Method | Route | Required Scope |
17
+ |---|---|---|---|
18
+ | `clawpool_agent_admin` | `POST` | `/agents/create` | `agent.api.create` |
19
+
20
+ ## Payload Template
21
+
22
+ ```json
23
+ {
24
+ "agentName": "ops-assistant",
25
+ "avatarUrl": "https://example.com/avatar.png"
26
+ }
27
+ ```
28
+
29
+ `agentName` validation rule for this skill:
30
+
31
+ - regex: `^[a-z][a-z0-9-]{2,31}$`
32
+ - only lowercase English letters, digits, and hyphen
33
+
34
+ ## Success Payload Highlights
35
+
36
+ ```json
37
+ {
38
+ "code": 0,
39
+ "data": {
40
+ "id": "2029786829095440384",
41
+ "agent_name": "ops-assistant",
42
+ "provider_type": 3,
43
+ "api_endpoint": "ws://host/v1/agent-api/ws?agent_id=2029786829095440384",
44
+ "api_key": "ak_2029786829095440384_xxx",
45
+ "api_key_hint": "xxxxxx12"
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Error Matrix
51
+
52
+ | HTTP/BizCode | Meaning | Skill Response |
53
+ |---|---|---|
54
+ | `403/20011` | `agent.api.create` scope missing | Ask owner to grant scope |
55
+ | `401/10001` | invalid or missing auth | Check `api_key` and account config |
56
+ | `403/10002` | caller agent inactive / invalid provider | Ask owner to activate caller agent |
57
+ | `409/20002` | duplicate agent name | Ask user for another `agent_name` |
58
+ | `400/20004` | owner quota exceeded | Ask owner to clean up unused agents |
59
+ | `400/10003` | invalid payload | Ask for corrected parameters |
60
+
61
+ ## Retry Policy
62
+
63
+ 1. Never auto-retry `agent_api_create` unless user explicitly confirms.
64
+ 2. Do not retry scope/auth/parameter failures.
65
+ 3. Network transient errors can be retried once after explicit confirmation.
66
+
67
+ ## Post-Create Handover
68
+
69
+ After `code=0`, return the explicit next-step commands for:
70
+
71
+ 1. `openclaw plugins install @dhfpub/clawpool && openclaw plugins enable clawpool`
72
+ 2. `openclaw channels add --channel clawpool ...`
73
+ 3. `openclaw gateway restart`
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: clawpool-group-governance
3
+ description: Use the typed `clawpool_group` tool for Clawpool group lifecycle and membership operations. Trigger when users ask to create, inspect, update, or dissolve groups, or when these operations fail with scope or permission errors.
4
+ ---
5
+
6
+ # Clawpool Group Governance
7
+
8
+ Operate group-governance actions through the `clawpool_group` tool.
9
+ This skill is about tool selection and guardrails, not protocol bridging.
10
+
11
+ ## Workflow
12
+
13
+ 1. Parse the user request into one action:
14
+ `create`, `detail`, `add_members`, `remove_members`, `update_member_role`, or `dissolve`.
15
+ 2. Validate required fields before any call.
16
+ 3. Call `clawpool_group` exactly once per business action.
17
+ 4. Classify failures by HTTP/BizCode and return exact remediation.
18
+ 5. Avoid duplicate side effects:
19
+ never auto-retry `create` or `dissolve` without explicit user confirmation.
20
+
21
+ ## Tool Contract
22
+
23
+ For Clawpool group governance, always call:
24
+
25
+ 1. Tool: `clawpool_group`
26
+ 2. `action`: one of `create`, `detail`, `add_members`, `remove_members`, `update_member_role`, `dissolve`
27
+ 3. `accountId`: optional; include it when the configured account is ambiguous
28
+
29
+ Rules:
30
+
31
+ 1. Pass business parameters with their exact typed field names.
32
+ 2. Use `sessionId`, `memberIds`, `memberTypes`, `memberId`, `memberType`, and `role` explicitly.
33
+ 3. Do not invent aliases or fallback fields.
34
+ 4. Keep one tool call per action for audit clarity.
35
+
36
+ ## Action Contracts
37
+
38
+ ### create
39
+
40
+ Purpose: create a new group session.
41
+
42
+ Required input:
43
+
44
+ 1. `name` (non-empty string)
45
+ 2. `memberIds` (optional string array; each item numeric text)
46
+ 3. `memberTypes` (optional int array; align with `memberIds`)
47
+
48
+ Guardrails:
49
+
50
+ 1. Ask for clarification if group name is missing.
51
+ 2. Ask for explicit confirmation before repeating the same create request.
52
+ 3. Treat this action as non-idempotent.
53
+
54
+ ### add_members
55
+
56
+ Purpose: add members into an existing group.
57
+
58
+ Required input:
59
+
60
+ 1. `sessionId` (non-empty string)
61
+ 2. `memberIds` (non-empty string array; each item numeric text)
62
+ 3. `memberTypes` (optional int array; align with `memberIds`)
63
+
64
+ Guardrails:
65
+
66
+ 1. Reject empty `sessionId` before calling the tool.
67
+ 2. Reject non-numeric `memberIds` before calling the tool.
68
+ 3. If `sessionId` is ambiguous, ask the user to confirm the target group first.
69
+
70
+ ### remove_members
71
+
72
+ Required input:
73
+
74
+ 1. `sessionId`
75
+ 2. `memberIds`
76
+
77
+ ### update_member_role
78
+
79
+ Required input:
80
+
81
+ 1. `sessionId`
82
+ 2. `memberId`
83
+ 3. `role`
84
+
85
+ Guardrails:
86
+
87
+ 1. Only use `memberType=1` for role updates.
88
+ 2. Never guess a role value; confirm when unclear.
89
+
90
+ ### detail / dissolve
91
+
92
+ Required input:
93
+
94
+ 1. `sessionId`
95
+
96
+ ## Error Handling Rules
97
+
98
+ 1. `403/20011`:
99
+ report missing scope and ask owner to grant the scope in Aibot Agent permission page.
100
+ 2. `401/10001`:
101
+ report invalid key/auth and suggest checking agent config or rotating API key.
102
+ 3. `403/10002`:
103
+ report agent is not active or invalid provider type.
104
+ 4. `400/10003`:
105
+ report invalid/missing parameters and ask user for corrected values.
106
+ 5. Other errors:
107
+ return backend `msg` and stop automatic retries.
108
+
109
+ ## Response Style
110
+
111
+ 1. State action result first.
112
+ 2. Include key identifiers (`session_id`, member count) when successful.
113
+ 3. Include exact remediation when failed.
114
+ 4. Never hide scope or auth errors behind generic wording.
115
+
116
+ ## References
117
+
118
+ 1. Load [references/api-contract.md](references/api-contract.md) when you need exact tool mapping, payload examples, and scope matrix.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Clawpool Group Governance"
3
+ short_description: "Use the typed clawpool_group tool for Clawpool group operations."
4
+ default_prompt: "Use this skill when users ask to create, inspect, update, or dissolve Clawpool groups. Validate parameters, call clawpool_group exactly once per action, and return clear remediation for scope/auth/parameter failures."
@@ -0,0 +1,71 @@
1
+ # API Contract
2
+
3
+ ## Purpose
4
+
5
+ Map high-level governance actions to Aibot Agent API HTTP routes.
6
+
7
+ ## Base Rules
8
+
9
+ 1. Base path: `/v1/agent-api`
10
+ 2. Auth: `Authorization: Bearer <agent_api_key>`
11
+ 3. Only `provider_type=3` and `status=active` agent can access.
12
+ 4. Scope middleware executes before service business checks.
13
+
14
+ ## Action Mapping (v1)
15
+
16
+ | Action | Method | Route | Required Scope |
17
+ |---|---|---|---|
18
+ | `group_create` | `POST` | `/sessions/create_group` | `group.create` |
19
+ | `group_member_add` | `POST` | `/sessions/members/add` | `group.member.add` |
20
+
21
+ ## OpenClaw Tool Mapping
22
+
23
+ Use the native `clawpool_group` tool with typed fields:
24
+
25
+ | Tool action | HTTP action | Required fields |
26
+ |---|---|---|
27
+ | `create` | `group_create` | `name` |
28
+ | `detail` | `group_detail_read` | `sessionId` |
29
+ | `add_members` | `group_member_add` | `sessionId`, `memberIds` |
30
+ | `remove_members` | `group_member_remove` | `sessionId`, `memberIds` |
31
+ | `update_member_role` | `group_member_role_update` | `sessionId`, `memberId`, `role` |
32
+ | `dissolve` | `group_dissolve` | `sessionId` |
33
+
34
+ ## Payload Templates
35
+
36
+ ### create
37
+
38
+ ```json
39
+ {
40
+ "action": "create",
41
+ "name": "项目协作群",
42
+ "memberIds": ["1002", "9991"],
43
+ "memberTypes": [1, 2]
44
+ }
45
+ ```
46
+
47
+ ### add_members
48
+
49
+ ```json
50
+ {
51
+ "action": "add_members",
52
+ "sessionId": "task_room_9083",
53
+ "memberIds": ["1003"],
54
+ "memberTypes": [1]
55
+ }
56
+ ```
57
+
58
+ ## Error Matrix
59
+
60
+ | HTTP/BizCode | Meaning | Skill Response |
61
+ |---|---|---|
62
+ | `403/20011` | agent scope forbidden | Tell owner to grant corresponding scope |
63
+ | `400/10003` | invalid request payload | Ask for missing or corrected parameters |
64
+ | `401/10001` | invalid or missing auth | Check api_key and account config |
65
+ | `403/10002` | agent not active / invalid provider | Ask owner to activate the agent |
66
+
67
+ ## Retry Policy
68
+
69
+ 1. Never auto-retry `group_create` unless user confirms.
70
+ 2. Allow one retry for transient network failure only.
71
+ 3. Do not retry auth/scope/parameter failures automatically.