0xray 3.4.7 → 3.4.9
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/AGENTS-consumer.md +1 -1
- package/AGENTS.md +1 -1
- package/README.md +15 -1
- package/SKILLS.md +1 -1
- package/dist/AGENTS.md +1 -1
- package/dist/CHANGELOG.md +28 -0
- package/dist/README.md +15 -1
- package/dist/SKILLS.md +1 -1
- package/dist/governance/governance-service.js +3 -1
- package/dist/governance/llm-governance-provider.d.ts +2 -0
- package/dist/governance/llm-governance-provider.js +146 -104
- package/dist/mcps/governance.server.d.ts +1 -0
- package/dist/mcps/governance.server.js +79 -8
- package/dist/mcps/knowledge-skills/code-review.server.js +1 -1
- package/dist/mcps/knowledge-skills/security-audit.server.js +1 -1
- package/dist/mcps/orchestrator/config/memory-routing-bridge.js +2 -1
- package/dist/mcps/orchestrator/execution/execution-planner.d.ts +2 -0
- package/dist/mcps/orchestrator/execution/execution-planner.js +22 -11
- package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +13 -1
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +40 -4
- package/dist/mcps/orchestrator/handlers/task-handler.js +13 -6
- package/dist/mcps/orchestrator/server.js +9 -1
- package/dist/mcps/orchestrator/types.d.ts +4 -1
- package/dist/mcps/researcher.server.js +1 -1
- package/dist/nucleus/autonomy-kernel.d.ts +5 -1
- package/dist/nucleus/autonomy-kernel.js +20 -16
- package/dist/skills/orchestrator/SKILL.md +3 -2
- package/package.json +1 -1
- package/scripts/node/release-gate.mjs +14 -6
- package/src/mcps/governance.server.ts +81 -8
- package/src/mcps/knowledge-skills/code-review.server.ts +1 -1
- package/src/mcps/knowledge-skills/security-audit.server.ts +1 -1
- package/src/mcps/orchestrator/config/memory-routing-bridge.ts +3 -1
- package/src/mcps/orchestrator/execution/execution-planner.ts +26 -11
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +62 -5
- package/src/mcps/orchestrator/handlers/task-handler.ts +22 -6
- package/src/mcps/orchestrator/server.ts +9 -1
- package/src/mcps/orchestrator/types.ts +4 -1
- package/src/mcps/researcher.server.ts +1 -1
- package/src/skills/orchestrator/SKILL.md +3 -2
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { getAgentCapabilitiesManager } from '../config/agent-capabilities.js';
|
|
7
7
|
import { getProvider, toMemoryTask, fromMemoryTask, } from '../config/memory-routing-bridge.js';
|
|
8
|
+
/** Dependency count from task-ID list or numeric hint from analyze-complexity */
|
|
9
|
+
export function dependencyCount(deps) {
|
|
10
|
+
if (deps == null)
|
|
11
|
+
return 0;
|
|
12
|
+
if (typeof deps === 'number' && Number.isFinite(deps)) {
|
|
13
|
+
return Math.max(0, Math.round(deps));
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(deps))
|
|
16
|
+
return deps.length;
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
function isDependencyIdList(deps) {
|
|
20
|
+
return Array.isArray(deps) && deps.length > 0;
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
* Execution Planner
|
|
10
24
|
* Plans and optimizes task execution strategies
|
|
@@ -33,8 +47,8 @@ export class ExecutionPlanner {
|
|
|
33
47
|
if (!task.type) {
|
|
34
48
|
errors.push(`Task ${i}: Missing required field 'type'`);
|
|
35
49
|
}
|
|
36
|
-
// Check dependencies exist
|
|
37
|
-
if (task.dependencies && task.id) {
|
|
50
|
+
// Check task-ID dependencies exist (numeric hints skip graph validation)
|
|
51
|
+
if (isDependencyIdList(task.dependencies) && task.id) {
|
|
38
52
|
const taskIds = new Set(tasks.map(t => t.id).filter((id) => !!id));
|
|
39
53
|
for (const depId of task.dependencies) {
|
|
40
54
|
if (!taskIds.has(depId)) {
|
|
@@ -117,11 +131,11 @@ export class ExecutionPlanner {
|
|
|
117
131
|
const independentTasks = [];
|
|
118
132
|
const dependentTasks = [];
|
|
119
133
|
for (const task of sortedTasks) {
|
|
120
|
-
if (
|
|
121
|
-
|
|
134
|
+
if (isDependencyIdList(task.dependencies)) {
|
|
135
|
+
dependentTasks.push(task);
|
|
122
136
|
}
|
|
123
137
|
else {
|
|
124
|
-
|
|
138
|
+
independentTasks.push(task);
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
// Assign independent tasks in parallel
|
|
@@ -140,7 +154,7 @@ export class ExecutionPlanner {
|
|
|
140
154
|
if (visited.has(task.id))
|
|
141
155
|
return;
|
|
142
156
|
visited.add(task.id);
|
|
143
|
-
if (task.dependencies) {
|
|
157
|
+
if (isDependencyIdList(task.dependencies)) {
|
|
144
158
|
for (const depId of task.dependencies) {
|
|
145
159
|
const depTask = taskMap.get(depId);
|
|
146
160
|
if (depTask)
|
|
@@ -263,10 +277,7 @@ export class ExecutionPlanner {
|
|
|
263
277
|
if (task.estimatedComplexity) {
|
|
264
278
|
complexity = (complexity + task.estimatedComplexity) / 2;
|
|
265
279
|
}
|
|
266
|
-
|
|
267
|
-
if (task.dependencies) {
|
|
268
|
-
complexity += task.dependencies.length * 5;
|
|
269
|
-
}
|
|
280
|
+
complexity += dependencyCount(task.dependencies) * 5;
|
|
270
281
|
if (task.metadata?.memoryComplexityBoost !== undefined) {
|
|
271
282
|
complexity += task.metadata.memoryComplexityBoost;
|
|
272
283
|
}
|
|
@@ -295,7 +306,7 @@ export class ExecutionPlanner {
|
|
|
295
306
|
*/
|
|
296
307
|
calculateParallelPotential(tasks) {
|
|
297
308
|
// Tasks without dependencies can run in parallel
|
|
298
|
-
const independent = tasks.filter(t => !t.dependencies
|
|
309
|
+
const independent = tasks.filter((t) => !isDependencyIdList(t.dependencies));
|
|
299
310
|
return independent.length / tasks.length;
|
|
300
311
|
}
|
|
301
312
|
}
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
* Handles complexity analysis requests
|
|
5
5
|
*/
|
|
6
6
|
import type { OrchestrationTask } from '../types.js';
|
|
7
|
+
type AnalyzeComplexityInput = {
|
|
8
|
+
id?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
files?: string[];
|
|
12
|
+
dependencyCount?: number;
|
|
13
|
+
dependencies?: string[] | number;
|
|
14
|
+
riskLevel?: string;
|
|
15
|
+
estimatedComplexity?: number;
|
|
16
|
+
priority?: OrchestrationTask['priority'];
|
|
17
|
+
};
|
|
7
18
|
/**
|
|
8
19
|
* Complexity Handler
|
|
9
20
|
* Processes analyze-complexity requests
|
|
@@ -14,7 +25,7 @@ export declare class ComplexityHandler {
|
|
|
14
25
|
* Handle analyze-complexity request
|
|
15
26
|
*/
|
|
16
27
|
handleAnalyzeComplexity(args: {
|
|
17
|
-
tasks:
|
|
28
|
+
tasks: AnalyzeComplexityInput[];
|
|
18
29
|
}, asideId?: string): Promise<{
|
|
19
30
|
content: Array<{
|
|
20
31
|
type: string;
|
|
@@ -30,3 +41,4 @@ export declare class ComplexityHandler {
|
|
|
30
41
|
*/
|
|
31
42
|
private formatComplexityResponse;
|
|
32
43
|
}
|
|
44
|
+
export {};
|
|
@@ -7,6 +7,34 @@ import { frameworkLogger } from '../../../core/framework-logger.js';
|
|
|
7
7
|
import { buildLeadDevPlan, isLeadDevModeActive, persistLeadDevPlan, } from '../../../nucleus/autonomy-kernel.js';
|
|
8
8
|
import { getExecutionPlanner } from '../execution/execution-planner.js';
|
|
9
9
|
import { addObservations, extractComplexityObservations } from '../aside-context.js';
|
|
10
|
+
function normalizeAnalyzeTasks(raw) {
|
|
11
|
+
return raw.map((task, index) => {
|
|
12
|
+
let deps;
|
|
13
|
+
if (Array.isArray(task.dependencies)) {
|
|
14
|
+
deps = task.dependencies;
|
|
15
|
+
}
|
|
16
|
+
else if (typeof task.dependencies === 'number' && Number.isFinite(task.dependencies)) {
|
|
17
|
+
deps = task.dependencies;
|
|
18
|
+
}
|
|
19
|
+
else if (typeof task.dependencyCount === 'number' &&
|
|
20
|
+
Number.isFinite(task.dependencyCount)) {
|
|
21
|
+
deps = task.dependencyCount;
|
|
22
|
+
}
|
|
23
|
+
const normalized = {
|
|
24
|
+
id: task.id ?? `task-${index + 1}`,
|
|
25
|
+
description: task.description ?? '',
|
|
26
|
+
type: task.type ?? 'implement',
|
|
27
|
+
};
|
|
28
|
+
if (task.priority)
|
|
29
|
+
normalized.priority = task.priority;
|
|
30
|
+
if (deps !== undefined)
|
|
31
|
+
normalized.dependencies = deps;
|
|
32
|
+
if (task.estimatedComplexity !== undefined) {
|
|
33
|
+
normalized.estimatedComplexity = task.estimatedComplexity;
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
10
38
|
/**
|
|
11
39
|
* Complexity Handler
|
|
12
40
|
* Processes analyze-complexity requests
|
|
@@ -17,7 +45,7 @@ export class ComplexityHandler {
|
|
|
17
45
|
* Handle analyze-complexity request
|
|
18
46
|
*/
|
|
19
47
|
async handleAnalyzeComplexity(args, asideId) {
|
|
20
|
-
const
|
|
48
|
+
const tasks = normalizeAnalyzeTasks(args.tasks ?? []);
|
|
21
49
|
await frameworkLogger.log('orchestrator.server', 'analyze-complexity-start', 'info', { taskCount: tasks.length });
|
|
22
50
|
try {
|
|
23
51
|
const analysis = await this.planner.analyzeTaskComplexity(tasks);
|
|
@@ -27,12 +55,20 @@ export class ComplexityHandler {
|
|
|
27
55
|
}
|
|
28
56
|
const primaryDescription = tasks.map((t) => t.description).filter(Boolean).join(' ');
|
|
29
57
|
const taskTypes = tasks.map((t) => t.type).filter(Boolean);
|
|
30
|
-
const
|
|
31
|
-
|
|
58
|
+
const planTaskInputs = tasks
|
|
59
|
+
.filter((t) => t.description?.trim())
|
|
60
|
+
.map((t) => ({ description: t.description, type: t.type }));
|
|
61
|
+
const leadDevPlan = isLeadDevModeActive() && (primaryDescription || planTaskInputs.length > 0)
|
|
62
|
+
? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'], planTaskInputs)
|
|
32
63
|
: null;
|
|
33
64
|
if (leadDevPlan) {
|
|
34
65
|
try {
|
|
35
|
-
persistLeadDevPlan(leadDevPlan);
|
|
66
|
+
const planPath = persistLeadDevPlan(leadDevPlan);
|
|
67
|
+
await frameworkLogger.log('orchestrator.server', 'lead-dev-plan-persisted', 'info', {
|
|
68
|
+
planPath,
|
|
69
|
+
phaseCount: leadDevPlan.phases.length,
|
|
70
|
+
todoCount: leadDevPlan.phases.reduce((n, p) => n + p.todos.length, 0),
|
|
71
|
+
});
|
|
36
72
|
}
|
|
37
73
|
catch (err) {
|
|
38
74
|
await frameworkLogger.log('orchestrator.server', 'lead-dev-plan-persist-failed', 'warning', { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -49,7 +49,6 @@ export class TaskHandler {
|
|
|
49
49
|
},
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
// Execute orchestration (simulated for MCP server)
|
|
53
52
|
const results = await this.executePlan(executionPlan, sessionId, timeout);
|
|
54
53
|
// Feed per-task outcomes back to memory provider (not aggregate session stats)
|
|
55
54
|
if (memoryProvider.ingestFeedback) {
|
|
@@ -74,6 +73,8 @@ export class TaskHandler {
|
|
|
74
73
|
orchestrationResult.success = results.success;
|
|
75
74
|
orchestrationResult.completedTasks = results.completedTasks;
|
|
76
75
|
orchestrationResult.failedTasks = results.failedTasks;
|
|
76
|
+
orchestrationResult.deferredTasks = results.deferredTasks;
|
|
77
|
+
orchestrationResult.recommendations.push(...results.recommendations);
|
|
77
78
|
orchestrationResult.agentUtilization = results.agentUtilization;
|
|
78
79
|
orchestrationResult.bottlenecks = results.bottlenecks;
|
|
79
80
|
orchestrationResult.recommendations = results.recommendations;
|
|
@@ -111,6 +112,7 @@ export class TaskHandler {
|
|
|
111
112
|
const agentUtilization = {};
|
|
112
113
|
let completedTasks = 0;
|
|
113
114
|
let failedTasks = 0;
|
|
115
|
+
let deferredTasks = 0;
|
|
114
116
|
const agentOutputs = {};
|
|
115
117
|
const taskOutcomes = [];
|
|
116
118
|
const skillServers = new Set([
|
|
@@ -163,20 +165,23 @@ export class TaskHandler {
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
else {
|
|
166
|
-
// For non-MCP agents we still count them as "assigned" (they will be handled by the caller or legacy path)
|
|
167
168
|
for (const task of tasks) {
|
|
168
|
-
|
|
169
|
+
deferredTasks++;
|
|
169
170
|
taskOutcomes.push({
|
|
170
171
|
taskId: task.id,
|
|
171
172
|
agent,
|
|
172
|
-
success:
|
|
173
|
+
success: false,
|
|
173
174
|
durationMs: 0,
|
|
174
175
|
});
|
|
176
|
+
await frameworkLogger.log('orchestrator.task-handler', 'delegate-deferred', 'info', { agent, taskId: task.id, sessionId });
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
180
|
const recommendations = [];
|
|
179
181
|
const bottlenecks = [];
|
|
182
|
+
if (deferredTasks > 0) {
|
|
183
|
+
recommendations.push(`${deferredTasks} task(s) require host delegation (Task/spawn_subagent per lead-dev-plan) — orchestrate-task only invokes MCP consult skills directly`);
|
|
184
|
+
}
|
|
180
185
|
if (plan.agentAssignments.size > 5) {
|
|
181
186
|
recommendations.push('Consider reducing the number of agents for better coordination');
|
|
182
187
|
}
|
|
@@ -186,9 +191,10 @@ export class TaskHandler {
|
|
|
186
191
|
}
|
|
187
192
|
}
|
|
188
193
|
return {
|
|
189
|
-
success: failedTasks === 0,
|
|
194
|
+
success: failedTasks === 0 && deferredTasks === 0,
|
|
190
195
|
completedTasks,
|
|
191
196
|
failedTasks,
|
|
197
|
+
deferredTasks,
|
|
192
198
|
agentUtilization,
|
|
193
199
|
bottlenecks,
|
|
194
200
|
recommendations,
|
|
@@ -232,9 +238,10 @@ export class TaskHandler {
|
|
|
232
238
|
**Session ID:** ${result.sessionId}
|
|
233
239
|
**Success:** ${result.success ? '✅ COMPLETED' : '❌ FAILED'}
|
|
234
240
|
**Duration:** ${result.duration}ms
|
|
235
|
-
**Tasks:** ${result.completedTasks + result.failedTasks} total
|
|
241
|
+
**Tasks:** ${result.completedTasks + result.failedTasks + (result.deferredTasks ?? 0)} total
|
|
236
242
|
- ✅ Completed: ${result.completedTasks}
|
|
237
243
|
- ❌ Failed: ${result.failedTasks}
|
|
244
|
+
- ⏸ Deferred (host delegation required): ${result.deferredTasks ?? 0}
|
|
238
245
|
|
|
239
246
|
**Agent Utilization:**
|
|
240
247
|
${Object.entries(result.agentUtilization)
|
|
@@ -117,7 +117,15 @@ export class OrchestratorServer {
|
|
|
117
117
|
description: { type: 'string' },
|
|
118
118
|
type: { type: 'string' },
|
|
119
119
|
files: { type: 'array', items: { type: 'string' } },
|
|
120
|
-
|
|
120
|
+
dependencyCount: {
|
|
121
|
+
type: 'number',
|
|
122
|
+
description: 'Upstream dependency count (complexity hint only)',
|
|
123
|
+
},
|
|
124
|
+
dependencies: {
|
|
125
|
+
type: 'array',
|
|
126
|
+
items: { type: 'string' },
|
|
127
|
+
description: 'Task IDs this task depends on',
|
|
128
|
+
},
|
|
121
129
|
riskLevel: {
|
|
122
130
|
type: 'string',
|
|
123
131
|
enum: ['low', 'medium', 'high', 'critical'],
|
|
@@ -27,7 +27,8 @@ export interface OrchestrationTask {
|
|
|
27
27
|
description: string;
|
|
28
28
|
type: string;
|
|
29
29
|
priority?: 'critical' | 'high' | 'medium' | 'low';
|
|
30
|
-
|
|
30
|
+
/** Task IDs (orchestrate-task) or dependency count hint (analyze-complexity) */
|
|
31
|
+
dependencies?: string[] | number;
|
|
31
32
|
estimatedComplexity?: number;
|
|
32
33
|
metadata?: OrchestrationTaskMetadata;
|
|
33
34
|
}
|
|
@@ -43,6 +44,8 @@ export interface OrchestrationResult {
|
|
|
43
44
|
success: boolean;
|
|
44
45
|
completedTasks: number;
|
|
45
46
|
failedTasks: number;
|
|
47
|
+
/** Implementation agents not executed by orchestrate-task — lead must delegate via host Task */
|
|
48
|
+
deferredTasks?: number;
|
|
46
49
|
duration: number;
|
|
47
50
|
agentUtilization: Record<string, number>;
|
|
48
51
|
agentOutputs?: Record<string, string>;
|
|
@@ -397,7 +397,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
|
|
|
397
397
|
if (!vote) {
|
|
398
398
|
const abstainReason = memoryContext?.confidence.highConfidenceTrapPresent
|
|
399
399
|
? "No LLM governance provider configured. Repertoire detected a high-confidence ontological trap — route to the recommended agent before proceeding."
|
|
400
|
-
: "No LLM governance provider configured.
|
|
400
|
+
: "No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.";
|
|
401
401
|
return {
|
|
402
402
|
content: [
|
|
403
403
|
{
|
|
@@ -18,6 +18,10 @@ export interface LeadDevPhase {
|
|
|
18
18
|
definitionOfDone: string;
|
|
19
19
|
todos: LeadDevTodo[];
|
|
20
20
|
}
|
|
21
|
+
export interface LeadDevPlanTaskInput {
|
|
22
|
+
description: string;
|
|
23
|
+
type?: string;
|
|
24
|
+
}
|
|
21
25
|
export interface LeadDevPlan {
|
|
22
26
|
active: boolean;
|
|
23
27
|
rules: readonly string[];
|
|
@@ -37,7 +41,7 @@ export interface LeadDevPlan {
|
|
|
37
41
|
export declare function isLeadDevModeActive(): boolean;
|
|
38
42
|
export declare function routeSubagent(taskType: string): SubagentRoute;
|
|
39
43
|
export declare function shouldFlagFullTestSuite(command: string): boolean;
|
|
40
|
-
export declare function buildLeadDevPlan(description: string, taskTypes?: string[]): LeadDevPlan | null;
|
|
44
|
+
export declare function buildLeadDevPlan(description: string, taskTypes?: string[], taskInputs?: LeadDevPlanTaskInput[]): LeadDevPlan | null;
|
|
41
45
|
export declare function buildSessionBootContext(): Record<string, unknown>;
|
|
42
46
|
/** Persist plan for PreToolUse spawn_subagent gate */
|
|
43
47
|
export declare function persistLeadDevPlan(plan: LeadDevPlan, projectRoot?: string): string;
|
|
@@ -57,14 +57,30 @@ export function shouldFlagFullTestSuite(command) {
|
|
|
57
57
|
/\btest:\w+/.test(cmd);
|
|
58
58
|
return isTest && !hasFocusedTarget;
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
function buildImplementationTodos(description, taskTypes, taskInputs, idPrefix) {
|
|
61
|
+
if (taskInputs.length > 0) {
|
|
62
|
+
return taskInputs.map((t, i) => ({
|
|
63
|
+
id: `${idPrefix}.${i + 1}`,
|
|
64
|
+
task: t.description.slice(0, 200),
|
|
65
|
+
subagent: routeSubagent(t.type ?? 'implement'),
|
|
66
|
+
status: 'pending',
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return taskTypes.map((type, i) => ({
|
|
70
|
+
id: `${idPrefix}.${i + 1}`,
|
|
71
|
+
task: description.slice(0, 200),
|
|
72
|
+
subagent: routeSubagent(type),
|
|
73
|
+
status: 'pending',
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
export function buildLeadDevPlan(description, taskTypes = ['implement'], taskInputs = []) {
|
|
61
77
|
if (!isLeadDevModeActive())
|
|
62
78
|
return null;
|
|
63
79
|
const cfg = orchestrationConfig();
|
|
64
80
|
const threshold = cfg.phased_plan_threshold ?? 25;
|
|
65
81
|
const score = scoreComplexity(description, { taskTypes });
|
|
66
82
|
const complexity = score.score;
|
|
67
|
-
const requiresPhasedPlan = complexity > threshold;
|
|
83
|
+
const requiresPhasedPlan = complexity > threshold || taskInputs.length > 1;
|
|
68
84
|
const mandatoryConsults = cfg.auto_consult_major_work !== false && requiresPhasedPlan
|
|
69
85
|
? [...MANDATORY_MAJOR_CONSULTS]
|
|
70
86
|
: [];
|
|
@@ -87,12 +103,7 @@ export function buildLeadDevPlan(description, taskTypes = ['implement']) {
|
|
|
87
103
|
name: 'Implementation',
|
|
88
104
|
goal: 'Execute with best subagent per todo',
|
|
89
105
|
definitionOfDone: 'Affected per-suite tests green',
|
|
90
|
-
todos:
|
|
91
|
-
id: `2.${i + 1}`,
|
|
92
|
-
task: description.slice(0, 200),
|
|
93
|
-
subagent: routeSubagent(type),
|
|
94
|
-
status: 'pending',
|
|
95
|
-
})),
|
|
106
|
+
todos: buildImplementationTodos(description, taskTypes, taskInputs, '2'),
|
|
96
107
|
},
|
|
97
108
|
{
|
|
98
109
|
id: 'phase-3',
|
|
@@ -121,14 +132,7 @@ export function buildLeadDevPlan(description, taskTypes = ['implement']) {
|
|
|
121
132
|
name: 'Direct execution',
|
|
122
133
|
goal: description.slice(0, 120),
|
|
123
134
|
definitionOfDone: 'Task complete with tests green',
|
|
124
|
-
todos:
|
|
125
|
-
{
|
|
126
|
-
id: '1.1',
|
|
127
|
-
task: description.slice(0, 200),
|
|
128
|
-
subagent: routeSubagent(taskTypes[0] ?? 'implement'),
|
|
129
|
-
status: 'pending',
|
|
130
|
-
},
|
|
131
|
-
],
|
|
135
|
+
todos: buildImplementationTodos(description, taskTypes, taskInputs, '1'),
|
|
132
136
|
},
|
|
133
137
|
];
|
|
134
138
|
return {
|
|
@@ -36,8 +36,9 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
|
|
|
36
36
|
|
|
37
37
|
| Step | MCP tool | Notes |
|
|
38
38
|
|------|----------|-------|
|
|
39
|
-
| **Intake + classify** | `analyze-complexity` | Pass `tasks` array;
|
|
40
|
-
| Delegate | `orchestrate-task` |
|
|
39
|
+
| **Intake + classify** | `analyze-complexity` | Pass `tasks` array; persists `.xray/state/lead-dev-plan.json` when mode is on |
|
|
40
|
+
| Delegate (consult) | `orchestrate-task` | Invokes MCP consult skills (code-review, researcher, security-audit, etc.) |
|
|
41
|
+
| Delegate (implement) | **host `Task` / `spawn_subagent`** | `orchestrate-task` **defers** backend-engineer, frontend-engineer, bug-triage — lead must spawn |
|
|
41
42
|
| Monitor | `get-orchestration-status` | Lead updates todos |
|
|
42
43
|
| Major work | researcher + architect-tools + code-review | Auto-listed in plan when complexity > threshold |
|
|
43
44
|
|
package/package.json
CHANGED
|
@@ -36,18 +36,26 @@ function main() {
|
|
|
36
36
|
|
|
37
37
|
try {
|
|
38
38
|
if (verifyOnly) {
|
|
39
|
-
step("1/
|
|
39
|
+
step("1/5 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
|
|
40
40
|
if (!skipDocs) {
|
|
41
|
-
step("2/
|
|
41
|
+
step("2/5 Release docs", "node scripts/node/validate-release-docs.mjs");
|
|
42
42
|
}
|
|
43
|
+
step(
|
|
44
|
+
"3/5 Plugin infrastructure",
|
|
45
|
+
"node scripts/test/validate-plugins-e2e.cjs --structural-only",
|
|
46
|
+
);
|
|
43
47
|
if (!skipSmoke) {
|
|
44
|
-
step("
|
|
48
|
+
step("4/5 Consumer install smoke", "node scripts/node/consumer-install-smoke.mjs");
|
|
45
49
|
}
|
|
46
50
|
} else {
|
|
47
|
-
step("1/
|
|
48
|
-
step("2/
|
|
51
|
+
step("1/5 Build", "npm run build");
|
|
52
|
+
step("2/5 Tests", "npm test");
|
|
53
|
+
step(
|
|
54
|
+
"3/5 Plugin infrastructure",
|
|
55
|
+
"node scripts/test/validate-plugins-e2e.cjs --structural-only",
|
|
56
|
+
);
|
|
49
57
|
if (!skipSmoke) {
|
|
50
|
-
step("
|
|
58
|
+
step("4/5 Consumer install smoke (fresh + upgrade)", "node scripts/node/consumer-install-smoke.mjs");
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -18,6 +18,7 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js
|
|
|
18
18
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
19
19
|
import {
|
|
20
20
|
CallToolRequestSchema,
|
|
21
|
+
isInitializeRequest,
|
|
21
22
|
type CallToolResult,
|
|
22
23
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
23
24
|
import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
|
|
@@ -419,6 +420,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
419
420
|
|
|
420
421
|
/**
|
|
421
422
|
* Run as HTTP server using Streamable HTTP transport (for Grok CLI compatibility).
|
|
423
|
+
* Uses per-session transports so concurrent clients can each initialize independently.
|
|
422
424
|
*/
|
|
423
425
|
async runHttp(port: number = parseInt(process.env.MCP_PORT ?? "3100", 10)): Promise<void> {
|
|
424
426
|
await this.initializeGovernance();
|
|
@@ -429,11 +431,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
429
431
|
host: mcpHost,
|
|
430
432
|
...(allowedHosts ? { allowedHosts } : {}),
|
|
431
433
|
});
|
|
432
|
-
const
|
|
433
|
-
sessionIdGenerator: () => randomUUID(),
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
await this.server.connect(transport as any);
|
|
434
|
+
const transports: Record<string, StreamableHTTPServerTransport> = {};
|
|
437
435
|
|
|
438
436
|
const apiKey = process.env.GOVERNANCE_API_KEY;
|
|
439
437
|
if (apiKey) {
|
|
@@ -455,8 +453,45 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
455
453
|
next();
|
|
456
454
|
});
|
|
457
455
|
|
|
458
|
-
|
|
456
|
+
const mcpPostHandler = async (req: any, res: any) => {
|
|
459
457
|
try {
|
|
458
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
459
|
+
let transport: StreamableHTTPServerTransport | undefined;
|
|
460
|
+
|
|
461
|
+
if (sessionId && transports[sessionId]) {
|
|
462
|
+
transport = transports[sessionId];
|
|
463
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
464
|
+
transport = new StreamableHTTPServerTransport({
|
|
465
|
+
sessionIdGenerator: () => randomUUID(),
|
|
466
|
+
onsessioninitialized: (sid) => {
|
|
467
|
+
if (transport) {
|
|
468
|
+
transports[sid] = transport;
|
|
469
|
+
frameworkLogger.log("governance-mcp", "session-initialized", "info", {
|
|
470
|
+
sessionId: sid,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
transport.onclose = () => {
|
|
476
|
+
const sid = transport?.sessionId;
|
|
477
|
+
if (sid && transports[sid]) {
|
|
478
|
+
delete transports[sid];
|
|
479
|
+
frameworkLogger.log("governance-mcp", "session-closed", "info", { sessionId: sid });
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
const sessionServer = new GovernanceServer();
|
|
483
|
+
await sessionServer.connect(transport as Transport);
|
|
484
|
+
await transport.handleRequest(req, res, req.body);
|
|
485
|
+
return;
|
|
486
|
+
} else {
|
|
487
|
+
res.status(400).json({
|
|
488
|
+
jsonrpc: "2.0",
|
|
489
|
+
error: { code: -32000, message: "Bad Request: No valid session ID provided" },
|
|
490
|
+
id: null,
|
|
491
|
+
});
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
460
495
|
await transport.handleRequest(req, res, req.body);
|
|
461
496
|
} catch (error) {
|
|
462
497
|
frameworkLogger.log("governance-mcp", "http-handler-error", "error", { error: String(error) });
|
|
@@ -464,18 +499,56 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
464
499
|
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error" }, id: null });
|
|
465
500
|
}
|
|
466
501
|
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
app.post("/mcp", mcpPostHandler);
|
|
505
|
+
|
|
506
|
+
app.get("/mcp", async (req: any, res: any) => {
|
|
507
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
508
|
+
if (!sessionId || !transports[sessionId]) {
|
|
509
|
+
res.status(400).send("Invalid or missing session ID");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
try {
|
|
513
|
+
await transports[sessionId].handleRequest(req, res);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
frameworkLogger.log("governance-mcp", "http-sse-error", "error", { error: String(error) });
|
|
516
|
+
if (!res.headersSent) {
|
|
517
|
+
res.status(500).send("Error processing SSE stream");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
app.delete("/mcp", async (req: any, res: any) => {
|
|
523
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
524
|
+
if (!sessionId || !transports[sessionId]) {
|
|
525
|
+
res.status(400).send("Invalid or missing session ID");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
await transports[sessionId].handleRequest(req, res);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
frameworkLogger.log("governance-mcp", "http-delete-error", "error", { error: String(error) });
|
|
532
|
+
if (!res.headersSent) {
|
|
533
|
+
res.status(500).send("Error processing session termination");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
467
536
|
});
|
|
468
537
|
|
|
469
538
|
app.get("/health", (_req: any, res: any) => {
|
|
470
539
|
res.json({ status: "ok", server: "governance" });
|
|
471
540
|
});
|
|
472
541
|
|
|
542
|
+
const closeAllTransports = async () => {
|
|
543
|
+
await Promise.all(Object.values(transports).map((t) => t.close()));
|
|
544
|
+
};
|
|
545
|
+
|
|
473
546
|
app.listen(port, () => {
|
|
474
547
|
frameworkLogger.log("governance-mcp", "http-listening", "info", { port });
|
|
475
548
|
});
|
|
476
549
|
|
|
477
|
-
process.on("SIGINT", () => {
|
|
478
|
-
process.on("SIGTERM", () => {
|
|
550
|
+
process.on("SIGINT", () => { void closeAllTransports().then(() => process.exit(0)); });
|
|
551
|
+
process.on("SIGTERM", () => { void closeAllTransports().then(() => process.exit(0)); });
|
|
479
552
|
}
|
|
480
553
|
}
|
|
481
554
|
|
|
@@ -448,7 +448,7 @@ class XrayCodeReviewServer extends XrayKnowledgeSkillBase {
|
|
|
448
448
|
content: [
|
|
449
449
|
{
|
|
450
450
|
type: "text",
|
|
451
|
-
text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured.
|
|
451
|
+
text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.",
|
|
452
452
|
},
|
|
453
453
|
],
|
|
454
454
|
};
|
|
@@ -1008,7 +1008,7 @@ class XraySecurityAuditServer extends XrayKnowledgeSkillBase {
|
|
|
1008
1008
|
content: [
|
|
1009
1009
|
{
|
|
1010
1010
|
type: "text",
|
|
1011
|
-
text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured.
|
|
1011
|
+
text: "DECISION: abstain\nCONFIDENCE: 0.50\nREASONING: No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.",
|
|
1012
1012
|
},
|
|
1013
1013
|
],
|
|
1014
1014
|
};
|
|
@@ -39,7 +39,9 @@ export function toMemoryTask(task: OrchestrationTask): MemoryOrchestrationTask {
|
|
|
39
39
|
type: task.type,
|
|
40
40
|
};
|
|
41
41
|
if (task.priority) result.priority = task.priority;
|
|
42
|
-
if (task.dependencies)
|
|
42
|
+
if (Array.isArray(task.dependencies) && task.dependencies.length > 0) {
|
|
43
|
+
result.dependencies = task.dependencies;
|
|
44
|
+
}
|
|
43
45
|
if (task.estimatedComplexity !== undefined) result.estimatedComplexity = task.estimatedComplexity;
|
|
44
46
|
if (task.metadata) result.metadata = task.metadata as Record<string, unknown>;
|
|
45
47
|
return result;
|