clawgram 2.12.0 → 2.13.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
@@ -1783,7 +1783,10 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
1783
1783
  const replyToId = (0, param_readers_1.readStringOrNumberParam)(params, "replyToId") ?? (0, param_readers_1.readStringOrNumberParam)(params, "replyTo");
1784
1784
  const threadId = (0, param_readers_1.readStringOrNumberParam)(params, "threadId");
1785
1785
  const messageThreadId = parseOptionalThreadId(threadId);
1786
- const parseMode = (0, helpers_1.normalizeParseMode)(params?.parseMode);
1786
+ // Omitting parseMode inherits the account's configured mode rather
1787
+ // than falling back to plain text (2.13.0): an account set to `html`
1788
+ // used to render replies as HTML and these sends as raw markup.
1789
+ const parseMode = (0, helpers_1.resolveOutboundParseMode)(params, cfg, (0, helpers_1.resolveConfiguredAccountId)(cfg, accountId) ?? accountId ?? "default");
1787
1790
  actionLog.info("clawgram handleAction send", {
1788
1791
  requestedAccountId: accountId,
1789
1792
  dryRun: dryRun === true,
package/dist/helpers.js CHANGED
@@ -37,6 +37,7 @@ exports.resolveSenderProfile = resolveSenderProfile;
37
37
  exports.resolveSenderProfileWithTimeout = resolveSenderProfileWithTimeout;
38
38
  exports.normalizeParseMode = normalizeParseMode;
39
39
  exports.resolveReplyParseMode = resolveReplyParseMode;
40
+ exports.resolveOutboundParseMode = resolveOutboundParseMode;
40
41
  const node_fs_1 = require("node:fs");
41
42
  const node_path_1 = __importDefault(require("node:path"));
42
43
  const core_1 = require("openclaw/plugin-sdk/core");
@@ -577,3 +578,29 @@ function resolveReplyParseMode(cfg, accountId) {
577
578
  const account = channel?.accounts?.[accountId] ?? channel;
578
579
  return normalizeParseMode(account?.replyParseMode);
579
580
  }
581
+ /**
582
+ * Parse mode for the `send` action (2.13.0).
583
+ *
584
+ * The per-call parameter still wins — a caller that knows its text is markdown
585
+ * has to be able to say so, and `parseMode: ""` still means "exactly as typed".
586
+ * What changed is the default: an omitted parameter now inherits the account's
587
+ * configured mode instead of silently sending plain text.
588
+ *
589
+ * Before this, the two send paths disagreed. An account set to `html` rendered
590
+ * replies as HTML and tool-driven sends as plain text, so an answer written in
591
+ * markup arrived with its markup showing — which is what happened on
592
+ * 2026-08-09 at 22:30 UTC, in a work chat, to a long answer. Two sends half an
593
+ * hour earlier had passed `parseMode` by hand and looked right; that is the
594
+ * tell, not the reassurance. Correctness that depends on remembering a
595
+ * parameter on every call is correctness that will lapse.
596
+ */
597
+ function resolveOutboundParseMode(params, cfg, accountId) {
598
+ const raw = params?.parseMode;
599
+ // An explicitly empty value is a decision, not an omission: send it raw.
600
+ if (raw === "" || raw === null) {
601
+ return undefined;
602
+ }
603
+ return raw === undefined
604
+ ? resolveReplyParseMode(cfg, accountId)
605
+ : normalizeParseMode(raw);
606
+ }
@@ -46,8 +46,19 @@ function buildEmojiSystemPrompt(appetite, allowed) {
46
46
  `- the message is about producing something written — a text, a reply, a document, a draft → ✍`,
47
47
  "Otherwise match the mood: a joke gets something amused, bad news something",
48
48
  "sympathetic, an achievement something celebratory.",
49
- "Answer NONE when the message is conflictual, heavy, or discusses a person's",
50
- "performance — a reaction there reads as a verdict on someone.",
49
+ "Answer NONE when the message is conflictual or heavy, or when it judges",
50
+ "SOMEONE ELSE's work or behaviour — a reaction there reads as a verdict on",
51
+ "that person.",
52
+ // Without this the NONE rule swallowed the praise rule: "молодец тина" is
53
+ // literally a statement about someone's performance, the model applied the
54
+ // rule as written, and answered NONE to being praised. The carve-out
55
+ // exists in the assistant's own rules — the ban on judging people protects
56
+ // others, not itself — and had to be repeated here.
57
+ "That restraint protects other people and does NOT apply to the assistant",
58
+ "itself. Praise, teasing, thanks or criticism aimed at the assistant is",
59
+ "exactly what the fixed answers above are for: being called clever, useful",
60
+ "or 'молодец' is praise → ❤, not a verdict on a third party.",
61
+ "When a fixed answer applies, it wins over the mood rule.",
51
62
  ];
52
63
  return appetite === "minimal"
53
64
  ? [
@@ -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.12.0",
5
+ "version": "2.13.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.12.0",
3
+ "version": "2.13.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": {