0xray 3.4.7 → 3.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) 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/governance-service.js +3 -1
  10. package/dist/governance/llm-governance-provider.d.ts +2 -0
  11. package/dist/governance/llm-governance-provider.js +146 -104
  12. package/dist/mcps/governance.server.d.ts +1 -0
  13. package/dist/mcps/governance.server.js +79 -8
  14. package/dist/mcps/knowledge-skills/code-review.server.js +1 -1
  15. package/dist/mcps/knowledge-skills/security-audit.server.js +1 -1
  16. package/dist/mcps/orchestrator/config/memory-routing-bridge.js +2 -1
  17. package/dist/mcps/orchestrator/execution/execution-planner.d.ts +2 -0
  18. package/dist/mcps/orchestrator/execution/execution-planner.js +22 -11
  19. package/dist/mcps/orchestrator/handlers/complexity-handler.d.ts +13 -1
  20. package/dist/mcps/orchestrator/handlers/complexity-handler.js +40 -4
  21. package/dist/mcps/orchestrator/handlers/task-handler.js +13 -6
  22. package/dist/mcps/orchestrator/server.js +9 -1
  23. package/dist/mcps/orchestrator/types.d.ts +4 -1
  24. package/dist/mcps/researcher.server.js +1 -1
  25. package/dist/nucleus/autonomy-kernel.d.ts +5 -1
  26. package/dist/nucleus/autonomy-kernel.js +20 -16
  27. package/dist/skills/orchestrator/SKILL.md +3 -2
  28. package/package.json +1 -1
  29. package/scripts/node/release-gate.mjs +14 -6
  30. package/src/mcps/governance.server.ts +81 -8
  31. package/src/mcps/knowledge-skills/code-review.server.ts +1 -1
  32. package/src/mcps/knowledge-skills/security-audit.server.ts +1 -1
  33. package/src/mcps/orchestrator/config/memory-routing-bridge.ts +3 -1
  34. package/src/mcps/orchestrator/execution/execution-planner.ts +26 -11
  35. package/src/mcps/orchestrator/handlers/complexity-handler.ts +62 -5
  36. package/src/mcps/orchestrator/handlers/task-handler.ts +22 -6
  37. package/src/mcps/orchestrator/server.ts +9 -1
  38. package/src/mcps/orchestrator/types.ts +4 -1
  39. package/src/mcps/researcher.server.ts +1 -1
  40. package/src/skills/orchestrator/SKILL.md +3 -2
@@ -4,7 +4,7 @@
4
4
 
5
5
  Quick reference for the 0xRay AI orchestration framework (shipped to your project on `npm install 0xray`).
6
6
 
7
- **v3.4.7** — 7 MCP servers · 45 skills · 68 codex terms · 4 platform bridges
7
+ **v3.4.9** — 7 MCP servers · 45 skills · 68 codex terms · 4 platform bridges
8
8
 
9
9
  ## Postinstall (automatic)
10
10
 
package/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xray Agents
2
2
 
3
- Quick reference for the xray AI orchestration framework (**v3.4.7**).
3
+ Quick reference for the xray AI orchestration framework (**v3.4.9**).
4
4
 
5
5
  **42 agents** · **45 skills** · **7 consumer MCP servers** · **68 codex terms** · **3,226 tests**
6
6
 
package/README.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # xray — MCP-Centric AI Governance OS
2
2
 
3
- **v3.4.7** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
3
+ **v3.4.9** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
4
+
5
+ ## Your AI Power Suit
6
+
7
+ **Your AI Power ⚡️ Suit 🦾** — an exoskeleton that blocks AI slop from becoming real damage.
8
+
9
+ - learns with **Repertoire** 🧠
10
+ - has a **Solar Ban Hammer** ☀️🔨 (Dynamo governance)
11
+ - can also **Confer** 🗣️ (3-agent internal deliberation)
12
+
13
+ The core AI Governance OS stack is **complete and open source**. A commercial version is next — powers up the suit further.
14
+
15
+ ```bash
16
+ npm install 0xray@latest
17
+ ```
4
18
 
5
19
  xray is the pure v2 three-subsystem AI orchestration framework — **MCP-centric**, governed by Dynamo, and autonomous via thinDispatch. Consumer `npm install 0xray` auto-wires all four platform bridges and seven MCP servers with zero manual config.
6
20
 
package/SKILLS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 0xray Skills
2
2
 
3
- **v3.4.7** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
3
+ **v3.4.9** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
4
4
 
5
5
  xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) across the full software lifecycle. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`), agent `@mentions`, or platform skill directories after postinstall.
6
6
 
package/dist/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xray Agents
2
2
 
3
- Quick reference for the xray AI orchestration framework (**v3.4.7**).
3
+ Quick reference for the xray AI orchestration framework (**v3.4.9**).
4
4
 
5
5
  **42 agents** · **45 skills** · **7 consumer MCP servers** · **68 codex terms** · **3,226 tests**
6
6
 
package/dist/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Conventional Commits](https://www.conventionalcommits.org/).
6
6
 
7
+ ## [3.4.9] - 2026-06-19
8
+
9
+ ### ⚠️ Breaking Changes
10
+ - **`orchestrate-task`** — returns `success: false` when implementation agents (backend-engineer, frontend-engineer, bug-triage, etc.) are **deferred**. Only MCP consult skills execute in-process. Callers must not treat a completed response as work done; delegate via host `Task` / `spawn_subagent` per lead-dev-plan.
11
+
12
+ ### 🐛 Bug Fixes
13
+ - **Orchestrator honesty** — no fake-complete for implementation agents; `delegate-deferred` + `lead-dev-plan-persisted` logged to `activity.log`
14
+ - **`analyze-complexity` NaN** — `dependencyCount` + `dependencies[]` schema; numeric dependency hints no longer produce NaN scores
15
+ - **Lead-dev plan** — `buildLeadDevPlan` uses per-task inputs; multi-task intake forces phased plan (not collapsed "Direct execution")
16
+ - **Hermes governance voters** — route through `hermes -z` CLI (same path as groover `hermes-runner`); honor `HERMES_BIN`, `HERMES_HOME`
17
+
18
+ ### ✅ Tests
19
+ - Unit coverage: `execution-planner-dependencies`, deferred task-handler, per-task lead-dev todos
20
+
21
+ ---
22
+
23
+ ## [3.4.8] - 2026-06-19
24
+
25
+ ### 🐛 Bug Fixes
26
+ - **Hermes v2 OAuth** — governance LLM voters read `providers.xai-oauth.tokens` from `~/.hermes/auth.json` (npm consumers on modern Hermes no longer abstain)
27
+ - **Headless deliberation** — honor `XRAY_GOVERNANCE_IN_PROCESS=1` for in-process skill voters without MCP subprocesses
28
+ - **Governance MCP HTTP** — per-session Streamable HTTP transports on Railway
29
+
30
+ ### ✅ Tests
31
+ - Unit coverage for Hermes v2 auth parsing and `XRAY_GOVERNANCE_IN_PROCESS` governance path
32
+
33
+ ---
34
+
7
35
  ## [3.4.7] - 2026-06-18
8
36
 
9
37
  ### 🐛 Bug Fixes
package/dist/README.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # xray — MCP-Centric AI Governance OS
2
2
 
3
- **v3.4.7** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
3
+ **v3.4.9** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
4
+
5
+ ## Your AI Power Suit
6
+
7
+ **Your AI Power ⚡️ Suit 🦾** — an exoskeleton that blocks AI slop from becoming real damage.
8
+
9
+ - learns with **Repertoire** 🧠
10
+ - has a **Solar Ban Hammer** ☀️🔨 (Dynamo governance)
11
+ - can also **Confer** 🗣️ (3-agent internal deliberation)
12
+
13
+ The core AI Governance OS stack is **complete and open source**. A commercial version is next — powers up the suit further.
14
+
15
+ ```bash
16
+ npm install 0xray@latest
17
+ ```
4
18
 
5
19
  xray is the pure v2 three-subsystem AI orchestration framework — **MCP-centric**, governed by Dynamo, and autonomous via thinDispatch. Consumer `npm install 0xray` auto-wires all four platform bridges and seven MCP servers with zero manual config.
6
20
 
package/dist/SKILLS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 0xray Skills
2
2
 
3
- **v3.4.7** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
3
+ **v3.4.9** — **45 skills** · invoked via `xray-skills` MCP · synced on postinstall
4
4
 
5
5
  xray ships 45 knowledge skills (`src/skills/<name>/SKILL.md`) across the full software lifecycle. Lead-dev operating model lives in **`orchestrator`** skill + `multi_agent_orchestration.lead_dev_mode` in features.json. Skills load on demand via the **xray-skills** MCP server (`npx -y 0xray mcp skills`), agent `@mentions`, or platform skill directories after postinstall.
6
6
 
@@ -128,7 +128,9 @@ export class GovernanceService {
128
128
  }
129
129
  async callSkillServer(serverName, proposals, context) {
130
130
  const votes = [];
131
- const useInProcess = process.env.VERCEL === '1' || !!process.env.RAILWAY_ENVIRONMENT;
131
+ const useInProcess = process.env.VERCEL === '1' ||
132
+ !!process.env.RAILWAY_ENVIRONMENT ||
133
+ process.env.XRAY_GOVERNANCE_IN_PROCESS === '1';
132
134
  for (const proposal of proposals) {
133
135
  try {
134
136
  let text = '';
@@ -4,6 +4,8 @@ export interface GovernanceVote {
4
4
  confidence: number;
5
5
  reasoning: string;
6
6
  }
7
+ export declare function resolveHermesBin(): string;
8
+ export declare function hermesCliAvailable(): boolean;
7
9
  export declare function tryLLMGovernance(role: GovernanceRole, proposalTitle: string, proposalDescription: string, evidence: string[], proposalType: string): Promise<GovernanceVote | null>;
8
10
  export declare function checkHermesOAuthStatus(): {
9
11
  configured: boolean;
@@ -1,9 +1,8 @@
1
- import { readFileSync, existsSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
1
+ import { execFileSync } from "node:child_process";
4
2
  import { frameworkLogger } from "../core/framework-logger.js";
5
- const XAI_ENDPOINT = "https://api.x.ai/v1/chat/completions";
6
- const XAI_MODEL = "grok-4.3";
3
+ const DEFAULT_HERMES_PROVIDER = "xai-oauth";
4
+ const DEFAULT_HERMES_MODEL = "grok-4.3";
5
+ const DEFAULT_HERMES_TIMEOUT_MS = 120_000;
7
6
  const ROLE_PROMPTS = {
8
7
  "code-review": `You are a senior code reviewer on a governance committee for a software project. Analyze the following proposal from a code quality, maintainability, readability, and engineering best practices perspective.
9
8
 
@@ -42,69 +41,97 @@ DECISION: approve|reject|abstain
42
41
  CONFIDENCE: <0.0-1.0>
43
42
  REASONING: <2-4 sentence analysis of the proposal's broader project impact>`,
44
43
  };
45
- function readHermesOAuthToken() {
46
- try {
47
- const authPath = join(homedir(), ".hermes", "auth.json");
48
- if (!existsSync(authPath))
49
- return null;
50
- const raw = readFileSync(authPath, "utf-8");
51
- const data = JSON.parse(raw);
52
- let entry;
53
- if (data && typeof data === "object" && !Array.isArray(data)) {
54
- entry = data["xai-oauth"];
55
- if (!entry?.access_token && data.access_token) {
56
- entry = data;
57
- }
58
- }
59
- if (!entry?.access_token && Array.isArray(data)) {
60
- entry = data.find((e) => e.provider === "xai-oauth" || !!e.access_token);
61
- }
62
- if (!entry?.access_token) {
63
- process.stderr.write("[llm] readHermesOAuthToken: no access_token in auth file\n");
64
- return null;
65
- }
66
- if (entry.expires_at && Date.now() / 1000 > entry.expires_at) {
67
- frameworkLogger.log("llm-governance", "hermes-token-expired", "warning", { expiresAt: entry.expires_at });
68
- return null;
69
- }
70
- return entry.access_token;
71
- }
72
- catch (err) {
73
- frameworkLogger.log("llm-governance", "hermes-auth-read-error", "warning", { error: String(err) });
74
- return null;
75
- }
44
+ function resolveHermesProvider() {
45
+ return process.env.HERMES_PROVIDER?.trim() || DEFAULT_HERMES_PROVIDER;
76
46
  }
77
- function getConfig() {
78
- const directEndpoint = process.env.XRAY_LLM_ENDPOINT ||
47
+ function resolveHermesModel() {
48
+ return process.env.HERMES_MODEL?.trim() || DEFAULT_HERMES_MODEL;
49
+ }
50
+ function resolveHermesTimeoutMs() {
51
+ const raw = process.env.HERMES_TIMEOUT_MS?.trim();
52
+ if (!raw)
53
+ return DEFAULT_HERMES_TIMEOUT_MS;
54
+ const parsed = Number(raw);
55
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_HERMES_TIMEOUT_MS;
56
+ }
57
+ export function resolveHermesBin() {
58
+ const explicit = process.env.HERMES_BIN?.trim();
59
+ if (explicit)
60
+ return explicit;
61
+ return "hermes";
62
+ }
63
+ function getDirectLlmConfig() {
64
+ const endpoint = process.env.XRAY_LLM_ENDPOINT ||
79
65
  process.env.XRAY_GOVERNANCE_LLM_ENDPOINT ||
80
66
  "";
81
- const directApiKey = process.env.XRAY_LLM_API_KEY ||
67
+ const apiKey = process.env.XRAY_LLM_API_KEY ||
82
68
  process.env.XRAY_GOVERNANCE_LLM_API_KEY ||
83
69
  "";
84
- const directModel = process.env.XRAY_LLM_MODEL ||
70
+ const model = process.env.XRAY_LLM_MODEL ||
85
71
  process.env.XRAY_GOVERNANCE_LLM_MODEL ||
86
72
  "gpt-4o";
87
- // First try: direct env-var config (highest priority)
88
73
  const directEnabled = process.env.XRAY_GOVERNANCE_LLM_ENABLED === "true" ||
89
74
  process.env.XRAY_LLM_ENABLED === "true";
90
- if (directEnabled && directEndpoint && directApiKey) {
91
- return { endpoint: directEndpoint, apiKey: directApiKey, model: directModel };
75
+ if (directEnabled && endpoint && apiKey) {
76
+ return { mode: "direct", endpoint, apiKey, model };
92
77
  }
93
- // Second try: Hermes xAI OAuth token from ~/.hermes/auth.json
94
- const hermesToken = readHermesOAuthToken();
95
- if (hermesToken) {
96
- frameworkLogger.log("llm-governance", "using-hermes-oauth", "info", { provider: "xai-oauth", endpoint: XAI_ENDPOINT, model: XAI_MODEL });
97
- return { endpoint: XAI_ENDPOINT, apiKey: hermesToken, model: XAI_MODEL };
78
+ if (endpoint && apiKey) {
79
+ return { mode: "direct", endpoint, apiKey, model };
98
80
  }
99
- // Third try: direct endpoint even without explicit enable flag (if endpoint+key set)
100
- if (directEndpoint && directApiKey) {
101
- return { endpoint: directEndpoint, apiKey: directApiKey, model: directModel };
102
- }
103
- if (process.env.DEBUG_LLM) {
104
- process.stderr.write("[llm-debug] No config found. directEnabled=" + directEnabled + " endpoint=" + !!directEndpoint + " apiKey=" + !!directApiKey + " hermes=" + !!hermesToken + "\n");
81
+ return null;
82
+ }
83
+ function getConfig() {
84
+ const direct = getDirectLlmConfig();
85
+ if (direct)
86
+ return direct;
87
+ if (hermesCliAvailable()) {
88
+ return {
89
+ mode: "hermes",
90
+ provider: resolveHermesProvider(),
91
+ model: resolveHermesModel(),
92
+ timeoutMs: resolveHermesTimeoutMs(),
93
+ };
105
94
  }
106
95
  return null;
107
96
  }
97
+ export function hermesCliAvailable() {
98
+ try {
99
+ execFileSync(resolveHermesBin(), ["--version"], {
100
+ encoding: "utf8",
101
+ stdio: ["ignore", "pipe", "pipe"],
102
+ timeout: 5_000,
103
+ });
104
+ return true;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ function buildGovernancePrompt(role, proposalTitle, proposalDescription, evidence, proposalType) {
111
+ const evidenceBlock = evidence.length > 0
112
+ ? `**Supporting Evidence:**\n${evidence.map((entry, index) => `${index + 1}. ${entry}`).join("\n")}\n\n`
113
+ : "";
114
+ return `${ROLE_PROMPTS[role]}
115
+
116
+ ## Proposal
117
+
118
+ **Title:** ${proposalTitle}
119
+
120
+ **Description:** ${proposalDescription}
121
+
122
+ **Type:** ${proposalType}
123
+
124
+ ${evidenceBlock}---
125
+
126
+ Please provide your structured vote.`;
127
+ }
128
+ function runHermesGovernanceInference(prompt, config) {
129
+ return execFileSync(resolveHermesBin(), ["-z", prompt, "--provider", config.provider, "--model", config.model], {
130
+ encoding: "utf8",
131
+ timeout: config.timeoutMs,
132
+ maxBuffer: 10 * 1024 * 1024,
133
+ }).trim();
134
+ }
108
135
  function parseVote(text) {
109
136
  const decisionMatch = text.match(/DECISION:\s*(approve|reject|abstain)/i);
110
137
  const confidenceMatch = text.match(/CONFIDENCE:\s*([0-9.]+)/i);
@@ -119,13 +146,7 @@ function parseVote(text) {
119
146
  reasoning: reasoningMatch?.[1]?.trim() || "No reasoning provided",
120
147
  };
121
148
  }
122
- export async function tryLLMGovernance(role, proposalTitle, proposalDescription, evidence, proposalType) {
123
- const config = getConfig();
124
- if (!config) {
125
- process.stderr.write("[llm] no config for role=" + role + "\n");
126
- return null;
127
- }
128
- process.stderr.write("[llm] using config endpoint=" + config.endpoint + " model=" + config.model + "\n");
149
+ async function callDirectLlm(role, config, proposalTitle, proposalDescription, evidence, proposalType) {
129
150
  const systemPrompt = ROLE_PROMPTS[role];
130
151
  const userMessage = `## Proposal
131
152
 
@@ -135,40 +156,61 @@ export async function tryLLMGovernance(role, proposalTitle, proposalDescription,
135
156
 
136
157
  **Type:** ${proposalType}
137
158
 
138
- ${evidence.length > 0 ? `**Supporting Evidence:**\n${evidence.map((e, i) => `${i + 1}. ${e}`).join("\n")}` : ""}
159
+ ${evidence.length > 0 ? `**Supporting Evidence:**\n${evidence.map((entry, index) => `${index + 1}. ${entry}`).join("\n")}` : ""}
139
160
 
140
161
  ---
141
162
 
142
163
  Please provide your structured vote.`;
143
- try {
144
- const response = await fetch(config.endpoint, {
145
- method: "POST",
146
- headers: {
147
- "Content-Type": "application/json",
148
- Authorization: `Bearer ${config.apiKey}`,
149
- },
150
- body: JSON.stringify({
151
- model: config.model,
152
- messages: [
153
- { role: "system", content: systemPrompt },
154
- { role: "user", content: userMessage },
155
- ],
156
- temperature: 0.3,
157
- max_tokens: 500,
158
- }),
164
+ const response = await fetch(config.endpoint, {
165
+ method: "POST",
166
+ headers: {
167
+ "Content-Type": "application/json",
168
+ Authorization: `Bearer ${config.apiKey}`,
169
+ },
170
+ body: JSON.stringify({
171
+ model: config.model,
172
+ messages: [
173
+ { role: "system", content: systemPrompt },
174
+ { role: "user", content: userMessage },
175
+ ],
176
+ temperature: 0.3,
177
+ max_tokens: 500,
178
+ }),
179
+ });
180
+ if (!response.ok) {
181
+ frameworkLogger.log("llm-governance", "api-call", "error", {
182
+ status: response.status,
183
+ statusText: response.statusText,
184
+ role,
159
185
  });
160
- if (!response.ok) {
161
- frameworkLogger.log("llm-governance", "api-call", "error", {
162
- status: response.status,
163
- statusText: response.statusText,
164
- role,
165
- });
166
- return null;
167
- }
168
- const data = (await response.json());
169
- const choices = data?.choices;
170
- const message = choices?.[0]?.message;
171
- const content = message?.content;
186
+ return null;
187
+ }
188
+ const data = (await response.json());
189
+ const choices = data?.choices;
190
+ const message = choices?.[0]?.message;
191
+ return message?.content ?? null;
192
+ }
193
+ export async function tryLLMGovernance(role, proposalTitle, proposalDescription, evidence, proposalType) {
194
+ const config = getConfig();
195
+ if (!config) {
196
+ process.stderr.write(`[llm] no config for role=${role}\n`);
197
+ return null;
198
+ }
199
+ if (config.mode === "hermes") {
200
+ process.stderr.write(`[llm] using hermes -z provider=${config.provider} model=${config.model}\n`);
201
+ frameworkLogger.log("llm-governance", "using-hermes-cli", "info", {
202
+ provider: config.provider,
203
+ model: config.model,
204
+ role,
205
+ });
206
+ }
207
+ else {
208
+ process.stderr.write(`[llm] using direct endpoint=${config.endpoint} model=${config.model}\n`);
209
+ }
210
+ try {
211
+ const content = config.mode === "hermes"
212
+ ? runHermesGovernanceInference(buildGovernancePrompt(role, proposalTitle, proposalDescription, evidence, proposalType), config)
213
+ : await callDirectLlm(role, config, proposalTitle, proposalDescription, evidence, proposalType);
172
214
  if (!content) {
173
215
  frameworkLogger.log("llm-governance", "parse", "warning", {
174
216
  issue: "missing content in response",
@@ -201,23 +243,23 @@ Please provide your structured vote.`;
201
243
  }
202
244
  }
203
245
  export function checkHermesOAuthStatus() {
204
- const config = getConfig();
205
- if (!config) {
206
- const hermesPath = join(homedir(), ".hermes", "auth.json");
207
- if (existsSync(hermesPath)) {
208
- return {
209
- configured: false,
210
- error: "Hermes auth file found but no valid xai-oauth token. Run: hermes auth add xai-oauth --no-browser",
211
- };
212
- }
246
+ const direct = getDirectLlmConfig();
247
+ if (direct) {
248
+ return {
249
+ configured: true,
250
+ endpoint: direct.endpoint,
251
+ model: direct.model,
252
+ };
253
+ }
254
+ if (hermesCliAvailable()) {
213
255
  return {
214
- configured: false,
215
- error: "No Hermes auth file found (~/.hermes/auth.json). Install Hermes and run: hermes auth add xai-oauth --no-browser",
256
+ configured: true,
257
+ endpoint: "hermes-cli",
258
+ model: resolveHermesModel(),
216
259
  };
217
260
  }
218
261
  return {
219
- configured: true,
220
- endpoint: XAI_ENDPOINT,
221
- model: XAI_MODEL,
262
+ configured: false,
263
+ error: "Hermes CLI not found on PATH. Install Hermes and ensure xai-oauth is configured (same path as gateway/cron: hermes auth add xai-oauth).",
222
264
  };
223
265
  }
@@ -29,6 +29,7 @@ declare class GovernanceServer extends XrayKnowledgeSkillBase {
29
29
  connect(transport: Transport): Promise<void>;
30
30
  /**
31
31
  * Run as HTTP server using Streamable HTTP transport (for Grok CLI compatibility).
32
+ * Uses per-session transports so concurrent clients can each initialize independently.
32
33
  */
33
34
  runHttp(port?: number): Promise<void>;
34
35
  }
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
15
15
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
16
- import { CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
16
+ import { CallToolRequestSchema, isInitializeRequest, } from "@modelcontextprotocol/sdk/types.js";
17
17
  import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
18
18
  import { frameworkLogger } from "../core/framework-logger.js";
19
19
  import { featuresConfigLoader } from "../core/features-config.js";
@@ -351,6 +351,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
351
351
  }
352
352
  /**
353
353
  * Run as HTTP server using Streamable HTTP transport (for Grok CLI compatibility).
354
+ * Uses per-session transports so concurrent clients can each initialize independently.
354
355
  */
355
356
  async runHttp(port = parseInt(process.env.MCP_PORT ?? "3100", 10)) {
356
357
  await this.initializeGovernance();
@@ -360,10 +361,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
360
361
  host: mcpHost,
361
362
  ...(allowedHosts ? { allowedHosts } : {}),
362
363
  });
363
- const transport = new StreamableHTTPServerTransport({
364
- sessionIdGenerator: () => randomUUID(),
365
- });
366
- await this.server.connect(transport);
364
+ const transports = {};
367
365
  const apiKey = process.env.GOVERNANCE_API_KEY;
368
366
  if (apiKey) {
369
367
  frameworkLogger.log("governance-mcp", "api-key-auth-enabled", "info", {});
@@ -383,8 +381,45 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
383
381
  }
384
382
  next();
385
383
  });
386
- app.post("/mcp", async (req, res) => {
384
+ const mcpPostHandler = async (req, res) => {
387
385
  try {
386
+ const sessionId = req.headers["mcp-session-id"];
387
+ let transport;
388
+ if (sessionId && transports[sessionId]) {
389
+ transport = transports[sessionId];
390
+ }
391
+ else if (!sessionId && isInitializeRequest(req.body)) {
392
+ transport = new StreamableHTTPServerTransport({
393
+ sessionIdGenerator: () => randomUUID(),
394
+ onsessioninitialized: (sid) => {
395
+ if (transport) {
396
+ transports[sid] = transport;
397
+ frameworkLogger.log("governance-mcp", "session-initialized", "info", {
398
+ sessionId: sid,
399
+ });
400
+ }
401
+ },
402
+ });
403
+ transport.onclose = () => {
404
+ const sid = transport?.sessionId;
405
+ if (sid && transports[sid]) {
406
+ delete transports[sid];
407
+ frameworkLogger.log("governance-mcp", "session-closed", "info", { sessionId: sid });
408
+ }
409
+ };
410
+ const sessionServer = new GovernanceServer();
411
+ await sessionServer.connect(transport);
412
+ await transport.handleRequest(req, res, req.body);
413
+ return;
414
+ }
415
+ else {
416
+ res.status(400).json({
417
+ jsonrpc: "2.0",
418
+ error: { code: -32000, message: "Bad Request: No valid session ID provided" },
419
+ id: null,
420
+ });
421
+ return;
422
+ }
388
423
  await transport.handleRequest(req, res, req.body);
389
424
  }
390
425
  catch (error) {
@@ -393,15 +428,51 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
393
428
  res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error" }, id: null });
394
429
  }
395
430
  }
431
+ };
432
+ app.post("/mcp", mcpPostHandler);
433
+ app.get("/mcp", async (req, res) => {
434
+ const sessionId = req.headers["mcp-session-id"];
435
+ if (!sessionId || !transports[sessionId]) {
436
+ res.status(400).send("Invalid or missing session ID");
437
+ return;
438
+ }
439
+ try {
440
+ await transports[sessionId].handleRequest(req, res);
441
+ }
442
+ catch (error) {
443
+ frameworkLogger.log("governance-mcp", "http-sse-error", "error", { error: String(error) });
444
+ if (!res.headersSent) {
445
+ res.status(500).send("Error processing SSE stream");
446
+ }
447
+ }
448
+ });
449
+ app.delete("/mcp", async (req, res) => {
450
+ const sessionId = req.headers["mcp-session-id"];
451
+ if (!sessionId || !transports[sessionId]) {
452
+ res.status(400).send("Invalid or missing session ID");
453
+ return;
454
+ }
455
+ try {
456
+ await transports[sessionId].handleRequest(req, res);
457
+ }
458
+ catch (error) {
459
+ frameworkLogger.log("governance-mcp", "http-delete-error", "error", { error: String(error) });
460
+ if (!res.headersSent) {
461
+ res.status(500).send("Error processing session termination");
462
+ }
463
+ }
396
464
  });
397
465
  app.get("/health", (_req, res) => {
398
466
  res.json({ status: "ok", server: "governance" });
399
467
  });
468
+ const closeAllTransports = async () => {
469
+ await Promise.all(Object.values(transports).map((t) => t.close()));
470
+ };
400
471
  app.listen(port, () => {
401
472
  frameworkLogger.log("governance-mcp", "http-listening", "info", { port });
402
473
  });
403
- process.on("SIGINT", () => { transport.close(); process.exit(0); });
404
- process.on("SIGTERM", () => { transport.close(); process.exit(0); });
474
+ process.on("SIGINT", () => { void closeAllTransports().then(() => process.exit(0)); });
475
+ process.on("SIGTERM", () => { void closeAllTransports().then(() => process.exit(0)); });
405
476
  }
406
477
  }
407
478
  // Start the server if this file is run directly
@@ -321,7 +321,7 @@ class XrayCodeReviewServer extends XrayKnowledgeSkillBase {
321
321
  content: [
322
322
  {
323
323
  type: "text",
324
- 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",
324
+ 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.",
325
325
  },
326
326
  ],
327
327
  };
@@ -666,7 +666,7 @@ class XraySecurityAuditServer extends XrayKnowledgeSkillBase {
666
666
  content: [
667
667
  {
668
668
  type: "text",
669
- 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",
669
+ 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.",
670
670
  },
671
671
  ],
672
672
  };
@@ -34,8 +34,9 @@ export function toMemoryTask(task) {
34
34
  };
35
35
  if (task.priority)
36
36
  result.priority = task.priority;
37
- if (task.dependencies)
37
+ if (Array.isArray(task.dependencies) && task.dependencies.length > 0) {
38
38
  result.dependencies = task.dependencies;
39
+ }
39
40
  if (task.estimatedComplexity !== undefined)
40
41
  result.estimatedComplexity = task.estimatedComplexity;
41
42
  if (task.metadata)
@@ -4,6 +4,8 @@
4
4
  * Creates optimized execution plans for orchestration tasks
5
5
  */
6
6
  import type { OrchestrationTask, ExecutionPlan, ComplexityAnalysis, TaskValidation } from '../types.js';
7
+ /** Dependency count from task-ID list or numeric hint from analyze-complexity */
8
+ export declare function dependencyCount(deps: OrchestrationTask['dependencies'] | undefined): number;
7
9
  /**
8
10
  * Execution Planner
9
11
  * Plans and optimizes task execution strategies