0xray 3.4.8 → 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 +16 -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/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/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
|
@@ -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
|
@@ -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;
|
|
@@ -17,6 +17,24 @@ import {
|
|
|
17
17
|
fromMemoryTask,
|
|
18
18
|
} from '../config/memory-routing-bridge.js';
|
|
19
19
|
|
|
20
|
+
/** Dependency count from task-ID list or numeric hint from analyze-complexity */
|
|
21
|
+
export function dependencyCount(
|
|
22
|
+
deps: OrchestrationTask['dependencies'] | undefined,
|
|
23
|
+
): number {
|
|
24
|
+
if (deps == null) return 0;
|
|
25
|
+
if (typeof deps === 'number' && Number.isFinite(deps)) {
|
|
26
|
+
return Math.max(0, Math.round(deps));
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(deps)) return deps.length;
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isDependencyIdList(
|
|
33
|
+
deps: OrchestrationTask['dependencies'] | undefined,
|
|
34
|
+
): deps is string[] {
|
|
35
|
+
return Array.isArray(deps) && deps.length > 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
/**
|
|
21
39
|
* Execution Planner
|
|
22
40
|
* Plans and optimizes task execution strategies
|
|
@@ -51,8 +69,8 @@ export class ExecutionPlanner {
|
|
|
51
69
|
errors.push(`Task ${i}: Missing required field 'type'`);
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
// Check dependencies exist
|
|
55
|
-
if (task.dependencies && task.id) {
|
|
72
|
+
// Check task-ID dependencies exist (numeric hints skip graph validation)
|
|
73
|
+
if (isDependencyIdList(task.dependencies) && task.id) {
|
|
56
74
|
const taskIds = new Set(tasks.map(t => t.id).filter((id): id is string => !!id));
|
|
57
75
|
for (const depId of task.dependencies) {
|
|
58
76
|
if (!taskIds.has(depId)) {
|
|
@@ -162,10 +180,10 @@ export class ExecutionPlanner {
|
|
|
162
180
|
const dependentTasks: OrchestrationTask[] = [];
|
|
163
181
|
|
|
164
182
|
for (const task of sortedTasks) {
|
|
165
|
-
if (
|
|
166
|
-
independentTasks.push(task);
|
|
167
|
-
} else {
|
|
183
|
+
if (isDependencyIdList(task.dependencies)) {
|
|
168
184
|
dependentTasks.push(task);
|
|
185
|
+
} else {
|
|
186
|
+
independentTasks.push(task);
|
|
169
187
|
}
|
|
170
188
|
}
|
|
171
189
|
|
|
@@ -188,7 +206,7 @@ export class ExecutionPlanner {
|
|
|
188
206
|
if (visited.has(task.id)) return;
|
|
189
207
|
visited.add(task.id);
|
|
190
208
|
|
|
191
|
-
if (task.dependencies) {
|
|
209
|
+
if (isDependencyIdList(task.dependencies)) {
|
|
192
210
|
for (const depId of task.dependencies) {
|
|
193
211
|
const depTask = taskMap.get(depId);
|
|
194
212
|
if (depTask) visit(depTask);
|
|
@@ -344,10 +362,7 @@ export class ExecutionPlanner {
|
|
|
344
362
|
complexity = (complexity + task.estimatedComplexity) / 2;
|
|
345
363
|
}
|
|
346
364
|
|
|
347
|
-
|
|
348
|
-
if (task.dependencies) {
|
|
349
|
-
complexity += task.dependencies.length * 5;
|
|
350
|
-
}
|
|
365
|
+
complexity += dependencyCount(task.dependencies) * 5;
|
|
351
366
|
|
|
352
367
|
if (task.metadata?.memoryComplexityBoost !== undefined) {
|
|
353
368
|
complexity += task.metadata.memoryComplexityBoost;
|
|
@@ -376,7 +391,7 @@ export class ExecutionPlanner {
|
|
|
376
391
|
*/
|
|
377
392
|
private calculateParallelPotential(tasks: OrchestrationTask[]): number {
|
|
378
393
|
// Tasks without dependencies can run in parallel
|
|
379
|
-
const independent = tasks.filter(t => !t.dependencies
|
|
394
|
+
const independent = tasks.filter((t) => !isDependencyIdList(t.dependencies));
|
|
380
395
|
return independent.length / tasks.length;
|
|
381
396
|
}
|
|
382
397
|
}
|
|
@@ -14,6 +14,46 @@ import { getExecutionPlanner } from '../execution/execution-planner.js';
|
|
|
14
14
|
import { addObservations, extractComplexityObservations } from '../aside-context.js';
|
|
15
15
|
import type { OrchestrationTask, ComplexityAnalysis } from '../types.js';
|
|
16
16
|
|
|
17
|
+
type AnalyzeComplexityInput = {
|
|
18
|
+
id?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
files?: string[];
|
|
22
|
+
dependencyCount?: number;
|
|
23
|
+
dependencies?: string[] | number;
|
|
24
|
+
riskLevel?: string;
|
|
25
|
+
estimatedComplexity?: number;
|
|
26
|
+
priority?: OrchestrationTask['priority'];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function normalizeAnalyzeTasks(raw: AnalyzeComplexityInput[]): OrchestrationTask[] {
|
|
30
|
+
return raw.map((task, index) => {
|
|
31
|
+
let deps: OrchestrationTask['dependencies'];
|
|
32
|
+
if (Array.isArray(task.dependencies)) {
|
|
33
|
+
deps = task.dependencies;
|
|
34
|
+
} else if (typeof task.dependencies === 'number' && Number.isFinite(task.dependencies)) {
|
|
35
|
+
deps = task.dependencies;
|
|
36
|
+
} else if (
|
|
37
|
+
typeof task.dependencyCount === 'number' &&
|
|
38
|
+
Number.isFinite(task.dependencyCount)
|
|
39
|
+
) {
|
|
40
|
+
deps = task.dependencyCount;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const normalized: OrchestrationTask = {
|
|
44
|
+
id: task.id ?? `task-${index + 1}`,
|
|
45
|
+
description: task.description ?? '',
|
|
46
|
+
type: task.type ?? 'implement',
|
|
47
|
+
};
|
|
48
|
+
if (task.priority) normalized.priority = task.priority;
|
|
49
|
+
if (deps !== undefined) normalized.dependencies = deps;
|
|
50
|
+
if (task.estimatedComplexity !== undefined) {
|
|
51
|
+
normalized.estimatedComplexity = task.estimatedComplexity;
|
|
52
|
+
}
|
|
53
|
+
return normalized;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
17
57
|
/**
|
|
18
58
|
* Complexity Handler
|
|
19
59
|
* Processes analyze-complexity requests
|
|
@@ -25,10 +65,10 @@ export class ComplexityHandler {
|
|
|
25
65
|
* Handle analyze-complexity request
|
|
26
66
|
*/
|
|
27
67
|
async handleAnalyzeComplexity(
|
|
28
|
-
args: { tasks:
|
|
68
|
+
args: { tasks: AnalyzeComplexityInput[] },
|
|
29
69
|
asideId?: string,
|
|
30
70
|
): Promise<{ content: Array<{ type: string; text: string }> }> {
|
|
31
|
-
const
|
|
71
|
+
const tasks = normalizeAnalyzeTasks(args.tasks ?? []);
|
|
32
72
|
|
|
33
73
|
await frameworkLogger.log(
|
|
34
74
|
'orchestrator.server',
|
|
@@ -47,14 +87,31 @@ export class ComplexityHandler {
|
|
|
47
87
|
|
|
48
88
|
const primaryDescription = tasks.map((t) => t.description).filter(Boolean).join(' ');
|
|
49
89
|
const taskTypes = tasks.map((t) => t.type).filter(Boolean) as string[];
|
|
90
|
+
const planTaskInputs = tasks
|
|
91
|
+
.filter((t) => t.description?.trim())
|
|
92
|
+
.map((t) => ({ description: t.description, type: t.type }));
|
|
50
93
|
const leadDevPlan =
|
|
51
|
-
isLeadDevModeActive() && primaryDescription
|
|
52
|
-
? buildLeadDevPlan(
|
|
94
|
+
isLeadDevModeActive() && (primaryDescription || planTaskInputs.length > 0)
|
|
95
|
+
? buildLeadDevPlan(
|
|
96
|
+
primaryDescription,
|
|
97
|
+
taskTypes.length ? taskTypes : ['implement'],
|
|
98
|
+
planTaskInputs,
|
|
99
|
+
)
|
|
53
100
|
: null;
|
|
54
101
|
|
|
55
102
|
if (leadDevPlan) {
|
|
56
103
|
try {
|
|
57
|
-
persistLeadDevPlan(leadDevPlan);
|
|
104
|
+
const planPath = persistLeadDevPlan(leadDevPlan);
|
|
105
|
+
await frameworkLogger.log(
|
|
106
|
+
'orchestrator.server',
|
|
107
|
+
'lead-dev-plan-persisted',
|
|
108
|
+
'info',
|
|
109
|
+
{
|
|
110
|
+
planPath,
|
|
111
|
+
phaseCount: leadDevPlan.phases.length,
|
|
112
|
+
todoCount: leadDevPlan.phases.reduce((n, p) => n + p.todos.length, 0),
|
|
113
|
+
},
|
|
114
|
+
);
|
|
58
115
|
} catch (err) {
|
|
59
116
|
await frameworkLogger.log(
|
|
60
117
|
'orchestrator.server',
|
|
@@ -88,7 +88,6 @@ export class TaskHandler {
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// Execute orchestration (simulated for MCP server)
|
|
92
91
|
const results = await this.executePlan(executionPlan, sessionId, timeout);
|
|
93
92
|
|
|
94
93
|
// Feed per-task outcomes back to memory provider (not aggregate session stats)
|
|
@@ -118,6 +117,8 @@ export class TaskHandler {
|
|
|
118
117
|
orchestrationResult.success = results.success;
|
|
119
118
|
orchestrationResult.completedTasks = results.completedTasks;
|
|
120
119
|
orchestrationResult.failedTasks = results.failedTasks;
|
|
120
|
+
orchestrationResult.deferredTasks = results.deferredTasks;
|
|
121
|
+
orchestrationResult.recommendations.push(...results.recommendations);
|
|
121
122
|
orchestrationResult.agentUtilization = results.agentUtilization;
|
|
122
123
|
orchestrationResult.bottlenecks = results.bottlenecks;
|
|
123
124
|
orchestrationResult.recommendations = results.recommendations;
|
|
@@ -167,6 +168,7 @@ export class TaskHandler {
|
|
|
167
168
|
success: boolean;
|
|
168
169
|
completedTasks: number;
|
|
169
170
|
failedTasks: number;
|
|
171
|
+
deferredTasks: number;
|
|
170
172
|
agentUtilization: Record<string, number>;
|
|
171
173
|
bottlenecks: string[];
|
|
172
174
|
recommendations: string[];
|
|
@@ -181,6 +183,7 @@ export class TaskHandler {
|
|
|
181
183
|
const agentUtilization: Record<string, number> = {};
|
|
182
184
|
let completedTasks = 0;
|
|
183
185
|
let failedTasks = 0;
|
|
186
|
+
let deferredTasks = 0;
|
|
184
187
|
const agentOutputs: Record<string, string> = {};
|
|
185
188
|
const taskOutcomes: Array<{
|
|
186
189
|
taskId: string;
|
|
@@ -240,15 +243,20 @@ export class TaskHandler {
|
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
} else {
|
|
243
|
-
// For non-MCP agents we still count them as "assigned" (they will be handled by the caller or legacy path)
|
|
244
246
|
for (const task of tasks) {
|
|
245
|
-
|
|
247
|
+
deferredTasks++;
|
|
246
248
|
taskOutcomes.push({
|
|
247
249
|
taskId: task.id,
|
|
248
250
|
agent,
|
|
249
|
-
success:
|
|
251
|
+
success: false,
|
|
250
252
|
durationMs: 0,
|
|
251
253
|
});
|
|
254
|
+
await frameworkLogger.log(
|
|
255
|
+
'orchestrator.task-handler',
|
|
256
|
+
'delegate-deferred',
|
|
257
|
+
'info',
|
|
258
|
+
{ agent, taskId: task.id, sessionId },
|
|
259
|
+
);
|
|
252
260
|
}
|
|
253
261
|
}
|
|
254
262
|
}
|
|
@@ -256,6 +264,12 @@ export class TaskHandler {
|
|
|
256
264
|
const recommendations: string[] = [];
|
|
257
265
|
const bottlenecks: string[] = [];
|
|
258
266
|
|
|
267
|
+
if (deferredTasks > 0) {
|
|
268
|
+
recommendations.push(
|
|
269
|
+
`${deferredTasks} task(s) require host delegation (Task/spawn_subagent per lead-dev-plan) — orchestrate-task only invokes MCP consult skills directly`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
259
273
|
if (plan.agentAssignments.size > 5) {
|
|
260
274
|
recommendations.push('Consider reducing the number of agents for better coordination');
|
|
261
275
|
}
|
|
@@ -266,9 +280,10 @@ export class TaskHandler {
|
|
|
266
280
|
}
|
|
267
281
|
|
|
268
282
|
return {
|
|
269
|
-
success: failedTasks === 0,
|
|
283
|
+
success: failedTasks === 0 && deferredTasks === 0,
|
|
270
284
|
completedTasks,
|
|
271
285
|
failedTasks,
|
|
286
|
+
deferredTasks,
|
|
272
287
|
agentUtilization,
|
|
273
288
|
bottlenecks,
|
|
274
289
|
recommendations,
|
|
@@ -321,9 +336,10 @@ export class TaskHandler {
|
|
|
321
336
|
**Session ID:** ${result.sessionId}
|
|
322
337
|
**Success:** ${result.success ? '✅ COMPLETED' : '❌ FAILED'}
|
|
323
338
|
**Duration:** ${result.duration}ms
|
|
324
|
-
**Tasks:** ${result.completedTasks + result.failedTasks} total
|
|
339
|
+
**Tasks:** ${result.completedTasks + result.failedTasks + (result.deferredTasks ?? 0)} total
|
|
325
340
|
- ✅ Completed: ${result.completedTasks}
|
|
326
341
|
- ❌ Failed: ${result.failedTasks}
|
|
342
|
+
- ⏸ Deferred (host delegation required): ${result.deferredTasks ?? 0}
|
|
327
343
|
|
|
328
344
|
**Agent Utilization:**
|
|
329
345
|
${Object.entries(result.agentUtilization)
|
|
@@ -152,7 +152,15 @@ export class OrchestratorServer {
|
|
|
152
152
|
description: { type: 'string' },
|
|
153
153
|
type: { type: 'string' },
|
|
154
154
|
files: { type: 'array', items: { type: 'string' } },
|
|
155
|
-
|
|
155
|
+
dependencyCount: {
|
|
156
|
+
type: 'number',
|
|
157
|
+
description: 'Upstream dependency count (complexity hint only)',
|
|
158
|
+
},
|
|
159
|
+
dependencies: {
|
|
160
|
+
type: 'array',
|
|
161
|
+
items: { type: 'string' },
|
|
162
|
+
description: 'Task IDs this task depends on',
|
|
163
|
+
},
|
|
156
164
|
riskLevel: {
|
|
157
165
|
type: 'string',
|
|
158
166
|
enum: ['low', 'medium', 'high', 'critical'],
|
|
@@ -30,7 +30,8 @@ export interface OrchestrationTask {
|
|
|
30
30
|
description: string;
|
|
31
31
|
type: string;
|
|
32
32
|
priority?: 'critical' | 'high' | 'medium' | 'low';
|
|
33
|
-
|
|
33
|
+
/** Task IDs (orchestrate-task) or dependency count hint (analyze-complexity) */
|
|
34
|
+
dependencies?: string[] | number;
|
|
34
35
|
estimatedComplexity?: number;
|
|
35
36
|
metadata?: OrchestrationTaskMetadata;
|
|
36
37
|
}
|
|
@@ -48,6 +49,8 @@ export interface OrchestrationResult {
|
|
|
48
49
|
success: boolean;
|
|
49
50
|
completedTasks: number;
|
|
50
51
|
failedTasks: number;
|
|
52
|
+
/** Implementation agents not executed by orchestrate-task — lead must delegate via host Task */
|
|
53
|
+
deferredTasks?: number;
|
|
51
54
|
duration: number;
|
|
52
55
|
agentUtilization: Record<string, number>;
|
|
53
56
|
agentOutputs?: Record<string, string>;
|
|
@@ -488,7 +488,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
|
|
|
488
488
|
if (!vote) {
|
|
489
489
|
const abstainReason = memoryContext?.confidence.highConfidenceTrapPresent
|
|
490
490
|
? "No LLM governance provider configured. Repertoire detected a high-confidence ontological trap — route to the recommended agent before proceeding."
|
|
491
|
-
: "No LLM governance provider configured.
|
|
491
|
+
: "No LLM governance provider configured. Install Hermes on PATH (hermes -z) with xai-oauth, or set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT.";
|
|
492
492
|
|
|
493
493
|
return {
|
|
494
494
|
content: [
|
|
@@ -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
|
|