@emqo/claudebridge 0.9.1 → 0.10.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/README.md +35 -9
- package/config.yaml.example +1 -1
- package/dist/core/agent.d.ts +4 -3
- package/dist/core/agent.js +50 -7
- package/dist/core/router.d.ts +13 -9
- package/dist/core/router.js +46 -24
- package/dist/core/schema.d.ts +3 -3
- package/dist/core/schema.js +1 -1
- package/dist/core/session.d.ts +10 -0
- package/dist/core/session.js +11 -0
- package/dist/core/store.d.ts +7 -0
- package/dist/core/store.js +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,14 @@ Instead of hardcoded commands, ClaudeBridge injects a **skill document** into Cl
|
|
|
38
38
|
- **Parallel Execution**: Multiple `claude` instances running simultaneously (`max_parallel` config)
|
|
39
39
|
- **Observability**: `/status` command shows task queue, chain progress, and execution stats
|
|
40
40
|
|
|
41
|
+
### v0.10.0: Dispatcher Architecture (Master-Worker Sessions)
|
|
42
|
+
|
|
43
|
+
- **Dispatcher (Master Session)**: Every user has a single dispatcher that receives all messages and routes them to the correct sub-session. Users never interact with sub-sessions directly
|
|
44
|
+
- **Intelligent Routing**: Fast path ($0) for 0-1 active sessions; Claude-powered classification with user memories + session summaries for 2+ sessions
|
|
45
|
+
- **Session Summaries**: Each sub-session maintains an auto-generated summary, giving the dispatcher context about what each conversation is doing
|
|
46
|
+
- **Memory-Aware Dispatch**: Dispatcher sees user memories + all active session summaries when classifying, enabling accurate routing even for ambiguous messages
|
|
47
|
+
- **Concurrent Sub-Sessions**: Multiple sub-sessions execute in parallel with per-session locks
|
|
48
|
+
|
|
41
49
|
## Quick Start
|
|
42
50
|
|
|
43
51
|
### Global Install (npm)
|
|
@@ -91,6 +99,11 @@ agent:
|
|
|
91
99
|
max_memories: 50
|
|
92
100
|
skill:
|
|
93
101
|
enabled: true
|
|
102
|
+
session:
|
|
103
|
+
enabled: true
|
|
104
|
+
max_per_user: 3
|
|
105
|
+
idle_timeout_minutes: 30
|
|
106
|
+
dispatcher_budget: 0.05
|
|
94
107
|
|
|
95
108
|
workspace:
|
|
96
109
|
base_dir: "./workspaces"
|
|
@@ -231,11 +244,13 @@ src/
|
|
|
231
244
|
ctl.ts claudebridge-ctl: memory/task/reminder/auto ops via SQLite
|
|
232
245
|
webhook.ts HTTP server + GitHub webhooks + cron scheduler
|
|
233
246
|
core/
|
|
234
|
-
agent.ts Claude CLI subprocess spawner
|
|
247
|
+
agent.ts Claude CLI subprocess spawner + session summary sync
|
|
235
248
|
config.ts YAML config with env fallback
|
|
236
249
|
keys.ts Endpoint round-robin with cooldown
|
|
237
|
-
lock.ts Per-user concurrency mutex (Redis or in-memory)
|
|
250
|
+
lock.ts Per-user/per-session concurrency mutex (Redis or in-memory)
|
|
238
251
|
store.ts SQLite (WAL): sessions, usage, history, memories, tasks
|
|
252
|
+
router.ts Dispatcher: message routing with memories + session summaries
|
|
253
|
+
session.ts Sub-session lifecycle management
|
|
239
254
|
permissions.ts Whitelist access control
|
|
240
255
|
markdown.ts Markdown → Telegram MarkdownV2
|
|
241
256
|
i18n.ts Internationalization (en/zh)
|
|
@@ -250,13 +265,16 @@ src/
|
|
|
250
265
|
### Data Flow
|
|
251
266
|
|
|
252
267
|
```
|
|
253
|
-
User message → Adapter → Access check
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
User message → Adapter → Access check
|
|
269
|
+
↓
|
|
270
|
+
Dispatcher (master session)
|
|
271
|
+
├─ Fast path: 0-1 sessions → direct route ($0)
|
|
272
|
+
└─ Classify: 2+ sessions → Claude call (memories + summaries)
|
|
273
|
+
↓
|
|
274
|
+
Sub-session execution (per-session lock)
|
|
275
|
+
├─ Inject memories + skill doc → spawn claude CLI
|
|
276
|
+
├─ Stream response back to adapter
|
|
277
|
+
└─ Post: save history, sync summary, auto-summarize to shared memory
|
|
260
278
|
```
|
|
261
279
|
|
|
262
280
|
## Prerequisites
|
|
@@ -305,6 +323,14 @@ MIT
|
|
|
305
323
|
- **并行执行**:多个 `claude` 实例同时运行(`max_parallel` 配置)
|
|
306
324
|
- **可观测性**:`/status` 命令显示任务队列、链路进度和执行统计
|
|
307
325
|
|
|
326
|
+
### v0.10.0:Dispatcher 架构(主从会话)
|
|
327
|
+
|
|
328
|
+
- **Dispatcher(主会话)**:每个用户有一个 Dispatcher 接收所有消息并路由到正确的子会话,用户无需感知子会话的存在
|
|
329
|
+
- **智能路由**:0-1 个活跃会话走快速路径($0);2+ 个会话时 Claude 分类器携带用户记忆 + 会话摘要进行判断
|
|
330
|
+
- **会话摘要**:每个子会话自动生成摘要,让 Dispatcher 了解每个对话在做什么
|
|
331
|
+
- **记忆感知分发**:Dispatcher 分类时可见用户记忆 + 所有活跃会话摘要,即使模糊消息也能准确路由
|
|
332
|
+
- **并发子会话**:多个子会话并行执行,per-session 锁保证安全
|
|
333
|
+
|
|
308
334
|
## 快速开始
|
|
309
335
|
|
|
310
336
|
### 全局安装(npm)
|
package/config.yaml.example
CHANGED
|
@@ -37,7 +37,7 @@ agent:
|
|
|
37
37
|
enabled: true # Enable multi-session (concurrent conversations per user)
|
|
38
38
|
max_per_user: 3 # Max concurrent sub-sessions per user
|
|
39
39
|
idle_timeout_minutes: 30 # Auto-close idle sub-sessions after this time
|
|
40
|
-
|
|
40
|
+
dispatcher_budget: 0.05 # Max budget for dispatcher classifier
|
|
41
41
|
classifier_model: "" # Model for classifier (empty = use default)
|
|
42
42
|
|
|
43
43
|
workspace:
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Store } from "./store.js";
|
|
|
3
3
|
import { AccessControl } from "./permissions.js";
|
|
4
4
|
import { EndpointRotator } from "./keys.js";
|
|
5
5
|
import { SessionManager } from "./session.js";
|
|
6
|
-
import {
|
|
6
|
+
import { Dispatcher } from "./router.js";
|
|
7
7
|
export interface AgentResponse {
|
|
8
8
|
text: string;
|
|
9
9
|
sessionId: string;
|
|
@@ -19,7 +19,7 @@ export declare class AgentEngine {
|
|
|
19
19
|
private lock;
|
|
20
20
|
private rotator;
|
|
21
21
|
private sessionMgr;
|
|
22
|
-
private
|
|
22
|
+
private dispatcher;
|
|
23
23
|
private sessionExpiryTimer?;
|
|
24
24
|
access: AccessControl;
|
|
25
25
|
constructor(config: Config, store: Store);
|
|
@@ -32,7 +32,7 @@ export declare class AgentEngine {
|
|
|
32
32
|
getEndpointCount(): number;
|
|
33
33
|
getMaxParallel(): number;
|
|
34
34
|
getSessionManager(): SessionManager;
|
|
35
|
-
|
|
35
|
+
getDispatcher(): Dispatcher;
|
|
36
36
|
getWorkDir(userId: string): string;
|
|
37
37
|
/** @deprecated Use isSessionLocked() for multi-session mode */
|
|
38
38
|
isLocked(userId: string): boolean;
|
|
@@ -68,5 +68,6 @@ export declare class AgentEngine {
|
|
|
68
68
|
private _execute;
|
|
69
69
|
/** Parallel execution without session resume. Thin wrapper around _spawnAgent. */
|
|
70
70
|
private _executeNoSession;
|
|
71
|
+
private _syncSessionSummary;
|
|
71
72
|
private _autoSummarize;
|
|
72
73
|
}
|
package/dist/core/agent.js
CHANGED
|
@@ -6,7 +6,7 @@ import { AccessControl } from "./permissions.js";
|
|
|
6
6
|
import { EndpointRotator } from "./keys.js";
|
|
7
7
|
import { generateSkillDoc } from "../skills/bridge.js";
|
|
8
8
|
import { SessionManager } from "./session.js";
|
|
9
|
-
import {
|
|
9
|
+
import { Dispatcher } from "./router.js";
|
|
10
10
|
import { log as rootLog } from "./logger.js";
|
|
11
11
|
import { getProvider } from "../providers/registry.js";
|
|
12
12
|
const log = rootLog.child("agent");
|
|
@@ -16,7 +16,7 @@ export class AgentEngine {
|
|
|
16
16
|
lock;
|
|
17
17
|
rotator;
|
|
18
18
|
sessionMgr;
|
|
19
|
-
|
|
19
|
+
dispatcher;
|
|
20
20
|
sessionExpiryTimer;
|
|
21
21
|
access;
|
|
22
22
|
constructor(config, store) {
|
|
@@ -26,7 +26,7 @@ export class AgentEngine {
|
|
|
26
26
|
this.access = new AccessControl(config.access.allowed_users, config.access.allowed_groups);
|
|
27
27
|
this.rotator = new EndpointRotator(config.endpoints);
|
|
28
28
|
this.sessionMgr = new SessionManager(store, config.agent.session);
|
|
29
|
-
this.
|
|
29
|
+
this.dispatcher = new Dispatcher(this.sessionMgr, this.rotator, config.agent.session, store);
|
|
30
30
|
// Periodic idle session expiry (every 5 min)
|
|
31
31
|
this.sessionExpiryTimer = setInterval(() => {
|
|
32
32
|
this.sessionMgr.expireIdle();
|
|
@@ -53,8 +53,8 @@ export class AgentEngine {
|
|
|
53
53
|
getSessionManager() {
|
|
54
54
|
return this.sessionMgr;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
return this.
|
|
56
|
+
getDispatcher() {
|
|
57
|
+
return this.dispatcher;
|
|
58
58
|
}
|
|
59
59
|
getWorkDir(userId) {
|
|
60
60
|
if (!this.config.workspace.isolation) {
|
|
@@ -80,8 +80,8 @@ export class AgentEngine {
|
|
|
80
80
|
* Routes to the correct sub-session and executes concurrently.
|
|
81
81
|
*/
|
|
82
82
|
async handleUserMessage(userId, prompt, platform, chatId, replyToMsgId, onChunk, overrideTimeoutMs) {
|
|
83
|
-
// 1.
|
|
84
|
-
const decision = await this.
|
|
83
|
+
// 1. Dispatch
|
|
84
|
+
const decision = await this.dispatcher.dispatch(userId, platform, chatId, prompt, replyToMsgId);
|
|
85
85
|
// 2. Create or get sub-session
|
|
86
86
|
let subSession;
|
|
87
87
|
if (decision.action === "create") {
|
|
@@ -121,6 +121,8 @@ export class AgentEngine {
|
|
|
121
121
|
// 6. Auto-summarize
|
|
122
122
|
if (this.config.agent.memory?.auto_summary)
|
|
123
123
|
this._autoSummarize(userId, prompt, res.text);
|
|
124
|
+
// 7. Sync sub-session summary for dispatcher context
|
|
125
|
+
this._syncSessionSummary(subSession, prompt, res.text);
|
|
124
126
|
return { ...res, subSessionId: subSession.id, label: subSession.label };
|
|
125
127
|
}
|
|
126
128
|
/**
|
|
@@ -336,6 +338,47 @@ export class AgentEngine {
|
|
|
336
338
|
logLabel: "parallel", verbose: false,
|
|
337
339
|
});
|
|
338
340
|
}
|
|
341
|
+
_syncSessionSummary(subSession, prompt, response) {
|
|
342
|
+
const ep = this.rotator.count
|
|
343
|
+
? this.rotator.next()
|
|
344
|
+
: { name: "default", provider: "claude", model: "" };
|
|
345
|
+
const summaryPrompt = `Summarize this conversation exchange in 1-2 sentences for a dispatcher that routes messages. Focus on the topic/task being discussed.\n\nUser: ${prompt.slice(0, 300)}\nAssistant: ${response.slice(0, 500)}`;
|
|
346
|
+
const args = ["-p", summaryPrompt, "--output-format", "stream-json", "--max-turns", "1", "--max-budget-usd", "0.02"];
|
|
347
|
+
if (ep.model)
|
|
348
|
+
args.push("--model", ep.model);
|
|
349
|
+
const env = { ...process.env };
|
|
350
|
+
const child = spawn("claude", args, { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
351
|
+
child.stdin.end();
|
|
352
|
+
const killTimer = setTimeout(() => { try {
|
|
353
|
+
child.kill("SIGTERM");
|
|
354
|
+
}
|
|
355
|
+
catch { } }, 30000);
|
|
356
|
+
let result = "";
|
|
357
|
+
let buffer = "";
|
|
358
|
+
child.stdout.on("data", (data) => {
|
|
359
|
+
buffer += data.toString();
|
|
360
|
+
const lines = buffer.split("\n");
|
|
361
|
+
buffer = lines.pop() || "";
|
|
362
|
+
for (const line of lines) {
|
|
363
|
+
if (!line.trim())
|
|
364
|
+
continue;
|
|
365
|
+
try {
|
|
366
|
+
const msg = JSON.parse(line);
|
|
367
|
+
if (msg.type === "result" && msg.result)
|
|
368
|
+
result = msg.result;
|
|
369
|
+
}
|
|
370
|
+
catch { }
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
child.on("close", () => {
|
|
374
|
+
clearTimeout(killTimer);
|
|
375
|
+
if (result && result.length > 0) {
|
|
376
|
+
this.sessionMgr.updateSummary(subSession.id, result.trim().slice(0, 200));
|
|
377
|
+
log.info("session summary synced", { sessionId: subSession.id.slice(0, 8) });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
child.on("error", (err) => { log.warn("session summary error", { error: err.message }); });
|
|
381
|
+
}
|
|
339
382
|
_autoSummarize(userId, prompt, response) {
|
|
340
383
|
const ep = this.rotator.count
|
|
341
384
|
? this.rotator.next()
|
package/dist/core/router.d.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { SessionManager } from "./session.js";
|
|
2
2
|
import { EndpointRotator } from "./keys.js";
|
|
3
3
|
import { SessionConfig } from "./config.js";
|
|
4
|
+
import { Store } from "./store.js";
|
|
4
5
|
export interface RouterDecision {
|
|
5
6
|
action: "route" | "create";
|
|
6
7
|
subSessionId?: string;
|
|
7
8
|
label?: string;
|
|
8
9
|
}
|
|
9
|
-
export declare class
|
|
10
|
+
export declare class Dispatcher {
|
|
10
11
|
private sessionMgr;
|
|
11
12
|
private rotator;
|
|
12
13
|
private config;
|
|
13
|
-
|
|
14
|
+
private store;
|
|
15
|
+
constructor(sessionMgr: SessionManager, rotator: EndpointRotator, config: SessionConfig, store: Store);
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Dispatch user message:
|
|
18
|
+
* Fast path: reply-to → direct route ($0)
|
|
19
|
+
* Fast path: 0 active → create ($0)
|
|
20
|
+
* Fast path: 1 active → route ($0)
|
|
21
|
+
* Classify: 2+ active → Claude classifier with memories + summaries
|
|
19
22
|
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
23
|
+
dispatch(userId: string, platform: string, chatId: string, messageText: string, replyToMsgId?: string): Promise<RouterDecision>;
|
|
24
|
+
/** Classify with user memories + sub-session summaries for context */
|
|
22
25
|
private _classify;
|
|
23
|
-
/** Spawn claude CLI for single-turn classification
|
|
26
|
+
/** Spawn claude CLI for single-turn classification */
|
|
24
27
|
private _callClassifier;
|
|
25
28
|
}
|
|
29
|
+
export { Dispatcher as SessionRouter };
|
package/dist/core/router.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { log as rootLog } from "./logger.js";
|
|
3
|
-
const log = rootLog.child("
|
|
4
|
-
export class
|
|
3
|
+
const log = rootLog.child("dispatcher");
|
|
4
|
+
export class Dispatcher {
|
|
5
5
|
sessionMgr;
|
|
6
6
|
rotator;
|
|
7
7
|
config;
|
|
8
|
-
|
|
8
|
+
store;
|
|
9
|
+
constructor(sessionMgr, rotator, config, store) {
|
|
9
10
|
this.sessionMgr = sessionMgr;
|
|
10
11
|
this.rotator = rotator;
|
|
11
12
|
this.config = config;
|
|
13
|
+
this.store = store;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Dispatch user message:
|
|
17
|
+
* Fast path: reply-to → direct route ($0)
|
|
18
|
+
* Fast path: 0 active → create ($0)
|
|
19
|
+
* Fast path: 1 active → route ($0)
|
|
20
|
+
* Classify: 2+ active → Claude classifier with memories + summaries
|
|
18
21
|
*/
|
|
19
|
-
async
|
|
20
|
-
//
|
|
22
|
+
async dispatch(userId, platform, chatId, messageText, replyToMsgId) {
|
|
23
|
+
// Fast path: reply-to routing
|
|
21
24
|
if (replyToMsgId) {
|
|
22
25
|
const sessId = this.sessionMgr.getSessionByMessage(replyToMsgId, chatId);
|
|
23
26
|
if (sessId) {
|
|
@@ -26,9 +29,8 @@ export class SessionRouter {
|
|
|
26
29
|
return { action: "route", subSessionId: sessId };
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
|
-
// reply-to pointed to closed/expired session — fall through to Tier 2/3
|
|
30
32
|
}
|
|
31
|
-
//
|
|
33
|
+
// Fast path: 0-1 active sessions
|
|
32
34
|
const active = this.sessionMgr.getActive(userId, platform);
|
|
33
35
|
if (active.length === 0) {
|
|
34
36
|
return { action: "create", label: messageText.slice(0, 50) };
|
|
@@ -36,49 +38,67 @@ export class SessionRouter {
|
|
|
36
38
|
if (active.length === 1) {
|
|
37
39
|
return { action: "route", subSessionId: active[0].id };
|
|
38
40
|
}
|
|
39
|
-
//
|
|
41
|
+
// 2+ sessions: classify with memories + summaries
|
|
40
42
|
return await this._classify(userId, platform, messageText, active);
|
|
41
43
|
}
|
|
42
|
-
/**
|
|
44
|
+
/** Classify with user memories + sub-session summaries for context */
|
|
43
45
|
async _classify(userId, platform, text, sessions) {
|
|
44
46
|
try {
|
|
47
|
+
// Gather context
|
|
48
|
+
const memories = this.store.getMemories(userId);
|
|
49
|
+
const summaries = this.sessionMgr.getSummaries(userId, platform);
|
|
45
50
|
const sessionList = sessions
|
|
46
51
|
.map(s => {
|
|
47
52
|
const ago = Math.round((Date.now() - s.lastActiveAt) / 60000);
|
|
48
|
-
|
|
53
|
+
const sum = summaries.find(x => x.id === s.id);
|
|
54
|
+
const summaryText = sum?.summary ? ` | Summary: ${sum.summary}` : "";
|
|
55
|
+
return `[${s.id.slice(0, 8)}] "${s.label || "(no topic)"}" (${ago}min ago${summaryText})`;
|
|
49
56
|
})
|
|
50
57
|
.join("\n");
|
|
51
|
-
const
|
|
58
|
+
const memoryBlock = memories.length
|
|
59
|
+
? `\nUser context:\n${memories.slice(0, 10).map(m => `- ${m.content}`).join("\n")}\n`
|
|
60
|
+
: "";
|
|
61
|
+
const prompt = `You are a message dispatcher. Route the user's message to the correct conversation, or decide to create a new one, or handle a management request.
|
|
62
|
+
${memoryBlock}
|
|
63
|
+
Active conversations:
|
|
64
|
+
${sessionList}
|
|
65
|
+
|
|
66
|
+
User message: "${text.slice(0, 300)}"
|
|
67
|
+
|
|
68
|
+
Reply with ONLY one of:
|
|
69
|
+
- An 8-char session ID to route to
|
|
70
|
+
- "new" to create a new conversation
|
|
71
|
+
|
|
72
|
+
No explanation.`;
|
|
52
73
|
const result = await this._callClassifier(prompt);
|
|
53
|
-
const cleaned = result.trim()
|
|
54
|
-
if (cleaned === "new") {
|
|
74
|
+
const cleaned = result.trim();
|
|
75
|
+
if (cleaned.toLowerCase() === "new") {
|
|
55
76
|
return { action: "create", label: text.slice(0, 50) };
|
|
56
77
|
}
|
|
57
78
|
// Match against active sessions (first 8 chars of ID)
|
|
58
|
-
const match = sessions.find(s => s.id.slice(0, 8) === cleaned);
|
|
79
|
+
const match = sessions.find(s => s.id.slice(0, 8) === cleaned.toLowerCase());
|
|
59
80
|
if (match) {
|
|
60
81
|
return { action: "route", subSessionId: match.id };
|
|
61
82
|
}
|
|
62
|
-
// Fallback:
|
|
83
|
+
// Fallback: route to most recently active
|
|
63
84
|
log.warn("classifier returned unexpected, falling back", { result: cleaned });
|
|
64
85
|
return { action: "route", subSessionId: sessions[0].id };
|
|
65
86
|
}
|
|
66
87
|
catch (err) {
|
|
67
|
-
// Classifier failed — fallback: create new session
|
|
68
88
|
log.warn("classifier error, creating new session", { error: err.message });
|
|
69
89
|
return { action: "create", label: text.slice(0, 50) };
|
|
70
90
|
}
|
|
71
91
|
}
|
|
72
|
-
/** Spawn claude CLI for single-turn classification
|
|
92
|
+
/** Spawn claude CLI for single-turn classification */
|
|
73
93
|
_callClassifier(prompt) {
|
|
74
94
|
return new Promise((resolve, reject) => {
|
|
95
|
+
const budget = this.config.dispatcher_budget ?? this.config.classifier_budget ?? 0.05;
|
|
75
96
|
const args = ["-p", prompt, "--output-format", "stream-json", "--max-turns", "1"];
|
|
76
|
-
if (
|
|
77
|
-
args.push("--max-budget-usd", String(
|
|
97
|
+
if (budget)
|
|
98
|
+
args.push("--max-budget-usd", String(budget));
|
|
78
99
|
if (this.config.classifier_model)
|
|
79
100
|
args.push("--model", this.config.classifier_model);
|
|
80
101
|
const env = { ...process.env };
|
|
81
|
-
// Use the first available endpoint for the classifier model
|
|
82
102
|
if (this.rotator.count) {
|
|
83
103
|
const ep = this.rotator.next();
|
|
84
104
|
if (!this.config.classifier_model && ep.model)
|
|
@@ -123,3 +143,5 @@ export class SessionRouter {
|
|
|
123
143
|
});
|
|
124
144
|
}
|
|
125
145
|
}
|
|
146
|
+
// Backward compatibility alias
|
|
147
|
+
export { Dispatcher as SessionRouter };
|
package/dist/core/schema.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ declare const SessionConfigSchema: z.ZodObject<{
|
|
|
16
16
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
17
17
|
max_per_user: z.ZodDefault<z.ZodNumber>;
|
|
18
18
|
idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
|
|
19
|
-
|
|
19
|
+
dispatcher_budget: z.ZodDefault<z.ZodNumber>;
|
|
20
20
|
classifier_model: z.ZodDefault<z.ZodString>;
|
|
21
21
|
}, z.core.$strip>;
|
|
22
22
|
declare const AgentConfigSchema: z.ZodObject<{
|
|
@@ -40,7 +40,7 @@ declare const AgentConfigSchema: z.ZodObject<{
|
|
|
40
40
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
41
41
|
max_per_user: z.ZodDefault<z.ZodNumber>;
|
|
42
42
|
idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
|
|
43
|
-
|
|
43
|
+
dispatcher_budget: z.ZodDefault<z.ZodNumber>;
|
|
44
44
|
classifier_model: z.ZodDefault<z.ZodString>;
|
|
45
45
|
}, z.core.$strip>>;
|
|
46
46
|
}, z.core.$strip>;
|
|
@@ -108,7 +108,7 @@ export declare const ConfigSchema: z.ZodObject<{
|
|
|
108
108
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
109
109
|
max_per_user: z.ZodDefault<z.ZodNumber>;
|
|
110
110
|
idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
|
|
111
|
-
|
|
111
|
+
dispatcher_budget: z.ZodDefault<z.ZodNumber>;
|
|
112
112
|
classifier_model: z.ZodDefault<z.ZodString>;
|
|
113
113
|
}, z.core.$strip>>;
|
|
114
114
|
}, z.core.$strip>>;
|
package/dist/core/schema.js
CHANGED
|
@@ -16,7 +16,7 @@ const SessionConfigSchema = z.object({
|
|
|
16
16
|
enabled: z.boolean().default(true),
|
|
17
17
|
max_per_user: z.number().int().positive().default(3),
|
|
18
18
|
idle_timeout_minutes: z.number().positive().default(30),
|
|
19
|
-
|
|
19
|
+
dispatcher_budget: z.number().nonnegative().default(0.05),
|
|
20
20
|
classifier_model: z.string().default(""),
|
|
21
21
|
});
|
|
22
22
|
const AgentConfigSchema = z.object({
|
package/dist/core/session.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface SubSession {
|
|
|
7
7
|
chatId: string;
|
|
8
8
|
claudeSessionId: string | null;
|
|
9
9
|
label: string;
|
|
10
|
+
summary: string;
|
|
10
11
|
status: "active" | "idle" | "expired" | "closed";
|
|
11
12
|
createdAt: number;
|
|
12
13
|
lastActiveAt: number;
|
|
@@ -47,4 +48,13 @@ export declare class SessionManager {
|
|
|
47
48
|
isUsable(session: SubSession): boolean;
|
|
48
49
|
/** Get all sub-sessions for a user (all statuses) */
|
|
49
50
|
getAll(userId: string): SubSession[];
|
|
51
|
+
/** Update the summary of a sub-session */
|
|
52
|
+
updateSummary(sessionId: string, summary: string): void;
|
|
53
|
+
/** Get summaries of active sub-sessions for dispatcher context */
|
|
54
|
+
getSummaries(userId: string, platform: string): {
|
|
55
|
+
id: string;
|
|
56
|
+
label: string;
|
|
57
|
+
summary: string;
|
|
58
|
+
lastActiveAt: number;
|
|
59
|
+
}[];
|
|
50
60
|
}
|
package/dist/core/session.js
CHANGED
|
@@ -10,6 +10,7 @@ function toSubSession(row) {
|
|
|
10
10
|
chatId: row.chat_id,
|
|
11
11
|
claudeSessionId: row.claude_session_id ?? null,
|
|
12
12
|
label: row.label,
|
|
13
|
+
summary: row.summary ?? "",
|
|
13
14
|
status: row.status,
|
|
14
15
|
createdAt: row.created_at,
|
|
15
16
|
lastActiveAt: row.last_active_at,
|
|
@@ -97,4 +98,14 @@ export class SessionManager {
|
|
|
97
98
|
getAll(userId) {
|
|
98
99
|
return this.store.getAllSubSessions(userId).map(toSubSession);
|
|
99
100
|
}
|
|
101
|
+
/** Update the summary of a sub-session */
|
|
102
|
+
updateSummary(sessionId, summary) {
|
|
103
|
+
this.store.updateSubSessionSummary(sessionId, summary);
|
|
104
|
+
}
|
|
105
|
+
/** Get summaries of active sub-sessions for dispatcher context */
|
|
106
|
+
getSummaries(userId, platform) {
|
|
107
|
+
return this.store.getSubSessionSummaries(userId, platform).map(r => ({
|
|
108
|
+
id: r.id, label: r.label, summary: r.summary, lastActiveAt: r.last_active_at,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
100
111
|
}
|
package/dist/core/store.d.ts
CHANGED
|
@@ -145,4 +145,11 @@ export declare class Store {
|
|
|
145
145
|
message_count: number;
|
|
146
146
|
total_cost: number;
|
|
147
147
|
}[];
|
|
148
|
+
updateSubSessionSummary(id: string, summary: string): void;
|
|
149
|
+
getSubSessionSummaries(userId: string, platform: string): {
|
|
150
|
+
id: string;
|
|
151
|
+
label: string;
|
|
152
|
+
summary: string;
|
|
153
|
+
last_active_at: number;
|
|
154
|
+
}[];
|
|
148
155
|
}
|
package/dist/core/store.js
CHANGED
|
@@ -81,7 +81,7 @@ export class Store {
|
|
|
81
81
|
PRIMARY KEY (platform_msg_id, chat_id)
|
|
82
82
|
);
|
|
83
83
|
`);
|
|
84
|
-
// Schema migration: add parent_id, result, and
|
|
84
|
+
// Schema migration: add parent_id, result, scheduled_at, and sub_session summary columns
|
|
85
85
|
try {
|
|
86
86
|
this.db.exec("ALTER TABLE tasks ADD COLUMN parent_id INTEGER");
|
|
87
87
|
}
|
|
@@ -94,6 +94,10 @@ export class Store {
|
|
|
94
94
|
this.db.exec("ALTER TABLE tasks ADD COLUMN scheduled_at INTEGER");
|
|
95
95
|
}
|
|
96
96
|
catch { }
|
|
97
|
+
try {
|
|
98
|
+
this.db.exec("ALTER TABLE sub_sessions ADD COLUMN summary TEXT DEFAULT ''");
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
97
101
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id)");
|
|
98
102
|
// Startup recovery: reset orphaned 'running' tasks back to 'auto' so they get re-executed
|
|
99
103
|
const orphaned = this.db.prepare("SELECT id, description FROM tasks WHERE status = 'running'").all();
|
|
@@ -309,4 +313,10 @@ export class Store {
|
|
|
309
313
|
getAllSubSessions(userId) {
|
|
310
314
|
return this.db.prepare("SELECT * FROM sub_sessions WHERE user_id = ? ORDER BY last_active_at DESC").all(userId);
|
|
311
315
|
}
|
|
316
|
+
updateSubSessionSummary(id, summary) {
|
|
317
|
+
this.db.prepare("UPDATE sub_sessions SET summary = ? WHERE id = ?").run(summary, id);
|
|
318
|
+
}
|
|
319
|
+
getSubSessionSummaries(userId, platform) {
|
|
320
|
+
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
|
+
}
|
|
312
322
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emqo/claudebridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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": {
|