@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,547 @@
|
|
|
1
|
+
import type { JsStore } from '@animalabs/chronicle';
|
|
2
|
+
import type { ContentBlock } from '@animalabs/membrane';
|
|
3
|
+
import type {
|
|
4
|
+
MessageId,
|
|
5
|
+
MessageMetadata,
|
|
6
|
+
MessageQuery,
|
|
7
|
+
MessageQueryResult,
|
|
8
|
+
StoredMessage,
|
|
9
|
+
ContextInjection,
|
|
10
|
+
} from '@animalabs/context-manager';
|
|
11
|
+
import type {
|
|
12
|
+
Module,
|
|
13
|
+
ModuleContext,
|
|
14
|
+
ProcessState,
|
|
15
|
+
ProcessQueue,
|
|
16
|
+
ToolDefinition,
|
|
17
|
+
ToolCall,
|
|
18
|
+
ToolResult,
|
|
19
|
+
AgentInfo,
|
|
20
|
+
ExternalIdRef,
|
|
21
|
+
SpeechContext,
|
|
22
|
+
SpeechHandlerOptions,
|
|
23
|
+
ProcessEvent,
|
|
24
|
+
} from './types/index.js';
|
|
25
|
+
import type { Agent } from './agent.js';
|
|
26
|
+
|
|
27
|
+
const MODULE_STATE_PREFIX = 'modules/';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Registered speech handler.
|
|
31
|
+
*/
|
|
32
|
+
interface SpeechHandler {
|
|
33
|
+
moduleName: string;
|
|
34
|
+
agents: '*' | string[];
|
|
35
|
+
priority: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Registry for managing modules.
|
|
41
|
+
*/
|
|
42
|
+
export class ModuleRegistry {
|
|
43
|
+
private modules: Map<string, Module> = new Map();
|
|
44
|
+
private moduleContexts: Map<string, ModuleContextImpl> = new Map();
|
|
45
|
+
private speechHandlers: SpeechHandler[] = [];
|
|
46
|
+
private store: JsStore;
|
|
47
|
+
private queue: ProcessQueue;
|
|
48
|
+
private getAgents: () => Agent[];
|
|
49
|
+
private addMessageFn: (participant: string, content: ContentBlock[], metadata?: MessageMetadata) => MessageId;
|
|
50
|
+
private editMessageFn: (id: MessageId, content: ContentBlock[]) => void;
|
|
51
|
+
private removeMessageFn: (id: MessageId) => void;
|
|
52
|
+
private getMessageFn: (id: MessageId) => StoredMessage | null;
|
|
53
|
+
private queryMessagesFn: (filter: MessageQuery) => MessageQueryResult;
|
|
54
|
+
|
|
55
|
+
private pushEventFn: (event: ProcessEvent) => void;
|
|
56
|
+
|
|
57
|
+
constructor(
|
|
58
|
+
store: JsStore,
|
|
59
|
+
queue: ProcessQueue,
|
|
60
|
+
options: {
|
|
61
|
+
getAgents: () => Agent[];
|
|
62
|
+
addMessage: (participant: string, content: ContentBlock[], metadata?: MessageMetadata) => MessageId;
|
|
63
|
+
editMessage: (id: MessageId, content: ContentBlock[]) => void;
|
|
64
|
+
removeMessage: (id: MessageId) => void;
|
|
65
|
+
getMessage: (id: MessageId) => StoredMessage | null;
|
|
66
|
+
queryMessages: (filter: MessageQuery) => MessageQueryResult;
|
|
67
|
+
pushEvent: (event: ProcessEvent) => void;
|
|
68
|
+
}
|
|
69
|
+
) {
|
|
70
|
+
this.store = store;
|
|
71
|
+
this.queue = queue;
|
|
72
|
+
this.getAgents = options.getAgents;
|
|
73
|
+
this.addMessageFn = options.addMessage;
|
|
74
|
+
this.editMessageFn = options.editMessage;
|
|
75
|
+
this.removeMessageFn = options.removeMessage;
|
|
76
|
+
this.getMessageFn = options.getMessage;
|
|
77
|
+
this.queryMessagesFn = options.queryMessages;
|
|
78
|
+
this.pushEventFn = options.pushEvent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Register and start a module.
|
|
83
|
+
*/
|
|
84
|
+
async addModule(module: Module): Promise<void> {
|
|
85
|
+
if (this.modules.has(module.name)) {
|
|
86
|
+
throw new Error(`Module already registered: ${module.name}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Register module state in store
|
|
90
|
+
const stateId = this.getStateId(module.name);
|
|
91
|
+
try {
|
|
92
|
+
this.store.registerState({
|
|
93
|
+
id: stateId,
|
|
94
|
+
strategy: 'snapshot',
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
// State already registered (from previous run)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if this is a restart (state exists)
|
|
101
|
+
const existingState = this.store.getStateJson(stateId);
|
|
102
|
+
const isRestart = existingState !== null;
|
|
103
|
+
|
|
104
|
+
// Create context
|
|
105
|
+
const context = new ModuleContextImpl(
|
|
106
|
+
module.name,
|
|
107
|
+
this.store,
|
|
108
|
+
stateId,
|
|
109
|
+
this.queue,
|
|
110
|
+
this,
|
|
111
|
+
isRestart,
|
|
112
|
+
this.getAgents,
|
|
113
|
+
this.addMessageFn,
|
|
114
|
+
this.editMessageFn,
|
|
115
|
+
this.removeMessageFn,
|
|
116
|
+
this.getMessageFn,
|
|
117
|
+
this.queryMessagesFn,
|
|
118
|
+
this.pushEventFn
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
this.modules.set(module.name, module);
|
|
122
|
+
this.moduleContexts.set(module.name, context);
|
|
123
|
+
|
|
124
|
+
// Start the module
|
|
125
|
+
await module.start(context);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Stop and unregister a module.
|
|
130
|
+
*/
|
|
131
|
+
async removeModule(name: string): Promise<void> {
|
|
132
|
+
const module = this.modules.get(name);
|
|
133
|
+
if (!module) {
|
|
134
|
+
throw new Error(`Module not found: ${name}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await module.stop();
|
|
138
|
+
this.modules.delete(name);
|
|
139
|
+
this.moduleContexts.delete(name);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get a module by name.
|
|
144
|
+
*/
|
|
145
|
+
getModule<T extends Module>(name: string): T | null {
|
|
146
|
+
return (this.modules.get(name) as T) ?? null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get all registered modules.
|
|
151
|
+
*/
|
|
152
|
+
getAllModules(): Module[] {
|
|
153
|
+
return Array.from(this.modules.values());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Set state for a module by name.
|
|
158
|
+
* Used by the framework to apply stateUpdate from EventResponse atomically.
|
|
159
|
+
*/
|
|
160
|
+
setModuleState(moduleName: string, state: unknown): void {
|
|
161
|
+
const ctx = this.moduleContexts.get(moduleName);
|
|
162
|
+
if (ctx) {
|
|
163
|
+
ctx.setState(state);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get state for a module by name.
|
|
169
|
+
*/
|
|
170
|
+
getModuleState<T>(moduleName: string): T | null {
|
|
171
|
+
const ctx = this.moduleContexts.get(moduleName);
|
|
172
|
+
if (ctx) {
|
|
173
|
+
return ctx.getState<T>();
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Create a ProcessState for a module to use during event processing.
|
|
180
|
+
*/
|
|
181
|
+
createProcessState(moduleName: string): ProcessState {
|
|
182
|
+
const ctx = this.moduleContexts.get(moduleName);
|
|
183
|
+
if (!ctx) {
|
|
184
|
+
throw new Error(`Module ${moduleName} not found`);
|
|
185
|
+
}
|
|
186
|
+
return new ProcessStateImpl(moduleName, ctx, this);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get all available tools from all modules.
|
|
191
|
+
*/
|
|
192
|
+
getAllTools(): ToolDefinition[] {
|
|
193
|
+
const tools: ToolDefinition[] = [];
|
|
194
|
+
for (const module of this.modules.values()) {
|
|
195
|
+
for (const tool of module.getTools()) {
|
|
196
|
+
tools.push({
|
|
197
|
+
...tool,
|
|
198
|
+
name: `${module.name}--${tool.name}`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return tools;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Handle a tool call by routing to the appropriate module.
|
|
207
|
+
*/
|
|
208
|
+
async handleToolCall(call: ToolCall): Promise<ToolResult> {
|
|
209
|
+
// Parse module--tool name
|
|
210
|
+
const sepIndex = call.name.indexOf('--');
|
|
211
|
+
if (sepIndex === -1) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: `Invalid tool name format: ${call.name}`,
|
|
215
|
+
isError: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const moduleName = call.name.substring(0, sepIndex);
|
|
220
|
+
const toolName = call.name.substring(sepIndex + 2);
|
|
221
|
+
|
|
222
|
+
const module = this.modules.get(moduleName);
|
|
223
|
+
if (!module) {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: `Module not found: ${moduleName}`,
|
|
227
|
+
isError: true,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Create a call with the un-prefixed name (preserving caller identity)
|
|
232
|
+
const moduleCall: ToolCall = {
|
|
233
|
+
id: call.id,
|
|
234
|
+
name: toolName,
|
|
235
|
+
input: call.input,
|
|
236
|
+
callerAgentName: call.callerAgentName,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
return await module.handleToolCall(moduleCall);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: error instanceof Error ? error.message : String(error),
|
|
245
|
+
isError: true,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Stop all modules.
|
|
252
|
+
*/
|
|
253
|
+
async stopAll(): Promise<void> {
|
|
254
|
+
const stopPromises = Array.from(this.modules.values()).map((m) => m.stop());
|
|
255
|
+
await Promise.all(stopPromises);
|
|
256
|
+
this.modules.clear();
|
|
257
|
+
this.moduleContexts.clear();
|
|
258
|
+
this.speechHandlers = [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Register a module as a speech handler.
|
|
263
|
+
*/
|
|
264
|
+
registerSpeechHandler(
|
|
265
|
+
moduleName: string,
|
|
266
|
+
agents: '*' | string[],
|
|
267
|
+
options: SpeechHandlerOptions = {}
|
|
268
|
+
): void {
|
|
269
|
+
const priority = options.priority ?? 0;
|
|
270
|
+
|
|
271
|
+
if (!options.additive) {
|
|
272
|
+
// Remove existing handlers for this module
|
|
273
|
+
this.speechHandlers = this.speechHandlers.filter(
|
|
274
|
+
(h) => h.moduleName !== moduleName
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.speechHandlers.push({ moduleName, agents, priority });
|
|
279
|
+
|
|
280
|
+
// Sort by priority (higher first)
|
|
281
|
+
this.speechHandlers.sort((a, b) => b.priority - a.priority);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Unregister a module as a speech handler.
|
|
286
|
+
*/
|
|
287
|
+
unregisterSpeechHandler(moduleName: string): void {
|
|
288
|
+
this.speechHandlers = this.speechHandlers.filter(
|
|
289
|
+
(h) => h.moduleName !== moduleName
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Gather context injections from all modules before inference.
|
|
295
|
+
* Calls each module's gatherContext() in parallel with a per-module timeout.
|
|
296
|
+
* Fail-open: timed-out or erroring modules are skipped silently.
|
|
297
|
+
* Adapted from Anarchid/agent-framework@mcpl-module-proto.
|
|
298
|
+
*/
|
|
299
|
+
async gatherContext(agentName: string, timeoutMs = 5000): Promise<ContextInjection[]> {
|
|
300
|
+
const injections: ContextInjection[] = [];
|
|
301
|
+
const promises: Promise<void>[] = [];
|
|
302
|
+
|
|
303
|
+
for (const module of this.modules.values()) {
|
|
304
|
+
if (!module.gatherContext) continue;
|
|
305
|
+
|
|
306
|
+
const promise = Promise.race([
|
|
307
|
+
module.gatherContext(agentName).then(r => injections.push(...r)),
|
|
308
|
+
new Promise<void>((_, rej) => setTimeout(() => rej(new Error('timeout')), timeoutMs)),
|
|
309
|
+
]).catch(err => console.error(`[${module.name}] gatherContext:`, err));
|
|
310
|
+
|
|
311
|
+
promises.push(promise as Promise<void>);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
await Promise.all(promises);
|
|
315
|
+
return injections;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Dispatch speech to registered handlers.
|
|
320
|
+
*/
|
|
321
|
+
async dispatchSpeech(
|
|
322
|
+
agentName: string,
|
|
323
|
+
content: ContentBlock[],
|
|
324
|
+
context: SpeechContext
|
|
325
|
+
): Promise<void> {
|
|
326
|
+
// Find handlers that match this agent
|
|
327
|
+
const handlers = this.speechHandlers.filter(
|
|
328
|
+
(h) => h.agents === '*' || h.agents.includes(agentName)
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// Call each handler
|
|
332
|
+
for (const handler of handlers) {
|
|
333
|
+
const module = this.modules.get(handler.moduleName);
|
|
334
|
+
if (module?.onAgentSpeech) {
|
|
335
|
+
try {
|
|
336
|
+
await module.onAgentSpeech(agentName, content, context);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(
|
|
339
|
+
`Speech handler ${handler.moduleName} error:`,
|
|
340
|
+
error
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private getStateId(moduleName: string): string {
|
|
348
|
+
return `${MODULE_STATE_PREFIX}${moduleName}/state`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Implementation of ModuleContext.
|
|
354
|
+
*/
|
|
355
|
+
class ModuleContextImpl implements ModuleContext {
|
|
356
|
+
private moduleName: string;
|
|
357
|
+
private store: JsStore;
|
|
358
|
+
private stateId: string;
|
|
359
|
+
readonly queue: ProcessQueue;
|
|
360
|
+
private registry: ModuleRegistry;
|
|
361
|
+
readonly isRestart: boolean;
|
|
362
|
+
private getAgentsFn: () => Agent[];
|
|
363
|
+
private addMessageFn: (participant: string, content: ContentBlock[], metadata?: MessageMetadata) => MessageId;
|
|
364
|
+
private editMessageFn: (id: MessageId, content: ContentBlock[]) => void;
|
|
365
|
+
private removeMessageFn: (id: MessageId) => void;
|
|
366
|
+
private getMessageFn: (id: MessageId) => StoredMessage | null;
|
|
367
|
+
private queryMessagesFn: (filter: MessageQuery) => MessageQueryResult;
|
|
368
|
+
private pushEventFn: (event: ProcessEvent) => void;
|
|
369
|
+
|
|
370
|
+
// External ID mapping stored in module state
|
|
371
|
+
private externalIdMap: Map<string, MessageId> = new Map();
|
|
372
|
+
|
|
373
|
+
constructor(
|
|
374
|
+
moduleName: string,
|
|
375
|
+
store: JsStore,
|
|
376
|
+
stateId: string,
|
|
377
|
+
queue: ProcessQueue,
|
|
378
|
+
registry: ModuleRegistry,
|
|
379
|
+
isRestart: boolean,
|
|
380
|
+
getAgents: () => Agent[],
|
|
381
|
+
addMessage: (participant: string, content: ContentBlock[], metadata?: MessageMetadata) => MessageId,
|
|
382
|
+
editMessage: (id: MessageId, content: ContentBlock[]) => void,
|
|
383
|
+
removeMessage: (id: MessageId) => void,
|
|
384
|
+
getMessage: (id: MessageId) => StoredMessage | null,
|
|
385
|
+
queryMessages: (filter: MessageQuery) => MessageQueryResult,
|
|
386
|
+
pushEvent: (event: ProcessEvent) => void
|
|
387
|
+
) {
|
|
388
|
+
this.moduleName = moduleName;
|
|
389
|
+
this.store = store;
|
|
390
|
+
this.stateId = stateId;
|
|
391
|
+
this.queue = queue;
|
|
392
|
+
this.registry = registry;
|
|
393
|
+
this.isRestart = isRestart;
|
|
394
|
+
this.getAgentsFn = getAgents;
|
|
395
|
+
this.addMessageFn = addMessage;
|
|
396
|
+
this.editMessageFn = editMessage;
|
|
397
|
+
this.removeMessageFn = removeMessage;
|
|
398
|
+
this.getMessageFn = getMessage;
|
|
399
|
+
this.queryMessagesFn = queryMessages;
|
|
400
|
+
this.pushEventFn = pushEvent;
|
|
401
|
+
|
|
402
|
+
// Load external ID map from state if exists
|
|
403
|
+
const state = this.getState<{ externalIdMap?: Record<string, string> }>();
|
|
404
|
+
if (state?.externalIdMap) {
|
|
405
|
+
this.externalIdMap = new Map(Object.entries(state.externalIdMap));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
getState<T>(): T | null {
|
|
410
|
+
const state = this.store.getStateJson(this.stateId);
|
|
411
|
+
return state as T | null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
setState<T>(state: T): void {
|
|
415
|
+
// Merge external ID map into state
|
|
416
|
+
const fullState = {
|
|
417
|
+
...(state as object),
|
|
418
|
+
externalIdMap: Object.fromEntries(this.externalIdMap),
|
|
419
|
+
};
|
|
420
|
+
this.store.setStateJson(this.stateId, fullState);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
getModule<T extends Module>(name: string): T | null {
|
|
424
|
+
return this.registry.getModule<T>(name);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
addMessage(
|
|
428
|
+
participant: string,
|
|
429
|
+
content: ContentBlock[],
|
|
430
|
+
metadata?: MessageMetadata & { external?: ExternalIdRef }
|
|
431
|
+
): MessageId {
|
|
432
|
+
const id = this.addMessageFn(participant, content, metadata);
|
|
433
|
+
|
|
434
|
+
// Track external ID if provided
|
|
435
|
+
if (metadata?.external) {
|
|
436
|
+
const key = `${metadata.external.source}:${metadata.external.id}`;
|
|
437
|
+
this.externalIdMap.set(key, id);
|
|
438
|
+
// Persist the mapping
|
|
439
|
+
this.persistExternalIdMap();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return id;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
editMessage(id: MessageId, content: ContentBlock[]): void {
|
|
446
|
+
this.editMessageFn(id, content);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
removeMessage(id: MessageId): void {
|
|
450
|
+
this.removeMessageFn(id);
|
|
451
|
+
// Clean up external ID mapping
|
|
452
|
+
for (const [key, msgId] of this.externalIdMap) {
|
|
453
|
+
if (msgId === id) {
|
|
454
|
+
this.externalIdMap.delete(key);
|
|
455
|
+
this.persistExternalIdMap();
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
findMessageByExternalId(source: string, externalId: string): MessageId | null {
|
|
462
|
+
const key = `${source}:${externalId}`;
|
|
463
|
+
return this.externalIdMap.get(key) ?? null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
getMessage(id: MessageId): StoredMessage | null {
|
|
467
|
+
return this.getMessageFn(id);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
queryMessages(filter: MessageQuery): MessageQueryResult {
|
|
471
|
+
return this.queryMessagesFn(filter);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
getAgents(): AgentInfo[] {
|
|
475
|
+
return this.getAgentsFn().map((a) => a.info);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
getActiveTools(): ToolDefinition[] {
|
|
479
|
+
return this.registry.getAllTools();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
registerSpeechHandler(
|
|
483
|
+
agents: '*' | string[],
|
|
484
|
+
options?: SpeechHandlerOptions
|
|
485
|
+
): void {
|
|
486
|
+
this.registry.registerSpeechHandler(this.moduleName, agents, options);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
unregisterSpeechHandler(): void {
|
|
490
|
+
this.registry.unregisterSpeechHandler(this.moduleName);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
pushEvent(event: ProcessEvent): void {
|
|
494
|
+
this.pushEventFn(event);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private persistExternalIdMap(): void {
|
|
498
|
+
const currentState = this.getState<object>() ?? {};
|
|
499
|
+
this.setState({
|
|
500
|
+
...currentState,
|
|
501
|
+
externalIdMap: Object.fromEntries(this.externalIdMap),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Implementation of ProcessState - read-only state access during event processing.
|
|
508
|
+
*/
|
|
509
|
+
class ProcessStateImpl implements ProcessState {
|
|
510
|
+
private moduleName: string;
|
|
511
|
+
private ctx: ModuleContextImpl;
|
|
512
|
+
private registry: ModuleRegistry;
|
|
513
|
+
|
|
514
|
+
constructor(moduleName: string, ctx: ModuleContextImpl, registry: ModuleRegistry) {
|
|
515
|
+
this.moduleName = moduleName;
|
|
516
|
+
this.ctx = ctx;
|
|
517
|
+
this.registry = registry;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
getState<T>(): T | null {
|
|
521
|
+
return this.ctx.getState<T>();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
getModuleState<T>(name: string): T | null {
|
|
525
|
+
return this.registry.getModuleState<T>(name);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
findMessageByExternalId(source: string, externalId: string): MessageId | null {
|
|
529
|
+
return this.ctx.findMessageByExternalId(source, externalId);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
getAgents(): AgentInfo[] {
|
|
533
|
+
return this.ctx.getAgents();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
getActiveTools(): ToolDefinition[] {
|
|
537
|
+
return this.ctx.getActiveTools();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
get queue(): ProcessQueue {
|
|
541
|
+
return this.ctx.queue;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
pushEvent(event: ProcessEvent): void {
|
|
545
|
+
this.ctx.pushEvent(event);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Module - handles events from the API server
|
|
3
|
+
*
|
|
4
|
+
* This bundled module processes api:* events and converts them
|
|
5
|
+
* into conversation messages and inference requests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Module,
|
|
10
|
+
ModuleContext,
|
|
11
|
+
ProcessState,
|
|
12
|
+
ProcessEvent,
|
|
13
|
+
EventResponse,
|
|
14
|
+
ToolCall,
|
|
15
|
+
ToolResult,
|
|
16
|
+
ApiMessageEvent,
|
|
17
|
+
ApiInferenceRequestEvent,
|
|
18
|
+
} from '../../types/index.js';
|
|
19
|
+
|
|
20
|
+
// Re-export event types for convenience
|
|
21
|
+
export type { ApiMessageEvent, ApiInferenceRequestEvent };
|
|
22
|
+
export type ApiEvent = ApiMessageEvent | ApiInferenceRequestEvent;
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// API Module
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
export class ApiModule implements Module {
|
|
29
|
+
readonly name = 'api';
|
|
30
|
+
|
|
31
|
+
private ctx!: ModuleContext;
|
|
32
|
+
|
|
33
|
+
async start(ctx: ModuleContext): Promise<void> {
|
|
34
|
+
this.ctx = ctx;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async stop(): Promise<void> {
|
|
38
|
+
// Nothing to clean up
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async handleToolCall(_call: ToolCall): Promise<ToolResult> {
|
|
42
|
+
// API module has no tools
|
|
43
|
+
return { success: false, error: 'Unknown tool' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async onProcess(event: ProcessEvent, _state: ProcessState): Promise<EventResponse> {
|
|
47
|
+
if (!event.type.startsWith('api:')) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
switch (event.type) {
|
|
52
|
+
case 'api:message':
|
|
53
|
+
return this.handleMessage(event as ApiMessageEvent);
|
|
54
|
+
|
|
55
|
+
case 'api:inference-request':
|
|
56
|
+
return this.handleInferenceRequest(event as ApiInferenceRequestEvent);
|
|
57
|
+
|
|
58
|
+
default:
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private handleMessage(event: ApiMessageEvent): EventResponse {
|
|
64
|
+
const response: EventResponse = {
|
|
65
|
+
addMessages: [
|
|
66
|
+
{
|
|
67
|
+
participant: event.participant,
|
|
68
|
+
content: [{ type: 'text', text: event.content }],
|
|
69
|
+
metadata: event.metadata,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (event.triggerInference !== false) {
|
|
75
|
+
response.requestInference = event.targetAgents ?? true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private handleInferenceRequest(event: ApiInferenceRequestEvent): EventResponse {
|
|
82
|
+
if (event.agentName) {
|
|
83
|
+
return { requestInference: [event.agentName] };
|
|
84
|
+
}
|
|
85
|
+
return { requestInference: true };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getTools() {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Default export for convenience
|
|
94
|
+
export default ApiModule;
|