@emqo/claudebridge 0.5.0 → 0.5.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/README.md +14 -14
- package/dist/adapters/discord.js +4 -5
- package/dist/adapters/telegram.js +6 -7
- package/dist/core/agent.js +3 -3
- package/dist/core/i18n.js +22 -22
- package/dist/core/store.d.ts +2 -1
- package/dist/core/store.js +7 -5
- package/dist/index.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,11 +32,11 @@ Instead of hardcoded commands, ClaudeBridge injects a **skill document** into Cl
|
|
|
32
32
|
|
|
33
33
|
### v0.5.0: Agent Gateway Features
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
35
|
+
- **Human-in-the-Loop (HITL)**: Critical tasks require user approval via inline buttons (Telegram) or commands (Discord) before execution
|
|
36
|
+
- **Conditional Branching**: Scout pattern — task 1 analyzes, saves results, dynamically creates follow-up tasks with `--parent` linking
|
|
37
|
+
- **Webhook Triggers**: HTTP API + GitHub webhooks + cron scheduler — trigger auto-tasks from external systems
|
|
38
|
+
- **Parallel Execution**: Multiple `claude` instances running simultaneously (`max_parallel` config)
|
|
39
|
+
- **Observability**: `/status` command shows task queue, chain progress, and execution stats
|
|
40
40
|
|
|
41
41
|
## Quick Start
|
|
42
42
|
|
|
@@ -149,7 +149,7 @@ All other interactions are handled naturally by Claude through the skill system
|
|
|
149
149
|
|
|
150
150
|
When Claude determines a task is critical (deployment, deletion, production changes), it uses `ctl auto add-approval` instead of `ctl auto add`. The task enters `approval_pending` status:
|
|
151
151
|
|
|
152
|
-
- **Telegram**: Inline keyboard with
|
|
152
|
+
- **Telegram**: Inline keyboard with Approve / Reject buttons
|
|
153
153
|
- **Discord**: Bot sends approval request, user replies `!approve <id>` or `!reject <id>`
|
|
154
154
|
|
|
155
155
|
Only after approval does the task enter the execution queue.
|
|
@@ -166,7 +166,7 @@ Task #1 (scout): "Analyze performance bottlenecks"
|
|
|
166
166
|
Task #4: "Run benchmarks" (--parent 1)
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
Chain progress is reported automatically:
|
|
169
|
+
Chain progress is reported automatically: `Chain #1 progress: 2/4 done`
|
|
170
170
|
|
|
171
171
|
Results are persisted via `ctl auto result <id> "summary"` and cross-task context flows through the memory system.
|
|
172
172
|
|
|
@@ -299,11 +299,11 @@ MIT
|
|
|
299
299
|
|
|
300
300
|
### v0.5.0:Agent Gateway 特性
|
|
301
301
|
|
|
302
|
-
-
|
|
303
|
-
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
302
|
+
- **人机协同 (HITL)**:关键任务需要用户通过内联按钮(Telegram)或命令(Discord)审批后才执行
|
|
303
|
+
- **条件分支**:侦查模式 — 任务1分析,保存结果,动态创建后续任务并通过 `--parent` 关联
|
|
304
|
+
- **Webhook 触发**:HTTP API + GitHub webhooks + 定时任务 — 从外部系统触发自动任务
|
|
305
|
+
- **并行执行**:多个 `claude` 实例同时运行(`max_parallel` 配置)
|
|
306
|
+
- **可观测性**:`/status` 命令显示任务队列、链路进度和执行统计
|
|
307
307
|
|
|
308
308
|
## 快速开始
|
|
309
309
|
|
|
@@ -352,7 +352,7 @@ Discord 还支持:`!approve <id>`、`!reject <id>` 用于 HITL 审批。
|
|
|
352
352
|
|
|
353
353
|
当 Claude 判定任务为关键操作(部署、删除、生产变更)时,使用 `ctl auto add-approval` 代替 `ctl auto add`。任务进入 `approval_pending` 状态:
|
|
354
354
|
|
|
355
|
-
- **Telegram**:内联键盘显示
|
|
355
|
+
- **Telegram**:内联键盘显示 Approve / Reject 按钮
|
|
356
356
|
- **Discord**:Bot 发送审批请求,用户回复 `!approve <id>` 或 `!reject <id>`
|
|
357
357
|
|
|
358
358
|
只有审批通过后,任务才进入执行队列。
|
|
@@ -369,7 +369,7 @@ Discord 还支持:`!approve <id>`、`!reject <id>` 用于 HITL 审批。
|
|
|
369
369
|
任务 #4:"运行基准测试" (--parent 1)
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
-
|
|
372
|
+
链路进度自动报告:`Chain #1 progress: 2/4 done`
|
|
373
373
|
|
|
374
374
|
## Webhook 与定时任务
|
|
375
375
|
|
package/dist/adapters/discord.js
CHANGED
|
@@ -159,7 +159,7 @@ export class DiscordAdapter {
|
|
|
159
159
|
const now = Date.now();
|
|
160
160
|
if (now - lastEdit < EDIT_INTERVAL)
|
|
161
161
|
return;
|
|
162
|
-
const preview = full.slice(-1900) + "\n\n
|
|
162
|
+
const preview = full.slice(-1900) + "\n\n...";
|
|
163
163
|
if (preview === lastText)
|
|
164
164
|
return;
|
|
165
165
|
lastText = preview;
|
|
@@ -292,13 +292,12 @@ export class DiscordAdapter {
|
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
294
|
const statusEmoji = {
|
|
295
|
-
auto: "
|
|
296
|
-
approval_pending: "
|
|
295
|
+
auto: "[queue]", running: "[run]", done: "[done]", failed: "[fail]",
|
|
296
|
+
approval_pending: "[pending]", cancelled: "[cancel]",
|
|
297
297
|
};
|
|
298
298
|
const lines = recent.map(task => {
|
|
299
|
-
const emoji = statusEmoji[task.status] || "❓";
|
|
300
299
|
const chain = task.parent_id ? ` (chain #${task.parent_id})` : "";
|
|
301
|
-
return `${
|
|
300
|
+
return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}] ${task.description.slice(0, 60)}${chain}`;
|
|
302
301
|
});
|
|
303
302
|
const stats = this.store.getAutoTaskStats();
|
|
304
303
|
const summary = stats.map(s => `${s.status}: ${s.count}`).join(" | ");
|
|
@@ -266,7 +266,7 @@ export class TelegramAdapter {
|
|
|
266
266
|
if (now - lastEdit < EDIT_INTERVAL)
|
|
267
267
|
return;
|
|
268
268
|
lastEdit = now;
|
|
269
|
-
const preview = full.slice(-3500) + "\n\n
|
|
269
|
+
const preview = full.slice(-3500) + "\n\n...";
|
|
270
270
|
await this.editMsg(chatId, msgId, preview);
|
|
271
271
|
});
|
|
272
272
|
console.log(`[telegram] claude done for ${uid}, cost=$${res.cost?.toFixed(4)}`);
|
|
@@ -423,8 +423,8 @@ export class TelegramAdapter {
|
|
|
423
423
|
const chatId = Number(task.chat_id);
|
|
424
424
|
const keyboard = {
|
|
425
425
|
inline_keyboard: [[
|
|
426
|
-
{ text: "
|
|
427
|
-
{ text: "
|
|
426
|
+
{ text: "Approve", callback_data: `approve:${task.id}` },
|
|
427
|
+
{ text: "Reject", callback_data: `reject:${task.id}` },
|
|
428
428
|
]],
|
|
429
429
|
};
|
|
430
430
|
await this.call("sendMessage", {
|
|
@@ -496,13 +496,12 @@ export class TelegramAdapter {
|
|
|
496
496
|
return;
|
|
497
497
|
}
|
|
498
498
|
const statusEmoji = {
|
|
499
|
-
auto: "
|
|
500
|
-
approval_pending: "
|
|
499
|
+
auto: "[queue]", running: "[run]", done: "[done]", failed: "[fail]",
|
|
500
|
+
approval_pending: "[pending]", cancelled: "[cancel]",
|
|
501
501
|
};
|
|
502
502
|
const lines = recent.map(task => {
|
|
503
|
-
const emoji = statusEmoji[task.status] || "❓";
|
|
504
503
|
const chain = task.parent_id ? ` (chain #${task.parent_id})` : "";
|
|
505
|
-
return `${
|
|
504
|
+
return `${statusEmoji[task.status] || "[?]"} #${task.id} [${task.status}] ${task.description.slice(0, 60)}${chain}`;
|
|
506
505
|
});
|
|
507
506
|
const stats = this.store.getAutoTaskStats();
|
|
508
507
|
const summary = stats.map(s => `${s.status}: ${s.count}`).join(" | ");
|
package/dist/core/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { mkdirSync } from "fs";
|
|
3
|
-
import { join
|
|
3
|
+
import { join } from "path";
|
|
4
4
|
import { UserLock } from "./lock.js";
|
|
5
5
|
import { AccessControl } from "./permissions.js";
|
|
6
6
|
import { EndpointRotator } from "./keys.js";
|
|
@@ -146,7 +146,7 @@ export class AgentEngine {
|
|
|
146
146
|
env.ANTHROPIC_API_KEY = ep.api_key;
|
|
147
147
|
if (ep.base_url)
|
|
148
148
|
env.ANTHROPIC_BASE_URL = ep.base_url;
|
|
149
|
-
env.CLAUDEBRIDGE_DB =
|
|
149
|
+
env.CLAUDEBRIDGE_DB = this.store.dbPath;
|
|
150
150
|
const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
151
151
|
child.stdin.end();
|
|
152
152
|
console.log(`[agent] spawned claude pid=${child.pid} cwd=${cwd} args=${args.join(" ")}`);
|
|
@@ -252,7 +252,7 @@ export class AgentEngine {
|
|
|
252
252
|
env.ANTHROPIC_API_KEY = ep.api_key;
|
|
253
253
|
if (ep.base_url)
|
|
254
254
|
env.ANTHROPIC_BASE_URL = ep.base_url;
|
|
255
|
-
env.CLAUDEBRIDGE_DB =
|
|
255
|
+
env.CLAUDEBRIDGE_DB = this.store.dbPath;
|
|
256
256
|
const child = spawn("claude", args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
257
257
|
child.stdin.end();
|
|
258
258
|
console.log(`[agent] spawned claude (parallel) pid=${child.pid} cwd=${cwd}`);
|
package/dist/core/i18n.js
CHANGED
|
@@ -6,21 +6,21 @@ const messages = {
|
|
|
6
6
|
no_history: "No history.",
|
|
7
7
|
config_reloaded: "Config reloaded.",
|
|
8
8
|
reload_failed: "Reload failed: ",
|
|
9
|
-
thinking: "
|
|
10
|
-
still_processing: "
|
|
9
|
+
thinking: "Thinking...",
|
|
10
|
+
still_processing: "Still processing...",
|
|
11
11
|
upload_failed: "Upload failed: ",
|
|
12
|
-
reminder_notify: "
|
|
13
|
-
auto_starting: "
|
|
14
|
-
auto_done: "
|
|
15
|
-
auto_failed: "
|
|
12
|
+
reminder_notify: "Reminder: {desc}",
|
|
13
|
+
auto_starting: "Auto task #{id} starting:\n{desc}",
|
|
14
|
+
auto_done: "Auto task #{id} done (cost: ${cost}):",
|
|
15
|
+
auto_failed: "Auto task #{id} failed: {err}",
|
|
16
16
|
page_expired: "Page expired. Please resend your question.",
|
|
17
|
-
approval_request: "
|
|
18
|
-
approval_approved: "
|
|
19
|
-
approval_rejected: "
|
|
17
|
+
approval_request: "Approval needed for auto task #{id}:\n{desc}",
|
|
18
|
+
approval_approved: "Auto task #{id} approved -- queued for execution.",
|
|
19
|
+
approval_rejected: "Auto task #{id} rejected.",
|
|
20
20
|
approval_decided: "Task #{id} has already been decided.",
|
|
21
21
|
no_auto_tasks: "No auto tasks found.",
|
|
22
|
-
status_report: "
|
|
23
|
-
chain_progress: "
|
|
22
|
+
status_report: "Auto Task Status:",
|
|
23
|
+
chain_progress: "Chain #{id} progress: {done}/{total} done{cost}",
|
|
24
24
|
},
|
|
25
25
|
zh: {
|
|
26
26
|
help: "ClaudeBridge 就绪。\n\n管理命令:\n/new - 清除会话\n/usage - 你的用量\n/allusage - 所有用量\n/history - 最近对话\n/model - 端点信息\n/status - 自动任务状态\n/reload - 重载配置\n/help - 显示帮助\n\n直接对话即可管理记忆、任务、提醒等 — Claude 会自动处理。",
|
|
@@ -29,21 +29,21 @@ const messages = {
|
|
|
29
29
|
no_history: "暂无历史记录。",
|
|
30
30
|
config_reloaded: "配置已重载。",
|
|
31
31
|
reload_failed: "重载失败:",
|
|
32
|
-
thinking: "
|
|
33
|
-
still_processing: "
|
|
32
|
+
thinking: "思考中...",
|
|
33
|
+
still_processing: "仍在处理...",
|
|
34
34
|
upload_failed: "上传失败:",
|
|
35
|
-
reminder_notify: "
|
|
36
|
-
auto_starting: "
|
|
37
|
-
auto_done: "
|
|
38
|
-
auto_failed: "
|
|
35
|
+
reminder_notify: "提醒:{desc}",
|
|
36
|
+
auto_starting: "自动任务 #{id} 开始执行:\n{desc}",
|
|
37
|
+
auto_done: "自动任务 #{id} 完成(花费:${cost}):",
|
|
38
|
+
auto_failed: "自动任务 #{id} 失败:{err}",
|
|
39
39
|
page_expired: "页面已过期,请重新发送问题。",
|
|
40
|
-
approval_request: "
|
|
41
|
-
approval_approved: "
|
|
42
|
-
approval_rejected: "
|
|
40
|
+
approval_request: "自动任务 #{id} 需要审批:\n{desc}",
|
|
41
|
+
approval_approved: "自动任务 #{id} 已批准 -- 已加入执行队列。",
|
|
42
|
+
approval_rejected: "自动任务 #{id} 已拒绝。",
|
|
43
43
|
approval_decided: "任务 #{id} 已被处理。",
|
|
44
44
|
no_auto_tasks: "暂无自动任务。",
|
|
45
|
-
status_report: "
|
|
46
|
-
chain_progress: "
|
|
45
|
+
status_report: "自动任务状态:",
|
|
46
|
+
chain_progress: "任务链 #{id} 进度:{done}/{total} 完成{cost}",
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
const commandDescriptions = {
|
package/dist/core/store.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare class Store {
|
|
2
2
|
private db;
|
|
3
|
-
|
|
3
|
+
readonly dbPath: string;
|
|
4
|
+
constructor(dbPath?: string);
|
|
4
5
|
getSession(userId: string): string | null;
|
|
5
6
|
setSession(userId: string, sessionId: string, platform: string): void;
|
|
6
7
|
clearSession(userId: string): void;
|
package/dist/core/store.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
import { mkdirSync } from "fs";
|
|
3
|
-
import { dirname } from "path";
|
|
4
|
-
const
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
const DEFAULT_DB_PATH = "./data/claudebridge.db";
|
|
5
5
|
export class Store {
|
|
6
6
|
db;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.
|
|
7
|
+
dbPath;
|
|
8
|
+
constructor(dbPath) {
|
|
9
|
+
this.dbPath = resolve(dbPath || DEFAULT_DB_PATH);
|
|
10
|
+
mkdirSync(dirname(this.dbPath), { recursive: true });
|
|
11
|
+
this.db = new Database(this.dbPath);
|
|
10
12
|
this.db.pragma("journal_mode = WAL");
|
|
11
13
|
this.db.exec(`
|
|
12
14
|
CREATE TABLE IF NOT EXISTS sessions (
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { setDefaultAutoSelectFamily } from "net";
|
|
2
2
|
setDefaultAutoSelectFamily(false);
|
|
3
3
|
import { watch } from "fs";
|
|
4
|
+
import { join, dirname, resolve } from "path";
|
|
4
5
|
import { loadConfig, reloadConfig } from "./core/config.js";
|
|
5
6
|
import { Store } from "./core/store.js";
|
|
6
7
|
import { AgentEngine } from "./core/agent.js";
|
|
@@ -11,7 +12,10 @@ async function main() {
|
|
|
11
12
|
const _cfgIdx = process.argv.indexOf("--config");
|
|
12
13
|
const _cfgPath = _cfgIdx !== -1 ? process.argv[_cfgIdx + 1] : undefined;
|
|
13
14
|
let config = loadConfig(_cfgPath);
|
|
14
|
-
|
|
15
|
+
// Derive DB path from config file directory (not CWD)
|
|
16
|
+
const configDir = _cfgPath ? dirname(resolve(_cfgPath)) : process.cwd();
|
|
17
|
+
const dbPath = join(configDir, "data", "claudebridge.db");
|
|
18
|
+
const store = new Store(dbPath);
|
|
15
19
|
const engine = new AgentEngine(config, store);
|
|
16
20
|
const adapters = [];
|
|
17
21
|
let webhookServer = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emqo/claudebridge",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Bridge claude CLI to chat platforms (Telegram, Discord) with HITL approval, conditional branching, webhook triggers, parallel execution, and observability",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|