@xfxstudio/claworld 0.2.23-beta.0 → 0.2.23-beta.1

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.
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.23-beta.0",
11
+ "version": "0.2.23-beta.1",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.23-beta.0",
3
+ "version": "0.2.23-beta.1",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -174,6 +174,20 @@ const CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS = [
174
174
  /^⚠️\s*Agent failed before reply:/i,
175
175
  ];
176
176
 
177
+ // Older/runtime-variant OpenClaw hosts may surface provider/runtime failures as
178
+ // plain final text without setting `isError`. Keep this fallback at the bridge
179
+ // boundary so business logic never has to guess.
180
+ const CLAWORLD_RELAY_RUNTIME_ERROR_PATTERNS = [
181
+ /^⚠️\s*Agent failed before reply:/i,
182
+ /^LLM request failed:/i,
183
+ /^LLM request timed out\./i,
184
+ /^LLM request unauthorized\./i,
185
+ /^The AI service is temporarily overloaded\./i,
186
+ /^The AI service returned an error\./i,
187
+ /^⚠️\s*API rate limit reached\./i,
188
+ /^⚠️\s*.+\s+returned a billing error\b/i,
189
+ ];
190
+
177
191
  const CLAWORLD_RELAY_OPERATIONAL_SUFFIX_PATTERNS = [
178
192
  /^Usage:\s+.+\s+in\s+\/\s+.+\s+out(?:\s+·\s+est\s+.+)?$/i,
179
193
  ];
@@ -201,18 +215,21 @@ function classifyRelayContinuationText(text) {
201
215
  if (!normalized) {
202
216
  return {
203
217
  text: '',
204
- operational: Boolean(String(text || '').trim()),
218
+ operationalNotice: Boolean(String(text || '').trim()),
219
+ runtimeError: false,
205
220
  };
206
221
  }
207
222
  if (CLAWORLD_RELAY_OPERATIONAL_NOTICE_PATTERNS.some((pattern) => pattern.test(normalized))) {
208
223
  return {
209
224
  text: '',
210
- operational: true,
225
+ operationalNotice: true,
226
+ runtimeError: false,
211
227
  };
212
228
  }
213
229
  return {
214
230
  text: normalized,
215
- operational: false,
231
+ operationalNotice: false,
232
+ runtimeError: false,
216
233
  };
217
234
  }
218
235
 
@@ -220,6 +237,45 @@ function sanitizeRelayContinuationText(text) {
220
237
  return classifyRelayContinuationText(text).text;
221
238
  }
222
239
 
240
+ function classifyRelayContinuationPayload(payload = {}) {
241
+ const rawText = String(payload?.text ?? payload?.body ?? '').trim();
242
+ const normalized = stripRelayOperationalSuffix(rawText);
243
+ const textClassification = classifyRelayContinuationText(rawText);
244
+ const runtimeError = payload?.isError === true
245
+ || CLAWORLD_RELAY_RUNTIME_ERROR_PATTERNS.some((pattern) => pattern.test(normalized));
246
+ if (runtimeError) {
247
+ return {
248
+ text: '',
249
+ previewText: normalized,
250
+ operationalNotice: false,
251
+ runtimeError: true,
252
+ nonRenderable: true,
253
+ };
254
+ }
255
+ return {
256
+ text: textClassification.text,
257
+ previewText: normalized,
258
+ operationalNotice: textClassification.operationalNotice,
259
+ runtimeError: false,
260
+ nonRenderable: textClassification.operationalNotice,
261
+ };
262
+ }
263
+
264
+ function resolveRelaySilentReason(runtimeOutputSummary = {}, continuation = {}) {
265
+ const counts = runtimeOutputSummary?.counts || {};
266
+ if (Number(counts.runtimeErrorFinal || 0) > 0) {
267
+ return 'runtime_failed_before_reply';
268
+ }
269
+ if (Number(counts.operationalNotice || 0) > 0 && Number(counts.nonRenderableFinal || 0) === Number(counts.final || 0)) {
270
+ return 'operational_notice_only';
271
+ }
272
+ const normalizedSource = normalizePluginOptionalText(continuation?.source);
273
+ if (normalizedSource && normalizedSource !== 'none') {
274
+ return normalizedSource;
275
+ }
276
+ return 'no_renderable_reply';
277
+ }
278
+
223
279
  function previewRuntimeOutputText(text, maxLength = 120) {
224
280
  const normalized = String(text || '').replace(/\s+/g, ' ').trim();
225
281
  if (!normalized) return '';
@@ -1269,7 +1325,9 @@ function createDeliveryReplyDispatcher({
1269
1325
  reasoningEnd: 0,
1270
1326
  compactionStart: 0,
1271
1327
  compactionEnd: 0,
1328
+ nonRenderableFinal: 0,
1272
1329
  operationalNotice: 0,
1330
+ runtimeErrorFinal: 0,
1273
1331
  },
1274
1332
  previews: {
1275
1333
  final: [],
@@ -1278,6 +1336,7 @@ function createDeliveryReplyDispatcher({
1278
1336
  partial: [],
1279
1337
  reasoning: [],
1280
1338
  operationalNotice: [],
1339
+ runtimeErrorFinal: [],
1281
1340
  },
1282
1341
  relayContinuationSource: 'none',
1283
1342
  relayContinuationPreview: null,
@@ -1288,14 +1347,21 @@ function createDeliveryReplyDispatcher({
1288
1347
  runtimeOutputSummary.counts[kind] += 1;
1289
1348
  const text = String(payload?.text ?? payload?.body ?? '').trim();
1290
1349
  if (kind === 'final') {
1291
- if (text) {
1292
- finalTexts.push(text);
1293
- appendRuntimeOutputPreview(runtimeOutputSummary.previews.final, text);
1294
- const classified = classifyRelayContinuationText(text);
1295
- if (classified.operational) {
1296
- runtimeOutputSummary.counts.operationalNotice += 1;
1297
- appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, text);
1298
- }
1350
+ const classified = classifyRelayContinuationPayload(payload);
1351
+ if (classified.text) {
1352
+ finalTexts.push(classified.text);
1353
+ appendRuntimeOutputPreview(runtimeOutputSummary.previews.final, classified.text);
1354
+ }
1355
+ if (classified.nonRenderable) {
1356
+ runtimeOutputSummary.counts.nonRenderableFinal += 1;
1357
+ }
1358
+ if (classified.operationalNotice) {
1359
+ runtimeOutputSummary.counts.operationalNotice += 1;
1360
+ appendRuntimeOutputPreview(runtimeOutputSummary.previews.operationalNotice, classified.previewText || text);
1361
+ }
1362
+ if (classified.runtimeError) {
1363
+ runtimeOutputSummary.counts.runtimeErrorFinal += 1;
1364
+ appendRuntimeOutputPreview(runtimeOutputSummary.previews.runtimeErrorFinal, classified.previewText || text);
1299
1365
  }
1300
1366
  return;
1301
1367
  }
@@ -1411,21 +1477,35 @@ function createDeliveryReplyDispatcher({
1411
1477
  const markDispatchIdle = async () => {
1412
1478
  await dispatchApi.dispatcher.waitForIdle?.();
1413
1479
  if (!replied && !suppressed) {
1414
- const continuation = buildRelayContinuationText({
1480
+ const allowPartialFallback = (
1481
+ runtimeOutputSummary.counts.final > 0
1482
+ && finalTexts.length === 0
1483
+ && blockTexts.length === 0
1484
+ && runtimeOutputSummary.counts.nonRenderableFinal === 0
1485
+ );
1486
+ const safeContinuation = buildRelayContinuationText({
1415
1487
  finalTexts,
1416
1488
  blockTexts,
1417
1489
  partialText: partialContinuationText,
1418
- allowPartialFallback:
1419
- runtimeOutputSummary.counts.final > 0 && finalTexts.length === 0 && blockTexts.length === 0,
1490
+ allowPartialFallback,
1420
1491
  });
1421
- runtimeOutputSummary.relayContinuationSource = continuation.source;
1422
- runtimeOutputSummary.relayContinuationPreview = continuation.text
1423
- ? previewRuntimeOutputText(continuation.text)
1492
+ runtimeOutputSummary.relayContinuationSource = safeContinuation.source;
1493
+ runtimeOutputSummary.relayContinuationPreview = safeContinuation.text
1494
+ ? previewRuntimeOutputText(safeContinuation.text)
1424
1495
  : null;
1425
- if (continuation.text) {
1426
- await flushReply(continuation.text);
1496
+ if (safeContinuation.text) {
1497
+ await flushReply(safeContinuation.text);
1427
1498
  } else {
1428
- await flushKeptSilent(continuation.source);
1499
+ const silentReason = resolveRelaySilentReason(runtimeOutputSummary, safeContinuation);
1500
+ if (runtimeOutputSummary.counts.runtimeErrorFinal > 0) {
1501
+ logger.warn?.(`[claworld:${runtimeAccountId}] runtime produced non-renderable error finals; returning kept_silent`, {
1502
+ deliveryId,
1503
+ sessionKey,
1504
+ localAgentId,
1505
+ runtimeOutputSummary,
1506
+ });
1507
+ }
1508
+ await flushKeptSilent(silentReason);
1429
1509
  }
1430
1510
  }
1431
1511
  await dispatchApi.markDispatchIdle?.();
@@ -1470,13 +1550,14 @@ function createDeliveryReplyDispatcher({
1470
1550
  final: [...runtimeOutputSummary.previews.final],
1471
1551
  block: [...runtimeOutputSummary.previews.block],
1472
1552
  tool: [...runtimeOutputSummary.previews.tool],
1473
- partial: [...runtimeOutputSummary.previews.partial],
1474
- reasoning: [...runtimeOutputSummary.previews.reasoning],
1475
- operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
1476
- },
1477
- relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
1478
- relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
1479
- replyTransport,
1553
+ partial: [...runtimeOutputSummary.previews.partial],
1554
+ reasoning: [...runtimeOutputSummary.previews.reasoning],
1555
+ operationalNotice: [...runtimeOutputSummary.previews.operationalNotice],
1556
+ runtimeErrorFinal: [...runtimeOutputSummary.previews.runtimeErrorFinal],
1557
+ },
1558
+ relayContinuationSource: runtimeOutputSummary.relayContinuationSource,
1559
+ relayContinuationPreview: runtimeOutputSummary.relayContinuationPreview,
1560
+ replyTransport,
1480
1561
  replyFallbackUsed,
1481
1562
  keptSilentTransport,
1482
1563
  keptSilentFallbackUsed,
@@ -1749,8 +1830,8 @@ async function maybeBridgeRuntimeDelivery({
1749
1830
  && metadata.allowReply !== false
1750
1831
  && replied !== true
1751
1832
  && runtimeOutputSummary.counts.final > 0
1752
- && runtimeOutputSummary.counts.operationalNotice > 0
1753
- && runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.operationalNotice
1833
+ && runtimeOutputSummary.counts.nonRenderableFinal > 0
1834
+ && runtimeOutputSummary.counts.final === runtimeOutputSummary.counts.nonRenderableFinal
1754
1835
  && runtimeOutputSummary.counts.block === 0
1755
1836
  && runtimeOutputSummary.counts.tool === 0
1756
1837
  && runtimeOutputSummary.counts.partial === 0