cognitive-modules-cli 1.2.0 → 1.3.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/README.md +21 -13
- package/dist/cli.js +35 -1
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +344 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +1 -0
- package/dist/modules/loader.js +7 -2
- package/dist/modules/runner.js +7 -2
- package/dist/modules/subagent.d.ts +65 -0
- package/dist/modules/subagent.js +185 -0
- package/dist/server/http.d.ts +20 -0
- package/dist/server/http.js +243 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +4 -0
- package/package.json +4 -1
- package/src/cli.ts +36 -1
- package/src/mcp/index.ts +5 -0
- package/src/mcp/server.ts +403 -0
- package/src/modules/index.ts +1 -0
- package/src/modules/loader.ts +8 -2
- package/src/modules/runner.ts +6 -2
- package/src/modules/subagent.ts +275 -0
- package/src/server/http.ts +316 -0
- package/src/server/index.ts +6 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cognitive Modules MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides MCP (Model Context Protocol) interface for Claude Code, Cursor, etc.
|
|
5
|
+
*
|
|
6
|
+
* Start with:
|
|
7
|
+
* cog mcp
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import {
|
|
13
|
+
CallToolRequestSchema,
|
|
14
|
+
ListToolsRequestSchema,
|
|
15
|
+
ListResourcesRequestSchema,
|
|
16
|
+
ReadResourceRequestSchema,
|
|
17
|
+
ListPromptsRequestSchema,
|
|
18
|
+
GetPromptRequestSchema,
|
|
19
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
|
|
21
|
+
import { loadModule, findModule, listModules, getDefaultSearchPaths } from '../modules/loader.js';
|
|
22
|
+
import { runModule } from '../modules/runner.js';
|
|
23
|
+
import { getProvider } from '../providers/index.js';
|
|
24
|
+
import type { CognitiveModule, ModuleResult } from '../types.js';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Server Setup
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
const server = new Server(
|
|
31
|
+
{
|
|
32
|
+
name: 'cognitive-modules',
|
|
33
|
+
version: '1.2.0',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
capabilities: {
|
|
37
|
+
tools: {},
|
|
38
|
+
resources: {},
|
|
39
|
+
prompts: {},
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const cwd = process.cwd();
|
|
45
|
+
const searchPaths = getDefaultSearchPaths(cwd);
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Tools
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
52
|
+
return {
|
|
53
|
+
tools: [
|
|
54
|
+
{
|
|
55
|
+
name: 'cognitive_run',
|
|
56
|
+
description: 'Run a Cognitive Module to get structured AI analysis results',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
module: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: 'Module name, e.g. "code-reviewer", "task-prioritizer"',
|
|
63
|
+
},
|
|
64
|
+
args: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Input arguments, e.g. code snippet or task list',
|
|
67
|
+
},
|
|
68
|
+
provider: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'LLM provider (optional), e.g. "openai", "anthropic"',
|
|
71
|
+
},
|
|
72
|
+
model: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'Model name (optional), e.g. "gpt-4o", "claude-3-5-sonnet"',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ['module', 'args'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'cognitive_list',
|
|
82
|
+
description: 'List all installed Cognitive Modules',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'cognitive_info',
|
|
90
|
+
description: 'Get detailed information about a Cognitive Module',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
module: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Module name',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ['module'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
107
|
+
const { name, arguments: args } = request.params;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
switch (name) {
|
|
111
|
+
case 'cognitive_run': {
|
|
112
|
+
const { module: moduleName, args: inputArgs, provider: providerName, model } = args as {
|
|
113
|
+
module: string;
|
|
114
|
+
args: string;
|
|
115
|
+
provider?: string;
|
|
116
|
+
model?: string;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Find module
|
|
120
|
+
const moduleData = await findModule(moduleName, searchPaths);
|
|
121
|
+
if (!moduleData) {
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: 'text',
|
|
126
|
+
text: JSON.stringify({ ok: false, error: `Module '${moduleName}' not found` }),
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create provider
|
|
133
|
+
const provider = getProvider(providerName, model);
|
|
134
|
+
|
|
135
|
+
// Run module
|
|
136
|
+
const result = await runModule(moduleData, provider, {
|
|
137
|
+
input: { query: inputArgs, code: inputArgs },
|
|
138
|
+
useV22: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'text',
|
|
145
|
+
text: JSON.stringify(result, null, 2),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case 'cognitive_list': {
|
|
152
|
+
const modules = await listModules(searchPaths);
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: JSON.stringify(
|
|
158
|
+
{
|
|
159
|
+
modules: modules.map((m) => ({
|
|
160
|
+
name: m.name,
|
|
161
|
+
location: m.location,
|
|
162
|
+
format: m.format,
|
|
163
|
+
tier: m.tier,
|
|
164
|
+
})),
|
|
165
|
+
count: modules.length,
|
|
166
|
+
},
|
|
167
|
+
null,
|
|
168
|
+
2
|
|
169
|
+
),
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
case 'cognitive_info': {
|
|
176
|
+
const { module: moduleName } = args as { module: string };
|
|
177
|
+
|
|
178
|
+
const moduleData = await findModule(moduleName, searchPaths);
|
|
179
|
+
if (!moduleData) {
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: JSON.stringify({ ok: false, error: `Module '${moduleName}' not found` }),
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: JSON.stringify(
|
|
195
|
+
{
|
|
196
|
+
ok: true,
|
|
197
|
+
name: moduleData.name,
|
|
198
|
+
version: moduleData.version,
|
|
199
|
+
responsibility: moduleData.responsibility,
|
|
200
|
+
tier: moduleData.tier,
|
|
201
|
+
format: moduleData.format,
|
|
202
|
+
inputSchema: moduleData.inputSchema,
|
|
203
|
+
outputSchema: moduleData.outputSchema,
|
|
204
|
+
},
|
|
205
|
+
null,
|
|
206
|
+
2
|
|
207
|
+
),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
default:
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: 'text',
|
|
218
|
+
text: JSON.stringify({ ok: false, error: `Unknown tool: ${name}` }),
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: JSON.stringify({
|
|
229
|
+
ok: false,
|
|
230
|
+
error: error instanceof Error ? error.message : String(error),
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// =============================================================================
|
|
239
|
+
// Resources
|
|
240
|
+
// =============================================================================
|
|
241
|
+
|
|
242
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
243
|
+
const modules = await listModules(searchPaths);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
resources: [
|
|
247
|
+
{
|
|
248
|
+
uri: 'cognitive://modules',
|
|
249
|
+
name: 'All Modules',
|
|
250
|
+
description: 'List of all installed Cognitive Modules',
|
|
251
|
+
mimeType: 'application/json',
|
|
252
|
+
},
|
|
253
|
+
...modules.map((m) => ({
|
|
254
|
+
uri: `cognitive://module/${m.name}`,
|
|
255
|
+
name: m.name,
|
|
256
|
+
description: m.responsibility || `Cognitive Module: ${m.name}`,
|
|
257
|
+
mimeType: 'text/markdown',
|
|
258
|
+
})),
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
264
|
+
const { uri } = request.params;
|
|
265
|
+
|
|
266
|
+
if (uri === 'cognitive://modules') {
|
|
267
|
+
const modules = await listModules(searchPaths);
|
|
268
|
+
return {
|
|
269
|
+
contents: [
|
|
270
|
+
{
|
|
271
|
+
uri,
|
|
272
|
+
mimeType: 'application/json',
|
|
273
|
+
text: JSON.stringify(modules.map((m) => m.name), null, 2),
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const match = uri.match(/^cognitive:\/\/module\/(.+)$/);
|
|
280
|
+
if (match) {
|
|
281
|
+
const moduleName = match[1];
|
|
282
|
+
const moduleData = await findModule(moduleName, searchPaths);
|
|
283
|
+
|
|
284
|
+
if (!moduleData) {
|
|
285
|
+
return {
|
|
286
|
+
contents: [
|
|
287
|
+
{
|
|
288
|
+
uri,
|
|
289
|
+
mimeType: 'text/plain',
|
|
290
|
+
text: `Module '${moduleName}' not found`,
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
contents: [
|
|
298
|
+
{
|
|
299
|
+
uri,
|
|
300
|
+
mimeType: 'text/markdown',
|
|
301
|
+
text: moduleData.prompt,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
contents: [
|
|
309
|
+
{
|
|
310
|
+
uri,
|
|
311
|
+
mimeType: 'text/plain',
|
|
312
|
+
text: `Unknown resource: ${uri}`,
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// =============================================================================
|
|
319
|
+
// Prompts
|
|
320
|
+
// =============================================================================
|
|
321
|
+
|
|
322
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
323
|
+
return {
|
|
324
|
+
prompts: [
|
|
325
|
+
{
|
|
326
|
+
name: 'code_review',
|
|
327
|
+
description: 'Generate a code review prompt',
|
|
328
|
+
arguments: [
|
|
329
|
+
{
|
|
330
|
+
name: 'code',
|
|
331
|
+
description: 'The code to review',
|
|
332
|
+
required: true,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'task_prioritize',
|
|
338
|
+
description: 'Generate a task prioritization prompt',
|
|
339
|
+
arguments: [
|
|
340
|
+
{
|
|
341
|
+
name: 'tasks',
|
|
342
|
+
description: 'The tasks to prioritize',
|
|
343
|
+
required: true,
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
352
|
+
const { name, arguments: args } = request.params;
|
|
353
|
+
|
|
354
|
+
switch (name) {
|
|
355
|
+
case 'code_review': {
|
|
356
|
+
const code = args?.code ?? '';
|
|
357
|
+
return {
|
|
358
|
+
messages: [
|
|
359
|
+
{
|
|
360
|
+
role: 'user',
|
|
361
|
+
content: {
|
|
362
|
+
type: 'text',
|
|
363
|
+
text: `Please use the cognitive_run tool to review the following code:\n\n\`\`\`\n${code}\n\`\`\`\n\nCall: cognitive_run("code-reviewer", "${code.slice(0, 100)}...")`,
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
case 'task_prioritize': {
|
|
371
|
+
const tasks = args?.tasks ?? '';
|
|
372
|
+
return {
|
|
373
|
+
messages: [
|
|
374
|
+
{
|
|
375
|
+
role: 'user',
|
|
376
|
+
content: {
|
|
377
|
+
type: 'text',
|
|
378
|
+
text: `Please use the cognitive_run tool to prioritize the following tasks:\n\n${tasks}\n\nCall: cognitive_run("task-prioritizer", "${tasks}")`,
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
default:
|
|
386
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// =============================================================================
|
|
391
|
+
// Server Start
|
|
392
|
+
// =============================================================================
|
|
393
|
+
|
|
394
|
+
export async function serve(): Promise<void> {
|
|
395
|
+
const transport = new StdioServerTransport();
|
|
396
|
+
await server.connect(transport);
|
|
397
|
+
console.error('Cognitive Modules MCP Server started');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Allow running directly
|
|
401
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
402
|
+
serve().catch(console.error);
|
|
403
|
+
}
|
package/src/modules/index.ts
CHANGED
package/src/modules/loader.ts
CHANGED
|
@@ -131,11 +131,17 @@ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
|
|
|
131
131
|
schema_output_alias: (compatRaw.schema_output_alias as 'data' | 'output') ?? 'data'
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
// Parse meta config (including risk_rule)
|
|
134
|
+
// Parse meta config (including risk_rule) with validation
|
|
135
135
|
const metaRaw = (manifest.meta as Record<string, unknown>) || {};
|
|
136
|
+
const rawRiskRule = metaRaw.risk_rule as string | undefined;
|
|
137
|
+
const validRiskRules = ['max_changes_risk', 'max_issues_risk', 'explicit'];
|
|
138
|
+
const validatedRiskRule = rawRiskRule && validRiskRules.includes(rawRiskRule)
|
|
139
|
+
? rawRiskRule as 'max_changes_risk' | 'max_issues_risk' | 'explicit'
|
|
140
|
+
: undefined;
|
|
141
|
+
|
|
136
142
|
const metaConfig: MetaConfig = {
|
|
137
143
|
required: metaRaw.required as string[] | undefined,
|
|
138
|
-
risk_rule:
|
|
144
|
+
risk_rule: validatedRiskRule,
|
|
139
145
|
};
|
|
140
146
|
|
|
141
147
|
return {
|
package/src/modules/runner.ts
CHANGED
|
@@ -80,9 +80,13 @@ function repairEnvelope(
|
|
|
80
80
|
if (!meta.risk) {
|
|
81
81
|
meta.risk = aggregateRisk(data, riskRule);
|
|
82
82
|
}
|
|
83
|
-
// Trim whitespace only (lossless),
|
|
83
|
+
// Trim whitespace only (lossless), validate is valid RiskLevel
|
|
84
84
|
if (typeof meta.risk === 'string') {
|
|
85
|
-
|
|
85
|
+
const trimmedRisk = meta.risk.trim().toLowerCase();
|
|
86
|
+
const validRisks = ['none', 'low', 'medium', 'high'];
|
|
87
|
+
meta.risk = validRisks.includes(trimmedRisk) ? trimmedRisk : 'medium';
|
|
88
|
+
} else {
|
|
89
|
+
meta.risk = 'medium'; // Default for invalid type
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
// Repair explain
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent - Orchestrate module calls with isolated execution contexts.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - @call:module-name - Call another module
|
|
6
|
+
* - @call:module-name(args) - Call with arguments
|
|
7
|
+
* - context: fork - Isolated execution (no shared state)
|
|
8
|
+
* - context: main - Shared execution (default)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
CognitiveModule,
|
|
13
|
+
ModuleResult,
|
|
14
|
+
ModuleInput,
|
|
15
|
+
Provider,
|
|
16
|
+
EnvelopeResponseV22
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
import { loadModule, findModule, getDefaultSearchPaths } from './loader.js';
|
|
19
|
+
import { runModule } from './runner.js';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export interface SubagentContext {
|
|
26
|
+
parentId: string | null;
|
|
27
|
+
depth: number;
|
|
28
|
+
maxDepth: number;
|
|
29
|
+
results: Record<string, unknown>;
|
|
30
|
+
isolated: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CallDirective {
|
|
34
|
+
module: string;
|
|
35
|
+
args: string;
|
|
36
|
+
match: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SubagentRunOptions {
|
|
40
|
+
input?: ModuleInput;
|
|
41
|
+
validateInput?: boolean;
|
|
42
|
+
validateOutput?: boolean;
|
|
43
|
+
maxDepth?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Context Management
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a new root context
|
|
52
|
+
*/
|
|
53
|
+
export function createContext(maxDepth: number = 5): SubagentContext {
|
|
54
|
+
return {
|
|
55
|
+
parentId: null,
|
|
56
|
+
depth: 0,
|
|
57
|
+
maxDepth,
|
|
58
|
+
results: {},
|
|
59
|
+
isolated: false
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fork context (isolated - no inherited results)
|
|
65
|
+
*/
|
|
66
|
+
export function forkContext(ctx: SubagentContext, moduleName: string): SubagentContext {
|
|
67
|
+
return {
|
|
68
|
+
parentId: moduleName,
|
|
69
|
+
depth: ctx.depth + 1,
|
|
70
|
+
maxDepth: ctx.maxDepth,
|
|
71
|
+
results: {},
|
|
72
|
+
isolated: true
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extend context (shared - inherits results)
|
|
78
|
+
*/
|
|
79
|
+
export function extendContext(ctx: SubagentContext, moduleName: string): SubagentContext {
|
|
80
|
+
return {
|
|
81
|
+
parentId: moduleName,
|
|
82
|
+
depth: ctx.depth + 1,
|
|
83
|
+
maxDepth: ctx.maxDepth,
|
|
84
|
+
results: { ...ctx.results },
|
|
85
|
+
isolated: false
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Call Parsing
|
|
91
|
+
// =============================================================================
|
|
92
|
+
|
|
93
|
+
// Pattern to match @call:module-name or @call:module-name(args)
|
|
94
|
+
const CALL_PATTERN = /@call:([a-zA-Z0-9_-]+)(?:\(([^)]*)\))?/g;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse @call directives from text
|
|
98
|
+
*/
|
|
99
|
+
export function parseCalls(text: string): CallDirective[] {
|
|
100
|
+
const calls: CallDirective[] = [];
|
|
101
|
+
let match: RegExpExecArray | null;
|
|
102
|
+
|
|
103
|
+
// Reset regex state
|
|
104
|
+
CALL_PATTERN.lastIndex = 0;
|
|
105
|
+
|
|
106
|
+
while ((match = CALL_PATTERN.exec(text)) !== null) {
|
|
107
|
+
calls.push({
|
|
108
|
+
module: match[1],
|
|
109
|
+
args: match[2] || '',
|
|
110
|
+
match: match[0]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return calls;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Replace @call directives with their results
|
|
119
|
+
*/
|
|
120
|
+
export function substituteCallResults(
|
|
121
|
+
text: string,
|
|
122
|
+
callResults: Record<string, unknown>
|
|
123
|
+
): string {
|
|
124
|
+
let result = text;
|
|
125
|
+
|
|
126
|
+
for (const [callStr, callResult] of Object.entries(callResults)) {
|
|
127
|
+
const resultStr = typeof callResult === 'object'
|
|
128
|
+
? JSON.stringify(callResult, null, 2)
|
|
129
|
+
: String(callResult);
|
|
130
|
+
|
|
131
|
+
result = result.replace(callStr, `[Result from ${callStr}]:\n${resultStr}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// Orchestrator
|
|
139
|
+
// =============================================================================
|
|
140
|
+
|
|
141
|
+
export class SubagentOrchestrator {
|
|
142
|
+
private provider: Provider;
|
|
143
|
+
private running: Set<string> = new Set();
|
|
144
|
+
private cwd: string;
|
|
145
|
+
|
|
146
|
+
constructor(provider: Provider, cwd: string = process.cwd()) {
|
|
147
|
+
this.provider = provider;
|
|
148
|
+
this.cwd = cwd;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Run a module with subagent support.
|
|
153
|
+
* Recursively resolves @call directives before final execution.
|
|
154
|
+
*/
|
|
155
|
+
async run(
|
|
156
|
+
moduleName: string,
|
|
157
|
+
options: SubagentRunOptions = {},
|
|
158
|
+
context?: SubagentContext
|
|
159
|
+
): Promise<ModuleResult> {
|
|
160
|
+
const {
|
|
161
|
+
input = {},
|
|
162
|
+
validateInput = true,
|
|
163
|
+
validateOutput = true,
|
|
164
|
+
maxDepth = 5
|
|
165
|
+
} = options;
|
|
166
|
+
|
|
167
|
+
// Initialize context
|
|
168
|
+
const ctx = context ?? createContext(maxDepth);
|
|
169
|
+
|
|
170
|
+
// Check depth limit
|
|
171
|
+
if (ctx.depth > ctx.maxDepth) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Max subagent depth (${ctx.maxDepth}) exceeded. Check for circular calls.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Prevent circular calls
|
|
178
|
+
if (this.running.has(moduleName)) {
|
|
179
|
+
throw new Error(`Circular call detected: ${moduleName}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.running.add(moduleName);
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// Find and load module
|
|
186
|
+
const searchPaths = getDefaultSearchPaths(this.cwd);
|
|
187
|
+
const module = await findModule(moduleName, searchPaths);
|
|
188
|
+
|
|
189
|
+
if (!module) {
|
|
190
|
+
throw new Error(`Module not found: ${moduleName}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check if this module wants isolated execution
|
|
194
|
+
const moduleContextMode = module.context ?? 'main';
|
|
195
|
+
|
|
196
|
+
// Parse @call directives from prompt
|
|
197
|
+
const calls = parseCalls(module.prompt);
|
|
198
|
+
const callResults: Record<string, unknown> = {};
|
|
199
|
+
|
|
200
|
+
// Resolve each @call directive
|
|
201
|
+
for (const call of calls) {
|
|
202
|
+
const childModule = call.module;
|
|
203
|
+
const childArgs = call.args;
|
|
204
|
+
|
|
205
|
+
// Prepare child input
|
|
206
|
+
const childInput: ModuleInput = childArgs
|
|
207
|
+
? { query: childArgs, code: childArgs }
|
|
208
|
+
: { ...input };
|
|
209
|
+
|
|
210
|
+
// Determine child context
|
|
211
|
+
const childContext = moduleContextMode === 'fork'
|
|
212
|
+
? forkContext(ctx, moduleName)
|
|
213
|
+
: extendContext(ctx, moduleName);
|
|
214
|
+
|
|
215
|
+
// Recursively run child module
|
|
216
|
+
const childResult = await this.run(
|
|
217
|
+
childModule,
|
|
218
|
+
{
|
|
219
|
+
input: childInput,
|
|
220
|
+
validateInput: false, // Skip validation for @call args
|
|
221
|
+
validateOutput
|
|
222
|
+
},
|
|
223
|
+
childContext
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Store result
|
|
227
|
+
if (childResult.ok && 'data' in childResult) {
|
|
228
|
+
callResults[call.match] = childResult.data;
|
|
229
|
+
} else if ('error' in childResult) {
|
|
230
|
+
callResults[call.match] = { error: childResult.error };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Substitute call results into prompt
|
|
235
|
+
let modifiedModule = module;
|
|
236
|
+
if (Object.keys(callResults).length > 0) {
|
|
237
|
+
const modifiedPrompt = substituteCallResults(module.prompt, callResults);
|
|
238
|
+
modifiedModule = {
|
|
239
|
+
...module,
|
|
240
|
+
prompt: modifiedPrompt + '\n\n## Subagent Results Available\nThe @call results have been injected above. Use them in your response.\n'
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Run the module
|
|
245
|
+
const result = await runModule(modifiedModule, this.provider, {
|
|
246
|
+
input,
|
|
247
|
+
verbose: false,
|
|
248
|
+
useV22: true
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Store result in context
|
|
252
|
+
if (result.ok && 'data' in result) {
|
|
253
|
+
ctx.results[moduleName] = result.data;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return result;
|
|
257
|
+
|
|
258
|
+
} finally {
|
|
259
|
+
this.running.delete(moduleName);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Convenience function to run a module with subagent support
|
|
266
|
+
*/
|
|
267
|
+
export async function runWithSubagents(
|
|
268
|
+
moduleName: string,
|
|
269
|
+
provider: Provider,
|
|
270
|
+
options: SubagentRunOptions & { cwd?: string } = {}
|
|
271
|
+
): Promise<ModuleResult> {
|
|
272
|
+
const { cwd = process.cwd(), ...runOptions } = options;
|
|
273
|
+
const orchestrator = new SubagentOrchestrator(provider, cwd);
|
|
274
|
+
return orchestrator.run(moduleName, runOptions);
|
|
275
|
+
}
|