@animalabs/agent-framework 0.1.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/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/src/agent.d.ts +130 -0
- package/dist/src/agent.d.ts.map +1 -0
- package/dist/src/agent.js +406 -0
- package/dist/src/agent.js.map +1 -0
- package/dist/src/api/mcp-server.d.ts +37 -0
- package/dist/src/api/mcp-server.d.ts.map +1 -0
- package/dist/src/api/mcp-server.js +598 -0
- package/dist/src/api/mcp-server.js.map +1 -0
- package/dist/src/api/server.d.ts +83 -0
- package/dist/src/api/server.d.ts.map +1 -0
- package/dist/src/api/server.js +785 -0
- package/dist/src/api/server.js.map +1 -0
- package/dist/src/api/types.d.ts +395 -0
- package/dist/src/api/types.d.ts.map +1 -0
- package/dist/src/api/types.js +5 -0
- package/dist/src/api/types.js.map +1 -0
- package/dist/src/framework.d.ts +309 -0
- package/dist/src/framework.d.ts.map +1 -0
- package/dist/src/framework.js +2309 -0
- package/dist/src/framework.js.map +1 -0
- package/dist/src/gate/event-gate.d.ts +75 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -0
- package/dist/src/gate/event-gate.js +503 -0
- package/dist/src/gate/event-gate.js.map +1 -0
- package/dist/src/gate/index.d.ts +3 -0
- package/dist/src/gate/index.d.ts.map +1 -0
- package/dist/src/gate/index.js +2 -0
- package/dist/src/gate/index.js.map +1 -0
- package/dist/src/gate/types.d.ts +82 -0
- package/dist/src/gate/types.d.ts.map +1 -0
- package/dist/src/gate/types.js +9 -0
- package/dist/src/gate/types.js.map +1 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +141 -0
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -0
- package/dist/src/mcpl/channel-registry.js +647 -0
- package/dist/src/mcpl/channel-registry.js.map +1 -0
- package/dist/src/mcpl/checkpoint-manager.d.ts +79 -0
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -0
- package/dist/src/mcpl/checkpoint-manager.js +430 -0
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -0
- package/dist/src/mcpl/errors.d.ts +52 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -0
- package/dist/src/mcpl/errors.js +93 -0
- package/dist/src/mcpl/errors.js.map +1 -0
- package/dist/src/mcpl/feature-set-manager.d.ts +91 -0
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -0
- package/dist/src/mcpl/feature-set-manager.js +250 -0
- package/dist/src/mcpl/feature-set-manager.js.map +1 -0
- package/dist/src/mcpl/hook-orchestrator.d.ts +51 -0
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -0
- package/dist/src/mcpl/hook-orchestrator.js +217 -0
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -0
- package/dist/src/mcpl/index.d.ts +14 -0
- package/dist/src/mcpl/index.d.ts.map +1 -0
- package/dist/src/mcpl/index.js +21 -0
- package/dist/src/mcpl/index.js.map +1 -0
- package/dist/src/mcpl/inference-router.d.ts +67 -0
- package/dist/src/mcpl/inference-router.d.ts.map +1 -0
- package/dist/src/mcpl/inference-router.js +249 -0
- package/dist/src/mcpl/inference-router.js.map +1 -0
- package/dist/src/mcpl/push-handler.d.ts +60 -0
- package/dist/src/mcpl/push-handler.d.ts.map +1 -0
- package/dist/src/mcpl/push-handler.js +162 -0
- package/dist/src/mcpl/push-handler.js.map +1 -0
- package/dist/src/mcpl/scope-manager.d.ts +76 -0
- package/dist/src/mcpl/scope-manager.d.ts.map +1 -0
- package/dist/src/mcpl/scope-manager.js +213 -0
- package/dist/src/mcpl/scope-manager.js.map +1 -0
- package/dist/src/mcpl/server-connection.d.ts +174 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -0
- package/dist/src/mcpl/server-connection.js +559 -0
- package/dist/src/mcpl/server-connection.js.map +1 -0
- package/dist/src/mcpl/server-registry.d.ts +63 -0
- package/dist/src/mcpl/server-registry.d.ts.map +1 -0
- package/dist/src/mcpl/server-registry.js +119 -0
- package/dist/src/mcpl/server-registry.js.map +1 -0
- package/dist/src/mcpl/types.d.ts +755 -0
- package/dist/src/mcpl/types.d.ts.map +1 -0
- package/dist/src/mcpl/types.js +48 -0
- package/dist/src/mcpl/types.js.map +1 -0
- package/dist/src/module-registry.d.ts +93 -0
- package/dist/src/module-registry.d.ts.map +1 -0
- package/dist/src/module-registry.js +386 -0
- package/dist/src/module-registry.js.map +1 -0
- package/dist/src/modules/api/index.d.ts +22 -0
- package/dist/src/modules/api/index.d.ts.map +1 -0
- package/dist/src/modules/api/index.js +63 -0
- package/dist/src/modules/api/index.js.map +1 -0
- package/dist/src/modules/discord/discord-js-client.d.ts +58 -0
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -0
- package/dist/src/modules/discord/discord-js-client.js +273 -0
- package/dist/src/modules/discord/discord-js-client.js.map +1 -0
- package/dist/src/modules/discord/index.d.ts +82 -0
- package/dist/src/modules/discord/index.d.ts.map +1 -0
- package/dist/src/modules/discord/index.js +959 -0
- package/dist/src/modules/discord/index.js.map +1 -0
- package/dist/src/modules/discord/types.d.ts +327 -0
- package/dist/src/modules/discord/types.d.ts.map +1 -0
- package/dist/src/modules/discord/types.js +5 -0
- package/dist/src/modules/discord/types.js.map +1 -0
- package/dist/src/modules/index.d.ts +11 -0
- package/dist/src/modules/index.d.ts.map +1 -0
- package/dist/src/modules/index.js +7 -0
- package/dist/src/modules/index.js.map +1 -0
- package/dist/src/modules/workspace/index.d.ts +59 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -0
- package/dist/src/modules/workspace/index.js +831 -0
- package/dist/src/modules/workspace/index.js.map +1 -0
- package/dist/src/modules/workspace/sync.d.ts +48 -0
- package/dist/src/modules/workspace/sync.d.ts.map +1 -0
- package/dist/src/modules/workspace/sync.js +253 -0
- package/dist/src/modules/workspace/sync.js.map +1 -0
- package/dist/src/modules/workspace/types.d.ts +166 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -0
- package/dist/src/modules/workspace/types.js +5 -0
- package/dist/src/modules/workspace/types.js.map +1 -0
- package/dist/src/modules/workspace/watcher.d.ts +43 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -0
- package/dist/src/modules/workspace/watcher.js +120 -0
- package/dist/src/modules/workspace/watcher.js.map +1 -0
- package/dist/src/queue.d.ts +50 -0
- package/dist/src/queue.d.ts.map +1 -0
- package/dist/src/queue.js +95 -0
- package/dist/src/queue.js.map +1 -0
- package/dist/src/safe-slice.d.ts +8 -0
- package/dist/src/safe-slice.d.ts.map +1 -0
- package/dist/src/safe-slice.js +18 -0
- package/dist/src/safe-slice.js.map +1 -0
- package/dist/src/types/agent.d.ts +118 -0
- package/dist/src/types/agent.d.ts.map +1 -0
- package/dist/src/types/agent.js +2 -0
- package/dist/src/types/agent.js.map +1 -0
- package/dist/src/types/events.d.ts +220 -0
- package/dist/src/types/events.d.ts.map +1 -0
- package/dist/src/types/events.js +2 -0
- package/dist/src/types/events.js.map +1 -0
- package/dist/src/types/framework.d.ts +184 -0
- package/dist/src/types/framework.d.ts.map +1 -0
- package/dist/src/types/framework.js +2 -0
- package/dist/src/types/framework.js.map +1 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +7 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/inference-log.d.ts +64 -0
- package/dist/src/types/inference-log.d.ts.map +1 -0
- package/dist/src/types/inference-log.js +5 -0
- package/dist/src/types/inference-log.js.map +1 -0
- package/dist/src/types/module.d.ts +302 -0
- package/dist/src/types/module.d.ts.map +1 -0
- package/dist/src/types/module.js +2 -0
- package/dist/src/types/module.js.map +1 -0
- package/dist/src/types/trace.d.ts +173 -0
- package/dist/src/types/trace.d.ts.map +1 -0
- package/dist/src/types/trace.js +2 -0
- package/dist/src/types/trace.js.map +1 -0
- package/dist/test/event-gate.test.d.ts +2 -0
- package/dist/test/event-gate.test.d.ts.map +1 -0
- package/dist/test/event-gate.test.js +535 -0
- package/dist/test/event-gate.test.js.map +1 -0
- package/dist/test/framework.test.d.ts +5 -0
- package/dist/test/framework.test.d.ts.map +1 -0
- package/dist/test/framework.test.js +738 -0
- package/dist/test/framework.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +58 -0
- package/src/agent.ts +512 -0
- package/src/api/mcp-server.ts +687 -0
- package/src/api/server.ts +1023 -0
- package/src/api/types.ts +542 -0
- package/src/framework.ts +2784 -0
- package/src/gate/event-gate.ts +623 -0
- package/src/gate/index.ts +12 -0
- package/src/gate/types.ts +112 -0
- package/src/index.ts +63 -0
- package/src/mcpl/channel-registry.ts +824 -0
- package/src/mcpl/checkpoint-manager.ts +545 -0
- package/src/mcpl/errors.ts +114 -0
- package/src/mcpl/feature-set-manager.ts +333 -0
- package/src/mcpl/hook-orchestrator.ts +281 -0
- package/src/mcpl/index.ts +134 -0
- package/src/mcpl/inference-router.ts +335 -0
- package/src/mcpl/push-handler.ts +232 -0
- package/src/mcpl/scope-manager.ts +262 -0
- package/src/mcpl/server-connection.ts +694 -0
- package/src/mcpl/server-registry.ts +144 -0
- package/src/mcpl/types.ts +991 -0
- package/src/module-registry.ts +547 -0
- package/src/modules/api/index.ts +94 -0
- package/src/modules/discord/discord-js-client.ts +353 -0
- package/src/modules/discord/index.ts +1146 -0
- package/src/modules/discord/types.ts +465 -0
- package/src/modules/index.ts +43 -0
- package/src/modules/workspace/index.ts +969 -0
- package/src/modules/workspace/sync.ts +314 -0
- package/src/modules/workspace/types.ts +202 -0
- package/src/modules/workspace/watcher.ts +142 -0
- package/src/queue.ts +106 -0
- package/src/safe-slice.ts +18 -0
- package/src/types/agent.ts +119 -0
- package/src/types/events.ts +252 -0
- package/src/types/framework.ts +211 -0
- package/src/types/index.ts +6 -0
- package/src/types/inference-log.ts +64 -0
- package/src/types/module.ts +367 -0
- package/src/types/trace.ts +196 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket API Server for the agent framework
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time bidirectional communication:
|
|
5
|
+
* - Commands from clients (send messages, branch, inspect)
|
|
6
|
+
* - Events from framework (inference, tools, speech)
|
|
7
|
+
*/
|
|
8
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
9
|
+
import { createServer } from 'node:http';
|
|
10
|
+
export * from './types.js';
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
port: 8765,
|
|
13
|
+
host: 'localhost',
|
|
14
|
+
path: '/ws',
|
|
15
|
+
enableHttp: true,
|
|
16
|
+
};
|
|
17
|
+
/** Polling interval for subscriptions (ms) */
|
|
18
|
+
const SUBSCRIPTION_POLL_INTERVAL = 50;
|
|
19
|
+
/**
|
|
20
|
+
* API Server for the agent framework.
|
|
21
|
+
*/
|
|
22
|
+
export class ApiServer {
|
|
23
|
+
config;
|
|
24
|
+
framework;
|
|
25
|
+
wss = null;
|
|
26
|
+
httpServer = null;
|
|
27
|
+
clients = new Set();
|
|
28
|
+
currentBranch = 'main';
|
|
29
|
+
/** Map from client to their subscription IDs */
|
|
30
|
+
clientSubscriptions = new Map();
|
|
31
|
+
/** Subscription poll interval timer */
|
|
32
|
+
subscriptionPollInterval = null;
|
|
33
|
+
constructor(framework, config = {}) {
|
|
34
|
+
this.framework = framework;
|
|
35
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Start the API server.
|
|
39
|
+
*/
|
|
40
|
+
async start() {
|
|
41
|
+
// Create HTTP server for both WebSocket upgrade and REST endpoints
|
|
42
|
+
this.httpServer = createServer((req, res) => {
|
|
43
|
+
if (this.config.enableHttp) {
|
|
44
|
+
this.handleHttpRequest(req, res);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
res.writeHead(404);
|
|
48
|
+
res.end();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// Create WebSocket server
|
|
52
|
+
this.wss = new WebSocketServer({
|
|
53
|
+
server: this.httpServer,
|
|
54
|
+
path: this.config.path,
|
|
55
|
+
});
|
|
56
|
+
this.wss.on('connection', (ws) => {
|
|
57
|
+
this.handleConnection(ws);
|
|
58
|
+
});
|
|
59
|
+
// Subscribe to trace events
|
|
60
|
+
this.framework.onTrace((event) => {
|
|
61
|
+
this.handleTraceEvent(event);
|
|
62
|
+
});
|
|
63
|
+
// Start listening
|
|
64
|
+
await new Promise((resolve) => {
|
|
65
|
+
this.httpServer.listen(this.config.port, this.config.host, () => {
|
|
66
|
+
console.log(`API server listening on ws://${this.config.host}:${this.config.port}${this.config.path}`);
|
|
67
|
+
resolve();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
// Start subscription polling
|
|
71
|
+
this.subscriptionPollInterval = setInterval(() => {
|
|
72
|
+
this.pollSubscriptions();
|
|
73
|
+
}, SUBSCRIPTION_POLL_INTERVAL);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Stop the API server.
|
|
77
|
+
*/
|
|
78
|
+
async stop() {
|
|
79
|
+
// Stop subscription polling
|
|
80
|
+
if (this.subscriptionPollInterval) {
|
|
81
|
+
clearInterval(this.subscriptionPollInterval);
|
|
82
|
+
this.subscriptionPollInterval = null;
|
|
83
|
+
}
|
|
84
|
+
// Cleanup all subscriptions
|
|
85
|
+
const store = this.framework.getStore();
|
|
86
|
+
for (const [_, subscriptionIds] of this.clientSubscriptions) {
|
|
87
|
+
for (const subId of subscriptionIds) {
|
|
88
|
+
store.unsubscribe(subId);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
this.clientSubscriptions.clear();
|
|
92
|
+
// Close all client connections
|
|
93
|
+
for (const client of this.clients) {
|
|
94
|
+
client.close();
|
|
95
|
+
}
|
|
96
|
+
this.clients.clear();
|
|
97
|
+
// Close servers
|
|
98
|
+
if (this.wss) {
|
|
99
|
+
this.wss.close();
|
|
100
|
+
this.wss = null;
|
|
101
|
+
}
|
|
102
|
+
if (this.httpServer) {
|
|
103
|
+
await new Promise((resolve) => {
|
|
104
|
+
this.httpServer.close(() => resolve());
|
|
105
|
+
});
|
|
106
|
+
this.httpServer = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the server URL.
|
|
111
|
+
*/
|
|
112
|
+
get url() {
|
|
113
|
+
return `ws://${this.config.host}:${this.config.port}${this.config.path}`;
|
|
114
|
+
}
|
|
115
|
+
// ==========================================================================
|
|
116
|
+
// WebSocket Handling
|
|
117
|
+
// ==========================================================================
|
|
118
|
+
handleConnection(ws) {
|
|
119
|
+
this.clients.add(ws);
|
|
120
|
+
ws.on('message', (data) => {
|
|
121
|
+
try {
|
|
122
|
+
const message = JSON.parse(data.toString());
|
|
123
|
+
this.handleClientMessage(ws, message);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
this.sendResponse(ws, {
|
|
127
|
+
type: 'response',
|
|
128
|
+
success: false,
|
|
129
|
+
error: 'Invalid JSON',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
ws.on('close', () => {
|
|
134
|
+
this.cleanupClientSubscriptions(ws);
|
|
135
|
+
this.clients.delete(ws);
|
|
136
|
+
});
|
|
137
|
+
ws.on('error', (error) => {
|
|
138
|
+
console.error('WebSocket error:', error);
|
|
139
|
+
this.cleanupClientSubscriptions(ws);
|
|
140
|
+
this.clients.delete(ws);
|
|
141
|
+
});
|
|
142
|
+
// Send welcome event
|
|
143
|
+
this.sendEvent(ws, 'connected', {
|
|
144
|
+
currentBranch: this.currentBranch,
|
|
145
|
+
agents: this.framework.getAllAgents().map((a) => a.name),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async handleClientMessage(ws, message) {
|
|
149
|
+
if (message.type !== 'request') {
|
|
150
|
+
this.sendResponse(ws, {
|
|
151
|
+
type: 'response',
|
|
152
|
+
id: message.id,
|
|
153
|
+
success: false,
|
|
154
|
+
error: 'Expected request message',
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const result = await this.executeCommand(ws, message.command, message.params);
|
|
160
|
+
this.sendResponse(ws, {
|
|
161
|
+
type: 'response',
|
|
162
|
+
id: message.id,
|
|
163
|
+
success: true,
|
|
164
|
+
data: result,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
this.sendResponse(ws, {
|
|
169
|
+
type: 'response',
|
|
170
|
+
id: message.id,
|
|
171
|
+
success: false,
|
|
172
|
+
error: error instanceof Error ? error.message : String(error),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
sendResponse(ws, response) {
|
|
177
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
178
|
+
ws.send(JSON.stringify(response));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
sendEvent(ws, event, data) {
|
|
182
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
183
|
+
const message = { type: 'event', event, data };
|
|
184
|
+
ws.send(JSON.stringify(message));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
broadcast(event, data) {
|
|
188
|
+
const message = { type: 'event', event, data };
|
|
189
|
+
const json = JSON.stringify(message);
|
|
190
|
+
for (const client of this.clients) {
|
|
191
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
192
|
+
client.send(json);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ==========================================================================
|
|
197
|
+
// Trace Event Handling
|
|
198
|
+
// ==========================================================================
|
|
199
|
+
handleTraceEvent(event) {
|
|
200
|
+
// Skip process:received — clients don't need raw input events
|
|
201
|
+
if (event.type === 'process:received') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Broadcast all other trace events directly — no translation needed
|
|
205
|
+
// TraceEvents are already serializable (error is string, not Error)
|
|
206
|
+
this.broadcast(event.type, event);
|
|
207
|
+
}
|
|
208
|
+
// ==========================================================================
|
|
209
|
+
// Command Execution
|
|
210
|
+
// ==========================================================================
|
|
211
|
+
async executeCommand(ws, command, params) {
|
|
212
|
+
switch (command) {
|
|
213
|
+
// Conversation
|
|
214
|
+
case 'message.send':
|
|
215
|
+
return this.cmdMessageSend(params);
|
|
216
|
+
case 'message.list':
|
|
217
|
+
return this.cmdMessageList(params);
|
|
218
|
+
case 'inference.request':
|
|
219
|
+
return this.cmdInferenceRequest(params);
|
|
220
|
+
case 'inference.abort':
|
|
221
|
+
return this.cmdInferenceAbort(params);
|
|
222
|
+
// Branching
|
|
223
|
+
case 'branch.list':
|
|
224
|
+
return this.cmdBranchList();
|
|
225
|
+
case 'branch.create':
|
|
226
|
+
return this.cmdBranchCreate(params);
|
|
227
|
+
case 'branch.switch':
|
|
228
|
+
return this.cmdBranchSwitch(params);
|
|
229
|
+
case 'branch.current':
|
|
230
|
+
return this.cmdBranchCurrent();
|
|
231
|
+
case 'branch.delete':
|
|
232
|
+
return this.cmdBranchDelete(params);
|
|
233
|
+
// Undo/Redo
|
|
234
|
+
case 'undo':
|
|
235
|
+
return this.cmdUndo(params);
|
|
236
|
+
case 'redo':
|
|
237
|
+
return this.cmdRedo(params);
|
|
238
|
+
case 'undo.state':
|
|
239
|
+
return this.cmdUndoState(params);
|
|
240
|
+
// Inspection
|
|
241
|
+
case 'agent.list':
|
|
242
|
+
return this.cmdAgentList();
|
|
243
|
+
case 'agent.context':
|
|
244
|
+
return this.cmdAgentContext(params);
|
|
245
|
+
case 'module.list':
|
|
246
|
+
return this.cmdModuleList();
|
|
247
|
+
case 'module.state':
|
|
248
|
+
return this.cmdModuleState(params);
|
|
249
|
+
case 'store.states':
|
|
250
|
+
return this.cmdStoreStates(params);
|
|
251
|
+
case 'store.inspect':
|
|
252
|
+
return this.cmdStoreInspect(params);
|
|
253
|
+
case 'store.search':
|
|
254
|
+
return this.cmdStoreSearch(params);
|
|
255
|
+
// Subscriptions
|
|
256
|
+
case 'store.subscribe':
|
|
257
|
+
return this.cmdStoreSubscribe(ws, params);
|
|
258
|
+
case 'store.unsubscribe':
|
|
259
|
+
return this.cmdStoreUnsubscribe(ws, params);
|
|
260
|
+
// Inference logs
|
|
261
|
+
case 'inference.tail':
|
|
262
|
+
return this.cmdInferenceTail(params);
|
|
263
|
+
case 'inference.inspect':
|
|
264
|
+
return this.cmdInferenceInspect(params);
|
|
265
|
+
case 'inference.search':
|
|
266
|
+
return this.cmdInferenceSearch(params);
|
|
267
|
+
// Event logs
|
|
268
|
+
case 'events.tail':
|
|
269
|
+
return this.cmdEventsTail(params);
|
|
270
|
+
case 'events.inspect':
|
|
271
|
+
return this.cmdEventsInspect(params);
|
|
272
|
+
case 'events.search':
|
|
273
|
+
return this.cmdEventsSearch(params);
|
|
274
|
+
case 'events.subscribe':
|
|
275
|
+
return this.cmdEventsSubscribe(params);
|
|
276
|
+
default:
|
|
277
|
+
throw new Error(`Unknown command: ${command}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// ==========================================================================
|
|
281
|
+
// Conversation Commands
|
|
282
|
+
// ==========================================================================
|
|
283
|
+
async cmdMessageSend(params) {
|
|
284
|
+
if (!params.participant || !params.content) {
|
|
285
|
+
throw new Error('participant and content are required');
|
|
286
|
+
}
|
|
287
|
+
// Push api:message event for the ApiModule to handle
|
|
288
|
+
this.framework.pushEvent({
|
|
289
|
+
type: 'api:message',
|
|
290
|
+
participant: params.participant,
|
|
291
|
+
content: params.content,
|
|
292
|
+
triggerInference: params.triggerInference ?? true,
|
|
293
|
+
targetAgents: params.targetAgents,
|
|
294
|
+
});
|
|
295
|
+
return { messageId: 'pending' }; // TODO: return actual ID
|
|
296
|
+
}
|
|
297
|
+
async cmdMessageList(params) {
|
|
298
|
+
// Get messages from first agent's context manager
|
|
299
|
+
const agent = this.framework.getAllAgents()[0];
|
|
300
|
+
if (!agent) {
|
|
301
|
+
return { messages: [] };
|
|
302
|
+
}
|
|
303
|
+
const contextManager = agent.getContextManager();
|
|
304
|
+
const { messages } = await contextManager.compile();
|
|
305
|
+
const limit = params?.limit ?? 50;
|
|
306
|
+
const offset = params?.offset ?? 0;
|
|
307
|
+
return {
|
|
308
|
+
messages: messages.slice(-(limit + offset), offset ? -offset : undefined),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async cmdInferenceRequest(params) {
|
|
312
|
+
// Push api:inference-request event for the ApiModule to handle
|
|
313
|
+
this.framework.pushEvent({
|
|
314
|
+
type: 'api:inference-request',
|
|
315
|
+
agentName: params?.agentName,
|
|
316
|
+
reason: params?.reason,
|
|
317
|
+
});
|
|
318
|
+
return { queued: true };
|
|
319
|
+
}
|
|
320
|
+
async cmdInferenceAbort(params) {
|
|
321
|
+
if (!params?.agentName) {
|
|
322
|
+
throw new Error('agentName is required');
|
|
323
|
+
}
|
|
324
|
+
const aborted = this.framework.abortInference(params.agentName, params.reason);
|
|
325
|
+
return { aborted };
|
|
326
|
+
}
|
|
327
|
+
// ==========================================================================
|
|
328
|
+
// Branch Commands
|
|
329
|
+
// ==========================================================================
|
|
330
|
+
async cmdBranchList() {
|
|
331
|
+
const store = this.framework.getStore();
|
|
332
|
+
const branches = store.listBranches();
|
|
333
|
+
return {
|
|
334
|
+
branches: branches.map((branch) => ({
|
|
335
|
+
name: branch.name,
|
|
336
|
+
isCurrent: branch.name === this.currentBranch,
|
|
337
|
+
})),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async cmdBranchCreate(params) {
|
|
341
|
+
if (!params.name) {
|
|
342
|
+
throw new Error('name is required');
|
|
343
|
+
}
|
|
344
|
+
const store = this.framework.getStore();
|
|
345
|
+
store.createBranch(params.name);
|
|
346
|
+
this.broadcast('branch:created', {
|
|
347
|
+
name: params.name,
|
|
348
|
+
parent: this.currentBranch,
|
|
349
|
+
});
|
|
350
|
+
if (params.switchTo) {
|
|
351
|
+
await this.cmdBranchSwitch({ name: params.name });
|
|
352
|
+
}
|
|
353
|
+
return { name: params.name };
|
|
354
|
+
}
|
|
355
|
+
async cmdBranchSwitch(params) {
|
|
356
|
+
if (!params.name) {
|
|
357
|
+
throw new Error('name is required');
|
|
358
|
+
}
|
|
359
|
+
const store = this.framework.getStore();
|
|
360
|
+
const previousBranch = this.currentBranch;
|
|
361
|
+
store.switchBranch(params.name);
|
|
362
|
+
this.currentBranch = params.name;
|
|
363
|
+
// Materialize config files from the new branch
|
|
364
|
+
const ws = this.framework.getModule('workspace');
|
|
365
|
+
if (ws && 'materializeMount' in ws) {
|
|
366
|
+
await ws.materializeMount('_config');
|
|
367
|
+
}
|
|
368
|
+
this.broadcast('branch:switched', {
|
|
369
|
+
from: previousBranch,
|
|
370
|
+
to: params.name,
|
|
371
|
+
});
|
|
372
|
+
return { switched: true };
|
|
373
|
+
}
|
|
374
|
+
async cmdBranchCurrent() {
|
|
375
|
+
return { name: this.currentBranch };
|
|
376
|
+
}
|
|
377
|
+
async cmdBranchDelete(params) {
|
|
378
|
+
if (!params.name) {
|
|
379
|
+
throw new Error('name is required');
|
|
380
|
+
}
|
|
381
|
+
if (params.name === this.currentBranch) {
|
|
382
|
+
throw new Error('Cannot delete current branch');
|
|
383
|
+
}
|
|
384
|
+
if (params.name === 'main') {
|
|
385
|
+
throw new Error('Cannot delete main branch');
|
|
386
|
+
}
|
|
387
|
+
const store = this.framework.getStore();
|
|
388
|
+
store.deleteBranch(params.name);
|
|
389
|
+
this.broadcast('branch:deleted', { name: params.name });
|
|
390
|
+
return { deleted: true };
|
|
391
|
+
}
|
|
392
|
+
// ==========================================================================
|
|
393
|
+
// Undo/Redo Commands
|
|
394
|
+
// ==========================================================================
|
|
395
|
+
cmdUndo(params) {
|
|
396
|
+
if (!params.agentName) {
|
|
397
|
+
throw new Error('agentName is required');
|
|
398
|
+
}
|
|
399
|
+
const result = this.framework.undoLastTurn(params.agentName);
|
|
400
|
+
if (result.undone) {
|
|
401
|
+
this.currentBranch = result.toBranch;
|
|
402
|
+
this.broadcast('branch:switched', {
|
|
403
|
+
from: result.fromBranch,
|
|
404
|
+
to: result.toBranch,
|
|
405
|
+
reason: 'undo',
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
cmdRedo(params) {
|
|
411
|
+
if (!params.agentName) {
|
|
412
|
+
throw new Error('agentName is required');
|
|
413
|
+
}
|
|
414
|
+
const result = this.framework.redo(params.agentName);
|
|
415
|
+
if (result.redone) {
|
|
416
|
+
this.currentBranch = result.toBranch;
|
|
417
|
+
this.broadcast('branch:switched', {
|
|
418
|
+
from: result.fromBranch,
|
|
419
|
+
to: result.toBranch,
|
|
420
|
+
reason: 'redo',
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
cmdUndoState(params) {
|
|
426
|
+
if (!params.agentName) {
|
|
427
|
+
throw new Error('agentName is required');
|
|
428
|
+
}
|
|
429
|
+
return this.framework.getUndoRedoState(params.agentName);
|
|
430
|
+
}
|
|
431
|
+
// ==========================================================================
|
|
432
|
+
// Inspection Commands
|
|
433
|
+
// ==========================================================================
|
|
434
|
+
async cmdAgentList() {
|
|
435
|
+
const agents = this.framework.getAllAgents();
|
|
436
|
+
return {
|
|
437
|
+
agents: agents.map((a) => ({
|
|
438
|
+
name: a.name,
|
|
439
|
+
model: a.model,
|
|
440
|
+
status: a.state.status,
|
|
441
|
+
systemPromptPreview: a.systemPrompt.substring(0, 200) + '...',
|
|
442
|
+
allowedTools: a.allowedTools,
|
|
443
|
+
})),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
async cmdAgentContext(params) {
|
|
447
|
+
if (!params.agentName) {
|
|
448
|
+
throw new Error('agentName is required');
|
|
449
|
+
}
|
|
450
|
+
const agent = this.framework.getAgent(params.agentName);
|
|
451
|
+
if (!agent) {
|
|
452
|
+
throw new Error(`Agent not found: ${params.agentName}`);
|
|
453
|
+
}
|
|
454
|
+
const contextManager = agent.getContextManager();
|
|
455
|
+
const { messages } = await contextManager.compile(params.maxTokens
|
|
456
|
+
? { maxTokens: params.maxTokens, reserveForResponse: 4096 }
|
|
457
|
+
: undefined);
|
|
458
|
+
return { context: messages };
|
|
459
|
+
}
|
|
460
|
+
async cmdModuleList() {
|
|
461
|
+
const modules = this.framework.getAllModules();
|
|
462
|
+
const allTools = this.framework.getAllTools();
|
|
463
|
+
return {
|
|
464
|
+
modules: modules.map((module) => ({
|
|
465
|
+
name: module.name,
|
|
466
|
+
tools: allTools
|
|
467
|
+
.filter((t) => t.name.startsWith(`${module.name}:`))
|
|
468
|
+
.map((t) => t.name),
|
|
469
|
+
})),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
async cmdModuleState(params) {
|
|
473
|
+
if (!params.moduleName) {
|
|
474
|
+
throw new Error('moduleName is required');
|
|
475
|
+
}
|
|
476
|
+
const store = this.framework.getStore();
|
|
477
|
+
const stateId = `modules/${params.moduleName}/state`;
|
|
478
|
+
const state = store.getStateJson(stateId);
|
|
479
|
+
return { state };
|
|
480
|
+
}
|
|
481
|
+
async cmdStoreStates(params) {
|
|
482
|
+
const store = this.framework.getStore();
|
|
483
|
+
let stateInfos = store.listStates();
|
|
484
|
+
// Filter by namespace
|
|
485
|
+
if (params?.namespace) {
|
|
486
|
+
stateInfos = stateInfos.filter((info) => info.id.startsWith(params.namespace));
|
|
487
|
+
}
|
|
488
|
+
// Apply limit (default 50)
|
|
489
|
+
const limit = params?.limit ?? 50;
|
|
490
|
+
stateInfos = stateInfos.slice(-limit);
|
|
491
|
+
return {
|
|
492
|
+
states: stateInfos.map((info) => ({
|
|
493
|
+
id: info.id,
|
|
494
|
+
strategy: info.strategy,
|
|
495
|
+
hasData: store.getStateJson(info.id) !== null,
|
|
496
|
+
})),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
async cmdStoreSearch(params) {
|
|
500
|
+
const store = this.framework.getStore();
|
|
501
|
+
let stateInfos = store.listStates();
|
|
502
|
+
// Filter by namespace
|
|
503
|
+
if (params?.namespace) {
|
|
504
|
+
stateInfos = stateInfos.filter((info) => info.id.startsWith(params.namespace));
|
|
505
|
+
}
|
|
506
|
+
const limit = params?.limit ?? 20;
|
|
507
|
+
const offset = params?.offset ?? 0;
|
|
508
|
+
const previewLength = params?.previewLength ?? 300;
|
|
509
|
+
const pattern = params?.contentPattern ? new RegExp(params.contentPattern, 'i') : null;
|
|
510
|
+
// Collect all matching results first (for accurate total count)
|
|
511
|
+
const allMatches = [];
|
|
512
|
+
for (const info of stateInfos) {
|
|
513
|
+
const data = store.getStateJson(info.id);
|
|
514
|
+
if (data === null)
|
|
515
|
+
continue;
|
|
516
|
+
const content = JSON.stringify(data);
|
|
517
|
+
// If pattern specified, filter by it
|
|
518
|
+
if (pattern && !pattern.test(content))
|
|
519
|
+
continue;
|
|
520
|
+
allMatches.push({ stateId: info.id, content });
|
|
521
|
+
}
|
|
522
|
+
// Apply pagination
|
|
523
|
+
const paged = allMatches.slice(offset, offset + limit);
|
|
524
|
+
const results = paged.map(({ stateId, content }) => ({
|
|
525
|
+
stateId,
|
|
526
|
+
preview: content.length > previewLength ? content.slice(0, previewLength) + '...' : content,
|
|
527
|
+
}));
|
|
528
|
+
return {
|
|
529
|
+
results,
|
|
530
|
+
total: allMatches.length,
|
|
531
|
+
hasMore: offset + limit < allMatches.length,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
async cmdStoreInspect(params) {
|
|
535
|
+
if (!params.stateId) {
|
|
536
|
+
throw new Error('stateId is required');
|
|
537
|
+
}
|
|
538
|
+
const store = this.framework.getStore();
|
|
539
|
+
const data = store.getStateJson(params.stateId);
|
|
540
|
+
return { data };
|
|
541
|
+
}
|
|
542
|
+
// ==========================================================================
|
|
543
|
+
// Inference Log Commands
|
|
544
|
+
// ==========================================================================
|
|
545
|
+
async cmdInferenceTail(params) {
|
|
546
|
+
const entries = this.framework.tailInferenceLogs(params?.count ?? 10, params?.agentName);
|
|
547
|
+
return { entries };
|
|
548
|
+
}
|
|
549
|
+
async cmdInferenceInspect(params) {
|
|
550
|
+
if (params.sequence === undefined || params.sequence === null) {
|
|
551
|
+
throw new Error('sequence is required');
|
|
552
|
+
}
|
|
553
|
+
const entry = this.framework.getInferenceLog(params.sequence);
|
|
554
|
+
if (!entry) {
|
|
555
|
+
throw new Error(`Inference log not found: ${params.sequence}`);
|
|
556
|
+
}
|
|
557
|
+
return { entry };
|
|
558
|
+
}
|
|
559
|
+
async cmdInferenceSearch(params) {
|
|
560
|
+
const result = this.framework.queryInferenceLogs({
|
|
561
|
+
agentName: params?.agentName,
|
|
562
|
+
limit: params?.limit,
|
|
563
|
+
offset: params?.offset,
|
|
564
|
+
pattern: params?.pattern,
|
|
565
|
+
errorsOnly: params?.errorsOnly,
|
|
566
|
+
});
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
// ==========================================================================
|
|
570
|
+
// Event Log Commands
|
|
571
|
+
// ==========================================================================
|
|
572
|
+
async cmdEventsTail(params) {
|
|
573
|
+
const entries = this.framework.tailProcessLogs(params?.count ?? 10, params?.eventType);
|
|
574
|
+
return { entries };
|
|
575
|
+
}
|
|
576
|
+
async cmdEventsInspect(params) {
|
|
577
|
+
if (params.sequence === undefined || params.sequence === null) {
|
|
578
|
+
throw new Error('sequence is required');
|
|
579
|
+
}
|
|
580
|
+
const entry = this.framework.getProcessLog(params.sequence);
|
|
581
|
+
if (!entry) {
|
|
582
|
+
throw new Error(`Process log not found: ${params.sequence}`);
|
|
583
|
+
}
|
|
584
|
+
return { entry };
|
|
585
|
+
}
|
|
586
|
+
async cmdEventsSearch(params) {
|
|
587
|
+
const result = this.framework.queryProcessLogs({
|
|
588
|
+
eventType: params?.eventType,
|
|
589
|
+
moduleName: params?.moduleName,
|
|
590
|
+
limit: params?.limit,
|
|
591
|
+
offset: params?.offset,
|
|
592
|
+
pattern: params?.pattern,
|
|
593
|
+
});
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
async cmdEventsSubscribe(params) {
|
|
597
|
+
const types = params?.types ?? ['*'];
|
|
598
|
+
const limit = params?.limit ?? 100;
|
|
599
|
+
// Get historical events from the process log
|
|
600
|
+
const processEntries = this.framework.tailProcessLogs(limit);
|
|
601
|
+
// Convert ProcessLogEntry to PersistedEvent format
|
|
602
|
+
const history = processEntries.map((item) => {
|
|
603
|
+
const { sequence, entry } = item;
|
|
604
|
+
const event = entry.processEvent;
|
|
605
|
+
const timestamp = entry.timestamp;
|
|
606
|
+
// Extract agent/module info from event if available
|
|
607
|
+
const agentName = event.agentName;
|
|
608
|
+
const moduleName = event.moduleName;
|
|
609
|
+
return {
|
|
610
|
+
id: `evt-${sequence}`,
|
|
611
|
+
sequence,
|
|
612
|
+
timestamp,
|
|
613
|
+
type: event?.type ?? 'unknown',
|
|
614
|
+
payload: event,
|
|
615
|
+
source: 'chronicle',
|
|
616
|
+
agentName,
|
|
617
|
+
moduleName,
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
// Filter by types if not wildcard
|
|
621
|
+
const filteredHistory = types.includes('*')
|
|
622
|
+
? history
|
|
623
|
+
: history.filter((evt) => types.some((pattern) => matchEventType(pattern, evt.type)));
|
|
624
|
+
return {
|
|
625
|
+
subscribed: types,
|
|
626
|
+
history: filteredHistory,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
// ==========================================================================
|
|
630
|
+
// HTTP Handling (optional REST endpoints)
|
|
631
|
+
// ==========================================================================
|
|
632
|
+
handleHttpRequest(req, res) {
|
|
633
|
+
// CORS headers
|
|
634
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
635
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
636
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
637
|
+
if (req.method === 'OPTIONS') {
|
|
638
|
+
res.writeHead(204);
|
|
639
|
+
res.end();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
|
643
|
+
// Health check
|
|
644
|
+
if (url.pathname === '/health') {
|
|
645
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
646
|
+
res.end(JSON.stringify({ status: 'ok', branch: this.currentBranch }));
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
// Status
|
|
650
|
+
if (url.pathname === '/status') {
|
|
651
|
+
const agents = this.framework.getAllAgents();
|
|
652
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
653
|
+
res.end(JSON.stringify({
|
|
654
|
+
branch: this.currentBranch,
|
|
655
|
+
agents: agents.map((a) => ({ name: a.name, status: a.state.status })),
|
|
656
|
+
clients: this.clients.size,
|
|
657
|
+
queueDepth: this.framework.getQueueDepth(),
|
|
658
|
+
}));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
662
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
663
|
+
}
|
|
664
|
+
// ==========================================================================
|
|
665
|
+
// Subscription Commands
|
|
666
|
+
// ==========================================================================
|
|
667
|
+
async cmdStoreSubscribe(ws, params) {
|
|
668
|
+
const store = this.framework.getStore();
|
|
669
|
+
// Build subscription config
|
|
670
|
+
const config = {};
|
|
671
|
+
if (params?.bufferSize !== undefined) {
|
|
672
|
+
config.bufferSize = params.bufferSize;
|
|
673
|
+
}
|
|
674
|
+
if (params?.maxSnapshotBytes !== undefined) {
|
|
675
|
+
config.maxSnapshotBytes = params.maxSnapshotBytes;
|
|
676
|
+
}
|
|
677
|
+
if (params?.fromSequence !== undefined) {
|
|
678
|
+
config.fromSequence = params.fromSequence;
|
|
679
|
+
}
|
|
680
|
+
if (params?.filter) {
|
|
681
|
+
config.filter = {
|
|
682
|
+
recordTypes: params.filter.recordTypes,
|
|
683
|
+
branch: params.filter.branch,
|
|
684
|
+
stateIds: params.filter.stateIds,
|
|
685
|
+
includeRecords: params.filter.includeRecords ?? false,
|
|
686
|
+
includeStateChanges: params.filter.includeStateChanges ?? false,
|
|
687
|
+
includeBranchEvents: params.filter.includeBranchEvents ?? false,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
// Create subscription
|
|
691
|
+
const subscriptionId = store.subscribe(config);
|
|
692
|
+
// Track this subscription for the client
|
|
693
|
+
if (!this.clientSubscriptions.has(ws)) {
|
|
694
|
+
this.clientSubscriptions.set(ws, new Set());
|
|
695
|
+
}
|
|
696
|
+
this.clientSubscriptions.get(ws).add(subscriptionId);
|
|
697
|
+
// If fromSequence is set, perform catch-up
|
|
698
|
+
if (params?.fromSequence !== undefined) {
|
|
699
|
+
store.catchUpSubscription(subscriptionId);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
// Mark as caught up immediately for live-only subscriptions
|
|
703
|
+
// The store will send the CaughtUp event
|
|
704
|
+
store.catchUpSubscription(subscriptionId);
|
|
705
|
+
}
|
|
706
|
+
return { subscriptionId };
|
|
707
|
+
}
|
|
708
|
+
async cmdStoreUnsubscribe(ws, params) {
|
|
709
|
+
if (!params.subscriptionId) {
|
|
710
|
+
throw new Error('subscriptionId is required');
|
|
711
|
+
}
|
|
712
|
+
const store = this.framework.getStore();
|
|
713
|
+
// Remove from client tracking
|
|
714
|
+
const clientSubs = this.clientSubscriptions.get(ws);
|
|
715
|
+
if (clientSubs) {
|
|
716
|
+
clientSubs.delete(params.subscriptionId);
|
|
717
|
+
}
|
|
718
|
+
// Unsubscribe from store
|
|
719
|
+
store.unsubscribe(params.subscriptionId);
|
|
720
|
+
return { success: true };
|
|
721
|
+
}
|
|
722
|
+
// ==========================================================================
|
|
723
|
+
// Subscription Helpers
|
|
724
|
+
// ==========================================================================
|
|
725
|
+
/**
|
|
726
|
+
* Cleanup all subscriptions for a disconnected client.
|
|
727
|
+
*/
|
|
728
|
+
cleanupClientSubscriptions(ws) {
|
|
729
|
+
const subscriptionIds = this.clientSubscriptions.get(ws);
|
|
730
|
+
if (!subscriptionIds)
|
|
731
|
+
return;
|
|
732
|
+
const store = this.framework.getStore();
|
|
733
|
+
for (const subId of subscriptionIds) {
|
|
734
|
+
store.unsubscribe(subId);
|
|
735
|
+
}
|
|
736
|
+
this.clientSubscriptions.delete(ws);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Poll all active subscriptions and push events to clients.
|
|
740
|
+
*/
|
|
741
|
+
pollSubscriptions() {
|
|
742
|
+
const store = this.framework.getStore();
|
|
743
|
+
for (const [ws, subscriptionIds] of this.clientSubscriptions) {
|
|
744
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
745
|
+
continue;
|
|
746
|
+
for (const subId of subscriptionIds) {
|
|
747
|
+
// Poll events from this subscription (non-blocking)
|
|
748
|
+
let event = store.pollSubscription(subId);
|
|
749
|
+
let eventCount = 0;
|
|
750
|
+
const maxEventsPerPoll = 100; // Prevent blocking too long
|
|
751
|
+
while (event && eventCount < maxEventsPerPoll) {
|
|
752
|
+
// Parse the event data and send to client
|
|
753
|
+
try {
|
|
754
|
+
const eventData = JSON.parse(event.data);
|
|
755
|
+
const eventType = `store:${event.eventType}`;
|
|
756
|
+
this.sendEvent(ws, eventType, {
|
|
757
|
+
subscriptionId: subId,
|
|
758
|
+
...eventData,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
console.error('Failed to parse subscription event:', error);
|
|
763
|
+
}
|
|
764
|
+
// Check for more events
|
|
765
|
+
event = store.pollSubscription(subId);
|
|
766
|
+
eventCount++;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Check if an event type matches a pattern.
|
|
774
|
+
* Supports wildcards like 'inference:*' or exact matches.
|
|
775
|
+
*/
|
|
776
|
+
function matchEventType(pattern, eventType) {
|
|
777
|
+
if (pattern === '*')
|
|
778
|
+
return true;
|
|
779
|
+
if (pattern.endsWith(':*')) {
|
|
780
|
+
const prefix = pattern.slice(0, -1);
|
|
781
|
+
return eventType.startsWith(prefix);
|
|
782
|
+
}
|
|
783
|
+
return pattern === eventType;
|
|
784
|
+
}
|
|
785
|
+
//# sourceMappingURL=server.js.map
|