polygram 0.10.0-rc.38 → 0.10.0-rc.39
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/.claude-plugin/plugin.json +1 -1
- package/lib/telegram/sanitize-reply.js +82 -0
- package/package.json +1 -1
- package/polygram.js +20 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "polygram",
|
|
4
|
-
"version": "0.10.0-rc.
|
|
4
|
+
"version": "0.10.0-rc.39",
|
|
5
5
|
"description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands plus history (transcript queries) and polygram-send (out-of-turn IPC sends with file-upload validation) skills.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"telegram",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sanitize-reply — outbound assistant-text sanitizer for claude-CLI
|
|
3
|
+
* canned-string leakage.
|
|
4
|
+
*
|
|
5
|
+
* The model occasionally emits CLI-context boilerplate strings
|
|
6
|
+
* verbatim as Telegram replies — typically when its reasoning
|
|
7
|
+
* decides "no response needed." `POLYGRAM_DISPLAY_HINT` (rc.37
|
|
8
|
+
* hardening) explicitly forbids them, but the hint mitigation
|
|
9
|
+
* proved partial: the model still leaked `No response requested.`
|
|
10
|
+
* on a substantive user question (shumorobot Music, 2026-05-22
|
|
11
|
+
* 14:14). Likely CLI-internal, not prompt-driven.
|
|
12
|
+
*
|
|
13
|
+
* This sanitizer is the polygram-side safety net. Runs AFTER
|
|
14
|
+
* `parseResponse` — sees the parsed text the streamer/deliver
|
|
15
|
+
* path will send. On a verbatim match against a narrow allowlist
|
|
16
|
+
* of known canned strings, replaces with an honest brief message
|
|
17
|
+
* the user can act on (rephrase / retry).
|
|
18
|
+
*
|
|
19
|
+
* Narrow allowlist on purpose:
|
|
20
|
+
* - Exact full-text match (not substring) — paranoia against
|
|
21
|
+
* accidentally rewriting legitimate replies that mention these
|
|
22
|
+
* phrases (e.g. an explanation of the issue itself).
|
|
23
|
+
* - Does NOT include `No response generated. Please try again.`
|
|
24
|
+
* because that's polygram's own R10 empty-turn fallback, which
|
|
25
|
+
* is intentional output.
|
|
26
|
+
* - Does NOT include `Stopped.` because that's polygram's `/stop`
|
|
27
|
+
* confirmation.
|
|
28
|
+
*
|
|
29
|
+
* If new canned strings are observed in production, add them to
|
|
30
|
+
* CANNED_STRINGS with a comment naming the production trace.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
'use strict';
|
|
34
|
+
|
|
35
|
+
// Exact-match (trimmed) canned strings to intercept. Keep this list
|
|
36
|
+
// short and explicit — every entry is a known production leak.
|
|
37
|
+
const CANNED_STRINGS = new Set([
|
|
38
|
+
// shumorobot 2026-05-22 (Music topic, 13:17 and 14:14, both on
|
|
39
|
+
// rc.36/37). Model emitted this verbatim on the first occurrence
|
|
40
|
+
// after an ambiguous ack ("okay"); on the second, after a real
|
|
41
|
+
// substantive question. Prompt-side mitigation (rc.37) didn't
|
|
42
|
+
// catch the second case — confirming this is CLI-internal.
|
|
43
|
+
'No response requested.',
|
|
44
|
+
// Listed in the rc.37 display hint as an adjacent variant. Treated
|
|
45
|
+
// the same way if it ever appears.
|
|
46
|
+
'No response needed.',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// Replacement text — italic, brief, honest, actionable. Avoids
|
|
50
|
+
// pretending the bot did useful work; tells the user explicitly that
|
|
51
|
+
// the model didn't generate a real reply.
|
|
52
|
+
const SANITIZED_REPLACEMENT =
|
|
53
|
+
'_(the model returned no actual reply — try rephrasing or asking again)_';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Inspect an outbound assistant text. If the FULL TRIMMED text
|
|
57
|
+
* matches a known CLI-context canned string, return the honest
|
|
58
|
+
* replacement and a `replaced` flag so the caller can log the
|
|
59
|
+
* substitution. Otherwise return the original text unchanged.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} text — the assistant text about to be sent.
|
|
62
|
+
* @returns {{ text: string, replaced: boolean, original?: string }}
|
|
63
|
+
*/
|
|
64
|
+
function sanitizeAssistantReply(text) {
|
|
65
|
+
if (typeof text !== 'string') return { text, replaced: false };
|
|
66
|
+
const trimmed = text.trim();
|
|
67
|
+
if (!trimmed) return { text, replaced: false };
|
|
68
|
+
if (CANNED_STRINGS.has(trimmed)) {
|
|
69
|
+
return {
|
|
70
|
+
text: SANITIZED_REPLACEMENT,
|
|
71
|
+
replaced: true,
|
|
72
|
+
original: trimmed,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { text, replaced: false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
CANNED_STRINGS,
|
|
80
|
+
SANITIZED_REPLACEMENT,
|
|
81
|
+
sanitizeAssistantReply,
|
|
82
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.10.0-rc.
|
|
3
|
+
"version": "0.10.0-rc.39",
|
|
4
4
|
"description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
|
|
5
5
|
"main": "lib/ipc/client.js",
|
|
6
6
|
"bin": {
|
package/polygram.js
CHANGED
|
@@ -80,6 +80,7 @@ const { transcribe: transcribeVoice, isVoiceAttachment } = require('./lib/telegr
|
|
|
80
80
|
const { createStreamer } = require('./lib/telegram/streamer');
|
|
81
81
|
const { chunkMarkdownText } = require('./lib/telegram/chunk');
|
|
82
82
|
const { deliverReplies } = require('./lib/telegram/deliver');
|
|
83
|
+
const { sanitizeAssistantReply } = require('./lib/telegram/sanitize-reply');
|
|
83
84
|
const { announce, shouldAnnounce } = require('./lib/announces');
|
|
84
85
|
const { isAbortRequest } = require('./lib/abort-detector');
|
|
85
86
|
const { startTyping } = require('./lib/telegram/typing');
|
|
@@ -1308,6 +1309,25 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1308
1309
|
}
|
|
1309
1310
|
|
|
1310
1311
|
const parsed = parseResponse(result.text);
|
|
1312
|
+
// rc.39: intercept CLI-context canned-string leaks (`No response
|
|
1313
|
+
// requested.` etc.) before they reach the streamer/deliver path.
|
|
1314
|
+
// Replaces with an honest brief message; logs the substitution
|
|
1315
|
+
// for forensic post-hoc analysis of how often the leak fires.
|
|
1316
|
+
// See lib/telegram/sanitize-reply.js for the (narrow) allowlist
|
|
1317
|
+
// and rationale — the rc.37 prompt-side hint mitigation proved
|
|
1318
|
+
// insufficient, so this is the polygram-layer safety net.
|
|
1319
|
+
if (parsed.text) {
|
|
1320
|
+
const sanitized = sanitizeAssistantReply(parsed.text);
|
|
1321
|
+
if (sanitized.replaced) {
|
|
1322
|
+
logEvent('canned-reply-suppressed', {
|
|
1323
|
+
chat_id: chatId,
|
|
1324
|
+
msg_id: msg.message_id,
|
|
1325
|
+
original: sanitized.original,
|
|
1326
|
+
backend: result?.backend || null,
|
|
1327
|
+
});
|
|
1328
|
+
parsed.text = sanitized.text;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1311
1331
|
const outMeta = { ...outMetaBase, sessionId: result.sessionId, costUsd: result.cost };
|
|
1312
1332
|
|
|
1313
1333
|
// 0.8.0-rc.39: send any inline stickers Claude embedded with
|