@starlink-awaken/agentmesh 1.0.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/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +243 -0
- package/README.zh-CN.md +243 -0
- package/config/gateway.yaml +190 -0
- package/docs/api.md +566 -0
- package/package.json +74 -0
- package/src/adapters/base.ts +34 -0
- package/src/adapters/claude-code.ts +122 -0
- package/src/adapters/openclaw.ts +120 -0
- package/src/adapters/process.ts +136 -0
- package/src/cli.ts +284 -0
- package/src/core/agent-registry.ts +334 -0
- package/src/core/config.ts +148 -0
- package/src/core/context-manager.ts +210 -0
- package/src/core/event-bus.ts +76 -0
- package/src/core/metrics.ts +216 -0
- package/src/core/router.ts +105 -0
- package/src/core/task-manager.ts +248 -0
- package/src/core/vector-store.ts +203 -0
- package/src/index.ts +84 -0
- package/src/routes/api.ts +158 -0
- package/src/routes/websocket.ts +91 -0
- package/src/types/index.ts +90 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { ProcessAdapter } from '../adapters/process.js';
|
|
2
|
+
import { ClaudeCodeAdapter } from '../adapters/claude-code.js';
|
|
3
|
+
import { OpenClawAdapter } from '../adapters/openclaw.js';
|
|
4
|
+
import type { AgentAdapter } from '../adapters/base.js';
|
|
5
|
+
import type { Agent } from '../types/index.js';
|
|
6
|
+
import { getAllAgentConfigs } from './config.js';
|
|
7
|
+
|
|
8
|
+
// 默认 Agent 配置
|
|
9
|
+
const DEFAULT_AGENT_CONFIGS: Record<string, {
|
|
10
|
+
name: string;
|
|
11
|
+
capabilities: string[];
|
|
12
|
+
command: string;
|
|
13
|
+
args?: string[];
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
}> = {
|
|
16
|
+
'claude-code': {
|
|
17
|
+
name: 'Claude Code',
|
|
18
|
+
capabilities: ['code-generation', 'code-review', 'debugging', 'refactoring', 'documentation', 'file-operations'],
|
|
19
|
+
command: 'claude',
|
|
20
|
+
args: ['-p']
|
|
21
|
+
},
|
|
22
|
+
'openclaw': {
|
|
23
|
+
name: 'OpenClaw',
|
|
24
|
+
capabilities: ['browser-automation', 'web-scraping', 'form-filling', 'ui-testing'],
|
|
25
|
+
command: 'openclaw',
|
|
26
|
+
args: ['--task']
|
|
27
|
+
},
|
|
28
|
+
'opencode': {
|
|
29
|
+
name: 'OpenCode',
|
|
30
|
+
capabilities: ['code-completion', 'code-generation', 'refactoring', 'debugging'],
|
|
31
|
+
command: 'opencode',
|
|
32
|
+
args: ['--task']
|
|
33
|
+
},
|
|
34
|
+
'gemini': {
|
|
35
|
+
name: 'Google Gemini CLI',
|
|
36
|
+
capabilities: ['code-generation', 'multimodal', 'reasoning', 'analysis'],
|
|
37
|
+
command: 'gemini',
|
|
38
|
+
args: ['--prompt']
|
|
39
|
+
},
|
|
40
|
+
'codex': {
|
|
41
|
+
name: 'OpenAI Codex',
|
|
42
|
+
capabilities: ['code-generation', 'code-explanation', 'refactoring'],
|
|
43
|
+
command: 'codex',
|
|
44
|
+
args: ['complete']
|
|
45
|
+
},
|
|
46
|
+
'github-copilot': {
|
|
47
|
+
name: 'GitHub Copilot',
|
|
48
|
+
capabilities: ['code-completion', 'code-suggestions', 'refactoring'],
|
|
49
|
+
command: 'copilot',
|
|
50
|
+
args: ['--ask']
|
|
51
|
+
},
|
|
52
|
+
'qwen-code': {
|
|
53
|
+
name: 'Qwen Code',
|
|
54
|
+
capabilities: ['code-generation', 'code-review', 'multilingual'],
|
|
55
|
+
command: 'qwen-code',
|
|
56
|
+
args: ['--task']
|
|
57
|
+
},
|
|
58
|
+
'crush': {
|
|
59
|
+
name: 'CRUSH AI',
|
|
60
|
+
capabilities: ['code-generation', 'debugging', 'security-analysis'],
|
|
61
|
+
command: 'crush',
|
|
62
|
+
args: ['run']
|
|
63
|
+
},
|
|
64
|
+
'droid': {
|
|
65
|
+
name: 'Droid Agent',
|
|
66
|
+
capabilities: ['android-development', 'mobile-debugging', 'device-control'],
|
|
67
|
+
command: 'droid',
|
|
68
|
+
args: ['--task']
|
|
69
|
+
},
|
|
70
|
+
'factory': {
|
|
71
|
+
name: 'Factory AI',
|
|
72
|
+
capabilities: ['code-generation', 'testing', 'documentation', 'refactoring'],
|
|
73
|
+
command: 'factory',
|
|
74
|
+
args: ['--task']
|
|
75
|
+
},
|
|
76
|
+
'cursor': {
|
|
77
|
+
name: 'Cursor',
|
|
78
|
+
capabilities: ['code-completion', 'code-generation', 'refactoring', 'chat'],
|
|
79
|
+
command: 'cursor',
|
|
80
|
+
args: ['--task']
|
|
81
|
+
},
|
|
82
|
+
'windsurf': {
|
|
83
|
+
name: 'Windsurf',
|
|
84
|
+
capabilities: ['code-generation', 'agentic-coding', 'flow-state'],
|
|
85
|
+
command: 'windsurf',
|
|
86
|
+
args: ['--task']
|
|
87
|
+
},
|
|
88
|
+
'zed': {
|
|
89
|
+
name: 'Zed AI',
|
|
90
|
+
capabilities: ['code-generation', 'collaboration', 'high-performance'],
|
|
91
|
+
command: 'zed',
|
|
92
|
+
args: ['--ai-task']
|
|
93
|
+
},
|
|
94
|
+
'aider': {
|
|
95
|
+
name: 'Aider',
|
|
96
|
+
capabilities: ['git-based-editing', 'code-refactoring', 'multi-file-changes'],
|
|
97
|
+
command: 'aider',
|
|
98
|
+
args: ['--message']
|
|
99
|
+
},
|
|
100
|
+
'cline': {
|
|
101
|
+
name: 'Cline',
|
|
102
|
+
capabilities: ['autonomous-coding', 'file-operations', 'command-execution'],
|
|
103
|
+
command: 'cline',
|
|
104
|
+
args: ['--task']
|
|
105
|
+
},
|
|
106
|
+
'roo-code': {
|
|
107
|
+
name: 'Roo Code',
|
|
108
|
+
capabilities: ['code-generation', 'agentic-mode', 'workspace-awareness'],
|
|
109
|
+
command: 'roo-code',
|
|
110
|
+
args: ['--task']
|
|
111
|
+
},
|
|
112
|
+
// 2026 新增 Agent
|
|
113
|
+
'perplexity': {
|
|
114
|
+
name: 'Perplexity',
|
|
115
|
+
capabilities: ['research', 'web-search', 'fact-checking', 'analysis'],
|
|
116
|
+
command: 'perplexity',
|
|
117
|
+
args: ['--query']
|
|
118
|
+
},
|
|
119
|
+
'grok': {
|
|
120
|
+
name: 'xAI Grok',
|
|
121
|
+
capabilities: ['reasoning', 'humor', 'code-generation', 'analysis'],
|
|
122
|
+
command: 'grok',
|
|
123
|
+
args: ['--prompt']
|
|
124
|
+
},
|
|
125
|
+
'phind': {
|
|
126
|
+
name: 'Phind',
|
|
127
|
+
capabilities: ['developer-search', 'code-search', 'documentation-search'],
|
|
128
|
+
command: 'phind',
|
|
129
|
+
args: ['--search']
|
|
130
|
+
},
|
|
131
|
+
'you': {
|
|
132
|
+
name: 'You.com AI',
|
|
133
|
+
capabilities: ['web-search', 'code-search', 'general-assistant'],
|
|
134
|
+
command: 'you',
|
|
135
|
+
args: ['--query']
|
|
136
|
+
},
|
|
137
|
+
'lepton': {
|
|
138
|
+
name: 'Lepton AI',
|
|
139
|
+
capabilities: ['code-generation', 'conversation', 'analysis'],
|
|
140
|
+
command: 'lepton',
|
|
141
|
+
args: ['--prompt']
|
|
142
|
+
},
|
|
143
|
+
'ollama': {
|
|
144
|
+
name: 'Ollama',
|
|
145
|
+
capabilities: ['local-llm', 'code-generation', 'privacy-focused'],
|
|
146
|
+
command: 'ollama',
|
|
147
|
+
args: ['run']
|
|
148
|
+
},
|
|
149
|
+
'llama': {
|
|
150
|
+
name: 'Meta Llama',
|
|
151
|
+
capabilities: ['code-generation', 'reasoning', 'open-source'],
|
|
152
|
+
command: 'llama',
|
|
153
|
+
args: ['--prompt']
|
|
154
|
+
},
|
|
155
|
+
'mistral': {
|
|
156
|
+
name: 'Mistral AI',
|
|
157
|
+
capabilities: ['code-generation', 'reasoning', 'multilingual'],
|
|
158
|
+
command: 'mistral',
|
|
159
|
+
args: ['--task']
|
|
160
|
+
},
|
|
161
|
+
'anthropic': {
|
|
162
|
+
name: 'Anthropic CLI',
|
|
163
|
+
capabilities: ['conversation', 'reasoning', 'code-generation'],
|
|
164
|
+
command: 'anthropic',
|
|
165
|
+
args: ['--prompt']
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export class AgentRegistry {
|
|
170
|
+
private adapters: Map<string, AgentAdapter> = new Map();
|
|
171
|
+
private initialized = false;
|
|
172
|
+
|
|
173
|
+
constructor() {
|
|
174
|
+
// 延迟初始化
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 初始化注册表
|
|
179
|
+
*/
|
|
180
|
+
initialize(): void {
|
|
181
|
+
if (this.initialized) return;
|
|
182
|
+
|
|
183
|
+
// 注册内置适配器
|
|
184
|
+
this.register(new ClaudeCodeAdapter());
|
|
185
|
+
this.register(new OpenClawAdapter());
|
|
186
|
+
|
|
187
|
+
// 从配置加载所有 Agent
|
|
188
|
+
this.registerAllFromConfig();
|
|
189
|
+
|
|
190
|
+
this.initialized = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 从配置注册所有 Agent
|
|
195
|
+
*/
|
|
196
|
+
private registerAllFromConfig(): void {
|
|
197
|
+
const configAgents = getAllAgentConfigs();
|
|
198
|
+
|
|
199
|
+
// 1. 先注册配置文件中的 Agent
|
|
200
|
+
for (const config of configAgents) {
|
|
201
|
+
if (this.adapters.has(config.id)) continue;
|
|
202
|
+
|
|
203
|
+
if (config.type === 'claude-code' || config.type === 'openclaw') {
|
|
204
|
+
// 跳过,内置适配器已注册
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (config.type === 'process' && config.command) {
|
|
209
|
+
const adapter = new ProcessAdapter(
|
|
210
|
+
config.id,
|
|
211
|
+
config.name,
|
|
212
|
+
config.capabilities,
|
|
213
|
+
{
|
|
214
|
+
command: config.command,
|
|
215
|
+
args: config.args,
|
|
216
|
+
env: config.env
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
this.adapters.set(config.id, adapter);
|
|
220
|
+
console.log(`[AgentRegistry] Registered from config: ${config.id}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 2. 再注册默认 Agent(未被配置覆盖的)
|
|
225
|
+
for (const [id, defaultConfig] of Object.entries(DEFAULT_AGENT_CONFIGS)) {
|
|
226
|
+
if (this.adapters.has(id)) continue;
|
|
227
|
+
|
|
228
|
+
const adapter = new ProcessAdapter(
|
|
229
|
+
id,
|
|
230
|
+
defaultConfig.name,
|
|
231
|
+
defaultConfig.capabilities,
|
|
232
|
+
{
|
|
233
|
+
command: defaultConfig.command,
|
|
234
|
+
args: defaultConfig.args,
|
|
235
|
+
env: defaultConfig.env
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
this.adapters.set(id, adapter);
|
|
240
|
+
console.log(`[AgentRegistry] Registered default: ${id}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 注册自定义适配器
|
|
246
|
+
*/
|
|
247
|
+
register(adapter: AgentAdapter): void {
|
|
248
|
+
this.adapters.set(adapter.id, adapter);
|
|
249
|
+
console.log(`[AgentRegistry] Registered adapter: ${adapter.id}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 获取适配器
|
|
254
|
+
*/
|
|
255
|
+
get(agentId: string): AgentAdapter | undefined {
|
|
256
|
+
return this.adapters.get(agentId);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 获取所有适配器
|
|
261
|
+
*/
|
|
262
|
+
getAll(): AgentAdapter[] {
|
|
263
|
+
return Array.from(this.adapters.values());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 获取所有 Agent 信息
|
|
268
|
+
*/
|
|
269
|
+
getAgents(): Agent[] {
|
|
270
|
+
return this.getAll().map(adapter => ({
|
|
271
|
+
id: adapter.id,
|
|
272
|
+
name: adapter.name,
|
|
273
|
+
type: adapter.type as Agent['type'],
|
|
274
|
+
capabilities: adapter.capabilities,
|
|
275
|
+
status: 'online' as const,
|
|
276
|
+
lastSeen: Date.now()
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 根据能力查找 Agent
|
|
282
|
+
*/
|
|
283
|
+
findByCapability(capability: string): Agent[] {
|
|
284
|
+
return this.getAll()
|
|
285
|
+
.filter(adapter => adapter.capabilities.includes(capability))
|
|
286
|
+
.map(adapter => ({
|
|
287
|
+
id: adapter.id,
|
|
288
|
+
name: adapter.name,
|
|
289
|
+
type: adapter.type as Agent['type'],
|
|
290
|
+
capabilities: adapter.capabilities,
|
|
291
|
+
status: 'online' as const,
|
|
292
|
+
lastSeen: Date.now()
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 检查适配器是否存在
|
|
298
|
+
*/
|
|
299
|
+
has(agentId: string): boolean {
|
|
300
|
+
return this.adapters.has(agentId);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 检查适配器是否健康
|
|
305
|
+
*/
|
|
306
|
+
async checkHealth(agentId: string): Promise<boolean> {
|
|
307
|
+
const adapter = this.adapters.get(agentId);
|
|
308
|
+
if (!adapter) return false;
|
|
309
|
+
return adapter.health();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 获取可用 Agent 列表(健康检查)
|
|
314
|
+
*/
|
|
315
|
+
async getAvailableAgents(): Promise<Agent[]> {
|
|
316
|
+
const results: Agent[] = [];
|
|
317
|
+
|
|
318
|
+
for (const adapter of this.adapters.values()) {
|
|
319
|
+
const isHealthy = await adapter.health().catch(() => false);
|
|
320
|
+
results.push({
|
|
321
|
+
id: adapter.id,
|
|
322
|
+
name: adapter.name,
|
|
323
|
+
type: adapter.type as Agent['type'],
|
|
324
|
+
capabilities: adapter.capabilities,
|
|
325
|
+
status: isHealthy ? 'online' as const : 'offline' as const,
|
|
326
|
+
lastSeen: Date.now()
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return results;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const agentRegistry = new AgentRegistry();
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export interface AgentConfig {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
type: 'claude-code' | 'openclaw' | 'process' | 'http';
|
|
9
|
+
command?: string;
|
|
10
|
+
args?: string[];
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
endpoint?: string;
|
|
13
|
+
capabilities: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RoutingRule {
|
|
17
|
+
name: string;
|
|
18
|
+
keywords: string[];
|
|
19
|
+
agent?: string;
|
|
20
|
+
strategy?: 'direct' | 'broadcast';
|
|
21
|
+
agents?: string[];
|
|
22
|
+
priority: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GatewayConfig {
|
|
26
|
+
port: number;
|
|
27
|
+
wsPort: number;
|
|
28
|
+
host: string;
|
|
29
|
+
dataDir: string;
|
|
30
|
+
logDir: string;
|
|
31
|
+
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
|
32
|
+
routing: {
|
|
33
|
+
defaultAgent?: string;
|
|
34
|
+
rules: RoutingRule[];
|
|
35
|
+
};
|
|
36
|
+
agents: AgentConfig[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DEFAULT_CONFIG: GatewayConfig = {
|
|
40
|
+
port: 3000,
|
|
41
|
+
wsPort: 3001,
|
|
42
|
+
host: '0.0.0.0',
|
|
43
|
+
dataDir: './data',
|
|
44
|
+
logDir: './logs',
|
|
45
|
+
logLevel: 'info',
|
|
46
|
+
routing: {
|
|
47
|
+
defaultAgent: 'claude-code',
|
|
48
|
+
rules: []
|
|
49
|
+
},
|
|
50
|
+
agents: []
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
let cachedConfig: GatewayConfig | null = null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 加载配置文件
|
|
57
|
+
*/
|
|
58
|
+
export function loadConfig(configPath?: string): GatewayConfig {
|
|
59
|
+
if (cachedConfig) {
|
|
60
|
+
return cachedConfig;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const paths = configPath
|
|
64
|
+
? [configPath]
|
|
65
|
+
: [
|
|
66
|
+
'./config/gateway.yaml',
|
|
67
|
+
'./config/gateway.yml',
|
|
68
|
+
join(process.cwd(), 'config/gateway.yaml'),
|
|
69
|
+
join(process.cwd(), 'config/gateway.yml')
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const path of paths) {
|
|
73
|
+
try {
|
|
74
|
+
if (existsSync(path)) {
|
|
75
|
+
console.log(`[Config] Loading from: ${path}`);
|
|
76
|
+
const content = readFileSync(path, 'utf-8');
|
|
77
|
+
const parsed = parse(content) as Partial<GatewayConfig>;
|
|
78
|
+
|
|
79
|
+
// 合并配置
|
|
80
|
+
cachedConfig = {
|
|
81
|
+
...DEFAULT_CONFIG,
|
|
82
|
+
...parsed,
|
|
83
|
+
routing: {
|
|
84
|
+
...DEFAULT_CONFIG.routing,
|
|
85
|
+
...parsed.routing,
|
|
86
|
+
rules: parsed.routing?.rules || DEFAULT_CONFIG.routing.rules
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
console.log(`[Config] Loaded successfully`);
|
|
91
|
+
return cachedConfig;
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn(`[Config] Failed to load ${path}:`, error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('[Config] Using default configuration');
|
|
99
|
+
cachedConfig = { ...DEFAULT_CONFIG };
|
|
100
|
+
return cachedConfig;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 重新加载配置
|
|
105
|
+
*/
|
|
106
|
+
export function reloadConfig(configPath?: string): GatewayConfig {
|
|
107
|
+
cachedConfig = null;
|
|
108
|
+
return loadConfig(configPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 获取配置
|
|
113
|
+
*/
|
|
114
|
+
export function getConfig(): GatewayConfig {
|
|
115
|
+
if (!cachedConfig) {
|
|
116
|
+
return loadConfig();
|
|
117
|
+
}
|
|
118
|
+
return cachedConfig;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取 Agent 配置
|
|
123
|
+
*/
|
|
124
|
+
export function getAgentConfig(agentId: string): AgentConfig | undefined {
|
|
125
|
+
const config = getConfig();
|
|
126
|
+
return config.agents.find(a => a.id === agentId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 获取所有 Agent 配置
|
|
131
|
+
*/
|
|
132
|
+
export function getAllAgentConfigs(): AgentConfig[] {
|
|
133
|
+
return getConfig().agents;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 获取路由规则
|
|
138
|
+
*/
|
|
139
|
+
export function getRoutingRules(): RoutingRule[] {
|
|
140
|
+
return getConfig().routing.rules;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 获取默认 Agent
|
|
145
|
+
*/
|
|
146
|
+
export function getDefaultAgent(): string | undefined {
|
|
147
|
+
return getConfig().routing.defaultAgent;
|
|
148
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
import type { AgentMessage, ContextRef } from '../types/index.js';
|
|
5
|
+
import { vectorStore } from './vector-store.js';
|
|
6
|
+
|
|
7
|
+
interface ContextData {
|
|
8
|
+
shared_space_id: string;
|
|
9
|
+
messages: AgentMessage[];
|
|
10
|
+
artifacts: Map<string, string>;
|
|
11
|
+
metadata: Record<string, unknown>;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ContextManager {
|
|
17
|
+
private memoryCache: Map<string, ContextData> = new Map();
|
|
18
|
+
private baseDir: string;
|
|
19
|
+
|
|
20
|
+
constructor(baseDir: string = './data/tasks') {
|
|
21
|
+
this.baseDir = baseDir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 创建共享空间
|
|
26
|
+
*/
|
|
27
|
+
async createSharedSpace(metadata: Record<string, unknown> = {}): Promise<string> {
|
|
28
|
+
const spaceId = uuidv4();
|
|
29
|
+
const context: ContextData = {
|
|
30
|
+
shared_space_id: spaceId,
|
|
31
|
+
messages: [],
|
|
32
|
+
artifacts: new Map(),
|
|
33
|
+
metadata,
|
|
34
|
+
createdAt: Date.now(),
|
|
35
|
+
updatedAt: Date.now()
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// L1: 内存缓存
|
|
39
|
+
this.memoryCache.set(spaceId, context);
|
|
40
|
+
|
|
41
|
+
// L2: 文件系统持久化
|
|
42
|
+
await this.persistToFile(spaceId, context);
|
|
43
|
+
|
|
44
|
+
return spaceId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取共享空间
|
|
49
|
+
*/
|
|
50
|
+
async getSharedSpace(spaceId: string): Promise<ContextData | null> {
|
|
51
|
+
// L1: 先从内存获取
|
|
52
|
+
if (this.memoryCache.has(spaceId)) {
|
|
53
|
+
return this.memoryCache.get(spaceId)!;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// L2: 从文件系统加载
|
|
57
|
+
try {
|
|
58
|
+
const context = await this.loadFromFile(spaceId);
|
|
59
|
+
if (context) {
|
|
60
|
+
this.memoryCache.set(spaceId, context);
|
|
61
|
+
return context;
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`[ContextManager] Failed to load context: ${spaceId}`, error);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 添加消息到共享空间
|
|
72
|
+
*/
|
|
73
|
+
async addMessage(spaceId: string, message: AgentMessage): Promise<void> {
|
|
74
|
+
const context = await this.getSharedSpace(spaceId);
|
|
75
|
+
if (!context) {
|
|
76
|
+
throw new Error(`Shared space not found: ${spaceId}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
context.messages.push(message);
|
|
80
|
+
context.updatedAt = Date.now();
|
|
81
|
+
|
|
82
|
+
// 更新内存缓存
|
|
83
|
+
this.memoryCache.set(spaceId, context);
|
|
84
|
+
|
|
85
|
+
// 持久化到文件系统
|
|
86
|
+
await this.persistToFile(spaceId, context);
|
|
87
|
+
|
|
88
|
+
// L3: 添加到向量数据库(异步,不阻塞)
|
|
89
|
+
if (vectorStore.isAvailable()) {
|
|
90
|
+
vectorStore.addMessage(spaceId, message).catch(err => {
|
|
91
|
+
console.error('[ContextManager] Vector store add failed:', err);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 获取消息历史
|
|
98
|
+
*/
|
|
99
|
+
async getMessages(spaceId: string, limit?: number): Promise<AgentMessage[]> {
|
|
100
|
+
const context = await this.getSharedSpace(spaceId);
|
|
101
|
+
if (!context) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const messages = context.messages;
|
|
106
|
+
return limit ? messages.slice(-limit) : messages;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 添加产物文件
|
|
111
|
+
*/
|
|
112
|
+
async addArtifact(spaceId: string, filename: string, content: string): Promise<string> {
|
|
113
|
+
const context = await this.getSharedSpace(spaceId);
|
|
114
|
+
if (!context) {
|
|
115
|
+
throw new Error(`Shared space not found: ${spaceId}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const artifactsDir = join(this.baseDir, spaceId, 'artifacts');
|
|
119
|
+
await fs.mkdir(artifactsDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
const filePath = join(artifactsDir, filename);
|
|
122
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
123
|
+
|
|
124
|
+
context.artifacts.set(filename, filePath);
|
|
125
|
+
context.updatedAt = Date.now();
|
|
126
|
+
|
|
127
|
+
// 更新内存缓存
|
|
128
|
+
this.memoryCache.set(spaceId, context);
|
|
129
|
+
|
|
130
|
+
// 持久化
|
|
131
|
+
await this.persistToFile(spaceId, context);
|
|
132
|
+
|
|
133
|
+
return filePath;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 获取产物文件
|
|
138
|
+
*/
|
|
139
|
+
async getArtifact(spaceId: string, filename: string): Promise<string | null> {
|
|
140
|
+
const context = await this.getSharedSpace(spaceId);
|
|
141
|
+
if (!context) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const filePath = context.artifacts.get(filename);
|
|
146
|
+
if (!filePath) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 持久化到文件系统
|
|
159
|
+
*/
|
|
160
|
+
private async persistToFile(spaceId: string, context: ContextData): Promise<void> {
|
|
161
|
+
const dir = join(this.baseDir, spaceId);
|
|
162
|
+
await fs.mkdir(dir, { recursive: true });
|
|
163
|
+
|
|
164
|
+
// 序列化 context(Map 需要转换)
|
|
165
|
+
const serialized = {
|
|
166
|
+
shared_space_id: context.shared_space_id,
|
|
167
|
+
messages: context.messages,
|
|
168
|
+
artifacts: Object.fromEntries(context.artifacts),
|
|
169
|
+
metadata: context.metadata,
|
|
170
|
+
createdAt: context.createdAt,
|
|
171
|
+
updatedAt: context.updatedAt
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
await fs.writeFile(
|
|
175
|
+
join(dir, 'context.json'),
|
|
176
|
+
JSON.stringify(serialized, null, 2),
|
|
177
|
+
'utf-8'
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 从文件系统加载
|
|
183
|
+
*/
|
|
184
|
+
private async loadFromFile(spaceId: string): Promise<ContextData | null> {
|
|
185
|
+
const filePath = join(this.baseDir, spaceId, 'context.json');
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
189
|
+
const parsed = JSON.parse(content);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
...parsed,
|
|
193
|
+
artifacts: new Map(Object.entries(parsed.artifacts || {}))
|
|
194
|
+
};
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 创建 ContextRef
|
|
202
|
+
*/
|
|
203
|
+
async createContextRef(spaceId: string): Promise<ContextRef> {
|
|
204
|
+
return {
|
|
205
|
+
shared_space_id: spaceId
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const contextManager = new ContextManager();
|