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.
Files changed (39) hide show
  1. package/AGENTS-consumer.md +1 -1
  2. package/AGENTS.md +1 -1
  3. package/README.md +15 -1
  4. package/SKILLS.md +1 -1
  5. package/dist/AGENTS.md +1 -1
  6. package/dist/CHANGELOG.md +28 -0
  7. package/dist/README.md +15 -1
  8. package/dist/SKILLS.md +1 -1
  9. package/dist/governance/llm-governance-provider.d.ts +2 -0
  10. package/dist/governance/llm-governance-provider.js +144 -158
  11. package/dist/mcps/knowledge-skills/code-review.server.js +1 -1
  12. package/dist/mcps/knowledge-skills/security-audit.server.js +1 -1
  13. package/dist/mcps/orchestrator/config/agent-capabilities.d.ts +1 -1
  14. package/dist/mcps/orchestrator/config/agent-capabilities.js +23 -1
  15. package/dist/mcps/orchestrator/config/memory-routing-bridge.js +2 -1
  16. package/dist/mcps/orchestrator/execution/execution-planner.d.ts +2 -0
  17. package/dist/mcps/orchestrator/execution/execution-planner.js +24 -13
  18. package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +13 -1
  19. package/dist/mcps/orchestrator/handlers/complexity-handler.js +40 -4
  20. package/dist/mcps/orchestrator/handlers/task-handler.js +13 -6
  21. package/dist/mcps/orchestrator/server.js +9 -1
  22. package/dist/mcps/orchestrator/types.d.ts +4 -1
  23. package/dist/mcps/researcher.server.js +1 -1
  24. package/dist/nucleus/autonomy-kernel.d.ts +5 -1
  25. package/dist/nucleus/autonomy-kernel.js +24 -17
  26. package/dist/skills/orchestrator/SKILL.md +3 -2
  27. package/package.json +4 -2
  28. package/scripts/mjs/verify-orchestrator-behavior.mjs +122 -0
  29. package/src/mcps/knowledge-skills/code-review.server.ts +1 -1
  30. package/src/mcps/knowledge-skills/security-audit.server.ts +1 -1
  31. package/src/mcps/orchestrator/config/agent-capabilities.ts +26 -0
  32. package/src/mcps/orchestrator/config/memory-routing-bridge.ts +3 -1
  33. package/src/mcps/orchestrator/execution/execution-planner.ts +28 -11
  34. package/src/mcps/orchestrator/handlers/complexity-handler.ts +63 -5
  35. package/src/mcps/orchestrator/handlers/task-handler.ts +22 -6
  36. package/src/mcps/orchestrator/server.ts +9 -1
  37. package/src/mcps/orchestrator/types.ts +4 -1
  38. package/src/mcps/researcher.server.ts +1 -1
  39. 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 (!task.dependencies || task.dependencies.length === 0) {
121
- independentTasks.push(task);
134
+ if (isDependencyIdList(task.dependencies)) {
135
+ dependentTasks.push(task);
122
136
  }
123
137
  else {
124
- dependentTasks.push(task);
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
- // Adjust based on dependencies
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 || t.dependencies.length === 0);
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: OrchestrationTask[];
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 { tasks = [] } = args;
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 leadDevPlan = isLeadDevModeActive() && primaryDescription
31
- ? buildLeadDevPlan(primaryDescription, taskTypes.length ? taskTypes : ['implement'])
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
- completedTasks++;
169
+ deferredTasks++;
169
170
  taskOutcomes.push({
170
171
  taskId: task.id,
171
172
  agent,
172
- success: true,
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
- dependencies: { type: 'number' },
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
- dependencies?: string[];
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. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser";
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
- export function buildLeadDevPlan(description, taskTypes = ['implement']) {
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 complexity = score.score;
67
- const requiresPhasedPlan = complexity > threshold;
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: taskTypes.map((type, i) => ({
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; response includes **lead-dev plan JSON** when mode is on |
40
- | Delegate | `orchestrate-task` | Include agent + sessionId (codex 67) |
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.8",
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. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser",
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. Set XRAY_GOVERNANCE_LLM_ENABLED=true + XRAY_LLM_ENDPOINT, or install Hermes and run: hermes auth add xai-oauth --no-browser",
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) result.dependencies = 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;