@gonzih/cc-tg 0.9.23 → 0.9.25

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/bot.d.ts CHANGED
@@ -58,7 +58,6 @@ export declare class CcTgBot {
58
58
  private stopTyping;
59
59
  private flushPending;
60
60
  private trackWrittenFiles;
61
- private isSensitiveFile;
62
61
  private uploadMentionedFiles;
63
62
  private extractToolName;
64
63
  /** Find cc-agent PIDs via pgrep. Returns array of numeric PIDs. */
package/dist/bot.js CHANGED
@@ -918,16 +918,6 @@ export class CcTgBot {
918
918
  }
919
919
  }
920
920
  }
921
- isSensitiveFile(filePath) {
922
- const name = basename(filePath).toLowerCase();
923
- const sensitivePatterns = [
924
- /credential/i, /secret/i, /password/i, /passwd/i, /\.env/i,
925
- /api[_-]?key/i, /token/i, /private[_-]?key/i, /id_rsa/i,
926
- /\.pem$/i, /\.key$/i, /\.pfx$/i, /\.p12$/i,
927
- /gmail/i, /oauth/i, /\bauth\b/i,
928
- ];
929
- return sensitivePatterns.some((p) => p.test(name));
930
- }
931
921
  uploadMentionedFiles(chatId, resultText, session) {
932
922
  // Extract file path candidates from result text
933
923
  // Match: /absolute/path/file.ext or relative like ./foo/bar.csv or just foo.pdf
@@ -974,13 +964,8 @@ export class CcTgBot {
974
964
  toUpload.push(resolved);
975
965
  }
976
966
  }
977
- // Deduplicate and filter sensitive files
978
967
  const unique = [...new Set(toUpload)];
979
968
  for (const filePath of unique) {
980
- if (this.isSensitiveFile(filePath)) {
981
- console.log(`[claude:files] skipping sensitive file: ${filePath}`);
982
- continue;
983
- }
984
969
  let fileSize;
985
970
  try {
986
971
  fileSize = statSync(filePath).size;
@@ -1202,10 +1187,6 @@ export class CcTgBot {
1202
1187
  await this.replyToChat(chatId, `Not a file: ${filePath}`, threadId);
1203
1188
  return;
1204
1189
  }
1205
- if (this.isSensitiveFile(filePath)) {
1206
- await this.replyToChat(chatId, "Access denied: sensitive file", threadId);
1207
- return;
1208
- }
1209
1190
  const MAX_TG_FILE_BYTES = 50 * 1024 * 1024;
1210
1191
  const fileSize = statSync(filePath).size;
1211
1192
  if (fileSize > MAX_TG_FILE_BYTES) {
@@ -4,6 +4,7 @@
4
4
  * Channels:
5
5
  * cca:notify:{namespace} — job completion notifications from cc-agent → forward to Telegram
6
6
  * cca:chat:incoming:{namespace} — messages from the web UI → echo to Telegram + feed into Claude session
7
+ * cca:chat:outgoing:* — meta-agent stdout lines (source=claude) → buffer+debounce → Telegram
7
8
  *
8
9
  * All messages (Telegram incoming, Claude responses) are also written to:
9
10
  * cca:chat:log:{namespace} — LPUSH + LTRIM 0 499 (last 500 messages)
package/dist/notifier.js CHANGED
@@ -4,11 +4,13 @@
4
4
  * Channels:
5
5
  * cca:notify:{namespace} — job completion notifications from cc-agent → forward to Telegram
6
6
  * cca:chat:incoming:{namespace} — messages from the web UI → echo to Telegram + feed into Claude session
7
+ * cca:chat:outgoing:* — meta-agent stdout lines (source=claude) → buffer+debounce → Telegram
7
8
  *
8
9
  * All messages (Telegram incoming, Claude responses) are also written to:
9
10
  * cca:chat:log:{namespace} — LPUSH + LTRIM 0 499 (last 500 messages)
10
11
  * cca:chat:outgoing:{namespace} — PUBLISH for web UI to consume
11
12
  */
13
+ import { splitLongMessage } from "./formatter.js";
12
14
  function log(level, ...args) {
13
15
  const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
14
16
  fn("[notifier]", ...args);
@@ -30,6 +32,11 @@ function shortenModelName(model, driver) {
30
32
  return model.slice(slashIdx + 1);
31
33
  return model;
32
34
  }
35
+ /** Strip ANSI escape sequences from a string before sending to Telegram. */
36
+ function stripAnsi(text) {
37
+ // eslint-disable-next-line no-control-regex
38
+ return text.replace(/\x1B\[[0-9;]*[mGKHF]/g, "");
39
+ }
33
40
  /**
34
41
  * Parse a notification payload and return the display text.
35
42
  * Appends a [driver] or [driver:model] badge whenever the driver field is present.
@@ -124,6 +131,65 @@ export function startNotifier(bot, chatId, namespace, redis, handleUserMessage,
124
131
  log("info", `subscribed to cca:chat:incoming:${namespace}`);
125
132
  }
126
133
  });
134
+ // cca:chat:outgoing:* — meta-agent stdout lines (source=claude) → buffer+debounce → Telegram
135
+ // Using psubscribe so we catch all namespaces (money-brain, isoc-nevada, etc.)
136
+ sub.psubscribe("cca:chat:outgoing:*", (err) => {
137
+ if (err) {
138
+ log("error", "psubscribe cca:chat:outgoing:* failed:", err.message);
139
+ }
140
+ else {
141
+ log("info", "psubscribed to cca:chat:outgoing:*");
142
+ }
143
+ });
144
+ // Per-namespace debounce buffer: accumulate streaming lines, flush after 1.5s silence
145
+ const metaAgentBuffers = new Map();
146
+ function flushMetaAgentBuffer(ns, targetChatId) {
147
+ const buf = metaAgentBuffers.get(ns);
148
+ if (!buf || !buf.text.trim())
149
+ return;
150
+ const text = stripAnsi(buf.text.trim());
151
+ buf.text = "";
152
+ buf.timer = null;
153
+ const chunks = splitLongMessage(text);
154
+ for (const chunk of chunks) {
155
+ bot.sendMessage(targetChatId, chunk).catch((err) => {
156
+ log("warn", `meta-agent flush sendMessage failed (ns=${ns}):`, err.message);
157
+ });
158
+ }
159
+ }
160
+ sub.on("pmessage", (pattern, channel, message) => {
161
+ void pattern; // used only as a type guard
162
+ const ns = channel.replace("cca:chat:outgoing:", "");
163
+ let parsed = null;
164
+ try {
165
+ parsed = JSON.parse(message);
166
+ }
167
+ catch {
168
+ return; // non-JSON line — skip
169
+ }
170
+ // Only forward messages from the meta-agent (source=claude).
171
+ // cc-tg itself publishes to this channel with source "cc-tg"/"telegram"/"ui" — skip those.
172
+ if (parsed.source !== "claude")
173
+ return;
174
+ const content = parsed.content;
175
+ if (!content)
176
+ return;
177
+ const targetChatId = chatId ?? getActiveChatId?.();
178
+ if (targetChatId == null) {
179
+ log("warn", `meta-agent output: no chatId for namespace=${ns}, dropping line`);
180
+ return;
181
+ }
182
+ // Accumulate into per-namespace buffer and (re-)arm debounce timer
183
+ let buf = metaAgentBuffers.get(ns);
184
+ if (!buf) {
185
+ buf = { text: "", timer: null };
186
+ metaAgentBuffers.set(ns, buf);
187
+ }
188
+ buf.text += (buf.text ? "\n" : "") + content;
189
+ if (buf.timer)
190
+ clearTimeout(buf.timer);
191
+ buf.timer = setTimeout(() => flushMetaAgentBuffer(ns, targetChatId), 1500);
192
+ });
127
193
  // Poll the cca:notify:{namespace} LIST every 5 seconds.
128
194
  // Jobs push to this list via RPUSH; pub/sub alone won't deliver those messages.
129
195
  const notifyListKey = `cca:notify:${namespace}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonzih/cc-tg",
3
- "version": "0.9.23",
3
+ "version": "0.9.25",
4
4
  "description": "Claude Code Telegram bot — chat with Claude Code via Telegram",
5
5
  "type": "module",
6
6
  "bin": {