clawgram 2.19.4 → 2.20.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/dist/channel.js CHANGED
@@ -70,6 +70,7 @@ const joins_1 = require("./joins");
70
70
  const reactions_1 = require("./reactions");
71
71
  const manage_1 = require("./manage");
72
72
  const silent_reaction_1 = require("./silent-reaction");
73
+ const system_notice_1 = require("./system-notice");
73
74
  const chat_info_1 = require("./chat-info");
74
75
  const topics_1 = require("./topics");
75
76
  const dialogs_1 = require("./dialogs");
@@ -2405,6 +2406,24 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
2405
2406
  });
2406
2407
  return { skipped: "silent" };
2407
2408
  }
2409
+ // Core's operational chatter (tool-error warnings, fallback notices)
2410
+ // stays out of group chats: it is telemetry for the operator, not a
2411
+ // reply to the room, and it has already been seen carrying shell
2412
+ // commands with secret-store paths. DMs keep it. The text itself is
2413
+ // never logged — see system-notice.ts for why.
2414
+ const suppressedNotice = (0, system_notice_1.shouldSuppressGroupSystemNotice)({
2415
+ targetKind: (0, helpers_1.inferOutboundTargetKind)(ctx.to),
2416
+ text: ctx.text,
2417
+ });
2418
+ if (suppressedNotice) {
2419
+ actionLog.warn("clawgram suppressing system notice in group", {
2420
+ accountId: ctx.accountId,
2421
+ rawTo: ctx.to,
2422
+ noticeKind: suppressedNotice,
2423
+ textLength: ctx.text.length,
2424
+ });
2425
+ return { skipped: "system-notice" };
2426
+ }
2408
2427
  const gram = runtimes.get(ctx.accountId);
2409
2428
  if (!gram) {
2410
2429
  throw new Error(`clawgram: runtime not found for account ${ctx.accountId}`);
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Core's operational chatter — and why a group chat never sees it.
4
+ *
5
+ * When a tool call fails or the model silently degrades, core appends a
6
+ * status line to the turn's outbound payloads: `⚠️ 🛠️ Exec failed: …`,
7
+ * `⚠️ ✉️ Message failed`, `↪️ Model Fallback: …`. In a DM with the owner that
8
+ * is legitimate telemetry. In a group it is the assistant narrating its own
9
+ * kitchen to an audience the message was never for — measured three days in a
10
+ * row in the owner's work chat (2026-08-30 … 09-01): a jq stack trace with
11
+ * server paths, a bare "Message failed", an exec step list. The people in the
12
+ * chat cannot act on any of it, and the owner reads it as the assistant
13
+ * being broken.
14
+ *
15
+ * Detection is by core's own exact prefixes, not by keyword: the assistant is
16
+ * allowed to *say* "⚠️" or discuss a failure in its own words — only core's
17
+ * machine-built notices match. The list mirrors what core actually emits
18
+ * (`isCronToolWarning` matches `⚠️ 🛠️ ` verbatim; the message-failed check is
19
+ * the same normalized comparison core uses; fallback notices are built by
20
+ * `buildFallbackNotice`/`buildFallbackClearedNotice`).
21
+ *
22
+ * Nothing is lost by dropping them here: the same failures live in the run's
23
+ * diagnostics, the cron job's `lastError` and the gateway log. The drop is
24
+ * logged with the notice's class and length — never its text, which has
25
+ * already been seen carrying secret-store paths and full shell commands.
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.classifySystemNotice = classifySystemNotice;
29
+ exports.shouldSuppressGroupSystemNotice = shouldSuppressGroupSystemNotice;
30
+ const TOOL_WARNING_PREFIX = "⚠️ 🛠️ ";
31
+ const MESSAGE_FAILED_PREFIX = "⚠️ ✉️ message failed";
32
+ const FALLBACK_NOTICE_PREFIX = "↪️ model fallback";
33
+ /**
34
+ * Classifies core's operational status lines. `undefined` means the text is a
35
+ * real reply and must be delivered untouched.
36
+ */
37
+ function classifySystemNotice(text) {
38
+ const trimmed = text.trim();
39
+ if (!trimmed) {
40
+ return undefined;
41
+ }
42
+ if (trimmed.startsWith(TOOL_WARNING_PREFIX)) {
43
+ return "tool-warning";
44
+ }
45
+ const lower = trimmed.toLowerCase();
46
+ if (lower === MESSAGE_FAILED_PREFIX || lower.startsWith(`${MESSAGE_FAILED_PREFIX}:`)) {
47
+ return "message-failed";
48
+ }
49
+ // Covers both "↪️ Model Fallback: …" and "↪️ Model Fallback cleared: …".
50
+ if (lower.startsWith(FALLBACK_NOTICE_PREFIX)) {
51
+ return "model-fallback";
52
+ }
53
+ return undefined;
54
+ }
55
+ /**
56
+ * A notice glued to a real reply is not a notice: `classifySystemNotice` looks
57
+ * at the start of the text on purpose, so an answer that quotes or discusses a
58
+ * warning still goes out. Only a payload that IS the notice — the whole text,
59
+ * possibly with the group-address prefix core adds — is suppressible.
60
+ *
61
+ * The address prefix ("@name, ") is applied by the channel after this check,
62
+ * so the text seen here is core's payload verbatim.
63
+ */
64
+ function shouldSuppressGroupSystemNotice(params) {
65
+ // DMs keep the telemetry: there the reader is the person running the agent.
66
+ if (params.targetKind !== "group" && params.targetKind !== "channel") {
67
+ return undefined;
68
+ }
69
+ return classifySystemNotice(params.text);
70
+ }
@@ -2,7 +2,7 @@
2
2
  "id": "clawgram",
3
3
  "name": "Clawgram",
4
4
  "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
- "version": "2.19.4",
5
+ "version": "2.20.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawgram",
3
- "version": "2.19.4",
3
+ "version": "2.20.0",
4
4
  "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {