@gonzih/cc-tg 0.7.0 → 0.7.2
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 +5 -0
- package/dist/bot.js +17 -0
- package/dist/cron.d.ts +39 -0
- package/dist/cron.js +148 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/bot.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ export declare class CcTgBot {
|
|
|
32
32
|
private getThreadCwdMap;
|
|
33
33
|
private isAllowed;
|
|
34
34
|
private handleTelegram;
|
|
35
|
+
/**
|
|
36
|
+
* Feed a text message into the active Claude session for the given chat.
|
|
37
|
+
* Called by the notifier when a UI message arrives via Redis pub/sub.
|
|
38
|
+
*/
|
|
39
|
+
handleUserMessage(chatId: number, text: string): Promise<void>;
|
|
35
40
|
private handleVoice;
|
|
36
41
|
private handlePhoto;
|
|
37
42
|
private handleDocument;
|
package/dist/bot.js
CHANGED
|
@@ -356,6 +356,23 @@ export class CcTgBot {
|
|
|
356
356
|
this.killSession(chatId, true, threadId);
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Feed a text message into the active Claude session for the given chat.
|
|
361
|
+
* Called by the notifier when a UI message arrives via Redis pub/sub.
|
|
362
|
+
*/
|
|
363
|
+
async handleUserMessage(chatId, text) {
|
|
364
|
+
const session = this.getOrCreateSession(chatId);
|
|
365
|
+
try {
|
|
366
|
+
const enriched = await enrichPromptWithUrls(text);
|
|
367
|
+
session.currentPrompt = enriched;
|
|
368
|
+
session.claude.sendPrompt(enriched);
|
|
369
|
+
this.startTyping(chatId, session);
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
await this.replyToChat(chatId, `Error sending to Claude: ${err.message}`);
|
|
373
|
+
this.killSession(chatId, true);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
359
376
|
async handleVoice(chatId, msg, threadId, threadName) {
|
|
360
377
|
const fileId = msg.voice?.file_id ?? msg.audio?.file_id;
|
|
361
378
|
if (!fileId)
|
package/dist/cron.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron job manager for cc-tg.
|
|
3
|
+
* Persists jobs to <cwd>/.cc-tg/crons.json.
|
|
4
|
+
* Fires prompts into Claude sessions on schedule.
|
|
5
|
+
*/
|
|
6
|
+
export interface CronJob {
|
|
7
|
+
id: string;
|
|
8
|
+
chatId: number;
|
|
9
|
+
intervalMs: number;
|
|
10
|
+
prompt: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
schedule: string;
|
|
13
|
+
}
|
|
14
|
+
/** Called when a job fires. `done` must be called when the task completes so
|
|
15
|
+
* the next scheduled tick is allowed to run. Until `done` is called, concurrent
|
|
16
|
+
* ticks for the same job are silently skipped (prevents the resume-loop explosion
|
|
17
|
+
* where each tick spawns more agents than the last). */
|
|
18
|
+
type FireCallback = (chatId: number, prompt: string, jobId: string, done: () => void) => void;
|
|
19
|
+
export declare class CronManager {
|
|
20
|
+
private jobs;
|
|
21
|
+
/** Job IDs whose fire callback has been invoked but whose `done` hasn't fired yet. */
|
|
22
|
+
private activeJobs;
|
|
23
|
+
private storePath;
|
|
24
|
+
private fire;
|
|
25
|
+
constructor(cwd: string, fire: FireCallback);
|
|
26
|
+
/** Parse "every 30m", "every 2h", "every 1d" → ms */
|
|
27
|
+
static parseSchedule(schedule: string): number | null;
|
|
28
|
+
add(chatId: number, schedule: string, prompt: string): CronJob | null;
|
|
29
|
+
remove(chatId: number, id: string): boolean;
|
|
30
|
+
clearAll(chatId: number): number;
|
|
31
|
+
list(chatId: number): CronJob[];
|
|
32
|
+
update(chatId: number, id: string, updates: {
|
|
33
|
+
schedule?: string;
|
|
34
|
+
prompt?: string;
|
|
35
|
+
}): CronJob | null | false;
|
|
36
|
+
private persist;
|
|
37
|
+
private load;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
package/dist/cron.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron job manager for cc-tg.
|
|
3
|
+
* Persists jobs to <cwd>/.cc-tg/crons.json.
|
|
4
|
+
* Fires prompts into Claude sessions on schedule.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
export class CronManager {
|
|
9
|
+
jobs = new Map();
|
|
10
|
+
/** Job IDs whose fire callback has been invoked but whose `done` hasn't fired yet. */
|
|
11
|
+
activeJobs = new Set();
|
|
12
|
+
storePath;
|
|
13
|
+
fire;
|
|
14
|
+
constructor(cwd, fire) {
|
|
15
|
+
this.storePath = join(cwd, ".cc-tg", "crons.json");
|
|
16
|
+
this.fire = fire;
|
|
17
|
+
this.load();
|
|
18
|
+
}
|
|
19
|
+
/** Parse "every 30m", "every 2h", "every 1d" → ms */
|
|
20
|
+
static parseSchedule(schedule) {
|
|
21
|
+
const m = schedule.trim().match(/^every\s+(\d+)(m|h|d)$/i);
|
|
22
|
+
if (!m)
|
|
23
|
+
return null;
|
|
24
|
+
const n = parseInt(m[1]);
|
|
25
|
+
const unit = m[2].toLowerCase();
|
|
26
|
+
if (unit === "m")
|
|
27
|
+
return n * 60_000;
|
|
28
|
+
if (unit === "h")
|
|
29
|
+
return n * 3_600_000;
|
|
30
|
+
if (unit === "d")
|
|
31
|
+
return n * 86_400_000;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
add(chatId, schedule, prompt) {
|
|
35
|
+
const intervalMs = CronManager.parseSchedule(schedule);
|
|
36
|
+
if (!intervalMs)
|
|
37
|
+
return null;
|
|
38
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
39
|
+
const job = { id, chatId, intervalMs, prompt, schedule, createdAt: new Date().toISOString() };
|
|
40
|
+
const timer = setInterval(() => {
|
|
41
|
+
if (this.activeJobs.has(id)) {
|
|
42
|
+
console.log(`[cron:${id}] skipping tick — previous task still running`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.activeJobs.add(id);
|
|
46
|
+
console.log(`[cron:${id}] firing for chat=${chatId} prompt="${prompt}"`);
|
|
47
|
+
this.fire(chatId, prompt, id, () => { this.activeJobs.delete(id); });
|
|
48
|
+
}, intervalMs);
|
|
49
|
+
this.jobs.set(id, { ...job, timer });
|
|
50
|
+
this.persist();
|
|
51
|
+
return job;
|
|
52
|
+
}
|
|
53
|
+
remove(chatId, id) {
|
|
54
|
+
const job = this.jobs.get(id);
|
|
55
|
+
if (!job || job.chatId !== chatId)
|
|
56
|
+
return false;
|
|
57
|
+
clearInterval(job.timer);
|
|
58
|
+
this.activeJobs.delete(id);
|
|
59
|
+
this.jobs.delete(id);
|
|
60
|
+
this.persist();
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
clearAll(chatId) {
|
|
64
|
+
let count = 0;
|
|
65
|
+
for (const [id, job] of this.jobs) {
|
|
66
|
+
if (job.chatId === chatId) {
|
|
67
|
+
clearInterval(job.timer);
|
|
68
|
+
this.activeJobs.delete(id);
|
|
69
|
+
this.jobs.delete(id);
|
|
70
|
+
count++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (count)
|
|
74
|
+
this.persist();
|
|
75
|
+
return count;
|
|
76
|
+
}
|
|
77
|
+
list(chatId) {
|
|
78
|
+
return [...this.jobs.values()]
|
|
79
|
+
.filter((j) => j.chatId === chatId)
|
|
80
|
+
.map(({ timer: _t, ...j }) => j);
|
|
81
|
+
}
|
|
82
|
+
update(chatId, id, updates) {
|
|
83
|
+
const job = this.jobs.get(id);
|
|
84
|
+
if (!job || job.chatId !== chatId)
|
|
85
|
+
return false;
|
|
86
|
+
if (updates.schedule !== undefined) {
|
|
87
|
+
const intervalMs = CronManager.parseSchedule(updates.schedule);
|
|
88
|
+
if (!intervalMs)
|
|
89
|
+
return null;
|
|
90
|
+
job.intervalMs = intervalMs;
|
|
91
|
+
job.schedule = updates.schedule;
|
|
92
|
+
}
|
|
93
|
+
if (updates.prompt !== undefined) {
|
|
94
|
+
job.prompt = updates.prompt;
|
|
95
|
+
}
|
|
96
|
+
// Recreate timer so it uses updated intervalMs and always reads latest job.prompt
|
|
97
|
+
clearInterval(job.timer);
|
|
98
|
+
// Also clear any active-job lock so the updated timer can fire immediately next tick
|
|
99
|
+
this.activeJobs.delete(job.id);
|
|
100
|
+
job.timer = setInterval(() => {
|
|
101
|
+
if (this.activeJobs.has(job.id)) {
|
|
102
|
+
console.log(`[cron:${job.id}] skipping tick — previous task still running`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.activeJobs.add(job.id);
|
|
106
|
+
console.log(`[cron:${job.id}] firing for chat=${job.chatId} prompt="${job.prompt}"`);
|
|
107
|
+
this.fire(job.chatId, job.prompt, job.id, () => { this.activeJobs.delete(job.id); });
|
|
108
|
+
}, job.intervalMs);
|
|
109
|
+
this.persist();
|
|
110
|
+
const { timer: _t, ...cronJob } = job;
|
|
111
|
+
return cronJob;
|
|
112
|
+
}
|
|
113
|
+
persist() {
|
|
114
|
+
try {
|
|
115
|
+
const dir = join(this.storePath, "..");
|
|
116
|
+
if (!existsSync(dir))
|
|
117
|
+
mkdirSync(dir, { recursive: true });
|
|
118
|
+
const data = [...this.jobs.values()].map(({ timer: _t, ...j }) => j);
|
|
119
|
+
writeFileSync(this.storePath, JSON.stringify(data, null, 2));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.error("[cron] persist error:", err.message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
load() {
|
|
126
|
+
if (!existsSync(this.storePath))
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
const data = JSON.parse(readFileSync(this.storePath, "utf8"));
|
|
130
|
+
for (const job of data) {
|
|
131
|
+
const timer = setInterval(() => {
|
|
132
|
+
if (this.activeJobs.has(job.id)) {
|
|
133
|
+
console.log(`[cron:${job.id}] skipping tick — previous task still running`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.activeJobs.add(job.id);
|
|
137
|
+
console.log(`[cron:${job.id}] firing for chat=${job.chatId} prompt="${job.prompt}"`);
|
|
138
|
+
this.fire(job.chatId, job.prompt, job.id, () => { this.activeJobs.delete(job.id); });
|
|
139
|
+
}, job.intervalMs);
|
|
140
|
+
this.jobs.set(job.id, { ...job, timer });
|
|
141
|
+
}
|
|
142
|
+
console.log(`[cron] loaded ${data.length} jobs from disk`);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
console.error("[cron] load error:", err.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -155,7 +155,7 @@ if (notifyChatId) {
|
|
|
155
155
|
if (!sharedRedis)
|
|
156
156
|
sharedRedis = new Redis(redisUrl);
|
|
157
157
|
const notifierBot = new TelegramBot(telegramToken, { polling: false });
|
|
158
|
-
startNotifier(notifierBot, notifyChatId, namespace, sharedRedis);
|
|
158
|
+
startNotifier(notifierBot, notifyChatId, namespace, sharedRedis, (cid, text) => bot.handleUserMessage(cid, text));
|
|
159
159
|
console.log(`[notifier] started for namespace=${namespace} chatId=${notifyChatId}`);
|
|
160
160
|
}
|
|
161
161
|
process.on("SIGINT", () => {
|