@emqo/claudebridge 0.7.0 → 0.9.0
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/config.yaml.example +12 -3
- package/dist/adapters/base.d.ts +1 -0
- package/dist/adapters/discord.d.ts +4 -0
- package/dist/adapters/discord.js +99 -23
- package/dist/adapters/telegram.d.ts +4 -0
- package/dist/adapters/telegram.js +138 -60
- package/dist/core/agent.d.ts +37 -0
- package/dist/core/agent.js +251 -232
- package/dist/core/config.d.ts +2 -70
- package/dist/core/config.js +9 -38
- package/dist/core/i18n.js +12 -4
- package/dist/core/keys.d.ts +2 -10
- package/dist/core/keys.js +7 -22
- package/dist/core/lock.d.ts +8 -4
- package/dist/core/lock.js +28 -17
- package/dist/core/logger.d.ts +11 -0
- package/dist/core/logger.js +24 -0
- package/dist/core/router.d.ts +25 -0
- package/dist/core/router.js +125 -0
- package/dist/core/schema.d.ts +166 -0
- package/dist/core/schema.js +85 -0
- package/dist/core/session.d.ts +50 -0
- package/dist/core/session.js +100 -0
- package/dist/core/store.d.ts +52 -15
- package/dist/core/store.js +105 -19
- package/dist/ctl.js +32 -30
- package/dist/index.js +42 -13
- package/dist/providers/base.d.ts +26 -0
- package/dist/providers/base.js +1 -0
- package/dist/providers/claude.d.ts +9 -0
- package/dist/providers/claude.js +53 -0
- package/dist/providers/codex.d.ts +9 -0
- package/dist/providers/codex.js +35 -0
- package/dist/providers/registry.d.ts +2 -0
- package/dist/providers/registry.js +12 -0
- package/dist/skills/bridge.d.ts +2 -0
- package/dist/skills/bridge.js +2 -0
- package/dist/webhook.js +7 -5
- package/package.json +8 -4
package/dist/ctl.js
CHANGED
|
@@ -16,6 +16,17 @@ function fail(msg) {
|
|
|
16
16
|
console.error(msg);
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
|
+
function extractFlag(parts, flag) {
|
|
20
|
+
// Search from end to avoid matching flag text inside description
|
|
21
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
22
|
+
if (parts[i] === flag) {
|
|
23
|
+
const val = parts[i + 1];
|
|
24
|
+
parts.splice(i, 2);
|
|
25
|
+
return val;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
19
30
|
if (category === "memory") {
|
|
20
31
|
if (action === "add") {
|
|
21
32
|
const [userId, ...contentParts] = rest;
|
|
@@ -102,21 +113,10 @@ else if (category === "auto") {
|
|
|
102
113
|
const [userId, platform, chatId, ...descParts] = rest;
|
|
103
114
|
if (!userId || !platform || !chatId || !descParts.length)
|
|
104
115
|
fail("Usage: auto add <user_id> <platform> <chat_id> <description> [--parent <id>]");
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
parentId = parseInt(descParts[parentIdx + 1]);
|
|
110
|
-
descParts.splice(parentIdx, 2);
|
|
111
|
-
}
|
|
112
|
-
// Parse optional --delay flag
|
|
113
|
-
let scheduledAt = null;
|
|
114
|
-
const delayIdx = descParts.indexOf("--delay");
|
|
115
|
-
if (delayIdx !== -1 && descParts[delayIdx + 1]) {
|
|
116
|
-
const delayMin = parseInt(descParts[delayIdx + 1]);
|
|
117
|
-
scheduledAt = Date.now() + delayMin * 60000;
|
|
118
|
-
descParts.splice(delayIdx, 2);
|
|
119
|
-
}
|
|
116
|
+
const parentRaw = extractFlag(descParts, "--parent");
|
|
117
|
+
const parentId = parentRaw ? parseInt(parentRaw) : null;
|
|
118
|
+
const delayRaw = extractFlag(descParts, "--delay");
|
|
119
|
+
const scheduledAt = delayRaw ? Date.now() + parseInt(delayRaw) * 60000 : null;
|
|
120
120
|
const desc = descParts.join(" ");
|
|
121
121
|
const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, scheduled_at, created_at) VALUES (?, ?, ?, ?, 'auto', ?, ?, ?)").run(userId, platform, chatId, desc, parentId, scheduledAt, Date.now());
|
|
122
122
|
output({ ok: true, id: Number(r.lastInsertRowid), scheduled_at: scheduledAt, message: scheduledAt ? `Auto task scheduled (in ${Math.ceil((scheduledAt - Date.now()) / 60000)} min)` : "Auto task queued" });
|
|
@@ -125,20 +125,10 @@ else if (category === "auto") {
|
|
|
125
125
|
const [userId, platform, chatId, ...descParts] = rest;
|
|
126
126
|
if (!userId || !platform || !chatId || !descParts.length)
|
|
127
127
|
fail("Usage: auto add-approval <user_id> <platform> <chat_id> <description> [--parent <id>] [--delay <minutes>]");
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
descParts.splice(parentIdx, 2);
|
|
133
|
-
}
|
|
134
|
-
// Parse optional --delay flag
|
|
135
|
-
let scheduledAt = null;
|
|
136
|
-
const delayIdx = descParts.indexOf("--delay");
|
|
137
|
-
if (delayIdx !== -1 && descParts[delayIdx + 1]) {
|
|
138
|
-
const delayMin = parseInt(descParts[delayIdx + 1]);
|
|
139
|
-
scheduledAt = Date.now() + delayMin * 60000;
|
|
140
|
-
descParts.splice(delayIdx, 2);
|
|
141
|
-
}
|
|
128
|
+
const parentRaw = extractFlag(descParts, "--parent");
|
|
129
|
+
const parentId = parentRaw ? parseInt(parentRaw) : null;
|
|
130
|
+
const delayRaw = extractFlag(descParts, "--delay");
|
|
131
|
+
const scheduledAt = delayRaw ? Date.now() + parseInt(delayRaw) * 60000 : null;
|
|
142
132
|
const desc = descParts.join(" ");
|
|
143
133
|
const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, scheduled_at, created_at) VALUES (?, ?, ?, ?, 'approval_pending', ?, ?, ?)").run(userId, platform, chatId, desc, parentId, scheduledAt, Date.now());
|
|
144
134
|
output({ ok: true, id: Number(r.lastInsertRowid), scheduled_at: scheduledAt, message: scheduledAt ? `Auto task queued for approval (scheduled in ${Math.ceil((scheduledAt - Date.now()) / 60000)} min)` : "Auto task queued for approval" });
|
|
@@ -176,7 +166,19 @@ else if (category === "auto") {
|
|
|
176
166
|
fail("Usage: auto <add|add-approval|result|list|cancel|clear> ...");
|
|
177
167
|
}
|
|
178
168
|
}
|
|
169
|
+
else if (category === "session") {
|
|
170
|
+
if (action === "list") {
|
|
171
|
+
const [userId] = rest;
|
|
172
|
+
if (!userId)
|
|
173
|
+
fail("Usage: session list <user_id>");
|
|
174
|
+
const rows = db.prepare("SELECT id, user_id, platform, chat_id, claude_session_id, label, status, created_at, last_active_at, message_count, total_cost FROM sub_sessions WHERE user_id = ? ORDER BY last_active_at DESC").all(userId);
|
|
175
|
+
output({ ok: true, sessions: rows });
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
fail("Usage: session <list> ...");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
179
181
|
else {
|
|
180
|
-
fail("Usage: claudebridge-ctl <memory|task|reminder|auto> <action> [args...]");
|
|
182
|
+
fail("Usage: claudebridge-ctl <memory|task|reminder|auto|session> <action> [args...]");
|
|
181
183
|
}
|
|
182
184
|
db.close();
|
package/dist/index.js
CHANGED
|
@@ -8,10 +8,13 @@ import { AgentEngine } from "./core/agent.js";
|
|
|
8
8
|
import { TelegramAdapter } from "./adapters/telegram.js";
|
|
9
9
|
import { DiscordAdapter } from "./adapters/discord.js";
|
|
10
10
|
import { WebhookServer } from "./webhook.js";
|
|
11
|
+
import { log, setLogLevel } from "./core/logger.js";
|
|
11
12
|
async function main() {
|
|
12
13
|
const _cfgIdx = process.argv.indexOf("--config");
|
|
13
14
|
const _cfgPath = _cfgIdx !== -1 ? process.argv[_cfgIdx + 1] : undefined;
|
|
14
15
|
let config = loadConfig(_cfgPath);
|
|
16
|
+
if (config.log_level)
|
|
17
|
+
setLogLevel(config.log_level);
|
|
15
18
|
// Derive DB path from config file directory (not CWD)
|
|
16
19
|
const configDir = _cfgPath ? dirname(resolve(_cfgPath)) : process.cwd();
|
|
17
20
|
const dbPath = join(configDir, "data", "claudebridge.db");
|
|
@@ -21,20 +24,20 @@ async function main() {
|
|
|
21
24
|
let webhookServer = null;
|
|
22
25
|
if (config.platforms.telegram.enabled) {
|
|
23
26
|
if (!config.platforms.telegram.token) {
|
|
24
|
-
|
|
27
|
+
log.error("TELEGRAM_BOT_TOKEN not set");
|
|
25
28
|
process.exit(1);
|
|
26
29
|
}
|
|
27
30
|
adapters.push(new TelegramAdapter(engine, store, config.platforms.telegram, config.locale));
|
|
28
31
|
}
|
|
29
32
|
if (config.platforms.discord.enabled) {
|
|
30
33
|
if (!config.platforms.discord.token) {
|
|
31
|
-
|
|
34
|
+
log.error("DISCORD_BOT_TOKEN not set");
|
|
32
35
|
process.exit(1);
|
|
33
36
|
}
|
|
34
37
|
adapters.push(new DiscordAdapter(engine, store, config.platforms.discord, config.locale));
|
|
35
38
|
}
|
|
36
39
|
if (!adapters.length) {
|
|
37
|
-
|
|
40
|
+
log.error("no platform enabled");
|
|
38
41
|
process.exit(1);
|
|
39
42
|
}
|
|
40
43
|
// Start webhook server if enabled
|
|
@@ -44,23 +47,38 @@ async function main() {
|
|
|
44
47
|
}
|
|
45
48
|
// --- Register signal handlers and hot-reload BEFORE starting adapters ---
|
|
46
49
|
const shutdown = () => {
|
|
47
|
-
|
|
50
|
+
log.info("shutting down...");
|
|
48
51
|
for (const a of adapters)
|
|
49
52
|
a.stop();
|
|
50
53
|
if (webhookServer)
|
|
51
54
|
webhookServer.stop();
|
|
55
|
+
store.close();
|
|
52
56
|
setTimeout(() => process.exit(0), 1000);
|
|
53
57
|
};
|
|
54
58
|
process.on("SIGINT", shutdown);
|
|
55
59
|
process.on("SIGTERM", shutdown);
|
|
60
|
+
process.on("uncaughtException", (err) => {
|
|
61
|
+
log.error("uncaught exception", { error: err.message, stack: err.stack });
|
|
62
|
+
shutdown();
|
|
63
|
+
});
|
|
64
|
+
process.on("unhandledRejection", (reason) => {
|
|
65
|
+
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
66
|
+
log.error("unhandled rejection", { error: msg });
|
|
67
|
+
});
|
|
56
68
|
process.on("SIGHUP", () => {
|
|
57
69
|
try {
|
|
58
70
|
config = reloadConfig();
|
|
59
71
|
engine.reloadConfig(config);
|
|
60
|
-
|
|
72
|
+
for (const a of adapters) {
|
|
73
|
+
if ('reloadConfig' in a && typeof a.reloadConfig === 'function') {
|
|
74
|
+
const plat = a.constructor.name === 'TelegramAdapter' ? config.platforms.telegram : config.platforms.discord;
|
|
75
|
+
a.reloadConfig(plat, config.locale);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
log.info("config reloaded (SIGHUP)");
|
|
61
79
|
}
|
|
62
80
|
catch (err) {
|
|
63
|
-
|
|
81
|
+
log.error("config reload failed", { error: err?.message });
|
|
64
82
|
}
|
|
65
83
|
});
|
|
66
84
|
// Hot reload config.yaml on file change
|
|
@@ -72,23 +90,34 @@ async function main() {
|
|
|
72
90
|
try {
|
|
73
91
|
config = reloadConfig();
|
|
74
92
|
engine.reloadConfig(config);
|
|
75
|
-
|
|
93
|
+
for (const a of adapters) {
|
|
94
|
+
if ('reloadConfig' in a && typeof a.reloadConfig === 'function') {
|
|
95
|
+
const plat = a.constructor.name === 'TelegramAdapter' ? config.platforms.telegram : config.platforms.discord;
|
|
96
|
+
a.reloadConfig(plat, config.locale);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
log.info("config reloaded");
|
|
76
100
|
}
|
|
77
101
|
catch (err) {
|
|
78
|
-
|
|
102
|
+
log.error("config reload failed", { error: err?.message });
|
|
79
103
|
}
|
|
80
104
|
}, 500); // debounce
|
|
81
105
|
});
|
|
82
|
-
// --- Start adapters
|
|
106
|
+
// --- Start adapters with crash recovery ---
|
|
83
107
|
for (const a of adapters) {
|
|
84
108
|
a.start().catch(err => {
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
log.error("adapter crashed, retry in 10s", { adapter: a.constructor.name, error: err?.message });
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
a.start().catch(err2 => {
|
|
112
|
+
log.error("adapter restart failed, exiting", { error: err2?.message });
|
|
113
|
+
process.exit(1);
|
|
114
|
+
});
|
|
115
|
+
}, 10000);
|
|
87
116
|
});
|
|
88
117
|
}
|
|
89
|
-
|
|
118
|
+
log.info("running", { adapters: adapters.length });
|
|
90
119
|
}
|
|
91
120
|
main().catch((err) => {
|
|
92
|
-
|
|
121
|
+
log.error("fatal", { error: err?.message });
|
|
93
122
|
process.exit(1);
|
|
94
123
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ProviderStreamEvent {
|
|
2
|
+
type: "session_init" | "text_chunk" | "result" | "unknown";
|
|
3
|
+
sessionId?: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
cost?: number;
|
|
6
|
+
isError?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ProviderExecOpts {
|
|
9
|
+
prompt: string;
|
|
10
|
+
model: string;
|
|
11
|
+
resumeSessionId?: string;
|
|
12
|
+
systemPrompt?: string;
|
|
13
|
+
appendSystemPrompt?: string;
|
|
14
|
+
allowedTools?: string[];
|
|
15
|
+
maxTurns?: number;
|
|
16
|
+
maxBudgetUsd?: number;
|
|
17
|
+
permissionMode?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Provider {
|
|
20
|
+
readonly binary: string;
|
|
21
|
+
readonly supportsSessionResume: boolean;
|
|
22
|
+
readonly supportsAppendSystemPrompt: boolean;
|
|
23
|
+
buildArgs(opts: ProviderExecOpts): string[];
|
|
24
|
+
buildEnv(extra: Record<string, string>): Record<string, string>;
|
|
25
|
+
parseLine(line: string): ProviderStreamEvent;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Provider, ProviderExecOpts, ProviderStreamEvent } from "./base.js";
|
|
2
|
+
export declare class ClaudeProvider implements Provider {
|
|
3
|
+
readonly binary = "claude";
|
|
4
|
+
readonly supportsSessionResume = true;
|
|
5
|
+
readonly supportsAppendSystemPrompt = true;
|
|
6
|
+
buildArgs(opts: ProviderExecOpts): string[];
|
|
7
|
+
buildEnv(extra: Record<string, string>): Record<string, string>;
|
|
8
|
+
parseLine(line: string): ProviderStreamEvent;
|
|
9
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class ClaudeProvider {
|
|
2
|
+
binary = "claude";
|
|
3
|
+
supportsSessionResume = true;
|
|
4
|
+
supportsAppendSystemPrompt = true;
|
|
5
|
+
buildArgs(opts) {
|
|
6
|
+
const args = ["-p", opts.prompt, "--verbose", "--output-format", "stream-json"];
|
|
7
|
+
if (opts.permissionMode)
|
|
8
|
+
args.push("--permission-mode", opts.permissionMode);
|
|
9
|
+
if (opts.model)
|
|
10
|
+
args.push("--model", opts.model);
|
|
11
|
+
if (opts.resumeSessionId)
|
|
12
|
+
args.push("-r", opts.resumeSessionId);
|
|
13
|
+
if (opts.systemPrompt)
|
|
14
|
+
args.push("--system-prompt", opts.systemPrompt);
|
|
15
|
+
if (opts.appendSystemPrompt)
|
|
16
|
+
args.push("--append-system-prompt", opts.appendSystemPrompt);
|
|
17
|
+
if (opts.allowedTools?.length)
|
|
18
|
+
args.push("--allowed-tools", opts.allowedTools.join(","));
|
|
19
|
+
if (opts.maxTurns)
|
|
20
|
+
args.push("--max-turns", String(opts.maxTurns));
|
|
21
|
+
if (opts.maxBudgetUsd)
|
|
22
|
+
args.push("--max-budget-usd", String(opts.maxBudgetUsd));
|
|
23
|
+
return args;
|
|
24
|
+
}
|
|
25
|
+
buildEnv(extra) {
|
|
26
|
+
return { ...process.env, ...extra };
|
|
27
|
+
}
|
|
28
|
+
parseLine(line) {
|
|
29
|
+
try {
|
|
30
|
+
const msg = JSON.parse(line);
|
|
31
|
+
if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
|
|
32
|
+
return { type: "session_init", sessionId: msg.session_id };
|
|
33
|
+
}
|
|
34
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
35
|
+
const texts = msg.message.content
|
|
36
|
+
.filter((b) => b.type === "text" && b.text)
|
|
37
|
+
.map((b) => b.text);
|
|
38
|
+
if (texts.length)
|
|
39
|
+
return { type: "text_chunk", text: texts.join("") };
|
|
40
|
+
}
|
|
41
|
+
if (msg.type === "result") {
|
|
42
|
+
return {
|
|
43
|
+
type: "result",
|
|
44
|
+
text: msg.result || undefined,
|
|
45
|
+
cost: msg.total_cost_usd || undefined,
|
|
46
|
+
isError: msg.is_error || false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
return { type: "unknown" };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Provider, ProviderExecOpts, ProviderStreamEvent } from "./base.js";
|
|
2
|
+
export declare class CodexProvider implements Provider {
|
|
3
|
+
readonly binary = "codex";
|
|
4
|
+
readonly supportsSessionResume = false;
|
|
5
|
+
readonly supportsAppendSystemPrompt = false;
|
|
6
|
+
buildArgs(opts: ProviderExecOpts): string[];
|
|
7
|
+
buildEnv(extra: Record<string, string>): Record<string, string>;
|
|
8
|
+
parseLine(line: string): ProviderStreamEvent;
|
|
9
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class CodexProvider {
|
|
2
|
+
binary = "codex";
|
|
3
|
+
supportsSessionResume = false;
|
|
4
|
+
supportsAppendSystemPrompt = false;
|
|
5
|
+
buildArgs(opts) {
|
|
6
|
+
const prompt = opts.appendSystemPrompt
|
|
7
|
+
? `[System Context]\n${opts.appendSystemPrompt}\n\n[User Message]\n${opts.prompt}`
|
|
8
|
+
: opts.prompt;
|
|
9
|
+
const args = ["exec", prompt, "--json", "--dangerously-bypass-approvals-and-sandbox"];
|
|
10
|
+
if (opts.model)
|
|
11
|
+
args.push("-m", opts.model);
|
|
12
|
+
return args;
|
|
13
|
+
}
|
|
14
|
+
buildEnv(extra) {
|
|
15
|
+
return { ...process.env, ...extra };
|
|
16
|
+
}
|
|
17
|
+
parseLine(line) {
|
|
18
|
+
try {
|
|
19
|
+
const msg = JSON.parse(line);
|
|
20
|
+
if (msg.type === "thread.started" && msg.thread_id) {
|
|
21
|
+
return { type: "session_init", sessionId: msg.thread_id };
|
|
22
|
+
}
|
|
23
|
+
if (msg.type === "item.completed" && msg.item?.type === "agent_message" && msg.item.text) {
|
|
24
|
+
return { type: "text_chunk", text: msg.item.text };
|
|
25
|
+
}
|
|
26
|
+
if (msg.type === "turn.completed") {
|
|
27
|
+
const usage = msg.usage || {};
|
|
28
|
+
const cost = ((usage.input_tokens || 0) * 0.000003 + (usage.output_tokens || 0) * 0.000012);
|
|
29
|
+
return { type: "result", cost: cost || undefined };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
return { type: "unknown" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ClaudeProvider } from "./claude.js";
|
|
2
|
+
import { CodexProvider } from "./codex.js";
|
|
3
|
+
const providers = new Map([
|
|
4
|
+
["claude", new ClaudeProvider()],
|
|
5
|
+
["codex", new CodexProvider()],
|
|
6
|
+
]);
|
|
7
|
+
export function getProvider(name) {
|
|
8
|
+
const p = providers.get(name);
|
|
9
|
+
if (!p)
|
|
10
|
+
throw new Error(`Unknown provider: ${name}. Available: ${[...providers.keys()].join(", ")}`);
|
|
11
|
+
return p;
|
|
12
|
+
}
|
package/dist/skills/bridge.d.ts
CHANGED
package/dist/skills/bridge.js
CHANGED
|
@@ -11,6 +11,7 @@ export function generateSkillDoc(ctx) {
|
|
|
11
11
|
``,
|
|
12
12
|
`你正在 ClaudeBridge 中运行,连接着 ${ctx.platform} 平台。`,
|
|
13
13
|
`当前用户 ID: ${ctx.userId} | 聊天 ID: ${ctx.chatId} | 平台: ${ctx.platform}`,
|
|
14
|
+
...(ctx.subSessionId ? [`当前子会话: ${ctx.subSessionId.slice(0, 8)} (话题: "${ctx.subSessionLabel || ""}")`] : []),
|
|
14
15
|
``,
|
|
15
16
|
`你可以通过 Bash 工具调用以下命令来管理用户的记忆、任务、提醒和自动任务:`,
|
|
16
17
|
``,
|
|
@@ -120,6 +121,7 @@ export function generateSkillDoc(ctx) {
|
|
|
120
121
|
``,
|
|
121
122
|
`You are running inside ClaudeBridge, connected to the ${ctx.platform} platform.`,
|
|
122
123
|
`Current user ID: ${ctx.userId} | Chat ID: ${ctx.chatId} | Platform: ${ctx.platform}`,
|
|
124
|
+
...(ctx.subSessionId ? [`Current sub-session: ${ctx.subSessionId.slice(0, 8)} (topic: "${ctx.subSessionLabel || ""}")`] : []),
|
|
123
125
|
``,
|
|
124
126
|
`You can use the Bash tool to call these commands to manage the user's memories, tasks, reminders, and auto-tasks:`,
|
|
125
127
|
``,
|
package/dist/webhook.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { log as rootLog } from "./core/logger.js";
|
|
4
|
+
const log = rootLog.child("webhook");
|
|
3
5
|
export class WebhookServer {
|
|
4
6
|
store;
|
|
5
7
|
config;
|
|
@@ -14,7 +16,7 @@ export class WebhookServer {
|
|
|
14
16
|
start() {
|
|
15
17
|
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
16
18
|
this.server.listen(this.config.port, () => {
|
|
17
|
-
|
|
19
|
+
log.info("HTTP server listening", { port: this.config.port });
|
|
18
20
|
});
|
|
19
21
|
// Start cron schedulers
|
|
20
22
|
for (const entry of this.cronEntries) {
|
|
@@ -22,14 +24,14 @@ export class WebhookServer {
|
|
|
22
24
|
const timer = setInterval(() => {
|
|
23
25
|
try {
|
|
24
26
|
const id = this.store.addTask(entry.user_id, entry.platform, entry.chat_id, entry.description, undefined, true);
|
|
25
|
-
|
|
27
|
+
log.info("cron created auto-task", { id, description: entry.description });
|
|
26
28
|
}
|
|
27
29
|
catch (e) {
|
|
28
|
-
|
|
30
|
+
log.error("cron failed to create task", { error: e?.message });
|
|
29
31
|
}
|
|
30
32
|
}, ms);
|
|
31
33
|
this.cronTimers.push(timer);
|
|
32
|
-
|
|
34
|
+
log.info("cron scheduled", { minutes: entry.schedule_minutes, description: entry.description });
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
stop() {
|
|
@@ -97,7 +99,7 @@ export class WebhookServer {
|
|
|
97
99
|
const event = req.headers["x-github-event"] || "unknown";
|
|
98
100
|
const description = this.buildGitHubDescription(event, payload);
|
|
99
101
|
const id = this.store.addTask(userId, platform, chatId, description, undefined, true);
|
|
100
|
-
|
|
102
|
+
log.info("github webhook", { event, taskId: id });
|
|
101
103
|
this.json(res, 201, { ok: true, id, event });
|
|
102
104
|
}
|
|
103
105
|
catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emqo/claudebridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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": {
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
|
-
"dev": "tsx src/index.ts"
|
|
14
|
+
"dev": "tsx src/index.ts",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest"
|
|
15
17
|
},
|
|
16
18
|
"keywords": [
|
|
17
19
|
"claude",
|
|
@@ -40,14 +42,16 @@
|
|
|
40
42
|
"discord.js": "^14.25.1",
|
|
41
43
|
"dotenv": "^17.3.1",
|
|
42
44
|
"ioredis": "^5.9.3",
|
|
43
|
-
"yaml": "^2.8.2"
|
|
45
|
+
"yaml": "^2.8.2",
|
|
46
|
+
"zod": "^4.3.6"
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@types/better-sqlite3": "^7.6.13",
|
|
47
50
|
"@types/ioredis": "^4.28.10",
|
|
48
51
|
"@types/node": "^25.3.0",
|
|
49
52
|
"tsx": "^4.21.0",
|
|
50
|
-
"typescript": "^5.9.3"
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"vitest": "^4.0.18"
|
|
51
55
|
},
|
|
52
56
|
"overrides": {
|
|
53
57
|
"undici": ">=6.23.0"
|