0xray 3.4.7 → 3.4.8

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.
@@ -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.8** — 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.8**).
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,6 @@
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.8** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
4
4
 
5
5
  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
6
 
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.8** — **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.8**).
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,18 @@ 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.8] - 2026-06-19
8
+
9
+ ### 🐛 Bug Fixes
10
+ - **Hermes v2 OAuth** — governance LLM voters read `providers.xai-oauth.tokens` from `~/.hermes/auth.json` (npm consumers on modern Hermes no longer abstain)
11
+ - **Headless deliberation** — honor `XRAY_GOVERNANCE_IN_PROCESS=1` for in-process skill voters without MCP subprocesses
12
+ - **Governance MCP HTTP** — per-session Streamable HTTP transports on Railway
13
+
14
+ ### ✅ Tests
15
+ - Unit coverage for Hermes v2 auth parsing and `XRAY_GOVERNANCE_IN_PROCESS` governance path
16
+
17
+ ---
18
+
7
19
  ## [3.4.7] - 2026-06-18
8
20
 
9
21
  ### 🐛 Bug Fixes
package/dist/README.md CHANGED
@@ -1,6 +1,6 @@
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.8** — 42 agents · 45 skills · 7 MCP servers · 68 codex terms · 3,226 tests
4
4
 
5
5
  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
6
 
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.8** — **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 = '';
@@ -42,6 +42,71 @@ DECISION: approve|reject|abstain
42
42
  CONFIDENCE: <0.0-1.0>
43
43
  REASONING: <2-4 sentence analysis of the proposal's broader project impact>`,
44
44
  };
45
+ function resolveHermesExpiresAt(provider, tokens) {
46
+ if (typeof provider.expires_at === "number")
47
+ return provider.expires_at;
48
+ const expiresIn = tokens?.expires_in ?? provider.expires_in;
49
+ if (typeof expiresIn !== "number")
50
+ return undefined;
51
+ const refreshCandidates = [provider.last_refresh, tokens?.last_refresh];
52
+ for (const lastRefresh of refreshCandidates) {
53
+ if (typeof lastRefresh !== "string")
54
+ continue;
55
+ const base = Date.parse(lastRefresh);
56
+ if (!Number.isNaN(base)) {
57
+ return Math.floor((base + expiresIn * 1000) / 1000);
58
+ }
59
+ }
60
+ return undefined;
61
+ }
62
+ function extractHermesOAuthEntry(data) {
63
+ if (!data || typeof data !== "object")
64
+ return undefined;
65
+ if (Array.isArray(data)) {
66
+ return data.find((e) => e.provider === "xai-oauth" || !!e.access_token);
67
+ }
68
+ const record = data;
69
+ const legacy = record["xai-oauth"];
70
+ if (legacy && typeof legacy === "object") {
71
+ const entry = legacy;
72
+ if (entry.access_token)
73
+ return entry;
74
+ }
75
+ if (typeof record.access_token === "string") {
76
+ return record;
77
+ }
78
+ for (const bucket of ["providers", "credential_pool"]) {
79
+ const pool = record[bucket];
80
+ if (!pool || typeof pool !== "object")
81
+ continue;
82
+ const provider = pool["xai-oauth"];
83
+ if (!provider || typeof provider !== "object")
84
+ continue;
85
+ const prov = provider;
86
+ const tokens = prov.tokens && typeof prov.tokens === "object"
87
+ ? prov.tokens
88
+ : undefined;
89
+ if (tokens && typeof tokens.access_token === "string") {
90
+ const entry = {
91
+ access_token: tokens.access_token,
92
+ provider: "xai-oauth",
93
+ };
94
+ if (typeof tokens.refresh_token === "string") {
95
+ entry.refresh_token = tokens.refresh_token;
96
+ }
97
+ const expiresAt = resolveHermesExpiresAt(prov, tokens);
98
+ if (expiresAt !== undefined)
99
+ entry.expires_at = expiresAt;
100
+ if (typeof tokens.token_type === "string")
101
+ entry.token_type = tokens.token_type;
102
+ return entry;
103
+ }
104
+ if (typeof prov.access_token === "string") {
105
+ return prov;
106
+ }
107
+ }
108
+ return undefined;
109
+ }
45
110
  function readHermesOAuthToken() {
46
111
  try {
47
112
  const authPath = join(homedir(), ".hermes", "auth.json");
@@ -49,16 +114,7 @@ function readHermesOAuthToken() {
49
114
  return null;
50
115
  const raw = readFileSync(authPath, "utf-8");
51
116
  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
- }
117
+ const entry = extractHermesOAuthEntry(data);
62
118
  if (!entry?.access_token) {
63
119
  process.stderr.write("[llm] readHermesOAuthToken: no access_token in auth file\n");
64
120
  return null;
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "3.4.7",
3
+ "version": "3.4.8",
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",
@@ -36,18 +36,26 @@ function main() {
36
36
 
37
37
  try {
38
38
  if (verifyOnly) {
39
- step("1/4 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
39
+ step("1/5 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
40
40
  if (!skipDocs) {
41
- step("2/4 Release docs", "node scripts/node/validate-release-docs.mjs");
41
+ step("2/5 Release docs", "node scripts/node/validate-release-docs.mjs");
42
42
  }
43
+ step(
44
+ "3/5 Plugin infrastructure",
45
+ "node scripts/test/validate-plugins-e2e.cjs --structural-only",
46
+ );
43
47
  if (!skipSmoke) {
44
- step("3/4 Consumer install smoke", "node scripts/node/consumer-install-smoke.mjs");
48
+ step("4/5 Consumer install smoke", "node scripts/node/consumer-install-smoke.mjs");
45
49
  }
46
50
  } else {
47
- step("1/4 Build", "npm run build");
48
- step("2/4 Tests", "npm test");
51
+ step("1/5 Build", "npm run build");
52
+ step("2/5 Tests", "npm test");
53
+ step(
54
+ "3/5 Plugin infrastructure",
55
+ "node scripts/test/validate-plugins-e2e.cjs --structural-only",
56
+ );
49
57
  if (!skipSmoke) {
50
- step("3/4 Consumer install smoke (fresh + upgrade)", "node scripts/node/consumer-install-smoke.mjs");
58
+ step("4/5 Consumer install smoke (fresh + upgrade)", "node scripts/node/consumer-install-smoke.mjs");
51
59
  }
52
60
  }
53
61
 
@@ -18,6 +18,7 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js
18
18
  import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
19
19
  import {
20
20
  CallToolRequestSchema,
21
+ isInitializeRequest,
21
22
  type CallToolResult,
22
23
  } from "@modelcontextprotocol/sdk/types.js";
23
24
  import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
@@ -419,6 +420,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
419
420
 
420
421
  /**
421
422
  * Run as HTTP server using Streamable HTTP transport (for Grok CLI compatibility).
423
+ * Uses per-session transports so concurrent clients can each initialize independently.
422
424
  */
423
425
  async runHttp(port: number = parseInt(process.env.MCP_PORT ?? "3100", 10)): Promise<void> {
424
426
  await this.initializeGovernance();
@@ -429,11 +431,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
429
431
  host: mcpHost,
430
432
  ...(allowedHosts ? { allowedHosts } : {}),
431
433
  });
432
- const transport = new StreamableHTTPServerTransport({
433
- sessionIdGenerator: () => randomUUID(),
434
- });
435
-
436
- await this.server.connect(transport as any);
434
+ const transports: Record<string, StreamableHTTPServerTransport> = {};
437
435
 
438
436
  const apiKey = process.env.GOVERNANCE_API_KEY;
439
437
  if (apiKey) {
@@ -455,8 +453,45 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
455
453
  next();
456
454
  });
457
455
 
458
- app.post("/mcp", async (req: any, res: any) => {
456
+ const mcpPostHandler = async (req: any, res: any) => {
459
457
  try {
458
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
459
+ let transport: StreamableHTTPServerTransport | undefined;
460
+
461
+ if (sessionId && transports[sessionId]) {
462
+ transport = transports[sessionId];
463
+ } else if (!sessionId && isInitializeRequest(req.body)) {
464
+ transport = new StreamableHTTPServerTransport({
465
+ sessionIdGenerator: () => randomUUID(),
466
+ onsessioninitialized: (sid) => {
467
+ if (transport) {
468
+ transports[sid] = transport;
469
+ frameworkLogger.log("governance-mcp", "session-initialized", "info", {
470
+ sessionId: sid,
471
+ });
472
+ }
473
+ },
474
+ });
475
+ transport.onclose = () => {
476
+ const sid = transport?.sessionId;
477
+ if (sid && transports[sid]) {
478
+ delete transports[sid];
479
+ frameworkLogger.log("governance-mcp", "session-closed", "info", { sessionId: sid });
480
+ }
481
+ };
482
+ const sessionServer = new GovernanceServer();
483
+ await sessionServer.connect(transport as Transport);
484
+ await transport.handleRequest(req, res, req.body);
485
+ return;
486
+ } else {
487
+ res.status(400).json({
488
+ jsonrpc: "2.0",
489
+ error: { code: -32000, message: "Bad Request: No valid session ID provided" },
490
+ id: null,
491
+ });
492
+ return;
493
+ }
494
+
460
495
  await transport.handleRequest(req, res, req.body);
461
496
  } catch (error) {
462
497
  frameworkLogger.log("governance-mcp", "http-handler-error", "error", { error: String(error) });
@@ -464,18 +499,56 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
464
499
  res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error" }, id: null });
465
500
  }
466
501
  }
502
+ };
503
+
504
+ app.post("/mcp", mcpPostHandler);
505
+
506
+ app.get("/mcp", async (req: any, res: any) => {
507
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
508
+ if (!sessionId || !transports[sessionId]) {
509
+ res.status(400).send("Invalid or missing session ID");
510
+ return;
511
+ }
512
+ try {
513
+ await transports[sessionId].handleRequest(req, res);
514
+ } catch (error) {
515
+ frameworkLogger.log("governance-mcp", "http-sse-error", "error", { error: String(error) });
516
+ if (!res.headersSent) {
517
+ res.status(500).send("Error processing SSE stream");
518
+ }
519
+ }
520
+ });
521
+
522
+ app.delete("/mcp", async (req: any, res: any) => {
523
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
524
+ if (!sessionId || !transports[sessionId]) {
525
+ res.status(400).send("Invalid or missing session ID");
526
+ return;
527
+ }
528
+ try {
529
+ await transports[sessionId].handleRequest(req, res);
530
+ } catch (error) {
531
+ frameworkLogger.log("governance-mcp", "http-delete-error", "error", { error: String(error) });
532
+ if (!res.headersSent) {
533
+ res.status(500).send("Error processing session termination");
534
+ }
535
+ }
467
536
  });
468
537
 
469
538
  app.get("/health", (_req: any, res: any) => {
470
539
  res.json({ status: "ok", server: "governance" });
471
540
  });
472
541
 
542
+ const closeAllTransports = async () => {
543
+ await Promise.all(Object.values(transports).map((t) => t.close()));
544
+ };
545
+
473
546
  app.listen(port, () => {
474
547
  frameworkLogger.log("governance-mcp", "http-listening", "info", { port });
475
548
  });
476
549
 
477
- process.on("SIGINT", () => { transport.close(); process.exit(0); });
478
- process.on("SIGTERM", () => { transport.close(); process.exit(0); });
550
+ process.on("SIGINT", () => { void closeAllTransports().then(() => process.exit(0)); });
551
+ process.on("SIGTERM", () => { void closeAllTransports().then(() => process.exit(0)); });
479
552
  }
480
553
  }
481
554