oomi-ai 0.3.1 → 0.3.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.
package/bin/oomi-ai.js CHANGED
@@ -1355,6 +1355,127 @@ function extractTextFromGatewayMessage(message) {
1355
1355
  .join(' ');
1356
1356
  }
1357
1357
 
1358
+ function normalizeWhitespace(value) {
1359
+ return String(value || '')
1360
+ .replace(/[ \t]+\n/g, '\n')
1361
+ .replace(/\n{3,}/g, '\n\n')
1362
+ .replace(/[ \t]{2,}/g, ' ')
1363
+ .trim();
1364
+ }
1365
+
1366
+ function sanitizeUserVisibleText(value) {
1367
+ let text = String(value || '');
1368
+ if (!text.trim()) return '';
1369
+
1370
+ text = text.replace(/<think>[\s\S]*?<\/think>/gi, ' ');
1371
+ if (/<think>/i.test(text)) {
1372
+ text = text.replace(/<think>[\s\S]*$/i, ' ');
1373
+ }
1374
+ text = text.replace(/<\/think>/gi, ' ');
1375
+
1376
+ text = text.replace(/(?:^|\n)System \(untrusted\):[\s\S]*?(?=\n\n|$)/gi, '\n');
1377
+ text = text.replace(
1378
+ /(?:^|\n)An async command you ran earlier has completed\.[\s\S]*?(?:\nCurrent time:[^\n]*(?:\n|$)|$)/gi,
1379
+ '\n'
1380
+ );
1381
+
1382
+ return normalizeWhitespace(text);
1383
+ }
1384
+
1385
+ function shouldSuppressOomiVisibleText(value) {
1386
+ const text = String(value || '').trim();
1387
+ if (!text) return true;
1388
+
1389
+ if (/System \(untrusted\):/i.test(text)) return true;
1390
+ if (/An async command you ran earlier has completed\./i.test(text)) return true;
1391
+ if (/Handle the result internally\. Do not relay it to the user/i.test(text)) return true;
1392
+ if (/Exec (failed|completed) \([^)]+\) ::/i.test(text)) return true;
1393
+ if (/^\s*<think>[\s\S]*$/i.test(text) && !/<\/think>/i.test(text)) return true;
1394
+
1395
+ return !sanitizeUserVisibleText(text);
1396
+ }
1397
+
1398
+ function sanitizeGatewayMessageForOomi(message) {
1399
+ if (!message || typeof message !== 'object') return null;
1400
+
1401
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
1402
+ if (role && !['assistant', 'user'].includes(role)) {
1403
+ return null;
1404
+ }
1405
+
1406
+ const rawText = extractTextFromGatewayMessage(message);
1407
+ if (shouldSuppressOomiVisibleText(rawText)) {
1408
+ return null;
1409
+ }
1410
+
1411
+ const content = sanitizeUserVisibleText(rawText);
1412
+ if (!content) return null;
1413
+
1414
+ return {
1415
+ ...message,
1416
+ role: role || 'assistant',
1417
+ content,
1418
+ };
1419
+ }
1420
+
1421
+ function sanitizeGatewayFrameForOomiClient(frameText) {
1422
+ const frame = parseJsonPayload(frameText);
1423
+ if (!frame || typeof frame !== 'object') {
1424
+ return { frameText, changed: false, suppressed: false };
1425
+ }
1426
+
1427
+ if (frame.type === 'res' && frame.payload && typeof frame.payload === 'object' && Array.isArray(frame.payload.messages)) {
1428
+ const nextMessages = frame.payload.messages
1429
+ .map((message) => sanitizeGatewayMessageForOomi(message))
1430
+ .filter(Boolean);
1431
+ const nextFrame = {
1432
+ ...frame,
1433
+ payload: {
1434
+ ...frame.payload,
1435
+ messages: nextMessages,
1436
+ },
1437
+ };
1438
+ const nextFrameText = JSON.stringify(nextFrame);
1439
+ return {
1440
+ frameText: nextFrameText,
1441
+ changed: nextFrameText !== frameText,
1442
+ suppressed: nextMessages.length < frame.payload.messages.length,
1443
+ };
1444
+ }
1445
+
1446
+ if (frame.type === 'event' && frame.event === 'chat') {
1447
+ const payload = frame.payload && typeof frame.payload === 'object' ? frame.payload : null;
1448
+ const message = payload?.message && typeof payload.message === 'object' ? payload.message : null;
1449
+ if (!payload || !message) {
1450
+ return { frameText, changed: false, suppressed: false };
1451
+ }
1452
+
1453
+ const sanitizedMessage = sanitizeGatewayMessageForOomi(message);
1454
+ const nextFrame = {
1455
+ ...frame,
1456
+ payload: {
1457
+ ...payload,
1458
+ message: sanitizedMessage || {
1459
+ ...message,
1460
+ content: '',
1461
+ metadata: {
1462
+ ...(message.metadata && typeof message.metadata === 'object' && !Array.isArray(message.metadata) ? message.metadata : {}),
1463
+ oomiSuppressed: true,
1464
+ },
1465
+ },
1466
+ },
1467
+ };
1468
+ const nextFrameText = JSON.stringify(nextFrame);
1469
+ return {
1470
+ frameText: nextFrameText,
1471
+ changed: nextFrameText !== frameText,
1472
+ suppressed: !sanitizedMessage,
1473
+ };
1474
+ }
1475
+
1476
+ return { frameText, changed: false, suppressed: false };
1477
+ }
1478
+
1358
1479
  function summarizeVoiceFrameContract(frameText) {
1359
1480
  const frame = parseJsonPayload(frameText);
1360
1481
  if (!frame || typeof frame !== 'object') {
@@ -2997,6 +3118,13 @@ async function startOpenclawBridge(flags) {
2997
3118
 
2998
3119
  gatewaySocket.on('message', runBridgeCallbackSafely((gatewayRaw) => {
2999
3120
  let frame = typeof gatewayRaw === 'string' ? gatewayRaw : gatewayRaw.toString();
3121
+ const visibleSanitized = sanitizeGatewayFrameForOomiClient(frame);
3122
+ if (visibleSanitized.changed) {
3123
+ frame = visibleSanitized.frameText;
3124
+ bridgeDebugLog(
3125
+ `[bridge] oomi.visible_sanitized ${sessionId} suppressed=${visibleSanitized.suppressed ? 'yes' : 'no'}`
3126
+ );
3127
+ }
3000
3128
  const spokenNormalized = normalizeAssistantGatewayFrame(sessionId, frame);
3001
3129
  if (spokenNormalized.changed) {
3002
3130
  frame = spokenNormalized.frameText;
@@ -4213,6 +4341,9 @@ if (__isDirectExecution) {
4213
4341
 
4214
4342
  export {
4215
4343
  prepareGatewayFrameForLocalGateway,
4344
+ sanitizeGatewayFrameForOomiClient,
4345
+ sanitizeUserVisibleText,
4346
+ shouldSuppressOomiVisibleText,
4216
4347
  ensureAssistantSpokenMetadata,
4217
4348
  normalizeAssistantGatewayFrame,
4218
4349
  runAssistantFinalDebugCheck,
@@ -107,6 +107,46 @@ function extractText(payload) {
107
107
  return '';
108
108
  }
109
109
 
110
+ function normalizeWhitespace(value) {
111
+ return String(value || '')
112
+ .replace(/[ \t]+\n/g, '\n')
113
+ .replace(/\n{3,}/g, '\n\n')
114
+ .replace(/[ \t]{2,}/g, ' ')
115
+ .trim();
116
+ }
117
+
118
+ function sanitizeUserVisibleText(value) {
119
+ let text = String(value || '');
120
+ if (!text.trim()) return '';
121
+
122
+ text = text.replace(/<think>[\s\S]*?<\/think>/gi, ' ');
123
+ if (/<think>/i.test(text)) {
124
+ text = text.replace(/<think>[\s\S]*$/i, ' ');
125
+ }
126
+ text = text.replace(/<\/think>/gi, ' ');
127
+
128
+ text = text.replace(/(?:^|\n)System \(untrusted\):[\s\S]*?(?=\n\n|$)/gi, '\n');
129
+ text = text.replace(
130
+ /(?:^|\n)An async command you ran earlier has completed\.[\s\S]*?(?:\nCurrent time:[^\n]*(?:\n|$)|$)/gi,
131
+ '\n'
132
+ );
133
+
134
+ return normalizeWhitespace(text);
135
+ }
136
+
137
+ function shouldSuppressOomiVisibleText(value) {
138
+ const text = String(value || '').trim();
139
+ if (!text) return true;
140
+
141
+ if (/System \(untrusted\):/i.test(text)) return true;
142
+ if (/An async command you ran earlier has completed\./i.test(text)) return true;
143
+ if (/Handle the result internally\. Do not relay it to the user/i.test(text)) return true;
144
+ if (/Exec (failed|completed) \([^)]+\) ::/i.test(text)) return true;
145
+ if (/^\s*<think>[\s\S]*$/i.test(text) && !/<\/think>/i.test(text)) return true;
146
+
147
+ return !sanitizeUserVisibleText(text);
148
+ }
149
+
110
150
  function extractConversationKey(payload) {
111
151
  const candidates = [
112
152
  payload?.conversationKey,
@@ -290,7 +330,16 @@ const oomiChannelPlugin = {
290
330
  };
291
331
  }
292
332
 
293
- const content = extractText(payload);
333
+ const rawContent = extractText(payload);
334
+ if (shouldSuppressOomiVisibleText(rawContent)) {
335
+ return {
336
+ ok: true,
337
+ suppressed: true,
338
+ reason: 'internal_content',
339
+ };
340
+ }
341
+
342
+ const content = sanitizeUserVisibleText(rawContent);
294
343
  if (!content) {
295
344
  return {
296
345
  ok: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oomi-ai",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Managed Oomi channel, bridge, voice, and persona app API tooling for OpenClaw",
5
5
  "bin": {
6
6
  "oomi": "bin/oomi-ai.js"