@inf-minds/kernel 0.0.1
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/dist/condition-evaluator.d.ts +69 -0
- package/dist/condition-evaluator.d.ts.map +1 -0
- package/dist/condition-evaluator.js +120 -0
- package/dist/condition-evaluator.js.map +1 -0
- package/dist/graph-executor.d.ts +63 -0
- package/dist/graph-executor.d.ts.map +1 -0
- package/dist/graph-executor.js +245 -0
- package/dist/graph-executor.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +45 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +202 -0
- package/dist/kernel.js.map +1 -0
- package/dist/registries/index.d.ts +3 -0
- package/dist/registries/index.d.ts.map +1 -0
- package/dist/registries/index.js +5 -0
- package/dist/registries/index.js.map +1 -0
- package/dist/registries/mind-registry.d.ts +11 -0
- package/dist/registries/mind-registry.d.ts.map +1 -0
- package/dist/registries/mind-registry.js +31 -0
- package/dist/registries/mind-registry.js.map +1 -0
- package/dist/registries/mode-registry.d.ts +11 -0
- package/dist/registries/mode-registry.d.ts.map +1 -0
- package/dist/registries/mode-registry.js +31 -0
- package/dist/registries/mode-registry.js.map +1 -0
- package/dist/session.d.ts +123 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +403 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +288 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
- package/src/condition-evaluator.ts +168 -0
- package/src/graph-executor.ts +315 -0
- package/src/index.ts +50 -0
- package/src/kernel.ts +242 -0
- package/src/registries/index.ts +5 -0
- package/src/registries/mind-registry.ts +38 -0
- package/src/registries/mode-registry.ts +38 -0
- package/src/session.ts +541 -0
- package/src/types.ts +280 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// ABOUTME: Graph executor for coordination mode execution
|
|
2
|
+
// ABOUTME: Handles node scheduling, edge traversal, and state management
|
|
3
|
+
|
|
4
|
+
import type { CoordinationMode } from '@inf-minds/coordination-modes';
|
|
5
|
+
import type { GraphState, GraphNodeState } from '@inf-minds/jobs';
|
|
6
|
+
import { evaluateCondition, type ConditionContext } from './condition-evaluator.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Result of determining the next nodes to execute.
|
|
10
|
+
*/
|
|
11
|
+
export interface NextNodesResult {
|
|
12
|
+
/** Node IDs to start executing */
|
|
13
|
+
nodesToStart: string[];
|
|
14
|
+
/** Node IDs that were skipped (condition not met) */
|
|
15
|
+
skippedNodes: string[];
|
|
16
|
+
/** Whether the session should complete */
|
|
17
|
+
shouldComplete: boolean;
|
|
18
|
+
/** Final output if session should complete */
|
|
19
|
+
finalOutput?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for determining next nodes.
|
|
24
|
+
*/
|
|
25
|
+
export interface DetermineNextNodesOptions {
|
|
26
|
+
/** The coordination mode */
|
|
27
|
+
mode: CoordinationMode;
|
|
28
|
+
/** Current graph state */
|
|
29
|
+
graphState: GraphState;
|
|
30
|
+
/** Node that just completed (or null if starting) */
|
|
31
|
+
completedNodeId: string | null;
|
|
32
|
+
/** Output from the completed node */
|
|
33
|
+
completedOutput: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Determine which nodes should be executed next after a node completes.
|
|
38
|
+
*
|
|
39
|
+
* This function:
|
|
40
|
+
* 1. Finds outgoing edges from the completed node
|
|
41
|
+
* 2. Evaluates edge conditions
|
|
42
|
+
* 3. Handles loop logic (exit conditions and max iterations)
|
|
43
|
+
* 4. Checks if session should complete
|
|
44
|
+
*/
|
|
45
|
+
export function determineNextNodes(options: DetermineNextNodesOptions): NextNodesResult {
|
|
46
|
+
const { mode, graphState, completedNodeId, completedOutput } = options;
|
|
47
|
+
const nodesToStart: string[] = [];
|
|
48
|
+
const skippedNodes: string[] = [];
|
|
49
|
+
|
|
50
|
+
// If no completed node, we're starting - go to entry node
|
|
51
|
+
if (!completedNodeId) {
|
|
52
|
+
return {
|
|
53
|
+
nodesToStart: [mode.graph.entryNode],
|
|
54
|
+
skippedNodes: [],
|
|
55
|
+
shouldComplete: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Get the completed node definition
|
|
60
|
+
const completedNode = mode.graph.nodes[completedNodeId];
|
|
61
|
+
if (!completedNode) {
|
|
62
|
+
return {
|
|
63
|
+
nodesToStart: [],
|
|
64
|
+
skippedNodes: [],
|
|
65
|
+
shouldComplete: true,
|
|
66
|
+
finalOutput: { error: `Node ${completedNodeId} not found in graph` },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if this is an exit node
|
|
71
|
+
const isExitNode = mode.graph.exitNodes.includes(completedNodeId);
|
|
72
|
+
|
|
73
|
+
// Count iterations for loop handling
|
|
74
|
+
const currentIteration = graphState.nodes[completedNodeId]?.jobId
|
|
75
|
+
? countIterations(graphState, completedNodeId)
|
|
76
|
+
: 0;
|
|
77
|
+
|
|
78
|
+
// Build condition context (reused for edge and exit conditions)
|
|
79
|
+
const context: ConditionContext = {
|
|
80
|
+
output: completedOutput,
|
|
81
|
+
graphState: {
|
|
82
|
+
completedNodes: graphState.completedNodes,
|
|
83
|
+
activeNodes: graphState.activeNodes,
|
|
84
|
+
nodeOutputs: extractNodeOutputs(graphState),
|
|
85
|
+
},
|
|
86
|
+
iteration: currentIteration,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Find outgoing edges
|
|
90
|
+
const outgoingEdges = mode.graph.edges.filter((e) => e.from === completedNodeId);
|
|
91
|
+
|
|
92
|
+
// Evaluate conditions for each outgoing edge FIRST
|
|
93
|
+
for (const edge of outgoingEdges) {
|
|
94
|
+
if (edge.condition) {
|
|
95
|
+
const evalResult = evaluateCondition(edge.condition, context);
|
|
96
|
+
|
|
97
|
+
if (evalResult.result) {
|
|
98
|
+
nodesToStart.push(edge.to);
|
|
99
|
+
} else {
|
|
100
|
+
skippedNodes.push(edge.to);
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// No condition - always traverse
|
|
104
|
+
nodesToStart.push(edge.to);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If we have nodes to traverse, go to them (regardless of exit condition)
|
|
109
|
+
if (nodesToStart.length > 0) {
|
|
110
|
+
return {
|
|
111
|
+
nodesToStart,
|
|
112
|
+
skippedNodes,
|
|
113
|
+
shouldComplete: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// No edges to traverse - check exit condition for potential looping
|
|
118
|
+
if (completedNode.exitCondition) {
|
|
119
|
+
const exitResult = evaluateCondition(completedNode.exitCondition, context);
|
|
120
|
+
|
|
121
|
+
// If exit condition is NOT met and under max iterations, loop back
|
|
122
|
+
if (!exitResult.result) {
|
|
123
|
+
const maxIterations = completedNode.maxIterations ?? Infinity;
|
|
124
|
+
if (currentIteration < maxIterations) {
|
|
125
|
+
// Loop: re-execute this node
|
|
126
|
+
return {
|
|
127
|
+
nodesToStart: [completedNodeId],
|
|
128
|
+
skippedNodes,
|
|
129
|
+
shouldComplete: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Exit condition met or max iterations reached - session should complete
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// No edges taken and either:
|
|
137
|
+
// - No exit condition (so we just complete)
|
|
138
|
+
// - Exit condition is met
|
|
139
|
+
// - Max iterations reached
|
|
140
|
+
if (isExitNode || outgoingEdges.length === 0) {
|
|
141
|
+
return {
|
|
142
|
+
nodesToStart: [],
|
|
143
|
+
skippedNodes,
|
|
144
|
+
shouldComplete: true,
|
|
145
|
+
finalOutput: completedOutput,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Dead end but not an exit node - this is a graph issue
|
|
150
|
+
return {
|
|
151
|
+
nodesToStart: [],
|
|
152
|
+
skippedNodes,
|
|
153
|
+
shouldComplete: true,
|
|
154
|
+
finalOutput: { error: `Node ${completedNodeId} has no traversable edges and is not an exit node` },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Count how many times a node has been executed in this session.
|
|
160
|
+
*/
|
|
161
|
+
function countIterations(graphState: GraphState, nodeId: string): number {
|
|
162
|
+
// In the current model, we track iterations through job count
|
|
163
|
+
// For now, count how many times the node appears in completedNodes
|
|
164
|
+
// A more robust implementation would track this in the node state
|
|
165
|
+
return graphState.completedNodes.filter((id) => id === nodeId).length + 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract node outputs from graph state.
|
|
170
|
+
*/
|
|
171
|
+
function extractNodeOutputs(graphState: GraphState): Record<string, unknown> {
|
|
172
|
+
const outputs: Record<string, unknown> = {};
|
|
173
|
+
for (const [nodeId, nodeState] of Object.entries(graphState.nodes)) {
|
|
174
|
+
if (nodeState.output !== undefined) {
|
|
175
|
+
outputs[nodeId] = nodeState.output;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return outputs;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update graph state when a node starts.
|
|
183
|
+
*/
|
|
184
|
+
export function updateStateForNodeStart(
|
|
185
|
+
graphState: GraphState,
|
|
186
|
+
nodeId: string,
|
|
187
|
+
jobId: string
|
|
188
|
+
): GraphState {
|
|
189
|
+
const nodeState: GraphNodeState = graphState.nodes[nodeId] ?? {
|
|
190
|
+
nodeId,
|
|
191
|
+
status: 'pending',
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...graphState,
|
|
196
|
+
nodes: {
|
|
197
|
+
...graphState.nodes,
|
|
198
|
+
[nodeId]: {
|
|
199
|
+
...nodeState,
|
|
200
|
+
status: 'active',
|
|
201
|
+
jobId,
|
|
202
|
+
startedAt: new Date().toISOString(),
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
activeNodes: [...graphState.activeNodes.filter((id) => id !== nodeId), nodeId],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Update graph state when a node completes.
|
|
211
|
+
*/
|
|
212
|
+
export function updateStateForNodeComplete(
|
|
213
|
+
graphState: GraphState,
|
|
214
|
+
nodeId: string,
|
|
215
|
+
output: unknown
|
|
216
|
+
): GraphState {
|
|
217
|
+
const nodeState = graphState.nodes[nodeId];
|
|
218
|
+
if (!nodeState) {
|
|
219
|
+
return graphState;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
...graphState,
|
|
224
|
+
nodes: {
|
|
225
|
+
...graphState.nodes,
|
|
226
|
+
[nodeId]: {
|
|
227
|
+
...nodeState,
|
|
228
|
+
status: 'completed',
|
|
229
|
+
output,
|
|
230
|
+
completedAt: new Date().toISOString(),
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
activeNodes: graphState.activeNodes.filter((id) => id !== nodeId),
|
|
234
|
+
completedNodes: [...graphState.completedNodes, nodeId],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Update graph state when a node fails.
|
|
240
|
+
*/
|
|
241
|
+
export function updateStateForNodeFailure(
|
|
242
|
+
graphState: GraphState,
|
|
243
|
+
nodeId: string,
|
|
244
|
+
error: string
|
|
245
|
+
): GraphState {
|
|
246
|
+
const nodeState = graphState.nodes[nodeId];
|
|
247
|
+
if (!nodeState) {
|
|
248
|
+
return graphState;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
...graphState,
|
|
253
|
+
nodes: {
|
|
254
|
+
...graphState.nodes,
|
|
255
|
+
[nodeId]: {
|
|
256
|
+
...nodeState,
|
|
257
|
+
status: 'failed',
|
|
258
|
+
error,
|
|
259
|
+
completedAt: new Date().toISOString(),
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
activeNodes: graphState.activeNodes.filter((id) => id !== nodeId),
|
|
263
|
+
failedNodes: [...graphState.failedNodes, nodeId],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Update graph state when a node is skipped.
|
|
269
|
+
*/
|
|
270
|
+
export function updateStateForNodeSkipped(
|
|
271
|
+
graphState: GraphState,
|
|
272
|
+
nodeId: string,
|
|
273
|
+
reason: string
|
|
274
|
+
): GraphState {
|
|
275
|
+
const nodeState: GraphNodeState = graphState.nodes[nodeId] ?? {
|
|
276
|
+
nodeId,
|
|
277
|
+
status: 'pending',
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
...graphState,
|
|
282
|
+
nodes: {
|
|
283
|
+
...graphState.nodes,
|
|
284
|
+
[nodeId]: {
|
|
285
|
+
...nodeState,
|
|
286
|
+
status: 'skipped',
|
|
287
|
+
error: reason,
|
|
288
|
+
completedAt: new Date().toISOString(),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
skippedNodes: [...graphState.skippedNodes, nodeId],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Mark graph state as completed.
|
|
297
|
+
*/
|
|
298
|
+
export function markGraphComplete(graphState: GraphState): GraphState {
|
|
299
|
+
return {
|
|
300
|
+
...graphState,
|
|
301
|
+
status: 'completed',
|
|
302
|
+
completedAt: new Date().toISOString(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Mark graph state as failed.
|
|
308
|
+
*/
|
|
309
|
+
export function markGraphFailed(graphState: GraphState): GraphState {
|
|
310
|
+
return {
|
|
311
|
+
...graphState,
|
|
312
|
+
status: 'failed',
|
|
313
|
+
completedAt: new Date().toISOString(),
|
|
314
|
+
};
|
|
315
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ABOUTME: Kernel package entry point
|
|
2
|
+
// ABOUTME: Exports kernel session orchestration and graph execution
|
|
3
|
+
|
|
4
|
+
// Type exports
|
|
5
|
+
export type {
|
|
6
|
+
SessionStatus,
|
|
7
|
+
Unsubscribe,
|
|
8
|
+
SessionFilter,
|
|
9
|
+
CreateSessionOptions,
|
|
10
|
+
SessionEvent,
|
|
11
|
+
KernelSession,
|
|
12
|
+
Kernel,
|
|
13
|
+
KernelOptions,
|
|
14
|
+
MindRegistry,
|
|
15
|
+
ModeRegistry,
|
|
16
|
+
StartNodeOptions,
|
|
17
|
+
NodeCompletionOptions,
|
|
18
|
+
GraphState,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
|
|
21
|
+
// Constant exports
|
|
22
|
+
export { SESSION_STATUS } from './types.js';
|
|
23
|
+
|
|
24
|
+
// Registry exports
|
|
25
|
+
export { createMindRegistry, createModeRegistry } from './registries/index.js';
|
|
26
|
+
|
|
27
|
+
// Kernel factory
|
|
28
|
+
export { createKernel } from './kernel.js';
|
|
29
|
+
|
|
30
|
+
// Condition evaluator
|
|
31
|
+
export {
|
|
32
|
+
evaluateCondition,
|
|
33
|
+
isValidConditionExpression,
|
|
34
|
+
COMMON_CONDITIONS,
|
|
35
|
+
type ConditionContext,
|
|
36
|
+
type EvaluationResult,
|
|
37
|
+
} from './condition-evaluator.js';
|
|
38
|
+
|
|
39
|
+
// Graph executor utilities
|
|
40
|
+
export {
|
|
41
|
+
determineNextNodes,
|
|
42
|
+
updateStateForNodeStart,
|
|
43
|
+
updateStateForNodeComplete,
|
|
44
|
+
updateStateForNodeFailure,
|
|
45
|
+
updateStateForNodeSkipped,
|
|
46
|
+
markGraphComplete,
|
|
47
|
+
markGraphFailed,
|
|
48
|
+
type NextNodesResult,
|
|
49
|
+
type DetermineNextNodesOptions,
|
|
50
|
+
} from './graph-executor.js';
|
package/src/kernel.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// ABOUTME: Kernel factory for creating kernel instances
|
|
2
|
+
// ABOUTME: The kernel manages sessions, minds, and coordination modes
|
|
3
|
+
|
|
4
|
+
import { validateGraph, type CoordinationMode } from '@inf-minds/coordination-modes';
|
|
5
|
+
import type { MindConfig } from '@inf-minds/mindkit';
|
|
6
|
+
import { JOB_TYPE, type Job } from '@inf-minds/jobs';
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Kernel,
|
|
10
|
+
KernelOptions,
|
|
11
|
+
KernelSession,
|
|
12
|
+
CreateSessionOptions,
|
|
13
|
+
SessionFilter,
|
|
14
|
+
GraphState,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
import { createMindRegistry, createModeRegistry } from './registries/index.js';
|
|
17
|
+
import { KernelSessionImpl } from './session.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new kernel instance.
|
|
21
|
+
*
|
|
22
|
+
* The kernel is the central orchestrator for multi-mind coordination.
|
|
23
|
+
* It manages:
|
|
24
|
+
* - Mind configurations (registered by name)
|
|
25
|
+
* - Coordination modes (execution graphs)
|
|
26
|
+
* - Sessions (executing instances of modes)
|
|
27
|
+
*
|
|
28
|
+
* @param options - Kernel configuration options
|
|
29
|
+
* @returns A new Kernel instance
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const kernel = createKernel({
|
|
34
|
+
* jobManager,
|
|
35
|
+
* eventAppender,
|
|
36
|
+
* artifactManager,
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Register minds
|
|
40
|
+
* kernel.registerMind('researcher', {
|
|
41
|
+
* model: 'claude-sonnet-4-20250514',
|
|
42
|
+
* systemPrompt: 'You are a research assistant...',
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Register modes
|
|
46
|
+
* kernel.registerMode(createPipelineMode({
|
|
47
|
+
* steps: [
|
|
48
|
+
* { id: 'research', mind: { type: 'registered', mindType: 'researcher' } },
|
|
49
|
+
* { id: 'analyze', mind: { type: 'registered', mindType: 'analyzer' } },
|
|
50
|
+
* ],
|
|
51
|
+
* }));
|
|
52
|
+
*
|
|
53
|
+
* // Create and run a session
|
|
54
|
+
* const session = await kernel.createSession({
|
|
55
|
+
* mode: 'pipeline',
|
|
56
|
+
* input: { topic: 'quantum computing' },
|
|
57
|
+
* accountId: 'acc-123',
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function createKernel(options: KernelOptions): Kernel {
|
|
62
|
+
const { jobManager, eventAppender, artifactManager, defaultArtifactRouter } = options;
|
|
63
|
+
|
|
64
|
+
// Create registries
|
|
65
|
+
const mindRegistry = createMindRegistry();
|
|
66
|
+
const modeRegistry = createModeRegistry();
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a mode from either a name or inline definition.
|
|
70
|
+
*/
|
|
71
|
+
function resolveMode(modeRef: string | CoordinationMode): CoordinationMode {
|
|
72
|
+
if (typeof modeRef === 'string') {
|
|
73
|
+
const mode = modeRegistry.get(modeRef);
|
|
74
|
+
if (!mode) {
|
|
75
|
+
throw new Error(`Mode '${modeRef}' not found in registry`);
|
|
76
|
+
}
|
|
77
|
+
return mode;
|
|
78
|
+
}
|
|
79
|
+
return modeRef;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create initial graph state for a new session.
|
|
84
|
+
*/
|
|
85
|
+
function createInitialGraphState(mode: CoordinationMode): GraphState {
|
|
86
|
+
const nodes: Record<string, { nodeId: string; status: 'pending' }> = {};
|
|
87
|
+
|
|
88
|
+
for (const nodeId of Object.keys(mode.graph.nodes)) {
|
|
89
|
+
nodes[nodeId] = {
|
|
90
|
+
nodeId,
|
|
91
|
+
status: 'pending',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
nodes,
|
|
97
|
+
activeNodes: [],
|
|
98
|
+
completedNodes: [],
|
|
99
|
+
failedNodes: [],
|
|
100
|
+
skippedNodes: [],
|
|
101
|
+
startedAt: new Date().toISOString(),
|
|
102
|
+
status: 'running',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Wrap a job as a KernelSession.
|
|
108
|
+
*/
|
|
109
|
+
async function wrapJobAsSession(job: Job): Promise<KernelSession> {
|
|
110
|
+
// Parse the job input to get mode name
|
|
111
|
+
const jobInput = job.input as { mode?: string; userInput?: unknown } | undefined;
|
|
112
|
+
const modeName = jobInput?.mode ?? 'unknown';
|
|
113
|
+
|
|
114
|
+
// Get the mode definition if available
|
|
115
|
+
const mode = modeRegistry.get(modeName);
|
|
116
|
+
|
|
117
|
+
return new KernelSessionImpl({
|
|
118
|
+
job,
|
|
119
|
+
mode,
|
|
120
|
+
modeName,
|
|
121
|
+
jobManager,
|
|
122
|
+
eventAppender,
|
|
123
|
+
artifactManager,
|
|
124
|
+
artifactRouter: defaultArtifactRouter,
|
|
125
|
+
mindRegistry,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
async createSession(sessionOptions: CreateSessionOptions): Promise<KernelSession> {
|
|
131
|
+
// Resolve the coordination mode
|
|
132
|
+
const mode = resolveMode(sessionOptions.mode);
|
|
133
|
+
|
|
134
|
+
// Validate the graph
|
|
135
|
+
const validation = validateGraph(mode.graph);
|
|
136
|
+
if (!validation.valid) {
|
|
137
|
+
throw new Error(`Invalid graph: ${validation.errors.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Warn about issues (but don't fail)
|
|
141
|
+
if (validation.warnings.length > 0) {
|
|
142
|
+
console.warn(`Graph warnings: ${validation.warnings.join(', ')}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create initial graph state
|
|
146
|
+
const graphState = createInitialGraphState(mode);
|
|
147
|
+
|
|
148
|
+
// Generate artifact base path for this session
|
|
149
|
+
const sessionId = crypto.randomUUID();
|
|
150
|
+
const artifactBasePath = `/sessions/${sessionId}`;
|
|
151
|
+
|
|
152
|
+
// Create the parent job
|
|
153
|
+
const job = await jobManager.create({
|
|
154
|
+
type: JOB_TYPE.KERNEL_SESSION,
|
|
155
|
+
accountId: sessionOptions.accountId,
|
|
156
|
+
input: {
|
|
157
|
+
mode: mode.name,
|
|
158
|
+
userInput: sessionOptions.input,
|
|
159
|
+
metadata: sessionOptions.metadata,
|
|
160
|
+
},
|
|
161
|
+
graphState,
|
|
162
|
+
artifactBasePath,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Create and return the session wrapper
|
|
166
|
+
const artifactRouter = sessionOptions.artifactRouter ?? defaultArtifactRouter;
|
|
167
|
+
|
|
168
|
+
return new KernelSessionImpl({
|
|
169
|
+
job,
|
|
170
|
+
mode,
|
|
171
|
+
modeName: mode.name,
|
|
172
|
+
jobManager,
|
|
173
|
+
eventAppender,
|
|
174
|
+
artifactManager,
|
|
175
|
+
artifactRouter,
|
|
176
|
+
mindRegistry,
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async getSession(sessionId: string): Promise<KernelSession | null> {
|
|
181
|
+
const job = await jobManager.get(sessionId);
|
|
182
|
+
|
|
183
|
+
if (!job) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Verify it's a kernel-session job
|
|
188
|
+
if (job.type !== JOB_TYPE.KERNEL_SESSION) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return wrapJobAsSession(job);
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
async listSessions(filter?: SessionFilter): Promise<KernelSession[]> {
|
|
196
|
+
// Use job manager to list kernel-session jobs
|
|
197
|
+
const result = await jobManager.list({
|
|
198
|
+
accountId: filter?.accountId ?? '', // Required by job manager
|
|
199
|
+
type: JOB_TYPE.KERNEL_SESSION,
|
|
200
|
+
status: filter?.status as Parameters<typeof jobManager.list>[0]['status'],
|
|
201
|
+
limit: filter?.limit,
|
|
202
|
+
offset: filter?.offset,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Filter by mode if specified
|
|
206
|
+
let jobs = result.jobs;
|
|
207
|
+
if (filter?.mode) {
|
|
208
|
+
jobs = jobs.filter((job) => {
|
|
209
|
+
const input = job.input as { mode?: string } | undefined;
|
|
210
|
+
return input?.mode === filter.mode;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Wrap jobs as sessions
|
|
215
|
+
return Promise.all(jobs.map(wrapJobAsSession));
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
registerMind(name: string, config: MindConfig): void {
|
|
219
|
+
mindRegistry.register(name, config);
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
getMind(name: string): MindConfig | undefined {
|
|
223
|
+
return mindRegistry.get(name);
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
registerMode(mode: CoordinationMode): void {
|
|
227
|
+
modeRegistry.register(mode);
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
getMode(name: string): CoordinationMode | undefined {
|
|
231
|
+
return modeRegistry.get(name);
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
listMindTypes(): string[] {
|
|
235
|
+
return mindRegistry.list();
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
listModeNames(): string[] {
|
|
239
|
+
return modeRegistry.list();
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// ABOUTME: Mind registry for storing named mind configurations
|
|
2
|
+
// ABOUTME: Allows registering and retrieving mind configs by name
|
|
3
|
+
|
|
4
|
+
import type { MindConfig } from '@inf-minds/mindkit';
|
|
5
|
+
import type { MindRegistry } from '../types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a new mind registry.
|
|
9
|
+
*
|
|
10
|
+
* The registry stores named mind configurations that can be referenced
|
|
11
|
+
* in coordination mode graph nodes.
|
|
12
|
+
*
|
|
13
|
+
* @returns A new MindRegistry instance
|
|
14
|
+
*/
|
|
15
|
+
export function createMindRegistry(): MindRegistry {
|
|
16
|
+
const minds = new Map<string, MindConfig>();
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
register(name: string, config: MindConfig): void {
|
|
20
|
+
if (minds.has(name)) {
|
|
21
|
+
throw new Error(`Mind '${name}' is already registered`);
|
|
22
|
+
}
|
|
23
|
+
minds.set(name, config);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
get(name: string): MindConfig | undefined {
|
|
27
|
+
return minds.get(name);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
has(name: string): boolean {
|
|
31
|
+
return minds.has(name);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
list(): string[] {
|
|
35
|
+
return Array.from(minds.keys());
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// ABOUTME: Mode registry for storing named coordination modes
|
|
2
|
+
// ABOUTME: Allows registering and retrieving modes by name
|
|
3
|
+
|
|
4
|
+
import type { CoordinationMode } from '@inf-minds/coordination-modes';
|
|
5
|
+
import type { ModeRegistry } from '../types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a new mode registry.
|
|
9
|
+
*
|
|
10
|
+
* The registry stores coordination modes that can be used when
|
|
11
|
+
* creating kernel sessions.
|
|
12
|
+
*
|
|
13
|
+
* @returns A new ModeRegistry instance
|
|
14
|
+
*/
|
|
15
|
+
export function createModeRegistry(): ModeRegistry {
|
|
16
|
+
const modes = new Map<string, CoordinationMode>();
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
register(mode: CoordinationMode): void {
|
|
20
|
+
if (modes.has(mode.name)) {
|
|
21
|
+
throw new Error(`Mode '${mode.name}' is already registered`);
|
|
22
|
+
}
|
|
23
|
+
modes.set(mode.name, mode);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
get(name: string): CoordinationMode | undefined {
|
|
27
|
+
return modes.get(name);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
has(name: string): boolean {
|
|
31
|
+
return modes.has(name);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
list(): string[] {
|
|
35
|
+
return Array.from(modes.keys());
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|