@gonzih/cc-tg 0.9.19 → 0.9.21

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/notifier.js DELETED
@@ -1,209 +0,0 @@
1
- /**
2
- * Notifier — subscribes to Redis pub/sub channels and bridges messages to Telegram.
3
- *
4
- * Channels:
5
- * cca:notify:{namespace} — job completion notifications from cc-agent → forward to Telegram
6
- * cca:chat:incoming:{namespace} — messages from the web UI → echo to Telegram + feed into Claude session
7
- *
8
- * All messages (Telegram incoming, Claude responses) are also written to:
9
- * cca:chat:log:{namespace} — LPUSH + LTRIM 0 499 (last 500 messages)
10
- * cca:chat:outgoing:{namespace} — PUBLISH for web UI to consume
11
- */
12
- function log(level, ...args) {
13
- const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
14
- fn("[notifier]", ...args);
15
- }
16
- /**
17
- * Write a message to the chat log in Redis.
18
- * Fire-and-forget — errors are logged but not thrown.
19
- */
20
- export function writeChatLog(redis, namespace, msg) {
21
- const logKey = `cca:chat:log:${namespace}`;
22
- const outKey = `cca:chat:outgoing:${namespace}`;
23
- const payload = JSON.stringify(msg);
24
- redis.lpush(logKey, payload).catch((err) => {
25
- log("warn", "writeChatLog lpush failed:", err.message);
26
- });
27
- redis.ltrim(logKey, 0, 499).catch((err) => {
28
- log("warn", "writeChatLog ltrim failed:", err.message);
29
- });
30
- redis.publish(outKey, payload).catch((err) => {
31
- log("warn", "writeChatLog publish failed:", err.message);
32
- });
33
- }
34
- /**
35
- * Start the notifier.
36
- *
37
- * @param bot - Telegram bot instance (for sending messages)
38
- * @param chatId - Telegram chat ID to forward notifications to. Pass null to use getActiveChatId.
39
- * @param namespace - cc-agent namespace (used to build Redis channel names)
40
- * @param redis - ioredis client in normal mode (will be duplicated for pub/sub)
41
- * @param handleUserMessage - Optional callback to feed UI messages into the active Claude session
42
- * @param getActiveChatId - Optional callback to resolve chatId dynamically (used when chatId is null)
43
- */
44
- export function startNotifier(bot, chatId, namespace, redis, handleUserMessage, getActiveChatId) {
45
- const sub = redis.duplicate({
46
- retryStrategy: (times) => {
47
- const delay = Math.min(1000 * Math.pow(2, times - 1), 30_000);
48
- log("info", `subscriber reconnecting in ${delay}ms (attempt ${times})`);
49
- return delay;
50
- },
51
- });
52
- sub.on("error", (err) => {
53
- log("warn", "subscriber error:", err.message);
54
- });
55
- sub.on("close", () => {
56
- log("info", "subscriber disconnected, will reconnect with backoff");
57
- });
58
- // cca:notify:{namespace} — forward job completion notifications to Telegram
59
- sub.subscribe(`cca:notify:${namespace}`, (err) => {
60
- if (err) {
61
- log("error", `subscribe cca:notify:${namespace} failed:`, err.message);
62
- }
63
- else {
64
- log("info", `subscribed to cca:notify:${namespace}`);
65
- }
66
- });
67
- // cca:chat:incoming:{namespace} — messages from UI
68
- sub.subscribe(`cca:chat:incoming:${namespace}`, (err) => {
69
- if (err) {
70
- log("error", `subscribe cca:chat:incoming:${namespace} failed:`, err.message);
71
- }
72
- else {
73
- log("info", `subscribed to cca:chat:incoming:${namespace}`);
74
- }
75
- });
76
- // Poll the cca:notify:{namespace} LIST every 5 seconds.
77
- // Jobs push to this list via RPUSH; pub/sub alone won't deliver those messages.
78
- const notifyListKey = `cca:notify:${namespace}`;
79
- const MAX_PER_CYCLE = 20;
80
- const pollNotifyList = async () => {
81
- const targetId = chatId ?? getActiveChatId?.();
82
- if (targetId == null)
83
- return;
84
- const items = [];
85
- try {
86
- for (let i = 0; i < MAX_PER_CYCLE; i++) {
87
- const item = await redis.rpop(notifyListKey);
88
- if (item === null)
89
- break;
90
- items.push(item);
91
- }
92
- }
93
- catch (err) {
94
- log("warn", "notify list rpop failed:", err.message);
95
- return;
96
- }
97
- if (items.length === 0)
98
- return;
99
- let remaining = 0;
100
- if (items.length === MAX_PER_CYCLE) {
101
- try {
102
- remaining = await redis.llen(notifyListKey);
103
- }
104
- catch (err) {
105
- log("warn", "notify list llen failed:", err.message);
106
- }
107
- }
108
- for (const raw of items) {
109
- let text = raw;
110
- try {
111
- const parsed = JSON.parse(raw);
112
- if (parsed.text)
113
- text = parsed.text;
114
- }
115
- catch {
116
- // not JSON — use raw string as-is
117
- }
118
- bot.sendMessage(targetId, text).catch((err) => {
119
- log("warn", "notify list sendMessage failed:", err.message);
120
- });
121
- }
122
- if (remaining > 0) {
123
- bot.sendMessage(targetId, `...and ${remaining} more notifications`).catch((err) => {
124
- log("warn", "notify list summary sendMessage failed:", err.message);
125
- });
126
- }
127
- };
128
- setInterval(() => {
129
- void pollNotifyList();
130
- }, 5_000);
131
- sub.on("message", (channel, message) => {
132
- const notifyChannel = `cca:notify:${namespace}`;
133
- const incomingChannel = `cca:chat:incoming:${namespace}`;
134
- if (channel === notifyChannel) {
135
- const targetId = chatId ?? getActiveChatId?.();
136
- if (targetId != null) {
137
- bot.sendMessage(targetId, message).catch((err) => {
138
- log("warn", "sendMessage failed:", err.message);
139
- });
140
- }
141
- else {
142
- log("warn", "notify: no chatId available, dropping notification");
143
- }
144
- return;
145
- }
146
- if (channel === incomingChannel) {
147
- let content = message;
148
- let originalTimestamp;
149
- try {
150
- const parsed = JSON.parse(message);
151
- if (parsed.content)
152
- content = parsed.content;
153
- if (parsed.timestamp)
154
- originalTimestamp = parsed.timestamp;
155
- }
156
- catch {
157
- // raw string message — use as-is
158
- }
159
- // Resolve the target chatId: prefer the fixed chatId, fall back to last active
160
- const targetChatId = chatId ?? getActiveChatId?.();
161
- if (targetChatId !== undefined) {
162
- // Echo to Telegram so the user sees UI messages in the chat
163
- bot.sendMessage(targetChatId, `📱 [from UI]: ${content}`).catch((err) => {
164
- log("warn", "sendMessage (UI echo) failed:", err.message);
165
- });
166
- // Log the incoming message — preserve original timestamp from UI if present
167
- const inMsg = {
168
- id: `ui-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
169
- source: "ui", // 'ui' distinguishes this from telegram/claude messages
170
- role: "user",
171
- content,
172
- // ISO 8601 — matches cc-agent-ui /chat/send format; preserve original if present
173
- timestamp: originalTimestamp ?? new Date().toISOString(),
174
- chatId: targetChatId,
175
- };
176
- writeChatLog(redis, namespace, inMsg);
177
- // Check if a meta-agent is running for this namespace; if so, route there instead
178
- void (async () => {
179
- let routedToMetaAgent = false;
180
- try {
181
- const statusRaw = await redis.get(`cca:meta-agent:status:${namespace}`);
182
- if (statusRaw) {
183
- const status = JSON.parse(statusRaw);
184
- if (status.status === "running") {
185
- const entry = JSON.stringify({
186
- id: crypto.randomUUID(),
187
- content,
188
- timestamp: new Date().toISOString(),
189
- });
190
- await redis.lpush(`cca:meta:${namespace}:input`, entry);
191
- log("info", `cca:chat:incoming: routed to meta-agent for namespace ${namespace}`);
192
- routedToMetaAgent = true;
193
- }
194
- }
195
- }
196
- catch (err) {
197
- log("warn", "meta-agent status check failed, falling back to coordinator:", err.message);
198
- }
199
- if (!routedToMetaAgent && handleUserMessage) {
200
- handleUserMessage(targetChatId, content);
201
- }
202
- })();
203
- }
204
- else {
205
- log("warn", "cca:chat:incoming: no active chatId to route message to");
206
- }
207
- }
208
- });
209
- }
package/dist/tokens.d.ts DELETED
@@ -1,22 +0,0 @@
1
- /**
2
- * OAuth token pool management.
3
- *
4
- * Supports CLAUDE_CODE_OAUTH_TOKENS (comma-separated list of tokens).
5
- * Falls back to CLAUDE_CODE_OAUTH_TOKEN for single-token / backwards compat.
6
- */
7
- /**
8
- * Load tokens from env vars. Called on startup; also re-callable in tests.
9
- * Priority: CLAUDE_CODE_OAUTH_TOKENS > CLAUDE_CODE_OAUTH_TOKEN > (empty)
10
- */
11
- export declare function loadTokens(): string[];
12
- /** Returns the current active token, or empty string if none configured. */
13
- export declare function getCurrentToken(): string;
14
- /**
15
- * Advance to the next token (wraps around).
16
- * Returns the new current token.
17
- */
18
- export declare function rotateToken(): string;
19
- /** Zero-based index of the current token. */
20
- export declare function getTokenIndex(): number;
21
- /** Total number of tokens in the pool. */
22
- export declare function getTokenCount(): number;
package/dist/tokens.js DELETED
@@ -1,56 +0,0 @@
1
- /**
2
- * OAuth token pool management.
3
- *
4
- * Supports CLAUDE_CODE_OAUTH_TOKENS (comma-separated list of tokens).
5
- * Falls back to CLAUDE_CODE_OAUTH_TOKEN for single-token / backwards compat.
6
- */
7
- let tokens = [];
8
- let currentIndex = 0;
9
- let initialized = false;
10
- /**
11
- * Load tokens from env vars. Called on startup; also re-callable in tests.
12
- * Priority: CLAUDE_CODE_OAUTH_TOKENS > CLAUDE_CODE_OAUTH_TOKEN > (empty)
13
- */
14
- export function loadTokens() {
15
- const multi = process.env.CLAUDE_CODE_OAUTH_TOKENS;
16
- if (multi) {
17
- tokens = multi.split(",").map((t) => t.trim()).filter(Boolean);
18
- }
19
- else {
20
- const single = process.env.CLAUDE_CODE_OAUTH_TOKEN;
21
- tokens = single ? [single] : [];
22
- }
23
- currentIndex = 0;
24
- initialized = true;
25
- return tokens;
26
- }
27
- function ensureInitialized() {
28
- if (!initialized)
29
- loadTokens();
30
- }
31
- /** Returns the current active token, or empty string if none configured. */
32
- export function getCurrentToken() {
33
- ensureInitialized();
34
- return tokens[currentIndex] ?? "";
35
- }
36
- /**
37
- * Advance to the next token (wraps around).
38
- * Returns the new current token.
39
- */
40
- export function rotateToken() {
41
- ensureInitialized();
42
- if (tokens.length === 0)
43
- return "";
44
- currentIndex = (currentIndex + 1) % tokens.length;
45
- return tokens[currentIndex];
46
- }
47
- /** Zero-based index of the current token. */
48
- export function getTokenIndex() {
49
- ensureInitialized();
50
- return currentIndex;
51
- }
52
- /** Total number of tokens in the pool. */
53
- export function getTokenCount() {
54
- ensureInitialized();
55
- return tokens.length;
56
- }