@emqo/claudebridge 0.10.0 → 0.10.1
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/adapters/discord.d.ts +2 -0
- package/dist/adapters/discord.js +32 -1
- package/dist/adapters/telegram.d.ts +2 -0
- package/dist/adapters/telegram.js +45 -0
- package/dist/core/store.d.ts +11 -0
- package/dist/core/store.js +25 -0
- package/dist/ctl.js +19 -1
- package/dist/skills/bridge.js +12 -0
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ export declare class DiscordAdapter implements Adapter {
|
|
|
11
11
|
private reminderTimer?;
|
|
12
12
|
private autoTimer?;
|
|
13
13
|
private approvalTimer?;
|
|
14
|
+
private fileSendTimer?;
|
|
14
15
|
private activeAutoTasks;
|
|
15
16
|
private maxParallel;
|
|
16
17
|
constructor(engine: AgentEngine, store: Store, config: DiscordConfig, locale?: string);
|
|
@@ -27,4 +28,5 @@ export declare class DiscordAdapter implements Adapter {
|
|
|
27
28
|
private checkApprovals;
|
|
28
29
|
private handleStatusCommand;
|
|
29
30
|
private handleSessionsCommand;
|
|
31
|
+
private checkFileSends;
|
|
30
32
|
}
|
package/dist/adapters/discord.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client, GatewayIntentBits } from "discord.js";
|
|
2
|
-
import { writeFileSync } from "fs";
|
|
2
|
+
import { writeFileSync, existsSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { chunkText } from "./base.js";
|
|
5
5
|
import { reloadConfig } from "../core/config.js";
|
|
@@ -16,6 +16,7 @@ export class DiscordAdapter {
|
|
|
16
16
|
reminderTimer;
|
|
17
17
|
autoTimer;
|
|
18
18
|
approvalTimer;
|
|
19
|
+
fileSendTimer;
|
|
19
20
|
activeAutoTasks = 0;
|
|
20
21
|
maxParallel = 1;
|
|
21
22
|
constructor(engine, store, config, locale = "en") {
|
|
@@ -255,6 +256,7 @@ export class DiscordAdapter {
|
|
|
255
256
|
this.reminderTimer = setInterval(() => this.checkReminders(), 30000);
|
|
256
257
|
this.autoTimer = setInterval(() => this.processAutoTasks(), 60000);
|
|
257
258
|
this.approvalTimer = setInterval(() => this.checkApprovals(), 15000);
|
|
259
|
+
this.fileSendTimer = setInterval(() => this.checkFileSends(), 5000);
|
|
258
260
|
}
|
|
259
261
|
stop() {
|
|
260
262
|
if (this.reminderTimer)
|
|
@@ -263,6 +265,8 @@ export class DiscordAdapter {
|
|
|
263
265
|
clearInterval(this.autoTimer);
|
|
264
266
|
if (this.approvalTimer)
|
|
265
267
|
clearInterval(this.approvalTimer);
|
|
268
|
+
if (this.fileSendTimer)
|
|
269
|
+
clearInterval(this.fileSendTimer);
|
|
266
270
|
this.client.destroy();
|
|
267
271
|
}
|
|
268
272
|
async checkReminders() {
|
|
@@ -409,4 +413,31 @@ export class DiscordAdapter {
|
|
|
409
413
|
});
|
|
410
414
|
await msg.reply(`${t(this.locale, "sessions_list")}\n${lines.join("\n")}`);
|
|
411
415
|
}
|
|
416
|
+
async checkFileSends() {
|
|
417
|
+
try {
|
|
418
|
+
const pending = this.store.getPendingFileSends("discord");
|
|
419
|
+
for (const f of pending) {
|
|
420
|
+
if (!existsSync(f.file_path)) {
|
|
421
|
+
this.store.markFileFailed(f.id);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const ch = await this.client.channels.fetch(f.chat_id);
|
|
426
|
+
if (!ch?.isTextBased() || !("send" in ch)) {
|
|
427
|
+
this.store.markFileFailed(f.id);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
await ch.send({ content: f.caption || undefined, files: [f.file_path] });
|
|
431
|
+
this.store.markFileSent(f.id);
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
log.error("file send error", { id: f.id, error: err?.message });
|
|
435
|
+
this.store.markFileFailed(f.id);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
log.error("checkFileSends error", { error: e?.message });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
412
443
|
}
|
|
@@ -12,6 +12,7 @@ export declare class TelegramAdapter implements Adapter {
|
|
|
12
12
|
private reminderTimer?;
|
|
13
13
|
private autoTimer?;
|
|
14
14
|
private approvalTimer?;
|
|
15
|
+
private fileSendTimer?;
|
|
15
16
|
private activeAutoTasks;
|
|
16
17
|
private maxParallel;
|
|
17
18
|
private pages;
|
|
@@ -38,4 +39,5 @@ export declare class TelegramAdapter implements Adapter {
|
|
|
38
39
|
private handleApprovalCallback;
|
|
39
40
|
private handleStatusCommand;
|
|
40
41
|
private handleSessionsCommand;
|
|
42
|
+
private checkFileSends;
|
|
41
43
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { chunkText } from "./base.js";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { basename, extname } from "path";
|
|
2
4
|
import { reloadConfig } from "../core/config.js";
|
|
3
5
|
import { toTelegramMarkdown } from "../core/markdown.js";
|
|
4
6
|
import { t, getCommandDescriptions } from "../core/i18n.js";
|
|
@@ -15,6 +17,7 @@ export class TelegramAdapter {
|
|
|
15
17
|
reminderTimer;
|
|
16
18
|
autoTimer;
|
|
17
19
|
approvalTimer;
|
|
20
|
+
fileSendTimer;
|
|
18
21
|
activeAutoTasks = 0;
|
|
19
22
|
maxParallel = 1;
|
|
20
23
|
pages = new Map();
|
|
@@ -381,6 +384,7 @@ export class TelegramAdapter {
|
|
|
381
384
|
this.reminderTimer = setInterval(() => this.checkReminders(), 30000);
|
|
382
385
|
this.autoTimer = setInterval(() => this.processAutoTasks(), 60000);
|
|
383
386
|
this.approvalTimer = setInterval(() => this.checkApprovals(), 15000);
|
|
387
|
+
this.fileSendTimer = setInterval(() => this.checkFileSends(), 5000);
|
|
384
388
|
await this.registerCommands();
|
|
385
389
|
let pollBackoff = 0;
|
|
386
390
|
while (this.running) {
|
|
@@ -416,6 +420,8 @@ export class TelegramAdapter {
|
|
|
416
420
|
clearInterval(this.autoTimer);
|
|
417
421
|
if (this.approvalTimer)
|
|
418
422
|
clearInterval(this.approvalTimer);
|
|
423
|
+
if (this.fileSendTimer)
|
|
424
|
+
clearInterval(this.fileSendTimer);
|
|
419
425
|
}
|
|
420
426
|
async registerCommands() {
|
|
421
427
|
try {
|
|
@@ -625,4 +631,43 @@ export class TelegramAdapter {
|
|
|
625
631
|
});
|
|
626
632
|
await this.reply(chatId, `${t(this.locale, "sessions_list")}\n${lines.join("\n")}`);
|
|
627
633
|
}
|
|
634
|
+
async checkFileSends() {
|
|
635
|
+
try {
|
|
636
|
+
const pending = this.store.getPendingFileSends("telegram");
|
|
637
|
+
for (const f of pending) {
|
|
638
|
+
if (!existsSync(f.file_path)) {
|
|
639
|
+
this.store.markFileFailed(f.id);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const chatId = Number(f.chat_id);
|
|
643
|
+
const ext = extname(f.file_path).toLowerCase();
|
|
644
|
+
const isPhoto = [".jpg", ".jpeg", ".png", ".gif"].includes(ext);
|
|
645
|
+
const method = isPhoto ? "sendPhoto" : "sendDocument";
|
|
646
|
+
const fieldName = isPhoto ? "photo" : "document";
|
|
647
|
+
try {
|
|
648
|
+
const form = new FormData();
|
|
649
|
+
form.append("chat_id", String(chatId));
|
|
650
|
+
const blob = new Blob([await import("fs").then(fs => fs.readFileSync(f.file_path))]);
|
|
651
|
+
form.append(fieldName, blob, basename(f.file_path));
|
|
652
|
+
if (f.caption)
|
|
653
|
+
form.append("caption", f.caption);
|
|
654
|
+
const res = await fetch(`${this.api}/${method}`, { method: "POST", body: form });
|
|
655
|
+
const json = await res.json();
|
|
656
|
+
if (json.ok)
|
|
657
|
+
this.store.markFileSent(f.id);
|
|
658
|
+
else {
|
|
659
|
+
log.error("file send API error", { id: f.id, desc: json.description });
|
|
660
|
+
this.store.markFileFailed(f.id);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
log.error("file send error", { id: f.id, error: err?.message });
|
|
665
|
+
this.store.markFileFailed(f.id);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch (e) {
|
|
670
|
+
log.error("checkFileSends error", { error: e?.message });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
628
673
|
}
|
package/dist/core/store.d.ts
CHANGED
|
@@ -152,4 +152,15 @@ export declare class Store {
|
|
|
152
152
|
summary: string;
|
|
153
153
|
last_active_at: number;
|
|
154
154
|
}[];
|
|
155
|
+
addFileSend(userId: string, platform: string, chatId: string, filePath: string, caption: string): number;
|
|
156
|
+
getPendingFileSends(platform: string): {
|
|
157
|
+
id: number;
|
|
158
|
+
user_id: string;
|
|
159
|
+
platform: string;
|
|
160
|
+
chat_id: string;
|
|
161
|
+
file_path: string;
|
|
162
|
+
caption: string;
|
|
163
|
+
}[];
|
|
164
|
+
markFileSent(id: number): void;
|
|
165
|
+
markFileFailed(id: number): void;
|
|
155
166
|
}
|
package/dist/core/store.js
CHANGED
|
@@ -72,6 +72,17 @@ export class Store {
|
|
|
72
72
|
message_count INTEGER NOT NULL DEFAULT 0,
|
|
73
73
|
total_cost REAL NOT NULL DEFAULT 0
|
|
74
74
|
);
|
|
75
|
+
CREATE TABLE IF NOT EXISTS file_sends (
|
|
76
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
|
+
user_id TEXT NOT NULL,
|
|
78
|
+
platform TEXT NOT NULL,
|
|
79
|
+
chat_id TEXT NOT NULL,
|
|
80
|
+
file_path TEXT NOT NULL,
|
|
81
|
+
caption TEXT NOT NULL DEFAULT '',
|
|
82
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
83
|
+
created_at INTEGER NOT NULL
|
|
84
|
+
);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_file_sends_status ON file_sends(platform, status);
|
|
75
86
|
CREATE INDEX IF NOT EXISTS idx_subsess_user ON sub_sessions(user_id, platform, status);
|
|
76
87
|
CREATE TABLE IF NOT EXISTS sub_session_messages (
|
|
77
88
|
platform_msg_id TEXT NOT NULL,
|
|
@@ -319,4 +330,18 @@ export class Store {
|
|
|
319
330
|
getSubSessionSummaries(userId, platform) {
|
|
320
331
|
return this.db.prepare("SELECT id, label, summary, last_active_at FROM sub_sessions WHERE user_id = ? AND platform = ? AND status IN ('active','idle') ORDER BY last_active_at DESC").all(userId, platform);
|
|
321
332
|
}
|
|
333
|
+
// --- file_sends ---
|
|
334
|
+
addFileSend(userId, platform, chatId, filePath, caption) {
|
|
335
|
+
const r = this.db.prepare("INSERT INTO file_sends (user_id, platform, chat_id, file_path, caption, status, created_at) VALUES (?, ?, ?, ?, ?, 'pending', ?)").run(userId, platform, chatId, filePath, caption, Date.now());
|
|
336
|
+
return Number(r.lastInsertRowid);
|
|
337
|
+
}
|
|
338
|
+
getPendingFileSends(platform) {
|
|
339
|
+
return this.db.prepare("SELECT id, user_id, platform, chat_id, file_path, caption FROM file_sends WHERE platform = ? AND status = 'pending'").all(platform);
|
|
340
|
+
}
|
|
341
|
+
markFileSent(id) {
|
|
342
|
+
this.db.prepare("UPDATE file_sends SET status = 'sent' WHERE id = ?").run(id);
|
|
343
|
+
}
|
|
344
|
+
markFileFailed(id) {
|
|
345
|
+
this.db.prepare("UPDATE file_sends SET status = 'failed' WHERE id = ?").run(id);
|
|
346
|
+
}
|
|
322
347
|
}
|
package/dist/ctl.js
CHANGED
|
@@ -166,6 +166,24 @@ else if (category === "auto") {
|
|
|
166
166
|
fail("Usage: auto <add|add-approval|result|list|cancel|clear> ...");
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
else if (category === "file") {
|
|
170
|
+
if (action === "send") {
|
|
171
|
+
const [userId, platform, chatId, filePath, ...captionParts] = rest;
|
|
172
|
+
if (!userId || !platform || !chatId || !filePath)
|
|
173
|
+
fail("Usage: file send <user_id> <platform> <chat_id> <file_path> [caption...]");
|
|
174
|
+
const { resolve } = await import("path");
|
|
175
|
+
const { existsSync } = await import("fs");
|
|
176
|
+
const resolved = resolve(filePath);
|
|
177
|
+
if (!existsSync(resolved))
|
|
178
|
+
fail(`File not found: ${resolved}`);
|
|
179
|
+
const caption = captionParts.join(" ");
|
|
180
|
+
const r = db.prepare("INSERT INTO file_sends (user_id, platform, chat_id, file_path, caption, status, created_at) VALUES (?, ?, ?, ?, ?, 'pending', ?)").run(userId, platform, chatId, resolved, caption, Date.now());
|
|
181
|
+
output({ ok: true, id: Number(r.lastInsertRowid), message: `File queued for sending: ${resolved}` });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
fail("Usage: file <send> ...");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
169
187
|
else if (category === "session") {
|
|
170
188
|
if (action === "list") {
|
|
171
189
|
const [userId] = rest;
|
|
@@ -179,6 +197,6 @@ else if (category === "session") {
|
|
|
179
197
|
}
|
|
180
198
|
}
|
|
181
199
|
else {
|
|
182
|
-
fail("Usage: claudebridge-ctl <memory|task|reminder|auto|session> <action> [args...]");
|
|
200
|
+
fail("Usage: claudebridge-ctl <memory|task|reminder|auto|file|session> <action> [args...]");
|
|
183
201
|
}
|
|
184
202
|
db.close();
|
package/dist/skills/bridge.js
CHANGED
|
@@ -36,6 +36,12 @@ export function generateSkillDoc(ctx) {
|
|
|
36
36
|
`- 取消自动任务: \`${ctl} auto cancel <任务ID>\``,
|
|
37
37
|
`- 清除已完成任务: \`${ctl} auto clear ${ctx.userId}\``,
|
|
38
38
|
``,
|
|
39
|
+
`### 文件发送`,
|
|
40
|
+
`- 发送文件: \`${ctl} file send ${ctx.userId} ${ctx.platform} ${ctx.chatId} <文件路径> "说明"\``,
|
|
41
|
+
`- filePath 为 workspace 中的文件路径(相对或绝对)`,
|
|
42
|
+
`- 支持图片(jpg/png/gif)和文档(pdf/csv/txt/zip 等)`,
|
|
43
|
+
`- 用户要求生成文件时:先创建文件,再调用此命令发送`,
|
|
44
|
+
``,
|
|
39
45
|
`### 使用指南`,
|
|
40
46
|
`- 用户要你记住某事 → 使用 memory add`,
|
|
41
47
|
`- 用户问你记住了什么 → 使用 memory list`,
|
|
@@ -146,6 +152,12 @@ export function generateSkillDoc(ctx) {
|
|
|
146
152
|
`- Cancel an auto task: \`${ctl} auto cancel <task_id>\``,
|
|
147
153
|
`- Clear completed tasks: \`${ctl} auto clear ${ctx.userId}\``,
|
|
148
154
|
``,
|
|
155
|
+
`### File Sending`,
|
|
156
|
+
`- Send a file: \`${ctl} file send ${ctx.userId} ${ctx.platform} ${ctx.chatId} <filePath> "caption"\``,
|
|
157
|
+
`- filePath can be relative (to workspace) or absolute`,
|
|
158
|
+
`- Supports images (jpg/png/gif) and documents (pdf/csv/txt/zip etc.)`,
|
|
159
|
+
`- When user asks to generate a file: create it first, then call this command to send`,
|
|
160
|
+
``,
|
|
149
161
|
`### Guidelines`,
|
|
150
162
|
`- User wants you to remember something → use memory add`,
|
|
151
163
|
`- User asks what you remember → use memory list`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emqo/claudebridge",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Bridge claude CLI to chat platforms (Telegram, Discord) with scheduled auto-tasks, autonomous project management, HITL approval, conditional branching, webhook triggers, parallel execution, and observability",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|