@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,203 @@
|
|
|
1
|
+
import { ChromaClient } from 'chromadb';
|
|
2
|
+
import type { Collection } from 'chromadb';
|
|
3
|
+
import type { AgentMessage } from '../types/index.js';
|
|
4
|
+
|
|
5
|
+
interface VectorEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
message: AgentMessage;
|
|
8
|
+
embedding?: number[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class VectorStore {
|
|
12
|
+
private client: ChromaClient | null = null;
|
|
13
|
+
private collection: Collection | null = null;
|
|
14
|
+
private isInitialized = false;
|
|
15
|
+
private baseDir: string;
|
|
16
|
+
|
|
17
|
+
constructor(baseDir: string = './data/vector-db') {
|
|
18
|
+
this.baseDir = baseDir;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 初始化向量数据库
|
|
23
|
+
*/
|
|
24
|
+
async initialize(): Promise<void> {
|
|
25
|
+
if (this.isInitialized) return;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
this.client = new ChromaClient({
|
|
29
|
+
path: 'http://localhost:8000'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 尝试获取或创建 collection
|
|
33
|
+
try {
|
|
34
|
+
this.collection = await this.client.getOrCreateCollection({
|
|
35
|
+
name: 'agent-context'
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
// Collection 可能不存在,创建新的
|
|
39
|
+
this.collection = await this.client.createCollection({
|
|
40
|
+
name: 'agent-context'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.isInitialized = true;
|
|
45
|
+
console.log('[VectorStore] Initialized successfully');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn('[VectorStore] Failed to initialize (ChromaDB not running?):', error);
|
|
48
|
+
this.isInitialized = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 添加消息到向量存储
|
|
54
|
+
*/
|
|
55
|
+
async addMessage(spaceId: string, message: AgentMessage): Promise<void> {
|
|
56
|
+
if (!this.isInitialized || !this.collection) {
|
|
57
|
+
console.warn('[VectorStore] Not initialized, skipping add');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const entry: VectorEntry = {
|
|
62
|
+
id: `${spaceId}_${message.id}`,
|
|
63
|
+
message
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// 简单文本向量化(使用消息内容)
|
|
68
|
+
const text = this.messageToText(message);
|
|
69
|
+
|
|
70
|
+
await this.collection.add({
|
|
71
|
+
ids: [entry.id],
|
|
72
|
+
documents: [text],
|
|
73
|
+
metadatas: [{
|
|
74
|
+
space_id: spaceId,
|
|
75
|
+
message_id: message.id,
|
|
76
|
+
timestamp: message.timestamp,
|
|
77
|
+
source: message.source,
|
|
78
|
+
type: message.type
|
|
79
|
+
}]
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log('[VectorStore] Added message:', entry.id);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('[VectorStore] Failed to add message:', error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 搜索相似上下文
|
|
90
|
+
*/
|
|
91
|
+
async searchSimilar(spaceId: string, query: string, limit: number = 5): Promise<AgentMessage[]> {
|
|
92
|
+
if (!this.isInitialized || !this.collection) {
|
|
93
|
+
console.warn('[VectorStore] Not initialized, returning empty');
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const results = await this.collection.query({
|
|
99
|
+
queryTexts: [query],
|
|
100
|
+
nResults: limit,
|
|
101
|
+
where: { space_id: spaceId }
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const messages: AgentMessage[] = [];
|
|
105
|
+
if (results.documents && results.documents[0]) {
|
|
106
|
+
for (let i = 0; i < results.documents[0].length; i++) {
|
|
107
|
+
const metadata = results.metadatas?.[0]?.[i];
|
|
108
|
+
if (metadata?.message_id) {
|
|
109
|
+
// 这里返回元数据,实际使用时可以从文件/内存中获取完整消息
|
|
110
|
+
messages.push({
|
|
111
|
+
id: metadata.message_id as string,
|
|
112
|
+
type: 'event',
|
|
113
|
+
source: metadata.source as string,
|
|
114
|
+
target: 'search',
|
|
115
|
+
correlation_id: '',
|
|
116
|
+
timestamp: metadata.timestamp as number,
|
|
117
|
+
payload: {
|
|
118
|
+
task: results.documents[0][i] || ''
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return messages;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('[VectorStore] Search failed:', error);
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 获取空间的向量数量
|
|
134
|
+
*/
|
|
135
|
+
async getCount(spaceId: string): Promise<number> {
|
|
136
|
+
if (!this.isInitialized || !this.collection) {
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const results = await this.collection.get({
|
|
142
|
+
where: { space_id: spaceId }
|
|
143
|
+
});
|
|
144
|
+
return results.ids?.length || 0;
|
|
145
|
+
} catch {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 删除空间的向量
|
|
152
|
+
*/
|
|
153
|
+
async deleteSpace(spaceId: string): Promise<void> {
|
|
154
|
+
if (!this.isInitialized || !this.collection) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// 获取该 space 的所有 ID
|
|
160
|
+
const results = await this.collection.get({
|
|
161
|
+
where: { space_id: spaceId }
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (results.ids && results.ids.length > 0) {
|
|
165
|
+
await this.collection.delete({
|
|
166
|
+
ids: results.ids
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[VectorStore] Delete space failed:', error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 将消息转换为可向量化的文本
|
|
176
|
+
*/
|
|
177
|
+
private messageToText(message: AgentMessage): string {
|
|
178
|
+
const parts: string[] = [];
|
|
179
|
+
|
|
180
|
+
if (message.payload?.task) {
|
|
181
|
+
parts.push(`Task: ${message.payload.task}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (message.result) {
|
|
185
|
+
parts.push(`Result: ${JSON.stringify(message.result)}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (message.error) {
|
|
189
|
+
parts.push(`Error: ${message.error.message}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return parts.join('\n') || JSON.stringify(message);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 检查是否可用
|
|
197
|
+
*/
|
|
198
|
+
isAvailable(): boolean {
|
|
199
|
+
return this.isInitialized;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const vectorStore = new VectorStore();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import cors from '@fastify/cors';
|
|
3
|
+
import { apiRoutes } from './routes/api.js';
|
|
4
|
+
import { websocketRoutes } from './routes/websocket.js';
|
|
5
|
+
import { eventBus } from './core/event-bus.js';
|
|
6
|
+
import { router } from './core/router.js';
|
|
7
|
+
import { agentRegistry } from './core/agent-registry.js';
|
|
8
|
+
import { vectorStore } from './core/vector-store.js';
|
|
9
|
+
import { loadConfig, getRoutingRules, getDefaultAgent } from './core/config.js';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
|
|
14
|
+
// 初始化 Fastify
|
|
15
|
+
const fastify = Fastify({
|
|
16
|
+
logger: {
|
|
17
|
+
level: config.logLevel
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 注册 CORS
|
|
22
|
+
await fastify.register(cors, {
|
|
23
|
+
origin: true
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 注册路由
|
|
27
|
+
await fastify.register(apiRoutes);
|
|
28
|
+
await fastify.register(websocketRoutes);
|
|
29
|
+
|
|
30
|
+
// 初始化组件
|
|
31
|
+
const rules = getRoutingRules();
|
|
32
|
+
const defaultAgent = getDefaultAgent();
|
|
33
|
+
router.configure(rules, defaultAgent);
|
|
34
|
+
|
|
35
|
+
// 初始化 Agent Registry
|
|
36
|
+
agentRegistry.initialize();
|
|
37
|
+
|
|
38
|
+
// 注册所有 Agent 到 Router
|
|
39
|
+
agentRegistry.getAgents().forEach(agent => {
|
|
40
|
+
router.registerAgent(agent);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 初始化向量存储(异步)
|
|
44
|
+
vectorStore.initialize().catch(err => {
|
|
45
|
+
console.warn('[VectorStore] Init failed:', err);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 启动服务器
|
|
49
|
+
try {
|
|
50
|
+
await fastify.listen({
|
|
51
|
+
port: config.port,
|
|
52
|
+
host: config.host
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(`
|
|
56
|
+
╔═══════════════════════════════════════════════════╗
|
|
57
|
+
║ Agent Gateway Server ║
|
|
58
|
+
╠═══════════════════════════════════════════════════╣
|
|
59
|
+
║ HTTP Server: http://${config.host}:${config.port} ║
|
|
60
|
+
║ WebSocket: ws://${config.host}:${config.port}/ws ║
|
|
61
|
+
║ SSE: http://${config.host}:${config.port}/events ║
|
|
62
|
+
║ Health: http://${config.host}:${config.port}/health ║
|
|
63
|
+
║ Tasks: http://${config.host}:${config.port}/tasks ║
|
|
64
|
+
║ Spaces: http://${config.host}:${config.port}/spaces ║
|
|
65
|
+
║ Agents: http://${config.host}:${config.port}/agents ║
|
|
66
|
+
╚═══════════════════════════════════════════════════╝
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
// 订阅事件日志
|
|
70
|
+
eventBus.getEventTypes().forEach(eventType => {
|
|
71
|
+
eventBus.subscribe(eventType, (event) => {
|
|
72
|
+
console.log(`[Event] ${event.type}:`, {
|
|
73
|
+
id: event.data.id,
|
|
74
|
+
correlation_id: event.data.correlation_id
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
fastify.log.error(error);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import type { AgentMessage, Agent } from '../types/index.js';
|
|
4
|
+
import { taskManager } from '../core/task-manager.js';
|
|
5
|
+
import { router } from '../core/router.js';
|
|
6
|
+
import { contextManager } from '../core/context-manager.js';
|
|
7
|
+
|
|
8
|
+
export async function apiRoutes(fastify: FastifyInstance) {
|
|
9
|
+
// 健康检查
|
|
10
|
+
fastify.get('/health', async (_request: FastifyRequest, _reply: FastifyReply) => {
|
|
11
|
+
return {
|
|
12
|
+
status: 'ok',
|
|
13
|
+
timestamp: Date.now(),
|
|
14
|
+
agents: router.getAllAgents().map(a => ({
|
|
15
|
+
id: a.id,
|
|
16
|
+
name: a.name,
|
|
17
|
+
status: a.status
|
|
18
|
+
}))
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 提交任务
|
|
23
|
+
fastify.post<{ Body: Partial<AgentMessage> }>(
|
|
24
|
+
'/tasks',
|
|
25
|
+
async (request: FastifyRequest<{ Body: Partial<AgentMessage> }>, reply: FastifyReply) => {
|
|
26
|
+
const body = request.body || {};
|
|
27
|
+
|
|
28
|
+
const message: AgentMessage = {
|
|
29
|
+
id: uuidv4(),
|
|
30
|
+
type: 'request',
|
|
31
|
+
source: body.source || 'api',
|
|
32
|
+
target: 'gateway',
|
|
33
|
+
correlation_id: body.correlation_id || uuidv4(),
|
|
34
|
+
timestamp: Date.now(),
|
|
35
|
+
payload: body.payload
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const task = await taskManager.processTask(message);
|
|
40
|
+
reply.code(202).send({
|
|
41
|
+
task_id: task.id,
|
|
42
|
+
status: task.status,
|
|
43
|
+
message: 'Task submitted successfully'
|
|
44
|
+
});
|
|
45
|
+
} catch (error: unknown) {
|
|
46
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
47
|
+
reply.code(500).send({
|
|
48
|
+
error: {
|
|
49
|
+
code: 'TASK_FAILED',
|
|
50
|
+
message: errorMessage
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// 获取任务状态
|
|
58
|
+
fastify.get<{ Params: { taskId: string } }>(
|
|
59
|
+
'/tasks/:taskId',
|
|
60
|
+
async (request: FastifyRequest<{ Params: { taskId: string } }>, reply: FastifyReply) => {
|
|
61
|
+
const { taskId } = request.params;
|
|
62
|
+
const task = taskManager.getTask(taskId);
|
|
63
|
+
|
|
64
|
+
if (!task) {
|
|
65
|
+
reply.code(404).send({
|
|
66
|
+
error: {
|
|
67
|
+
code: 'TASK_NOT_FOUND',
|
|
68
|
+
message: `Task ${taskId} not found`
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
reply.send({
|
|
75
|
+
id: task.id,
|
|
76
|
+
status: task.status,
|
|
77
|
+
assigned_agents: task.assignedAgents,
|
|
78
|
+
result: task.result,
|
|
79
|
+
error: task.error,
|
|
80
|
+
created_at: task.createdAt,
|
|
81
|
+
updated_at: task.updatedAt
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// 获取所有任务
|
|
87
|
+
fastify.get('/tasks', async (_request: FastifyRequest, reply: FastifyReply) => {
|
|
88
|
+
const tasks = taskManager.getAllTasks();
|
|
89
|
+
reply.send(tasks.map(t => ({
|
|
90
|
+
id: t.id,
|
|
91
|
+
status: t.status,
|
|
92
|
+
assigned_agents: t.assignedAgents,
|
|
93
|
+
created_at: t.createdAt
|
|
94
|
+
})));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 创建共享空间
|
|
98
|
+
fastify.post<{ Body: { metadata?: Record<string, unknown> } }>(
|
|
99
|
+
'/spaces',
|
|
100
|
+
async (request: FastifyRequest<{ Body: { metadata?: Record<string, unknown> } }>, reply: FastifyReply) => {
|
|
101
|
+
const { metadata } = request.body || {};
|
|
102
|
+
const spaceId = await contextManager.createSharedSpace(metadata);
|
|
103
|
+
reply.code(201).send({ space_id: spaceId });
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// 获取共享空间
|
|
108
|
+
fastify.get<{ Params: { spaceId: string } }>(
|
|
109
|
+
'/spaces/:spaceId',
|
|
110
|
+
async (request: FastifyRequest<{ Params: { spaceId: string } }>, reply: FastifyReply) => {
|
|
111
|
+
const { spaceId } = request.params;
|
|
112
|
+
const context = await contextManager.getSharedSpace(spaceId);
|
|
113
|
+
|
|
114
|
+
if (!context) {
|
|
115
|
+
reply.code(404).send({
|
|
116
|
+
error: {
|
|
117
|
+
code: 'SPACE_NOT_FOUND',
|
|
118
|
+
message: `Shared space ${spaceId} not found`
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
reply.send({
|
|
125
|
+
shared_space_id: context.shared_space_id,
|
|
126
|
+
message_count: context.messages.length,
|
|
127
|
+
artifact_count: context.artifacts.size,
|
|
128
|
+
metadata: context.metadata,
|
|
129
|
+
created_at: context.createdAt,
|
|
130
|
+
updated_at: context.updatedAt
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// 获取 Agent 列表
|
|
136
|
+
fastify.get('/agents', async (_request: FastifyRequest, reply: FastifyReply) => {
|
|
137
|
+
const agents = router.getAllAgents();
|
|
138
|
+
reply.send(agents);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// 注册 Agent
|
|
142
|
+
fastify.post<{ Body: Partial<Agent> }>(
|
|
143
|
+
'/agents',
|
|
144
|
+
async (request: FastifyRequest<{ Body: Partial<Agent> }>, reply: FastifyReply) => {
|
|
145
|
+
const body = request.body || {};
|
|
146
|
+
router.registerAgent({
|
|
147
|
+
id: body.id || 'unknown',
|
|
148
|
+
name: body.name || 'Unknown',
|
|
149
|
+
type: body.type || 'process',
|
|
150
|
+
capabilities: body.capabilities || [],
|
|
151
|
+
status: 'online',
|
|
152
|
+
endpoint: body.endpoint,
|
|
153
|
+
lastSeen: Date.now()
|
|
154
|
+
});
|
|
155
|
+
reply.code(201).send({ id: body.id, status: 'registered' });
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import type { AgentMessage } from '../types/index.js';
|
|
4
|
+
import { taskManager } from '../core/task-manager.js';
|
|
5
|
+
import { agentRegistry } from '../core/agent-registry.js';
|
|
6
|
+
|
|
7
|
+
interface SSEClient {
|
|
8
|
+
id: string;
|
|
9
|
+
reply: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function websocketRoutes(fastify: FastifyInstance) {
|
|
13
|
+
const clients: Map<string, SSEClient> = new Map();
|
|
14
|
+
|
|
15
|
+
// SSE 端点 - 用于实时任务更新
|
|
16
|
+
fastify.get<{ Querystring: { taskId?: string; spaceId?: string } }>(
|
|
17
|
+
'/events',
|
|
18
|
+
async (request, reply) => {
|
|
19
|
+
const { taskId, spaceId } = request.query;
|
|
20
|
+
|
|
21
|
+
reply.raw.writeHead(200, {
|
|
22
|
+
'Content-Type': 'text/event-stream',
|
|
23
|
+
'Cache-Control': 'no-cache',
|
|
24
|
+
'Connection': 'keep-alive'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const clientId = uuidv4();
|
|
28
|
+
|
|
29
|
+
clients.set(clientId, {
|
|
30
|
+
id: clientId,
|
|
31
|
+
reply
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 发送初始连接消息
|
|
35
|
+
reply.raw.write(`data: ${JSON.stringify({ type: 'connected', client_id: clientId })}\n\n`);
|
|
36
|
+
|
|
37
|
+
// 订阅任务事件
|
|
38
|
+
if (taskId) {
|
|
39
|
+
const task = taskManager.getTask(taskId);
|
|
40
|
+
if (task) {
|
|
41
|
+
reply.raw.write(`data: ${JSON.stringify({ type: 'task_status', task })}\n\n`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 发送欢迎消息
|
|
46
|
+
reply.raw.write(`data: ${JSON.stringify({ type: 'welcome', agents: agentRegistry.getAgents() })}\n\n`);
|
|
47
|
+
|
|
48
|
+
// 心跳
|
|
49
|
+
const heartbeat = setInterval(() => {
|
|
50
|
+
reply.raw.write(`data: ${JSON.stringify({ type: 'heartbeat', timestamp: Date.now() })}\n\n`);
|
|
51
|
+
}, 30000);
|
|
52
|
+
|
|
53
|
+
request.raw.on('close', () => {
|
|
54
|
+
clearInterval(heartbeat);
|
|
55
|
+
clients.delete(clientId);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// WebSocket 模拟端点 - 返回 SSE 连接信息
|
|
61
|
+
fastify.get('/ws-info', async (_request, reply) => {
|
|
62
|
+
reply.send({
|
|
63
|
+
message: 'Use /events for Server-Sent Events streaming',
|
|
64
|
+
endpoints: {
|
|
65
|
+
events: '/events?taskId=<task_id>&spaceId=<space_id>',
|
|
66
|
+
description: 'Subscribe to real-time task updates and agent responses'
|
|
67
|
+
},
|
|
68
|
+
example: 'curl -N http://localhost:3000/events?taskId=<task_id>'
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 广播消息到所有客户端
|
|
73
|
+
fastify.post<{ Body: { type: string; data: any } }>(
|
|
74
|
+
'/broadcast',
|
|
75
|
+
async (request, reply) => {
|
|
76
|
+
const { type, data } = request.body || {};
|
|
77
|
+
|
|
78
|
+
const message = JSON.stringify({ type, data, timestamp: Date.now() });
|
|
79
|
+
|
|
80
|
+
for (const client of clients.values()) {
|
|
81
|
+
try {
|
|
82
|
+
client.reply.raw.write(`data: ${message}\n\n`);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// 客户端可能已断开
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
reply.send({ delivered: clients.size });
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Agent Gateway 通信协议类型定义
|
|
2
|
+
|
|
3
|
+
export type MessageType = 'request' | 'response' | 'event' | 'stream' | 'stream_end';
|
|
4
|
+
export type EventType =
|
|
5
|
+
| 'agent.registered'
|
|
6
|
+
| 'agent.unregistered'
|
|
7
|
+
| 'task.submitted'
|
|
8
|
+
| 'task.assigned'
|
|
9
|
+
| 'task.started'
|
|
10
|
+
| 'task.progress'
|
|
11
|
+
| 'task.completed'
|
|
12
|
+
| 'task.failed'
|
|
13
|
+
| 'context.updated';
|
|
14
|
+
|
|
15
|
+
export interface ContextRef {
|
|
16
|
+
shared_space_id: string;
|
|
17
|
+
history?: string[];
|
|
18
|
+
artifacts?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Payload {
|
|
22
|
+
task?: string;
|
|
23
|
+
context?: ContextRef;
|
|
24
|
+
files?: string[];
|
|
25
|
+
options?: {
|
|
26
|
+
stream?: boolean;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Error {
|
|
32
|
+
code: string;
|
|
33
|
+
message: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AgentMessage {
|
|
37
|
+
id: string;
|
|
38
|
+
type: MessageType;
|
|
39
|
+
source: string;
|
|
40
|
+
target: string;
|
|
41
|
+
correlation_id: string;
|
|
42
|
+
timestamp: number;
|
|
43
|
+
payload?: Payload;
|
|
44
|
+
event_type?: EventType;
|
|
45
|
+
event_data?: Record<string, unknown>;
|
|
46
|
+
result?: unknown;
|
|
47
|
+
error?: Error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Agent {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
type: 'claude-code' | 'openclaw' | 'process' | 'http';
|
|
54
|
+
capabilities: string[];
|
|
55
|
+
status: 'online' | 'offline' | 'busy';
|
|
56
|
+
endpoint?: string;
|
|
57
|
+
lastSeen: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Task {
|
|
61
|
+
id: string;
|
|
62
|
+
status: 'pending' | 'assigned' | 'running' | 'completed' | 'failed';
|
|
63
|
+
request: AgentMessage;
|
|
64
|
+
assignedAgents: string[];
|
|
65
|
+
result?: unknown;
|
|
66
|
+
error?: Error;
|
|
67
|
+
createdAt: number;
|
|
68
|
+
updatedAt: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface RoutingRule {
|
|
72
|
+
name: string;
|
|
73
|
+
keywords: string[];
|
|
74
|
+
agent?: string;
|
|
75
|
+
strategy?: 'direct' | 'broadcast';
|
|
76
|
+
agents?: string[];
|
|
77
|
+
priority: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface GatewayConfig {
|
|
81
|
+
port: number;
|
|
82
|
+
wsPort: number;
|
|
83
|
+
host: string;
|
|
84
|
+
dataDir: string;
|
|
85
|
+
logDir: string;
|
|
86
|
+
routing: {
|
|
87
|
+
rules: RoutingRule[];
|
|
88
|
+
defaultAgent?: string;
|
|
89
|
+
};
|
|
90
|
+
}
|