0xray 3.4.8 → 3.4.10
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/llm-governance-provider.d.ts +2 -0
- package/dist/governance/llm-governance-provider.js +144 -158
- 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/agent-capabilities.d.ts +1 -1
- package/dist/mcps/orchestrator/config/agent-capabilities.js +23 -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 +24 -13
- 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 +24 -17
- package/dist/skills/orchestrator/SKILL.md +3 -2
- package/package.json +4 -2
- package/scripts/mjs/verify-orchestrator-behavior.mjs +122 -0
- 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/agent-capabilities.ts +26 -0
- package/src/mcps/orchestrator/config/memory-routing-bridge.ts +3 -1
- package/src/mcps/orchestrator/execution/execution-planner.ts +28 -11
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +63 -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)
|
|
@@ -167,7 +181,7 @@ export class ExecutionPlanner {
|
|
|
167
181
|
const operationDescription = task.metadata?.memoryHighConfidenceTrap
|
|
168
182
|
? `${task.description} TYPE: ontological-trap`
|
|
169
183
|
: task.description;
|
|
170
|
-
const agent = this.capabilitiesManager.selectAgentForTask(requiredCaps, complexity, operationDescription) || 'orchestrator';
|
|
184
|
+
const agent = this.capabilitiesManager.selectAgentForTask(requiredCaps, complexity, operationDescription, task.type) || 'orchestrator';
|
|
171
185
|
if (!agentAssignments.has(agent)) {
|
|
172
186
|
agentAssignments.set(agent, []);
|
|
173
187
|
}
|
|
@@ -209,7 +223,7 @@ export class ExecutionPlanner {
|
|
|
209
223
|
task.type,
|
|
210
224
|
...(task.metadata?.memorySignals ?? []),
|
|
211
225
|
];
|
|
212
|
-
const agent = this.capabilitiesManager.selectAgentForTask(requiredCaps, complexity, task.description) || 'orchestrator';
|
|
226
|
+
const agent = this.capabilitiesManager.selectAgentForTask(requiredCaps, complexity, task.description, task.type) || 'orchestrator';
|
|
213
227
|
if (!assignments.has(agent)) {
|
|
214
228
|
assignments.set(agent, []);
|
|
215
229
|
}
|
|
@@ -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, analysis.overallComplexity)
|
|
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[], mcpOverallComplexity?: number): 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,33 @@ 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 = [], mcpOverallComplexity) {
|
|
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
|
-
const
|
|
67
|
-
|
|
82
|
+
const mcpScore = mcpOverallComplexity !== undefined && Number.isFinite(mcpOverallComplexity)
|
|
83
|
+
? Math.round(mcpOverallComplexity)
|
|
84
|
+
: 0;
|
|
85
|
+
const complexity = Math.max(score.score, mcpScore);
|
|
86
|
+
const requiresPhasedPlan = complexity > threshold || taskInputs.length > 1;
|
|
68
87
|
const mandatoryConsults = cfg.auto_consult_major_work !== false && requiresPhasedPlan
|
|
69
88
|
? [...MANDATORY_MAJOR_CONSULTS]
|
|
70
89
|
: [];
|
|
@@ -87,12 +106,7 @@ export function buildLeadDevPlan(description, taskTypes = ['implement']) {
|
|
|
87
106
|
name: 'Implementation',
|
|
88
107
|
goal: 'Execute with best subagent per todo',
|
|
89
108
|
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
|
-
})),
|
|
109
|
+
todos: buildImplementationTodos(description, taskTypes, taskInputs, '2'),
|
|
96
110
|
},
|
|
97
111
|
{
|
|
98
112
|
id: 'phase-3',
|
|
@@ -121,14 +135,7 @@ export function buildLeadDevPlan(description, taskTypes = ['implement']) {
|
|
|
121
135
|
name: 'Direct execution',
|
|
122
136
|
goal: description.slice(0, 120),
|
|
123
137
|
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
|
-
],
|
|
138
|
+
todos: buildImplementationTodos(description, taskTypes, taskInputs, '1'),
|
|
132
139
|
},
|
|
133
140
|
];
|
|
134
141
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.10",
|
|
4
4
|
"description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"license": "MIT",
|
|
@@ -125,7 +125,8 @@
|
|
|
125
125
|
"pre-publish-guard": "node scripts/node/pre-publish-guard.js",
|
|
126
126
|
"safe-publish": "npm run prepare-consumer && npm run build",
|
|
127
127
|
"publish": "npm run release:gate && npm run safe-publish && npm publish --access public",
|
|
128
|
-
"release:patch": "node scripts/node/release.mjs patch"
|
|
128
|
+
"release:patch": "node scripts/node/release.mjs patch",
|
|
129
|
+
"verify:orchestrator-behavior": "npm run build && node scripts/mjs/verify-orchestrator-behavior.mjs"
|
|
129
130
|
},
|
|
130
131
|
"files": [
|
|
131
132
|
"dist/",
|
|
@@ -137,6 +138,7 @@
|
|
|
137
138
|
"scripts/mjs/test-mcp-functionality.mjs",
|
|
138
139
|
"scripts/mjs/validate-mcp-connectivity.cjs",
|
|
139
140
|
"scripts/mjs/validate-postinstall-config.mjs",
|
|
141
|
+
"scripts/mjs/verify-orchestrator-behavior.mjs",
|
|
140
142
|
"src/opencode/",
|
|
141
143
|
"src/opencode/codex.codex",
|
|
142
144
|
"src/opencode/commands/",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Consumer behavior verify — orchestrator honesty (ships in 0xray tarball).
|
|
4
|
+
* Run: node node_modules/0xray/scripts/mjs/verify-orchestrator-behavior.mjs
|
|
5
|
+
*/
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const packageRoot = join(__dirname, '../..');
|
|
11
|
+
const dist = (rel) => pathToFileURL(join(packageRoot, 'dist', rel)).href;
|
|
12
|
+
|
|
13
|
+
let failed = 0;
|
|
14
|
+
|
|
15
|
+
function pass(name) {
|
|
16
|
+
console.log(`✅ ${name}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function fail(name, detail = '') {
|
|
20
|
+
failed++;
|
|
21
|
+
console.error(`❌ ${name}${detail ? ` — ${detail}` : ''}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { dependencyCount, getExecutionPlanner } = await import(
|
|
25
|
+
dist('mcps/orchestrator/execution/execution-planner.js')
|
|
26
|
+
);
|
|
27
|
+
const { buildLeadDevPlan } = await import(dist('nucleus/autonomy-kernel.js'));
|
|
28
|
+
const { TaskHandler } = await import(
|
|
29
|
+
dist('mcps/orchestrator/handlers/task-handler.js')
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const planner = getExecutionPlanner();
|
|
33
|
+
|
|
34
|
+
// 1. numeric deps ≠ NaN
|
|
35
|
+
const analysis = await planner.analyzeTaskComplexity([
|
|
36
|
+
{
|
|
37
|
+
id: 't1',
|
|
38
|
+
description: 'Jelly migration',
|
|
39
|
+
type: 'implement',
|
|
40
|
+
dependencies: 8,
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
if (Number.isFinite(analysis.overallComplexity) && analysis.overallComplexity > 0) {
|
|
44
|
+
pass('numeric dependencyCount produces finite MCP complexity');
|
|
45
|
+
} else {
|
|
46
|
+
fail('NaN guard', `overallComplexity=${analysis.overallComplexity}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. multi-task → ≥3 todos
|
|
50
|
+
const multiPlan = buildLeadDevPlan(
|
|
51
|
+
'G0 J1 J2',
|
|
52
|
+
['implement'],
|
|
53
|
+
[
|
|
54
|
+
{ description: 'Publish 0xray', type: 'release' },
|
|
55
|
+
{ description: 'Jelly swap', type: 'migration' },
|
|
56
|
+
{ description: 'Wire tests', type: 'test' },
|
|
57
|
+
],
|
|
58
|
+
70,
|
|
59
|
+
);
|
|
60
|
+
const todoCount =
|
|
61
|
+
multiPlan?.phases.reduce((n, p) => n + p.todos.length, 0) ?? 0;
|
|
62
|
+
if (multiPlan?.requiresPhasedPlan && todoCount >= 3) {
|
|
63
|
+
pass(`multi-task phased plan (${todoCount} todos)`);
|
|
64
|
+
} else {
|
|
65
|
+
fail('multi-task plan', `requiresPhasedPlan=${multiPlan?.requiresPhasedPlan} todos=${todoCount}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. implement → backend-engineer
|
|
69
|
+
const implAnalysis = await planner.analyzeTaskComplexity([
|
|
70
|
+
{ id: 'impl-1', description: 'swap deps', type: 'implement' },
|
|
71
|
+
]);
|
|
72
|
+
const implAgent = implAnalysis.agentAssignments[0]?.agent;
|
|
73
|
+
if (implAgent === 'backend-engineer') {
|
|
74
|
+
pass('implement routes to backend-engineer');
|
|
75
|
+
} else {
|
|
76
|
+
fail('implement routing', `got ${implAgent}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 4. MCP complexity drives phased plan (single task, high MCP score)
|
|
80
|
+
const singleHigh = buildLeadDevPlan(
|
|
81
|
+
'single complex migration',
|
|
82
|
+
['implement'],
|
|
83
|
+
[{ description: 'single complex migration', type: 'implement' }],
|
|
84
|
+
70,
|
|
85
|
+
);
|
|
86
|
+
if (singleHigh?.requiresPhasedPlan && (singleHigh.phases.length ?? 0) > 1) {
|
|
87
|
+
pass('MCP overallComplexity 70 forces phased plan');
|
|
88
|
+
} else {
|
|
89
|
+
fail('MCP phased plan', `requiresPhasedPlan=${singleHigh?.requiresPhasedPlan}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 5. orchestrate-task defers implement (not fake-complete)
|
|
93
|
+
const handler = new TaskHandler();
|
|
94
|
+
const orch = await handler.handleOrchestrateTask(
|
|
95
|
+
{
|
|
96
|
+
description: 'implement feature',
|
|
97
|
+
sessionId: 'verify-behavior',
|
|
98
|
+
tasks: [
|
|
99
|
+
{
|
|
100
|
+
id: 'be-1',
|
|
101
|
+
description: 'implementation work',
|
|
102
|
+
type: 'implement',
|
|
103
|
+
estimatedComplexity: 40,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{ taskHistory: [], activeTasks: new Map() },
|
|
108
|
+
);
|
|
109
|
+
const text = orch.content[0]?.text ?? '';
|
|
110
|
+
if (text.includes('Deferred') && text.includes('❌ FAILED')) {
|
|
111
|
+
pass('orchestrate-task defers implement (success: false)');
|
|
112
|
+
} else {
|
|
113
|
+
fail('defer honesty', text.slice(0, 200));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(
|
|
117
|
+
'\n' +
|
|
118
|
+
(failed === 0
|
|
119
|
+
? '🎉 Orchestrator behavior verify passed (5/5).'
|
|
120
|
+
: `⚠️ ${failed} behavior check(s) failed.`),
|
|
121
|
+
);
|
|
122
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -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
|
};
|
|
@@ -13,6 +13,25 @@ import {
|
|
|
13
13
|
toMemoryCapabilityMap,
|
|
14
14
|
fromMemoryCapabilityMap,
|
|
15
15
|
} from './memory-routing-bridge.js';
|
|
16
|
+
import { routeSubagent } from '../../../nucleus/autonomy-kernel.js';
|
|
17
|
+
|
|
18
|
+
/** routeSubagent names → AGENT_REGISTRY keys */
|
|
19
|
+
const ROUTE_AGENT_ALIASES: Record<string, string> = {
|
|
20
|
+
'code-review': 'code-reviewer',
|
|
21
|
+
'security-audit': 'security-auditor',
|
|
22
|
+
'bug-triage': 'bug-triage-specialist',
|
|
23
|
+
'architect-tools': 'architect',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function resolveRoutedAgent(
|
|
27
|
+
routed: string,
|
|
28
|
+
capabilities: Map<string, AgentCapability>,
|
|
29
|
+
): string | null {
|
|
30
|
+
const candidate = ROUTE_AGENT_ALIASES[routed] ?? routed;
|
|
31
|
+
if (capabilities.has(candidate)) return candidate;
|
|
32
|
+
if (capabilities.has(routed)) return routed;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
16
35
|
|
|
17
36
|
const DEFAULT_AGENT_CAPABILITIES: Record<string, AgentCapability> = Object.fromEntries(
|
|
18
37
|
Object.entries(AGENT_REGISTRY).map(([name, entry]) => [
|
|
@@ -81,7 +100,14 @@ export class AgentCapabilitiesManager {
|
|
|
81
100
|
requiredCapabilities: string[],
|
|
82
101
|
complexity: number,
|
|
83
102
|
operationDescription = '',
|
|
103
|
+
taskType = '',
|
|
84
104
|
): string | null {
|
|
105
|
+
if (taskType.trim()) {
|
|
106
|
+
const routed = routeSubagent(taskType);
|
|
107
|
+
const resolved = resolveRoutedAgent(routed, this.capabilities);
|
|
108
|
+
if (resolved) return resolved;
|
|
109
|
+
}
|
|
110
|
+
|
|
85
111
|
const provider = getProvider();
|
|
86
112
|
|
|
87
113
|
if (provider.id !== 'null' && operationDescription) {
|
|
@@ -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;
|