@dhfpub/clawpool 0.1.0 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +69 -1
  2. package/dist/index.js +171 -86
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -40,6 +40,7 @@ Typical use cases:
40
40
  ```bash
41
41
  openclaw plugins install @dhfpub/clawpool
42
42
  openclaw plugins enable clawpool
43
+ openclaw gateway restart
43
44
  ```
44
45
 
45
46
  After installation, you can verify the plugin with:
@@ -47,6 +48,72 @@ After installation, you can verify the plugin with:
47
48
  ```bash
48
49
  openclaw plugins list
49
50
  openclaw plugins doctor
51
+ openclaw gateway status
52
+ ```
53
+
54
+ For source-level local development from this repository, use the repo debug scripts or merge this fragment into `~/.openclaw/openclaw.json` so source edits take effect after a gateway restart. This path intentionally points to the source entry `index.ts`, not the built artifact:
55
+
56
+ ```json
57
+ {
58
+ "plugins": {
59
+ "load": {
60
+ "paths": ["/path/to/repo/openclaw_plugins/clawpool/index.ts"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ Apply source-entry changes with:
67
+
68
+ ```bash
69
+ openclaw gateway restart
70
+ openclaw gateway status
71
+ ```
72
+
73
+ If you want parity with the published npm package, build first and then install the plugin directory. In this mode OpenClaw loads `dist/index.js` through the package manifest:
74
+
75
+ ```bash
76
+ npm install
77
+ npm run build
78
+ openclaw plugins install -l /path/to/repo/openclaw_plugins/clawpool
79
+ openclaw plugins enable clawpool
80
+ openclaw gateway restart
81
+ openclaw gateway status
82
+ ```
83
+
84
+ If you prefer to point `plugins.load.paths` at the compiled file directly, use:
85
+
86
+ ```json
87
+ {
88
+ "plugins": {
89
+ "load": {
90
+ "paths": ["/path/to/repo/openclaw_plugins/clawpool/dist/index.js"]
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ## Updating
97
+
98
+ If you installed from npm, update the plugin with:
99
+
100
+ ```bash
101
+ openclaw plugins update clawpool
102
+ openclaw gateway restart
103
+ openclaw plugins list
104
+ openclaw plugins doctor
105
+ openclaw gateway status
106
+ ```
107
+
108
+ If you installed with `openclaw plugins install -l` from this repository, rebuild the plugin and then restart the gateway:
109
+
110
+ ```bash
111
+ npm install
112
+ npm run build
113
+ openclaw gateway restart
114
+ openclaw plugins list
115
+ openclaw plugins doctor
116
+ openclaw gateway status
50
117
  ```
51
118
 
52
119
  ## Configuration
@@ -120,4 +187,5 @@ If you want to provide the default account through environment variables, use:
120
187
 
121
188
  - Run `openclaw plugins doctor` once after the first installation
122
189
  - Send at least one real message to confirm the account and network setup are correct
123
- - If you change ClawPool channel settings, restart the OpenClaw gateway to apply them
190
+ - After `install` / `enable` / `update` or changes under `plugins.load.paths`, run `openclaw gateway restart`
191
+ - If the gateway is not running yet, start it with `openclaw gateway start`
package/dist/index.js CHANGED
@@ -1171,6 +1171,93 @@ function requireActiveAibotClient(accountId) {
1171
1171
  return client;
1172
1172
  }
1173
1173
 
1174
+ // src/target-resolver.ts
1175
+ var aibotSessionIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1176
+ function isAibotSessionID(value) {
1177
+ const normalized = String(value ?? "").trim();
1178
+ if (!normalized) {
1179
+ return false;
1180
+ }
1181
+ return aibotSessionIDPattern.test(normalized);
1182
+ }
1183
+ function normalizeAibotSessionTarget2(raw) {
1184
+ const trimmed = String(raw ?? "").trim();
1185
+ if (!trimmed) {
1186
+ return "";
1187
+ }
1188
+ return trimmed.replace(/^clawpool:/i, "").replace(/^session:/i, "").trim();
1189
+ }
1190
+ function buildRouteSessionKeyCandidates(rawTarget, normalizedTarget) {
1191
+ if (rawTarget === normalizedTarget) {
1192
+ return [rawTarget];
1193
+ }
1194
+ return [rawTarget, normalizedTarget].filter((candidate) => candidate.length > 0);
1195
+ }
1196
+ async function resolveAibotOutboundTarget(params) {
1197
+ const rawTarget = String(params.to ?? "").trim();
1198
+ if (!rawTarget) {
1199
+ throw new Error("clawpool outbound target must be non-empty");
1200
+ }
1201
+ const normalizedTarget = normalizeAibotSessionTarget2(rawTarget);
1202
+ if (!normalizedTarget) {
1203
+ throw new Error("clawpool outbound target must contain session_id or route_session_key");
1204
+ }
1205
+ if (isAibotSessionID(normalizedTarget)) {
1206
+ return {
1207
+ sessionId: normalizedTarget,
1208
+ rawTarget,
1209
+ normalizedTarget,
1210
+ resolveSource: "direct"
1211
+ };
1212
+ }
1213
+ if (/^\d+$/.test(normalizedTarget)) {
1214
+ throw new Error(
1215
+ `clawpool outbound target "${rawTarget}" is numeric; expected session_id(UUID) or route.sessionKey`
1216
+ );
1217
+ }
1218
+ const routeSessionKeyCandidates = buildRouteSessionKeyCandidates(rawTarget, normalizedTarget);
1219
+ let lastResolveError = null;
1220
+ for (const routeSessionKey of routeSessionKeyCandidates) {
1221
+ try {
1222
+ const ack = await params.client.resolveSessionRoute("clawpool", params.accountId, routeSessionKey);
1223
+ const sessionId = String(ack.session_id ?? "").trim();
1224
+ if (!isAibotSessionID(sessionId)) {
1225
+ throw new Error(
1226
+ `session_route_resolve returned invalid session_id for route_session_key="${routeSessionKey}"`
1227
+ );
1228
+ }
1229
+ return {
1230
+ sessionId,
1231
+ rawTarget,
1232
+ normalizedTarget,
1233
+ resolveSource: "sessionRouteMap"
1234
+ };
1235
+ } catch (err) {
1236
+ lastResolveError = err instanceof Error ? err : new Error(String(err));
1237
+ }
1238
+ }
1239
+ if (lastResolveError) {
1240
+ throw new Error(
1241
+ `clawpool outbound target resolve failed target="${rawTarget}" accountId=${params.accountId}: ${lastResolveError.message}`
1242
+ );
1243
+ }
1244
+ throw new Error(`clawpool outbound target resolve failed target="${rawTarget}" accountId=${params.accountId}`);
1245
+ }
1246
+
1247
+ // src/delete-target-resolver.ts
1248
+ async function resolveAibotDeleteTarget(params) {
1249
+ const rawTarget = String(params.sessionId ?? "").trim() || String(params.to ?? "").trim() || String(params.topic ?? "").trim() || String(params.currentChannelId ?? "").trim();
1250
+ if (!rawTarget) {
1251
+ return "";
1252
+ }
1253
+ const resolved = await resolveAibotOutboundTarget({
1254
+ client: params.client,
1255
+ accountId: params.accountId,
1256
+ to: rawTarget
1257
+ });
1258
+ return resolved.sessionId;
1259
+ }
1260
+
1174
1261
  // src/actions.ts
1175
1262
  var SUPPORTED_AIBOT_MESSAGE_ACTIONS = /* @__PURE__ */ new Set(["unsend", "delete"]);
1176
1263
  function toSnakeCaseKey(key) {
@@ -1188,10 +1275,6 @@ function readStringishParam(params, key) {
1188
1275
  }
1189
1276
  return void 0;
1190
1277
  }
1191
- function resolveDeleteSessionId(params) {
1192
- const direct = readStringParam(params.params, "sessionId") ?? readStringParam(params.params, "to") ?? params.currentChannelId;
1193
- return normalizeAibotSessionTarget(direct ?? "");
1194
- }
1195
1278
  var aibotMessageActions = {
1196
1279
  listActions: ({ cfg }) => {
1197
1280
  const hasConfiguredAccount = listAibotAccountIds(cfg).map((accountId) => resolveAibotAccount({ cfg, accountId })).some((account) => account.enabled && account.configured);
@@ -1211,8 +1294,12 @@ var aibotMessageActions = {
1211
1294
  if (!messageId) {
1212
1295
  throw new Error("Clawpool unsend requires messageId.");
1213
1296
  }
1214
- const sessionId = resolveDeleteSessionId({
1215
- params,
1297
+ const sessionId = await resolveAibotDeleteTarget({
1298
+ client,
1299
+ accountId: account.accountId,
1300
+ sessionId: readStringishParam(params, "sessionId"),
1301
+ to: readStringishParam(params, "to"),
1302
+ topic: readStringishParam(params, "topic"),
1216
1303
  currentChannelId: toolContext?.currentChannelId
1217
1304
  });
1218
1305
  if (!sessionId) {
@@ -1351,6 +1438,56 @@ function buildBodyWithQuotedReplyId(rawBody, quotedMessageId) {
1351
1438
  ${rawBody}`;
1352
1439
  }
1353
1440
 
1441
+ // src/revoke-event.ts
1442
+ function toStringId(value) {
1443
+ return String(value ?? "").trim();
1444
+ }
1445
+ function resolveChatType(sessionType) {
1446
+ if (sessionType === 1) {
1447
+ return "direct";
1448
+ }
1449
+ if (sessionType === 2) {
1450
+ return "group";
1451
+ }
1452
+ throw new Error(`clawpool revoke event has unsupported session_type=${sessionType}`);
1453
+ }
1454
+ function enqueueRevokeSystemEvent(params) {
1455
+ const sessionId = toStringId(params.event.session_id);
1456
+ const messageId = toStringId(params.event.msg_id);
1457
+ const senderId = toStringId(params.event.sender_id);
1458
+ const sessionType = Number(params.event.session_type);
1459
+ if (!sessionId || !messageId) {
1460
+ throw new Error(
1461
+ `invalid event_revoke payload: session_id=${sessionId || "<empty>"} msg_id=${messageId || "<empty>"}`
1462
+ );
1463
+ }
1464
+ const chatType = resolveChatType(sessionType);
1465
+ const route = params.core.channel.routing.resolveAgentRoute({
1466
+ cfg: params.config,
1467
+ channel: "clawpool",
1468
+ accountId: params.account.accountId,
1469
+ peer: {
1470
+ kind: chatType,
1471
+ id: sessionId
1472
+ }
1473
+ });
1474
+ const metadataParts = [`session_id=${sessionId}`, `msg_id=${messageId}`];
1475
+ if (senderId) {
1476
+ metadataParts.push(`sender_id=${senderId}`);
1477
+ }
1478
+ const text = `Clawpool ${chatType} message deleted [${metadataParts.join(" ")}]`;
1479
+ params.core.system.enqueueSystemEvent(text, {
1480
+ sessionKey: route.sessionKey,
1481
+ contextKey: `clawpool:revoke:${sessionId}:${messageId}`
1482
+ });
1483
+ return {
1484
+ messageId,
1485
+ sessionId,
1486
+ sessionKey: route.sessionKey,
1487
+ text
1488
+ };
1489
+ }
1490
+
1354
1491
  // src/monitor.ts
1355
1492
  var activeMonitorClients = /* @__PURE__ */ new Map();
1356
1493
  function registerActiveMonitor(accountId, client) {
@@ -1376,7 +1513,7 @@ function clearActiveMonitor(accountId, client) {
1376
1513
  }
1377
1514
  activeMonitorClients.delete(accountId);
1378
1515
  }
1379
- function toStringId(value) {
1516
+ function toStringId2(value) {
1380
1517
  const text = String(value ?? "").trim();
1381
1518
  return text;
1382
1519
  }
@@ -1391,7 +1528,7 @@ function toTimestampMs(value) {
1391
1528
  return Math.floor(n);
1392
1529
  }
1393
1530
  function normalizeNumericMessageId(value) {
1394
- const raw = toStringId(value);
1531
+ const raw = toStringId2(value);
1395
1532
  if (!raw) {
1396
1533
  return void 0;
1397
1534
  }
@@ -1572,8 +1709,8 @@ async function bindSessionRouteMapping(params) {
1572
1709
  async function processEvent(params) {
1573
1710
  const { event, account, config, runtime: runtime2, client, statusSink } = params;
1574
1711
  const core = getAibotRuntime();
1575
- const sessionId = toStringId(event.session_id);
1576
- const messageSid = toStringId(event.msg_id);
1712
+ const sessionId = toStringId2(event.session_id);
1713
+ const messageSid = toStringId2(event.msg_id);
1577
1714
  const rawBody = String(event.content ?? "").trim();
1578
1715
  if (!sessionId || !messageSid || !rawBody) {
1579
1716
  const reason = `invalid event_msg payload: session_id=${sessionId || "<empty>"} msg_id=${messageSid || "<empty>"}`;
@@ -1581,10 +1718,10 @@ async function processEvent(params) {
1581
1718
  statusSink?.({ lastError: reason });
1582
1719
  return;
1583
1720
  }
1584
- const eventId = toStringId(event.event_id);
1721
+ const eventId = toStringId2(event.event_id);
1585
1722
  const quotedMessageId = normalizeNumericMessageId(event.quoted_message_id);
1586
1723
  const bodyForAgent = buildBodyWithQuotedReplyId(rawBody, quotedMessageId);
1587
- const senderId = toStringId(event.sender_id);
1724
+ const senderId = toStringId2(event.sender_id);
1588
1725
  const isGroup = Number(event.session_type ?? 0) === 2 || String(event.event_type ?? "").startsWith("group_");
1589
1726
  const chatType = isGroup ? "group" : "direct";
1590
1727
  const createdAt = toTimestampMs(event.created_at);
@@ -1933,6 +2070,27 @@ async function monitorAibotProvider(options) {
1933
2070
  runtime2.error(`[clawpool:${account.accountId}] process event failed: ${msg}`);
1934
2071
  guardedStatusSink({ lastError: msg });
1935
2072
  });
2073
+ },
2074
+ onEventRevoke: (event) => {
2075
+ if (!isActiveMonitor(account.accountId, client)) {
2076
+ return;
2077
+ }
2078
+ guardedStatusSink({ lastInboundAt: Date.now() });
2079
+ try {
2080
+ const revokeEvent = enqueueRevokeSystemEvent({
2081
+ core: getAibotRuntime(),
2082
+ event,
2083
+ account,
2084
+ config
2085
+ });
2086
+ runtime2.log(
2087
+ `[clawpool:${account.accountId}] inbound revoke sessionId=${revokeEvent.sessionId} messageSid=${revokeEvent.messageId} routeSessionKey=${revokeEvent.sessionKey}`
2088
+ );
2089
+ } catch (err) {
2090
+ const msg = err instanceof Error ? err.message : String(err);
2091
+ runtime2.error(`[clawpool:${account.accountId}] process revoke event failed: ${msg}`);
2092
+ guardedStatusSink({ lastError: msg });
2093
+ }
1936
2094
  }
1937
2095
  });
1938
2096
  const previousClient = registerActiveMonitor(account.accountId, client);
@@ -2181,85 +2339,12 @@ var clawpoolOnboardingAdapter = {
2181
2339
  })
2182
2340
  };
2183
2341
 
2184
- // src/target-resolver.ts
2185
- var aibotSessionIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2186
- function isAibotSessionID(value) {
2187
- const normalized = String(value ?? "").trim();
2188
- if (!normalized) {
2189
- return false;
2190
- }
2191
- return aibotSessionIDPattern.test(normalized);
2192
- }
2193
- function normalizeAibotSessionTarget2(raw) {
2194
- const trimmed = String(raw ?? "").trim();
2195
- if (!trimmed) {
2196
- return "";
2197
- }
2198
- return trimmed.replace(/^clawpool:/i, "").replace(/^session:/i, "").trim();
2199
- }
2200
- function buildRouteSessionKeyCandidates(rawTarget, normalizedTarget) {
2201
- if (rawTarget === normalizedTarget) {
2202
- return [rawTarget];
2203
- }
2204
- return [rawTarget, normalizedTarget].filter((candidate) => candidate.length > 0);
2205
- }
2206
- async function resolveAibotOutboundTarget(params) {
2207
- const rawTarget = String(params.to ?? "").trim();
2208
- if (!rawTarget) {
2209
- throw new Error("clawpool outbound target must be non-empty");
2210
- }
2211
- const normalizedTarget = normalizeAibotSessionTarget2(rawTarget);
2212
- if (!normalizedTarget) {
2213
- throw new Error("clawpool outbound target must contain session_id or route_session_key");
2214
- }
2215
- if (isAibotSessionID(normalizedTarget)) {
2216
- return {
2217
- sessionId: normalizedTarget,
2218
- rawTarget,
2219
- normalizedTarget,
2220
- resolveSource: "direct"
2221
- };
2222
- }
2223
- if (/^\d+$/.test(normalizedTarget)) {
2224
- throw new Error(
2225
- `clawpool outbound target "${rawTarget}" is numeric; expected session_id(UUID) or route.sessionKey`
2226
- );
2227
- }
2228
- const routeSessionKeyCandidates = buildRouteSessionKeyCandidates(rawTarget, normalizedTarget);
2229
- let lastResolveError = null;
2230
- for (const routeSessionKey of routeSessionKeyCandidates) {
2231
- try {
2232
- const ack = await params.client.resolveSessionRoute("clawpool", params.accountId, routeSessionKey);
2233
- const sessionId = String(ack.session_id ?? "").trim();
2234
- if (!isAibotSessionID(sessionId)) {
2235
- throw new Error(
2236
- `session_route_resolve returned invalid session_id for route_session_key="${routeSessionKey}"`
2237
- );
2238
- }
2239
- return {
2240
- sessionId,
2241
- rawTarget,
2242
- normalizedTarget,
2243
- resolveSource: "sessionRouteMap"
2244
- };
2245
- } catch (err) {
2246
- lastResolveError = err instanceof Error ? err : new Error(String(err));
2247
- }
2248
- }
2249
- if (lastResolveError) {
2250
- throw new Error(
2251
- `clawpool outbound target resolve failed target="${rawTarget}" accountId=${params.accountId}: ${lastResolveError.message}`
2252
- );
2253
- }
2254
- throw new Error(`clawpool outbound target resolve failed target="${rawTarget}" accountId=${params.accountId}`);
2255
- }
2256
-
2257
2342
  // src/channel.ts
2258
2343
  var meta = {
2259
2344
  id: "clawpool",
2260
2345
  label: "Clawpool",
2261
2346
  selectionLabel: "Clawpool",
2262
- blurb: "Bridge OpenClaw to Clawpool over the Aibot Agent API WebSocket.",
2347
+ blurb: "Bridge OpenClaw to Clawpool over the ClawPool Agent API WebSocket.",
2263
2348
  aliases: ["cp", "clowpool"],
2264
2349
  order: 90
2265
2350
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhfpub/clawpool",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "OpenClaw channel plugin for ClawPool",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,7 +52,7 @@
52
52
  "id": "clawpool",
53
53
  "label": "Clawpool",
54
54
  "selectionLabel": "Clawpool",
55
- "blurb": "Bridge OpenClaw to Clawpool over the Aibot Agent API WebSocket.",
55
+ "blurb": "Bridge OpenClaw to Clawpool over the ClawPool Agent API WebSocket.",
56
56
  "aliases": [
57
57
  "cp",
58
58
  "clowpool"