0xray 3.0.3 → 3.0.4
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/.opencode/plugin/xray-codex-injection.js +624 -0
- package/README.md +13 -14
- package/dist/CHANGELOG.md +16 -0
- package/dist/README.md +13 -14
- package/dist/core/boot-orchestrator.js +1 -1
- package/package.json +3 -2
- package/scripts/node/universal-version-manager.js +2 -2
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer runtime compat (xray codex injection + XRAY_ env + .xray fallbacks).
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import { beforeToolHook, afterToolHook } from '../integrations/enforcement-gate.js';
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Dynamic module holders (loaded via candidate-based resolution)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
let _resolveCodexPath = null;
|
|
12
|
+
let _resolveStateDir = null;
|
|
13
|
+
let _frameworkLogger = null;
|
|
14
|
+
let _systemPromptGenerator = null;
|
|
15
|
+
let _featuresConfigLoader = null;
|
|
16
|
+
let _detectTaskType = null;
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Module loaders (candidate-based resolution for dev, dist, and consumer paths)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
async function loadFrameworkLogger() {
|
|
21
|
+
if (_frameworkLogger)
|
|
22
|
+
return _frameworkLogger;
|
|
23
|
+
const candidates = [
|
|
24
|
+
"../core/framework-logger.js",
|
|
25
|
+
"../../node_modules/0xray/dist/core/framework-logger.js",
|
|
26
|
+
];
|
|
27
|
+
for (const p of candidates) {
|
|
28
|
+
try {
|
|
29
|
+
const mod = await import(p);
|
|
30
|
+
_frameworkLogger = mod.frameworkLogger;
|
|
31
|
+
return _frameworkLogger;
|
|
32
|
+
}
|
|
33
|
+
catch (_) {
|
|
34
|
+
// try next candidate
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
_frameworkLogger = {
|
|
38
|
+
log: (_module, _event, _status, _data) => { },
|
|
39
|
+
};
|
|
40
|
+
return _frameworkLogger;
|
|
41
|
+
}
|
|
42
|
+
async function loadConfigPaths() {
|
|
43
|
+
if (_resolveCodexPath && _resolveStateDir)
|
|
44
|
+
return;
|
|
45
|
+
const candidates = [
|
|
46
|
+
"../core/config-paths.js",
|
|
47
|
+
"../../node_modules/0xray/dist/core/config-paths.js",
|
|
48
|
+
];
|
|
49
|
+
for (const p of candidates) {
|
|
50
|
+
try {
|
|
51
|
+
const mod = await import(p);
|
|
52
|
+
_resolveCodexPath = mod.resolveCodexPath;
|
|
53
|
+
_resolveStateDir = mod.resolveStateDir;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
catch (_) {
|
|
57
|
+
// try next candidate
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const logger = await loadFrameworkLogger();
|
|
61
|
+
logger.log("xray-codex-plugin", "config-paths-load-failed", "warning", { warning: "Failed to load config-paths module from any location" });
|
|
62
|
+
}
|
|
63
|
+
async function resolveCodexPath(root) {
|
|
64
|
+
await loadConfigPaths();
|
|
65
|
+
if (!_resolveCodexPath)
|
|
66
|
+
throw new Error("resolveCodexPath not available after loading");
|
|
67
|
+
return _resolveCodexPath(root);
|
|
68
|
+
}
|
|
69
|
+
async function resolveStateDir(root) {
|
|
70
|
+
await loadConfigPaths();
|
|
71
|
+
if (!_resolveStateDir)
|
|
72
|
+
throw new Error("resolveStateDir not available after loading");
|
|
73
|
+
return _resolveStateDir(root);
|
|
74
|
+
}
|
|
75
|
+
async function importSystemPromptGenerator() {
|
|
76
|
+
if (_systemPromptGenerator)
|
|
77
|
+
return;
|
|
78
|
+
const candidates = [
|
|
79
|
+
"../core/system-prompt-generator.js",
|
|
80
|
+
"../../node_modules/0xray/dist/core/system-prompt-generator.js",
|
|
81
|
+
];
|
|
82
|
+
for (const p of candidates) {
|
|
83
|
+
try {
|
|
84
|
+
const module = await import(p);
|
|
85
|
+
_systemPromptGenerator = module.generateLeanSystemPrompt;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
catch (_) {
|
|
89
|
+
// try next candidate
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const logger = await loadFrameworkLogger();
|
|
93
|
+
logger.log("xray-codex-plugin", "system-prompt-generator-load-failed", "warning", { warning: "Failed to load lean system prompt generator, using fallback" });
|
|
94
|
+
}
|
|
95
|
+
function validateModulePath(resolvedPath, allowedPrefix) {
|
|
96
|
+
const normalized = path.resolve(resolvedPath);
|
|
97
|
+
const allowed = path.resolve(allowedPrefix);
|
|
98
|
+
if (!normalized.startsWith(allowed)) {
|
|
99
|
+
throw new Error(`Module path validation failed: ${normalized} is outside allowed path ${allowed}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function loadXrayComponents() {
|
|
103
|
+
if (_featuresConfigLoader)
|
|
104
|
+
return;
|
|
105
|
+
const logger = await getOrCreateLogger(process.cwd());
|
|
106
|
+
try {
|
|
107
|
+
const root = process.cwd();
|
|
108
|
+
const distPrefix = path.join(root, 'dist');
|
|
109
|
+
validateModulePath(`${root}/dist/core/features-config.js`, distPrefix);
|
|
110
|
+
const featuresModule = await import(`${root}/dist/core/features-config.js`);
|
|
111
|
+
_featuresConfigLoader = featuresModule.featuresConfigLoader;
|
|
112
|
+
_detectTaskType = featuresModule.detectTaskType;
|
|
113
|
+
logger.log(`✅ Loaded from cwd/dist/`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
118
|
+
logger.log(`❌ Failed to load from cwd/dist/: ${message}`);
|
|
119
|
+
}
|
|
120
|
+
const pluginPaths = ["0xray"];
|
|
121
|
+
for (const pluginPath of pluginPaths) {
|
|
122
|
+
try {
|
|
123
|
+
const nodeModulesPrefix = path.join(process.cwd(), 'node_modules');
|
|
124
|
+
validateModulePath(`${process.cwd()}/node_modules/${pluginPath}/dist/core/features-config.js`, nodeModulesPrefix);
|
|
125
|
+
const fm = await import(`${process.cwd()}/node_modules/${pluginPath}/dist/core/features-config.js`);
|
|
126
|
+
_featuresConfigLoader = fm.featuresConfigLoader;
|
|
127
|
+
_detectTaskType = fm.detectTaskType;
|
|
128
|
+
logger.log(`✅ Loaded from node_modules/${pluginPath}/dist/`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
133
|
+
logger.log(`❌ Failed to load from node_modules/${pluginPath}/dist/: ${message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Helpers
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
function spawnPromise(command, args, cwd) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const child = spawn(command, args, {
|
|
143
|
+
cwd,
|
|
144
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
145
|
+
});
|
|
146
|
+
let stderr = "";
|
|
147
|
+
if (child.stderr) {
|
|
148
|
+
child.stderr.on("data", (data) => {
|
|
149
|
+
stderr += data.toString();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
child.on("close", (code) => {
|
|
153
|
+
if (code === 0) {
|
|
154
|
+
resolve({ stdout: "", stderr });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
reject(new Error(`Process exited with code ${code}: ${stderr}`));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
child.on("error", (error) => {
|
|
161
|
+
reject(error);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
class PluginLogger {
|
|
166
|
+
logPath;
|
|
167
|
+
constructor(directory) {
|
|
168
|
+
const logsDir = path.join(directory, ".opencode", "logs");
|
|
169
|
+
if (!fs.existsSync(logsDir)) {
|
|
170
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
const today = new Date().toISOString().split("T")[0];
|
|
173
|
+
this.logPath = path.join(logsDir, `xray-plugin-${today}.log`);
|
|
174
|
+
}
|
|
175
|
+
async logAsync(message) {
|
|
176
|
+
try {
|
|
177
|
+
const timestamp = new Date().toISOString();
|
|
178
|
+
const logEntry = `[${timestamp}] ${message}\n`;
|
|
179
|
+
await fs.promises.appendFile(this.logPath, logEntry, "utf-8");
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Silent fail - logging failure should not break plugin
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
log(message) {
|
|
186
|
+
void this.logAsync(message);
|
|
187
|
+
}
|
|
188
|
+
warn(message) {
|
|
189
|
+
this.log(`WARN: ${message}`);
|
|
190
|
+
}
|
|
191
|
+
error(message, error) {
|
|
192
|
+
const errorDetail = error instanceof Error ? `: ${error.message}` : "";
|
|
193
|
+
this.log(`ERROR: ${message}${errorDetail}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let loggerInstance = null;
|
|
197
|
+
let loggerInitPromise = null;
|
|
198
|
+
async function getOrCreateLogger(directory) {
|
|
199
|
+
if (loggerInstance) {
|
|
200
|
+
return loggerInstance;
|
|
201
|
+
}
|
|
202
|
+
if (loggerInitPromise) {
|
|
203
|
+
return loggerInitPromise;
|
|
204
|
+
}
|
|
205
|
+
loggerInitPromise = (async () => {
|
|
206
|
+
const logger = new PluginLogger(directory);
|
|
207
|
+
loggerInstance = logger;
|
|
208
|
+
return logger;
|
|
209
|
+
})();
|
|
210
|
+
return loggerInitPromise;
|
|
211
|
+
}
|
|
212
|
+
function getFrameworkVersion() {
|
|
213
|
+
try {
|
|
214
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
215
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
216
|
+
return packageJson.version || "1.4.6";
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return "1.4.6";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function getFrameworkIdentity() {
|
|
223
|
+
const version = getFrameworkVersion();
|
|
224
|
+
return `0xRay Framework v${version} - AI Orchestration
|
|
225
|
+
|
|
226
|
+
🔧 Core: architect, code-reviewer, refactorer, testing-lead, strategist
|
|
227
|
+
📚 Codex: 5 Essential Terms (99.6% Error Prevention Target)
|
|
228
|
+
🎯 Goal: Progressive, production-ready development workflow
|
|
229
|
+
|
|
230
|
+
📖 Documentation: config dir (codex, config, agents docs) — resolved via config-paths
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
let cachedCodexContexts = null;
|
|
234
|
+
async function getCodexFileLocations(directory) {
|
|
235
|
+
const root = directory || process.cwd();
|
|
236
|
+
const resolved = await resolveCodexPath(root);
|
|
237
|
+
resolved.push(path.join(root, ".opencode", "codex.codex"), path.join(root, ".xray", "agents_template.md"), path.join(root, ".opencode", "xray", "agents_template.md"), path.join(root, "AGENTS.md"));
|
|
238
|
+
return resolved;
|
|
239
|
+
}
|
|
240
|
+
function readFileContent(filePath) {
|
|
241
|
+
try {
|
|
242
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const logger = new PluginLogger(process.cwd());
|
|
246
|
+
logger.error(`Failed to read file ${filePath}`, error);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function extractCodexMetadata(content) {
|
|
251
|
+
if (content.trim().startsWith("{")) {
|
|
252
|
+
try {
|
|
253
|
+
const parsed = JSON.parse(content);
|
|
254
|
+
const version = parsed.version || "1.6.0";
|
|
255
|
+
const terms = parsed.terms || {};
|
|
256
|
+
const termCount = Object.keys(terms).length;
|
|
257
|
+
return { version, termCount };
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Not valid JSON, try markdown format
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const versionMatch = content.match(/\*\*Version\*\*:\s*(\d+\.\d+\.\d+)/);
|
|
264
|
+
const version = versionMatch?.[1] ?? "1.6.0";
|
|
265
|
+
const termMatches = content.match(/####\s*\d+\.\s/g);
|
|
266
|
+
const termCount = termMatches ? termMatches.length : 0;
|
|
267
|
+
return { version, termCount };
|
|
268
|
+
}
|
|
269
|
+
function createCodexContextEntry(filePath, content) {
|
|
270
|
+
const metadata = extractCodexMetadata(content);
|
|
271
|
+
return {
|
|
272
|
+
id: `xray-codex-${path.basename(filePath)}`,
|
|
273
|
+
source: filePath,
|
|
274
|
+
content,
|
|
275
|
+
priority: "critical",
|
|
276
|
+
metadata: {
|
|
277
|
+
version: metadata.version,
|
|
278
|
+
termCount: metadata.termCount,
|
|
279
|
+
loadedAt: new Date().toISOString(),
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async function loadCodexContext(directory) {
|
|
284
|
+
if (cachedCodexContexts) {
|
|
285
|
+
return cachedCodexContexts;
|
|
286
|
+
}
|
|
287
|
+
const codexContexts = [];
|
|
288
|
+
const locations = await getCodexFileLocations(directory);
|
|
289
|
+
for (const fileLocation of locations) {
|
|
290
|
+
const fullPath = path.isAbsolute(fileLocation) ? fileLocation : path.join(directory, fileLocation);
|
|
291
|
+
const content = readFileContent(fullPath);
|
|
292
|
+
if (content && content.trim().length > 0) {
|
|
293
|
+
const entry = createCodexContextEntry(fullPath, content);
|
|
294
|
+
if (entry.metadata.termCount > 0) {
|
|
295
|
+
codexContexts.push(entry);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
cachedCodexContexts = codexContexts;
|
|
300
|
+
if (codexContexts.length === 0) {
|
|
301
|
+
void getOrCreateLogger(directory).then((l) => l.error(`No valid codex files found. Checked: ${locations.join(", ")}`));
|
|
302
|
+
}
|
|
303
|
+
return codexContexts;
|
|
304
|
+
}
|
|
305
|
+
function formatCodexContext(contexts) {
|
|
306
|
+
if (contexts.length === 0) {
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
309
|
+
const parts = [];
|
|
310
|
+
for (const context of contexts) {
|
|
311
|
+
parts.push(`# 0xRay Codex Context v${context.metadata.version}`, `Source: ${context.source}`, `Terms Loaded: ${context.metadata.termCount}`, `Loaded At: ${context.metadata.loadedAt}`, "", context.content, "", "---", "");
|
|
312
|
+
}
|
|
313
|
+
return parts.join("\n");
|
|
314
|
+
}
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
// Analytics and task classification
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
const INFERENCE_TUNE_INTERVAL = 100;
|
|
319
|
+
let _openCodeToolCallCount = 0;
|
|
320
|
+
let _lastTuneToolCallCount = 0;
|
|
321
|
+
const TOOL_AGENT_MAP = {
|
|
322
|
+
write: { agent: "code-reviewer", skill: "write" },
|
|
323
|
+
edit: { agent: "code-reviewer", skill: "edit" },
|
|
324
|
+
multiedit: { agent: "code-reviewer", skill: "multiedit" },
|
|
325
|
+
bash: { agent: "testing-lead", skill: "execution" },
|
|
326
|
+
search: { agent: "researcher", skill: "search" },
|
|
327
|
+
read: { agent: "researcher", skill: "read" },
|
|
328
|
+
glob: { agent: "researcher", skill: "glob" },
|
|
329
|
+
grep: { agent: "researcher", skill: "search" },
|
|
330
|
+
ls: { agent: "researcher", skill: "list" },
|
|
331
|
+
};
|
|
332
|
+
function classifyTaskType(tool, args) {
|
|
333
|
+
const cmd = String(args?.command ?? "").toLowerCase().trim();
|
|
334
|
+
if (tool === "bash" && cmd) {
|
|
335
|
+
if (/(npm|yarn|pnpm)\s+test|jest|vitest|mocha|pytest/.test(cmd))
|
|
336
|
+
return "testing";
|
|
337
|
+
if (/(npm|yarn|pnpm)\s+run|npx|cargo|go run|make\s/.test(cmd))
|
|
338
|
+
return "build";
|
|
339
|
+
if (/audit|security|snyk|owasp|bandit/.test(cmd))
|
|
340
|
+
return "security";
|
|
341
|
+
if (/eslint|prettier|black|ruff|lint|format/.test(cmd))
|
|
342
|
+
return "lint";
|
|
343
|
+
if (/git\s/.test(cmd))
|
|
344
|
+
return "git";
|
|
345
|
+
if (/(npm|yarn|pnpm)\s+install|pip install|cargo add/.test(cmd))
|
|
346
|
+
return "install";
|
|
347
|
+
if (/grep|rg |find |ls |cat |head |tail /.test(cmd))
|
|
348
|
+
return "search";
|
|
349
|
+
}
|
|
350
|
+
if (tool === "write")
|
|
351
|
+
return "write";
|
|
352
|
+
if (tool === "edit" || tool === "multiedit")
|
|
353
|
+
return "edit";
|
|
354
|
+
if (tool === "read")
|
|
355
|
+
return "read";
|
|
356
|
+
if (tool === "search" || tool === "grep" || tool === "glob")
|
|
357
|
+
return "search";
|
|
358
|
+
return "unknown";
|
|
359
|
+
}
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// Shared helpers extracted from duplicated plugin logic
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
function isWriteEditOperation(tool) {
|
|
364
|
+
return tool === "write" || tool === "edit" || tool === "multiedit";
|
|
365
|
+
}
|
|
366
|
+
function resolveAgentName(input) {
|
|
367
|
+
const globalAgent = globalThis.currentAgent;
|
|
368
|
+
if (globalAgent?.agentType)
|
|
369
|
+
return globalAgent.agentType;
|
|
370
|
+
if (globalAgent?.type)
|
|
371
|
+
return globalAgent.type;
|
|
372
|
+
if (input?.agentType)
|
|
373
|
+
return input.agentType;
|
|
374
|
+
return "architect";
|
|
375
|
+
}
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// Feature detection helpers
|
|
378
|
+
// Main plugin function
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
export default async function xrayCodexPlugin(input) {
|
|
381
|
+
const { directory: inputDirectory } = input;
|
|
382
|
+
const directory = inputDirectory || process.cwd();
|
|
383
|
+
return {
|
|
384
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
385
|
+
try {
|
|
386
|
+
await importSystemPromptGenerator();
|
|
387
|
+
let leanPrompt = getFrameworkIdentity();
|
|
388
|
+
if (_systemPromptGenerator) {
|
|
389
|
+
leanPrompt = await _systemPromptGenerator({
|
|
390
|
+
showWelcomeBanner: true,
|
|
391
|
+
showCodexContext: false,
|
|
392
|
+
enableTokenOptimization: true,
|
|
393
|
+
maxTokenBudget: 8192,
|
|
394
|
+
showCriticalTermsOnly: true,
|
|
395
|
+
showEssentialLinks: true,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (output.system && Array.isArray(output.system)) {
|
|
399
|
+
output.system = [leanPrompt];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
const logger = await getOrCreateLogger(directory);
|
|
404
|
+
logger.error("System prompt injection failed:", error);
|
|
405
|
+
const fallback = getFrameworkIdentity();
|
|
406
|
+
if (output.system && Array.isArray(output.system)) {
|
|
407
|
+
output.system = [fallback];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
"tool.execute.before": async (input, output) => {
|
|
412
|
+
const logger = await getOrCreateLogger(directory);
|
|
413
|
+
logger.log(`🚀 TOOL EXECUTE BEFORE HOOK FIRED: ${input.tool}`);
|
|
414
|
+
logger.log(`📥 Full input: ${JSON.stringify(input)}`);
|
|
415
|
+
await loadXrayComponents();
|
|
416
|
+
if (_featuresConfigLoader && _detectTaskType) {
|
|
417
|
+
try {
|
|
418
|
+
const config = _featuresConfigLoader.loadConfig();
|
|
419
|
+
if (config.model_routing?.enabled) {
|
|
420
|
+
const taskType = _detectTaskType(input.tool);
|
|
421
|
+
const routing = taskType !== "unknown"
|
|
422
|
+
? config.model_routing.task_routing?.[taskType]
|
|
423
|
+
: undefined;
|
|
424
|
+
if (routing?.model) {
|
|
425
|
+
output.model = routing.model;
|
|
426
|
+
logger.log(`Model routed: ${input.tool} → ${taskType} → ${routing.model}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
logger.error("Model routing error", e);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const { tool, args } = input;
|
|
435
|
+
const gateResult = await beforeToolHook(tool, args);
|
|
436
|
+
if (gateResult.blocked) {
|
|
437
|
+
const msgs = gateResult.violations.map(v => `[${v.severity}] ${v.message}`).join("; ");
|
|
438
|
+
logger.error(`🚫 Enforcement gate blocked: ${msgs}`);
|
|
439
|
+
throw new Error(`ENFORCER BLOCKED: ${msgs}`);
|
|
440
|
+
}
|
|
441
|
+
if (gateResult.violations.length > 0) {
|
|
442
|
+
logger.warn(`⚠️ Enforcement gate: ${gateResult.violations.length} warning(s), resonance=${gateResult.resonance.toFixed(2)}`);
|
|
443
|
+
}
|
|
444
|
+
logger.log(`✅ Enforcement gate passed for ${tool}`);
|
|
445
|
+
if (isWriteEditOperation(tool)) {
|
|
446
|
+
logger.log(`🔧 Processors delegated to enforcement gate for ${tool}`);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
"tool.execute.after": async (input, _output) => {
|
|
450
|
+
const logger = await getOrCreateLogger(directory);
|
|
451
|
+
await loadXrayComponents();
|
|
452
|
+
const { tool, args, result } = input;
|
|
453
|
+
try {
|
|
454
|
+
const { routingOutcomeTracker } = await import("../delegation/analytics/outcome-tracker.js");
|
|
455
|
+
const mapping = TOOL_AGENT_MAP[tool];
|
|
456
|
+
const taskType = classifyTaskType(tool, args);
|
|
457
|
+
const rawDesc = args?.content
|
|
458
|
+
? String(args.content).slice(0, 150)
|
|
459
|
+
: args?.filePath
|
|
460
|
+
? String(args.filePath)
|
|
461
|
+
: args?.command
|
|
462
|
+
? String(args.command).slice(0, 150)
|
|
463
|
+
: tool;
|
|
464
|
+
const description = `[${taskType}] ${rawDesc}`;
|
|
465
|
+
const outcomeFields = {
|
|
466
|
+
taskId: `opencode-${_openCodeToolCallCount}`,
|
|
467
|
+
taskDescription: description,
|
|
468
|
+
routedAgent: mapping?.agent ?? "direct",
|
|
469
|
+
routedSkill: mapping?.skill ?? tool,
|
|
470
|
+
confidence: mapping ? 0.8 : 0.5,
|
|
471
|
+
success: result?.error == null,
|
|
472
|
+
routingMethod: mapping ? "keyword" : "default",
|
|
473
|
+
};
|
|
474
|
+
if (taskType !== "unknown")
|
|
475
|
+
outcomeFields.taskType = taskType;
|
|
476
|
+
routingOutcomeTracker.recordOutcome(outcomeFields);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
// Outcome tracker not available — skip silently
|
|
480
|
+
}
|
|
481
|
+
logger.log(`📥 After hook input: ${JSON.stringify({ tool, hasArgs: !!args, args, hasResult: !!result }).slice(0, 200)}`);
|
|
482
|
+
// Run enforcement after-hook for code-producing tools
|
|
483
|
+
if (tool && (tool.includes('write') || tool.includes('edit') || tool.includes('create') || tool.includes('modify'))) {
|
|
484
|
+
try {
|
|
485
|
+
const afterResult = await afterToolHook(tool, args, result, null);
|
|
486
|
+
if (afterResult.processorResults.length > 0) {
|
|
487
|
+
logger.log(`📊 Post-tool processors: ${afterResult.processorResults.filter(r => r.success).length}/${afterResult.processorResults.length} passed`);
|
|
488
|
+
}
|
|
489
|
+
if (afterResult.governanceTriggered) {
|
|
490
|
+
logger.log(`🏛️ Governance cycle triggered from ${tool} output`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch (e) {
|
|
494
|
+
logger.log(`⚠️ Post-tool enforcement hook skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
_openCodeToolCallCount++;
|
|
498
|
+
if (_openCodeToolCallCount - _lastTuneToolCallCount >= INFERENCE_TUNE_INTERVAL) {
|
|
499
|
+
_lastTuneToolCallCount = _openCodeToolCallCount;
|
|
500
|
+
try {
|
|
501
|
+
const { inferenceTuner } = await import("../services/inference-tuner.js");
|
|
502
|
+
inferenceTuner
|
|
503
|
+
.runTuningCycle()
|
|
504
|
+
.then(() => {
|
|
505
|
+
logger.log(`🔄 Inference tuning cycle completed (call #${_openCodeToolCallCount})`);
|
|
506
|
+
})
|
|
507
|
+
.catch((err) => {
|
|
508
|
+
logger.log(`⚠️ Inference tuning cycle skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
// Tuner not available in this environment — skip silently
|
|
513
|
+
}
|
|
514
|
+
// DISABLED: Auto governance proposal voting causes runaway token consumption
|
|
515
|
+
// See bug fix: inference cycle auto-trigger disabled to prevent recursive
|
|
516
|
+
// opencode process spawning via invokeViaOpencode() → architect → task subagents
|
|
517
|
+
//
|
|
518
|
+
// try {
|
|
519
|
+
// const { InferenceCycle } = await import(
|
|
520
|
+
// "../inference/inference-cycle.js"
|
|
521
|
+
// );
|
|
522
|
+
// const cycle = new InferenceCycle(directory);
|
|
523
|
+
// cycle.maybeRunCycle()...
|
|
524
|
+
// } catch {}
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
"chat.message": async (input, output) => {
|
|
528
|
+
const logger = await getOrCreateLogger(directory);
|
|
529
|
+
if (!output.parts) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const textContent = input.parts?.find(p => p.type === "text")?.text ?? "";
|
|
533
|
+
if (!textContent) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const agentMentionRegex = /@(\w+)(?:\s+(.+?))?(?=$|\n\n|\r\r)/g;
|
|
537
|
+
let match;
|
|
538
|
+
let hasAgentMention = false;
|
|
539
|
+
let transformedText = textContent;
|
|
540
|
+
const knownAgents = {
|
|
541
|
+
"architect": "architect",
|
|
542
|
+
"strategist": "strategist",
|
|
543
|
+
"testing-lead": "testing-lead",
|
|
544
|
+
"bug-triage-specialist": "bug-triage-specialist",
|
|
545
|
+
"code-reviewer": "code-reviewer",
|
|
546
|
+
"security-auditor": "security-auditor",
|
|
547
|
+
"refactorer": "refactorer",
|
|
548
|
+
"researcher": "researcher",
|
|
549
|
+
"code-analyzer": "code-analyzer",
|
|
550
|
+
"frontend-engineer": "frontend-engineer",
|
|
551
|
+
"frontend-ui-ux-engineer": "frontend-ui-ux-engineer",
|
|
552
|
+
"backend-engineer": "backend-engineer",
|
|
553
|
+
"database-engineer": "database-engineer",
|
|
554
|
+
"devops-engineer": "devops-engineer",
|
|
555
|
+
"performance-engineer": "performance-engineer",
|
|
556
|
+
"mobile-developer": "mobile-developer",
|
|
557
|
+
"content-creator": "content-creator",
|
|
558
|
+
"growth-strategist": "growth-strategist",
|
|
559
|
+
"seo-consultant": "seo-consultant",
|
|
560
|
+
"tech-writer": "tech-writer",
|
|
561
|
+
"multimodal-looker": "multimodal-looker",
|
|
562
|
+
"log-monitor": "log-monitor",
|
|
563
|
+
};
|
|
564
|
+
while ((match = agentMentionRegex.exec(textContent)) !== null) {
|
|
565
|
+
const agentName = match[1].toLowerCase().replace(/-/g, "");
|
|
566
|
+
const taskPart = match[2]?.trim() ?? "";
|
|
567
|
+
if (knownAgents[agentName]) {
|
|
568
|
+
hasAgentMention = true;
|
|
569
|
+
const canonicalAgent = knownAgents[agentName];
|
|
570
|
+
logger.log(`🎯 Agent mention detected: @${canonicalAgent}`);
|
|
571
|
+
const prefix = `\n[DELEGATE TO AGENT: ${canonicalAgent}]\n`;
|
|
572
|
+
transformedText = prefix + (taskPart || textContent.replace(`@${match[1]}`, "").trim());
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (hasAgentMention) {
|
|
577
|
+
const textPart = output.parts.find(p => p.type === "text");
|
|
578
|
+
if (textPart) {
|
|
579
|
+
textPart.text = transformedText;
|
|
580
|
+
logger.log(`✅ Transformed prompt for agent routing`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
config: async (_config) => {
|
|
585
|
+
const lockFile = path.join(directory, ".opencode", "logs", ".xray-init.lock");
|
|
586
|
+
const now = Date.now();
|
|
587
|
+
try {
|
|
588
|
+
if (fs.existsSync(lockFile)) {
|
|
589
|
+
const stat = fs.statSync(lockFile);
|
|
590
|
+
if (now - stat.mtimeMs < 15000) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
fs.writeFileSync(lockFile, String(now));
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
// lock check failed — proceed anyway
|
|
598
|
+
}
|
|
599
|
+
const logger = await getOrCreateLogger(directory);
|
|
600
|
+
logger.log("🔧 Plugin config hook triggered - initializing 0xRay integration");
|
|
601
|
+
let initScriptPath = path.join(directory, ".opencode", "init.sh");
|
|
602
|
+
const pkgInitPath = path.join(directory, "node_modules", "0xray", ".opencode", "init.sh");
|
|
603
|
+
if (!fs.existsSync(initScriptPath) && fs.existsSync(pkgInitPath)) {
|
|
604
|
+
initScriptPath = pkgInitPath;
|
|
605
|
+
}
|
|
606
|
+
if (fs.existsSync(initScriptPath)) {
|
|
607
|
+
try {
|
|
608
|
+
const { stderr } = await spawnPromise("bash", [initScriptPath], directory);
|
|
609
|
+
if (stderr) {
|
|
610
|
+
logger.error(`Framework init error: ${stderr}`);
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
logger.log("✅ 0xRay Framework initialized successfully");
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
logger.error("Framework initialization failed", error);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
logger.log("✅ Plugin config hook completed");
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
//# sourceMappingURL=xray-codex-injection.js.map
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0xRay — Self-Healing AI Governance OS
|
|
2
2
|
|
|
3
|
-
**v3.0.
|
|
3
|
+
**v3.0.4** — 41 agents · 44 skills · 15 MCPs servers · 20 codex terms · 160 test files
|
|
4
4
|
|
|
5
5
|
[](https://0xrayai.github.io/xray/)
|
|
6
6
|
|
|
@@ -14,25 +14,24 @@ Think of it as an **AI supervisor for your AI coders**: catch hallucinations, pr
|
|
|
14
14
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
|
+
Pick your AI coding tool and run **one command**:
|
|
18
|
+
|
|
17
19
|
```bash
|
|
18
|
-
|
|
20
|
+
# OpenCode (most common)
|
|
21
|
+
npx -y 0xray opencode install
|
|
19
22
|
|
|
20
|
-
#
|
|
21
|
-
npx 0xray
|
|
23
|
+
# Grok CLI
|
|
24
|
+
npx -y 0xray grok install
|
|
22
25
|
|
|
23
|
-
#
|
|
24
|
-
npx 0xray
|
|
25
|
-
npx 0xray grok install # Grok CLI
|
|
26
|
-
npx 0xray hermes install # Hermes Agent
|
|
27
|
-
npx 0xray openclaw install # OpenClaw
|
|
26
|
+
# Hermes Agent
|
|
27
|
+
npx -y 0xray hermes install
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
npx 0xray
|
|
31
|
-
|
|
32
|
-
# See all available commands
|
|
33
|
-
npx 0xray --help
|
|
29
|
+
# OpenClaw
|
|
30
|
+
npx -y 0xray openclaw install
|
|
34
31
|
```
|
|
35
32
|
|
|
33
|
+
That's it — 0xRay installs as a native plugin with agents, governance, and MCP servers for your platform. Run `npx 0xray --help` to see all commands.
|
|
34
|
+
|
|
36
35
|
**Requirements:** Node.js 18+, npm 9+, macOS or Linux.
|
|
37
36
|
|
|
38
37
|
---
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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.0.4] - 2026-06-13
|
|
8
|
+
|
|
9
|
+
### 🔄 Changes
|
|
10
|
+
|
|
11
|
+
- Version bump
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## [3.0.4] - 2026-06-13
|
|
16
|
+
|
|
17
|
+
### 🔄 Changes
|
|
18
|
+
|
|
19
|
+
- Version bump
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
7
23
|
## [3.0.3] - 2026-06-13
|
|
8
24
|
|
|
9
25
|
### 🔄 Changes
|
package/dist/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0xRay — Self-Healing AI Governance OS
|
|
2
2
|
|
|
3
|
-
**v3.0.
|
|
3
|
+
**v3.0.4** — 41 agents · 44 skills · 15 MCPs servers · 20 codex terms · 160 test files
|
|
4
4
|
|
|
5
5
|
[](https://0xrayai.github.io/xray/)
|
|
6
6
|
|
|
@@ -14,25 +14,24 @@ Think of it as an **AI supervisor for your AI coders**: catch hallucinations, pr
|
|
|
14
14
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
|
+
Pick your AI coding tool and run **one command**:
|
|
18
|
+
|
|
17
19
|
```bash
|
|
18
|
-
|
|
20
|
+
# OpenCode (most common)
|
|
21
|
+
npx -y 0xray opencode install
|
|
19
22
|
|
|
20
|
-
#
|
|
21
|
-
npx 0xray
|
|
23
|
+
# Grok CLI
|
|
24
|
+
npx -y 0xray grok install
|
|
22
25
|
|
|
23
|
-
#
|
|
24
|
-
npx 0xray
|
|
25
|
-
npx 0xray grok install # Grok CLI
|
|
26
|
-
npx 0xray hermes install # Hermes Agent
|
|
27
|
-
npx 0xray openclaw install # OpenClaw
|
|
26
|
+
# Hermes Agent
|
|
27
|
+
npx -y 0xray hermes install
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
npx 0xray
|
|
31
|
-
|
|
32
|
-
# See all available commands
|
|
33
|
-
npx 0xray --help
|
|
29
|
+
# OpenClaw
|
|
30
|
+
npx -y 0xray openclaw install
|
|
34
31
|
```
|
|
35
32
|
|
|
33
|
+
That's it — 0xRay installs as a native plugin with agents, governance, and MCP servers for your platform. Run `npx 0xray --help` to see all commands.
|
|
34
|
+
|
|
36
35
|
**Requirements:** Node.js 18+, npm 9+, macOS or Linux.
|
|
37
36
|
|
|
38
37
|
---
|
|
@@ -719,7 +719,7 @@ export class BootOrchestrator {
|
|
|
719
719
|
const xrayConfig = {
|
|
720
720
|
version: "3.0.3",
|
|
721
721
|
codex_enabled: true,
|
|
722
|
-
codex_version: "v3.0.
|
|
722
|
+
codex_version: "v3.0.3",
|
|
723
723
|
codex_terms: [
|
|
724
724
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
|
725
725
|
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"postinstall": "node scripts/node/postinstall.cjs",
|
|
74
74
|
"prepare": "npm run build",
|
|
75
75
|
"prebuild": "rm -rf dist tsconfig.tsbuildinfo tsconfig.*.tsbuildinfo",
|
|
76
|
-
"build": "tsc && tsc -p tsconfig.full.json 2>/dev/null || true && mkdir -p dist/public dist/scripts && cp -r public/* dist/public/ && cp scripts/hooks/pre-command dist/scripts/ && cp scripts/hooks/pre-command.mjs dist/scripts/ && cp README.md AGENTS.md CHANGELOG.md LICENSE dist/ && find src -name '*.mjs' ! -path '*/__tests__/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done && find dist -name '*.d.ts' -path '*/__tests__/*' -delete; find dist -name '*.js' -path '*/__tests__/*' -delete; for dir in skills integrations mcps; do find src/$dir -type f ! -name '*.ts' ! -path '*/.pytest_cache/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done; done && mkdir -p dist/plugin && (find dist/plugin -name '*codex-injection*.js' -exec cp {} dist/plugin/xray-codex-injection.js \\; 2>/dev/null || true) && cp -r src/opencode/ .opencode/ && echo 'build artifacts in .opencode/xray/ removed — runtime reads from xray/ directly'",
|
|
76
|
+
"build": "tsc && tsc -p tsconfig.full.json 2>/dev/null || true && mkdir -p dist/public dist/scripts && cp -r public/* dist/public/ && cp scripts/hooks/pre-command dist/scripts/ && cp scripts/hooks/pre-command.mjs dist/scripts/ && cp README.md AGENTS.md CHANGELOG.md LICENSE dist/ && find src -name '*.mjs' ! -path '*/__tests__/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done && find dist -name '*.d.ts' -path '*/__tests__/*' -delete; find dist -name '*.js' -path '*/__tests__/*' -delete; for dir in skills integrations mcps; do find src/$dir -type f ! -name '*.ts' ! -path '*/.pytest_cache/*' | while read f; do tgt=\"dist/${f#src/}\"; mkdir -p \"$(dirname $tgt)\"; cp \"$f\" \"$tgt\"; done; done && mkdir -p dist/plugin && (find dist/plugin -name '*codex-injection*.js' -exec cp {} dist/plugin/xray-codex-injection.js \\; 2>/dev/null || true) && cp -r src/opencode/ .opencode/ && mkdir -p .opencode/plugin && cp dist/plugin/xray-codex-injection.js .opencode/plugin/ && echo 'build artifacts in .opencode/xray/ removed — runtime reads from xray/ directly'",
|
|
77
77
|
"build:all": "npm run build",
|
|
78
78
|
"ci-install": "npm ci",
|
|
79
79
|
"clean": "rm -rf dist tsconfig.tsbuildinfo tsconfig.*.tsbuildinfo",
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
".opencode/workflows/",
|
|
164
164
|
".opencode/codex.codex",
|
|
165
165
|
".opencode/enforcer-config.json",
|
|
166
|
+
".opencode/plugin/",
|
|
166
167
|
"opencode.json",
|
|
167
168
|
"README.md",
|
|
168
169
|
"AGENTS.md",
|
|
@@ -147,7 +147,7 @@ function detectCodexInfo() {
|
|
|
147
147
|
return { version: cver.startsWith("v") ? cver : `v${cver}`, termsCount: termCount, lastUpdated: new Date().toISOString().split("T")[0] };
|
|
148
148
|
} catch {}
|
|
149
149
|
}
|
|
150
|
-
return { version: "v3.0.
|
|
150
|
+
return { version: "v3.0.3", termsCount: 68, lastUpdated: new Date().toISOString().split("T")[0] };
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const detectedCodex = detectCodexInfo();
|
|
@@ -155,7 +155,7 @@ const detectedCodex = detectCodexInfo();
|
|
|
155
155
|
const OFFICIAL_VERSIONS = {
|
|
156
156
|
// Framework version
|
|
157
157
|
framework: {
|
|
158
|
-
version: "3.0.
|
|
158
|
+
version: "3.0.4",
|
|
159
159
|
displayName: "xray: Self-Healing AI Governance OS",
|
|
160
160
|
lastUpdated: "2026-06-13",
|
|
161
161
|
// Counts (auto-calculated, but can be overridden)
|