strray-ai 1.22.44 â 1.22.45
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/strray-codex-injection.js +241 -291
- package/.opencode/strray/codex.json +1 -1
- package/.opencode/strray/config.json +1 -1
- package/.opencode/strray/features.json +1 -1
- package/.opencode/strray/integrations.json +3 -3
- package/.strray/codex.json +1 -1
- package/.strray/config.json +1 -1
- package/.strray/features.json +1 -1
- package/.strray/integrations.json +3 -3
- package/AGENTS.md +4 -4
- package/dist/AGENTS.md +4 -4
- package/dist/plugin/strray-codex-injection.d.ts +63 -0
- package/dist/plugin/strray-codex-injection.d.ts.map +1 -0
- package/dist/plugin/strray-codex-injection.js +840 -0
- package/dist/plugin/strray-codex-injection.js.map +1 -0
- package/package.json +3 -3
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 0xRay Codex Injection Plugin for OpenCode
|
|
3
3
|
*
|
|
4
4
|
* This plugin automatically injects the Universal Development Codex v1.2.0
|
|
5
5
|
* into the system prompt for all AI agents, ensuring codex terms are
|
|
6
6
|
* consistently enforced across the entire development session.
|
|
7
7
|
*
|
|
8
8
|
* @version 1.0.0
|
|
9
|
-
* @author
|
|
9
|
+
* @author 0xRay Framework
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from "fs";
|
|
12
12
|
import * as path from "path";
|
|
13
13
|
import { spawn } from "child_process";
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
let
|
|
18
|
-
let
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Dynamic module holders (loaded via candidate-based resolution)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
let _resolveCodexPath = null;
|
|
18
|
+
let _resolveStateDir = null;
|
|
19
|
+
let _frameworkLogger = null;
|
|
20
|
+
let _systemPromptGenerator = null;
|
|
21
|
+
let _ProcessorManager = null;
|
|
22
|
+
let _StrRayStateManager = null;
|
|
23
|
+
let _featuresConfigLoader = null;
|
|
24
|
+
let _detectTaskType = null;
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Module loaders (candidate-based resolution for dev, dist, and consumer paths)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
19
28
|
async function loadFrameworkLogger() {
|
|
20
29
|
if (_frameworkLogger)
|
|
21
30
|
return _frameworkLogger;
|
|
@@ -34,7 +43,6 @@ async function loadFrameworkLogger() {
|
|
|
34
43
|
// try next candidate
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
|
-
// Fallback: no-op logger so plugin doesn't crash
|
|
38
46
|
_frameworkLogger = {
|
|
39
47
|
log: (_module, _event, _status, _data) => { },
|
|
40
48
|
};
|
|
@@ -62,63 +70,59 @@ async function loadConfigPaths() {
|
|
|
62
70
|
const logger = await loadFrameworkLogger();
|
|
63
71
|
logger.log("strray-codex-plugin", "config-paths-load-failed", "warning", { warning: "Failed to load config-paths module from any location" });
|
|
64
72
|
}
|
|
65
|
-
|
|
66
|
-
async function resolveCodexPath(...args) {
|
|
73
|
+
async function resolveCodexPath(root) {
|
|
67
74
|
await loadConfigPaths();
|
|
68
|
-
|
|
75
|
+
if (!_resolveCodexPath)
|
|
76
|
+
throw new Error("resolveCodexPath not available after loading");
|
|
77
|
+
return _resolveCodexPath(root);
|
|
69
78
|
}
|
|
70
|
-
async function resolveStateDir(
|
|
79
|
+
async function resolveStateDir(root) {
|
|
71
80
|
await loadConfigPaths();
|
|
72
|
-
|
|
81
|
+
if (!_resolveStateDir)
|
|
82
|
+
throw new Error("resolveStateDir not available after loading");
|
|
83
|
+
return _resolveStateDir(root);
|
|
73
84
|
}
|
|
74
|
-
// Import lean system prompt generator
|
|
75
|
-
let SystemPromptGenerator;
|
|
76
85
|
async function importSystemPromptGenerator() {
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
if (_systemPromptGenerator)
|
|
87
|
+
return;
|
|
88
|
+
const candidates = [
|
|
89
|
+
"../core/system-prompt-generator.js",
|
|
90
|
+
"../../dist/core/system-prompt-generator.js",
|
|
91
|
+
"../../node_modules/strray-ai/dist/core/system-prompt-generator.js",
|
|
92
|
+
];
|
|
93
|
+
for (const p of candidates) {
|
|
94
|
+
try {
|
|
95
|
+
const module = await import(p);
|
|
96
|
+
_systemPromptGenerator = module.generateLeanSystemPrompt;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
catch (_) {
|
|
100
|
+
// try next candidate
|
|
92
101
|
}
|
|
93
|
-
const logger = await loadFrameworkLogger();
|
|
94
|
-
logger.log("strray-codex-plugin", "system-prompt-generator-load-failed", "warning", { warning: "Failed to load lean system prompt generator, using fallback" });
|
|
95
102
|
}
|
|
103
|
+
const logger = await loadFrameworkLogger();
|
|
104
|
+
logger.log("strray-codex-plugin", "system-prompt-generator-load-failed", "warning", { warning: "Failed to load lean system prompt generator, using fallback" });
|
|
96
105
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let featuresConfigLoader;
|
|
100
|
-
let detectTaskType;
|
|
101
|
-
async function loadStrRayComponents() {
|
|
102
|
-
if (ProcessorManager && StrRayStateManager && featuresConfigLoader)
|
|
106
|
+
async function loadStringRayComponents() {
|
|
107
|
+
if (_ProcessorManager && _StrRayStateManager && _featuresConfigLoader)
|
|
103
108
|
return;
|
|
104
109
|
const logger = await getOrCreateLogger(process.cwd());
|
|
105
|
-
// Try local dist first (for development)
|
|
106
110
|
try {
|
|
107
111
|
logger.log(`đ Attempting to load from ../../dist/`);
|
|
108
112
|
const procModule = await import("../../dist/processors/processor-manager.js");
|
|
109
113
|
const stateModule = await import("../../dist/state/state-manager.js");
|
|
110
114
|
const featuresModule = await import("../../dist/core/features-config.js");
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
_ProcessorManager = procModule.ProcessorManager;
|
|
116
|
+
_StrRayStateManager = stateModule.StrRayStateManager;
|
|
117
|
+
_featuresConfigLoader = featuresModule.featuresConfigLoader;
|
|
118
|
+
_detectTaskType = featuresModule.detectTaskType;
|
|
115
119
|
logger.log(`â
Loaded from ../../dist/`);
|
|
116
120
|
return;
|
|
117
121
|
}
|
|
118
122
|
catch (e) {
|
|
119
|
-
|
|
123
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
124
|
+
logger.log(`â Failed to load from ../../dist/: ${message}`);
|
|
120
125
|
}
|
|
121
|
-
// Try node_modules (for consumer installation)
|
|
122
126
|
const pluginPaths = ["strray-ai", "strray-framework"];
|
|
123
127
|
for (const pluginPath of pluginPaths) {
|
|
124
128
|
try {
|
|
@@ -126,28 +130,29 @@ async function loadStrRayComponents() {
|
|
|
126
130
|
const pm = await import(`../../node_modules/${pluginPath}/dist/processors/processor-manager.js`);
|
|
127
131
|
const sm = await import(`../../node_modules/${pluginPath}/dist/state/state-manager.js`);
|
|
128
132
|
const fm = await import(`../../node_modules/${pluginPath}/dist/core/features-config.js`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
_ProcessorManager = pm.ProcessorManager;
|
|
134
|
+
_StrRayStateManager = sm.StrRayStateManager;
|
|
135
|
+
_featuresConfigLoader = fm.featuresConfigLoader;
|
|
136
|
+
_detectTaskType = fm.detectTaskType;
|
|
133
137
|
logger.log(`â
Loaded from ../../node_modules/${pluginPath}/dist/`);
|
|
134
138
|
return;
|
|
135
139
|
}
|
|
136
140
|
catch (e) {
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
142
|
+
logger.log(`â Failed to load from ../../node_modules/${pluginPath}/dist/: ${message}`);
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Helpers
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
142
149
|
function spawnPromise(command, args, cwd) {
|
|
143
150
|
return new Promise((resolve, reject) => {
|
|
144
151
|
const child = spawn(command, args, {
|
|
145
152
|
cwd,
|
|
146
|
-
stdio: ["ignore", "inherit", "pipe"],
|
|
153
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
147
154
|
});
|
|
148
|
-
let stdout = "";
|
|
149
155
|
let stderr = "";
|
|
150
|
-
// Capture stderr only (stdout goes to inherit/terminal)
|
|
151
156
|
if (child.stderr) {
|
|
152
157
|
child.stderr.on("data", (data) => {
|
|
153
158
|
stderr += data.toString();
|
|
@@ -155,7 +160,7 @@ function spawnPromise(command, args, cwd) {
|
|
|
155
160
|
}
|
|
156
161
|
child.on("close", (code) => {
|
|
157
162
|
if (code === 0) {
|
|
158
|
-
resolve({ stdout, stderr });
|
|
163
|
+
resolve({ stdout: "", stderr });
|
|
159
164
|
}
|
|
160
165
|
else {
|
|
161
166
|
reject(new Error(`Process exited with code ${code}: ${stderr}`));
|
|
@@ -182,7 +187,7 @@ class PluginLogger {
|
|
|
182
187
|
const logEntry = `[${timestamp}] ${message}\n`;
|
|
183
188
|
await fs.promises.appendFile(this.logPath, logEntry, "utf-8");
|
|
184
189
|
}
|
|
185
|
-
catch
|
|
190
|
+
catch {
|
|
186
191
|
// Silent fail - logging failure should not break plugin
|
|
187
192
|
}
|
|
188
193
|
}
|
|
@@ -210,9 +215,6 @@ async function getOrCreateLogger(directory) {
|
|
|
210
215
|
})();
|
|
211
216
|
return loggerInitPromise;
|
|
212
217
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Get the current framework version from package.json
|
|
215
|
-
*/
|
|
216
218
|
function getFrameworkVersion() {
|
|
217
219
|
try {
|
|
218
220
|
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
@@ -223,12 +225,9 @@ function getFrameworkVersion() {
|
|
|
223
225
|
return "1.4.6";
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Get lean framework identity message (token-efficient version)
|
|
228
|
-
*/
|
|
229
228
|
function getFrameworkIdentity() {
|
|
230
229
|
const version = getFrameworkVersion();
|
|
231
|
-
return `
|
|
230
|
+
return `0xRay Framework v${version} - AI Orchestration
|
|
232
231
|
|
|
233
232
|
đ§ Core: enforcer, architect, orchestrator, code-reviewer, refactorer, testing-lead
|
|
234
233
|
đ Codex: 5 Essential Terms (99.6% Error Prevention Target)
|
|
@@ -237,18 +236,15 @@ function getFrameworkIdentity() {
|
|
|
237
236
|
đ Documentation: config dir (codex, config, agents docs) â resolved via config-paths
|
|
238
237
|
`;
|
|
239
238
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
*/
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Enforcer quality gate
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
244
242
|
async function runEnforcerQualityGate(input, logger) {
|
|
245
243
|
const violations = [];
|
|
246
244
|
const { tool, args } = input;
|
|
247
245
|
try {
|
|
248
|
-
// Lazy load RuleEnforcer to avoid circular dependencies
|
|
249
246
|
const { RuleEnforcer } = await import("../enforcement/rule-enforcer.js");
|
|
250
247
|
const ruleEnforcer = new RuleEnforcer();
|
|
251
|
-
// Build validation context from the input
|
|
252
248
|
const context = {
|
|
253
249
|
operation: tool === "write" ? "write" : tool === "edit" ? "edit" : "read",
|
|
254
250
|
};
|
|
@@ -258,25 +254,20 @@ async function runEnforcerQualityGate(input, logger) {
|
|
|
258
254
|
if (args?.content) {
|
|
259
255
|
context.newCode = args.content;
|
|
260
256
|
}
|
|
261
|
-
// Use RuleEnforcer.validateOperation() instead of hardcoded rules
|
|
262
257
|
const report = await ruleEnforcer.validateOperation(tool, context);
|
|
263
|
-
// Collect blocking violations from errors and failed results
|
|
264
258
|
const blockingViolations = [];
|
|
265
259
|
const allViolations = [];
|
|
266
|
-
// Check errors (these are blocking by nature)
|
|
267
260
|
if (report.errors && report.errors.length > 0) {
|
|
268
261
|
for (const error of report.errors) {
|
|
269
262
|
allViolations.push(error);
|
|
270
|
-
blockingViolations.push(error);
|
|
263
|
+
blockingViolations.push(error);
|
|
271
264
|
}
|
|
272
265
|
}
|
|
273
|
-
// Check failed results - only block on error severity, not warnings
|
|
274
266
|
if (report.results) {
|
|
275
267
|
for (const result of report.results) {
|
|
276
268
|
if (!result.passed) {
|
|
277
269
|
const isBlocking = result.severity === "error" || result.severity === "blocking" || result.severity === "high";
|
|
278
270
|
allViolations.push(result.message);
|
|
279
|
-
// Only block on error/blocking/high severity, allow warnings
|
|
280
271
|
if (isBlocking) {
|
|
281
272
|
blockingViolations.push(result.message);
|
|
282
273
|
}
|
|
@@ -303,9 +294,7 @@ async function runEnforcerQualityGate(input, logger) {
|
|
|
303
294
|
return { passed, violations };
|
|
304
295
|
}
|
|
305
296
|
catch (error) {
|
|
306
|
-
// If RuleEnforcer fails to load or run, fall back to minimal checks
|
|
307
297
|
logger.log(`Warning: RuleEnforcer unavailable, using fallback checks: ${error instanceof Error ? error.message : String(error)}`);
|
|
308
|
-
// Fallback: minimal hardcoded checks (the old logic, as safety net)
|
|
309
298
|
if (tool === "write" && args?.filePath) {
|
|
310
299
|
const filePath = args.filePath;
|
|
311
300
|
if (filePath.endsWith(".ts") &&
|
|
@@ -337,24 +326,13 @@ async function runEnforcerQualityGate(input, logger) {
|
|
|
337
326
|
return { passed, violations };
|
|
338
327
|
}
|
|
339
328
|
}
|
|
340
|
-
/**
|
|
341
|
-
* Global codex context cache (loaded once)
|
|
342
|
-
*/
|
|
343
329
|
let cachedCodexContexts = null;
|
|
344
|
-
/**
|
|
345
|
-
* Codex file locations resolved through the standard priority chain.
|
|
346
|
-
* Falls back to additional OpenCode-specific files not covered by the resolver.
|
|
347
|
-
*/
|
|
348
330
|
async function getCodexFileLocations(directory) {
|
|
349
331
|
const root = directory || process.cwd();
|
|
350
332
|
const resolved = await resolveCodexPath(root);
|
|
351
|
-
// Add OpenCode-specific fallbacks not in the standard chain
|
|
352
333
|
resolved.push(path.join(root, ".opencode", "codex.codex"), path.join(root, ".strray", "agents_template.md"), path.join(root, ".opencode", "strray", "agents_template.md"), path.join(root, "AGENTS.md"));
|
|
353
334
|
return resolved;
|
|
354
335
|
}
|
|
355
|
-
/**
|
|
356
|
-
* Read file content safely
|
|
357
|
-
*/
|
|
358
336
|
function readFileContent(filePath) {
|
|
359
337
|
try {
|
|
360
338
|
return fs.readFileSync(filePath, "utf-8");
|
|
@@ -365,11 +343,7 @@ function readFileContent(filePath) {
|
|
|
365
343
|
return null;
|
|
366
344
|
}
|
|
367
345
|
}
|
|
368
|
-
/**
|
|
369
|
-
* Extract codex metadata from content
|
|
370
|
-
*/
|
|
371
346
|
function extractCodexMetadata(content) {
|
|
372
|
-
// Try JSON format first (codex.json)
|
|
373
347
|
if (content.trim().startsWith("{")) {
|
|
374
348
|
try {
|
|
375
349
|
const parsed = JSON.parse(content);
|
|
@@ -382,16 +356,12 @@ function extractCodexMetadata(content) {
|
|
|
382
356
|
// Not valid JSON, try markdown format
|
|
383
357
|
}
|
|
384
358
|
}
|
|
385
|
-
// Markdown format (AGENTS.md, agents_template.md via config-paths resolver)
|
|
386
359
|
const versionMatch = content.match(/\*\*Version\*\*:\s*(\d+\.\d+\.\d+)/);
|
|
387
|
-
const version = versionMatch
|
|
360
|
+
const version = versionMatch?.[1] ?? "1.6.0";
|
|
388
361
|
const termMatches = content.match(/####\s*\d+\.\s/g);
|
|
389
362
|
const termCount = termMatches ? termMatches.length : 0;
|
|
390
363
|
return { version, termCount };
|
|
391
364
|
}
|
|
392
|
-
/**
|
|
393
|
-
* Create codex context entry
|
|
394
|
-
*/
|
|
395
365
|
function createCodexContextEntry(filePath, content) {
|
|
396
366
|
const metadata = extractCodexMetadata(content);
|
|
397
367
|
return {
|
|
@@ -406,9 +376,6 @@ function createCodexContextEntry(filePath, content) {
|
|
|
406
376
|
},
|
|
407
377
|
};
|
|
408
378
|
}
|
|
409
|
-
/**
|
|
410
|
-
* Load codex context (cached globally, loaded once)
|
|
411
|
-
*/
|
|
412
379
|
async function loadCodexContext(directory) {
|
|
413
380
|
if (cachedCodexContexts) {
|
|
414
381
|
return cachedCodexContexts;
|
|
@@ -431,34 +398,22 @@ async function loadCodexContext(directory) {
|
|
|
431
398
|
}
|
|
432
399
|
return codexContexts;
|
|
433
400
|
}
|
|
434
|
-
/**
|
|
435
|
-
* Format codex context for injection
|
|
436
|
-
*/
|
|
437
401
|
function formatCodexContext(contexts) {
|
|
438
402
|
if (contexts.length === 0) {
|
|
439
403
|
return "";
|
|
440
404
|
}
|
|
441
405
|
const parts = [];
|
|
442
406
|
for (const context of contexts) {
|
|
443
|
-
parts.push(`#
|
|
407
|
+
parts.push(`# 0xRay Codex Context v${context.metadata.version}`, `Source: ${context.source}`, `Terms Loaded: ${context.metadata.termCount}`, `Loaded At: ${context.metadata.loadedAt}`, "", context.content, "", "---", "");
|
|
444
408
|
}
|
|
445
409
|
return parts.join("\n");
|
|
446
410
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
* This plugin hooks into experimental.chat.system.transform event
|
|
451
|
-
* to inject codex terms into system prompt before it's sent to LLM.
|
|
452
|
-
*/
|
|
453
|
-
/** Inference tuning: run every N tool calls */
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Analytics and task classification
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
454
414
|
const INFERENCE_TUNE_INTERVAL = 100;
|
|
455
415
|
let _openCodeToolCallCount = 0;
|
|
456
416
|
let _lastTuneToolCallCount = 0;
|
|
457
|
-
/**
|
|
458
|
-
* Map tool names to agent/skill identifiers for outcome tracking.
|
|
459
|
-
* This lets the analytics pipeline correlate tool usage patterns
|
|
460
|
-
* with agent routing effectiveness.
|
|
461
|
-
*/
|
|
462
417
|
const TOOL_AGENT_MAP = {
|
|
463
418
|
write: { agent: "code-reviewer", skill: "write" },
|
|
464
419
|
edit: { agent: "code-reviewer", skill: "edit" },
|
|
@@ -470,11 +425,6 @@ const TOOL_AGENT_MAP = {
|
|
|
470
425
|
grep: { agent: "researcher", skill: "search" },
|
|
471
426
|
ls: { agent: "researcher", skill: "list" },
|
|
472
427
|
};
|
|
473
|
-
/**
|
|
474
|
-
* Classify a tool call into a meaningful task type for analytics.
|
|
475
|
-
* Mirrors _classify_task_type in the Hermes plugin so both plugins
|
|
476
|
-
* produce comparable outcome data for the inference tuner.
|
|
477
|
-
*/
|
|
478
428
|
function classifyTaskType(tool, args) {
|
|
479
429
|
const cmd = String(args?.command ?? "").toLowerCase().trim();
|
|
480
430
|
if (tool === "bash" && cmd) {
|
|
@@ -503,36 +453,104 @@ function classifyTaskType(tool, args) {
|
|
|
503
453
|
return "search";
|
|
504
454
|
return "unknown";
|
|
505
455
|
}
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
// Shared helpers extracted from duplicated plugin logic
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
function isWriteEditOperation(tool) {
|
|
460
|
+
return tool === "write" || tool === "edit" || tool === "multiedit";
|
|
461
|
+
}
|
|
462
|
+
function isPublishOperation(tool) {
|
|
463
|
+
return tool === "publish" || tool === "release" || tool === "npm-publish" || tool === "strray-release";
|
|
464
|
+
}
|
|
465
|
+
function resolveAgentName(input) {
|
|
466
|
+
const globalAgent = globalThis.currentAgent;
|
|
467
|
+
if (globalAgent?.agentType)
|
|
468
|
+
return globalAgent.agentType;
|
|
469
|
+
if (globalAgent?.type)
|
|
470
|
+
return globalAgent.type;
|
|
471
|
+
if (input?.agentType)
|
|
472
|
+
return input.agentType;
|
|
473
|
+
return "orchestrator";
|
|
474
|
+
}
|
|
475
|
+
function registerAllProcessors(pm) {
|
|
476
|
+
pm.registerProcessor({ name: "preValidate", type: "pre", priority: 10, enabled: true });
|
|
477
|
+
pm.registerProcessor({ name: "codexCompliance", type: "pre", priority: 20, enabled: true });
|
|
478
|
+
pm.registerProcessor({ name: "versionCompliance", type: "pre", priority: 25, enabled: true });
|
|
479
|
+
pm.registerProcessor({ name: "testAutoCreation", type: "post", priority: 5, enabled: true });
|
|
480
|
+
pm.registerProcessor({ name: "testExecution", type: "post", priority: 10, enabled: true });
|
|
481
|
+
pm.registerProcessor({ name: "coverageAnalysis", type: "post", priority: 20, enabled: true });
|
|
482
|
+
}
|
|
483
|
+
function registerAfterPostProcessors(pm) {
|
|
484
|
+
pm.registerProcessor({ name: "testAutoCreation", type: "post", priority: 50, enabled: true });
|
|
485
|
+
pm.registerProcessor({ name: "testExecution", type: "post", priority: 10, enabled: true });
|
|
486
|
+
pm.registerProcessor({ name: "coverageAnalysis", type: "post", priority: 20, enabled: true });
|
|
487
|
+
}
|
|
488
|
+
function logPreProcessorResults(results, logger) {
|
|
489
|
+
logger.log(`đ Pre-processor result: ${results.success ? "SUCCESS" : "FAILED"} (${results.results.length} processors)`);
|
|
490
|
+
if (!results.success) {
|
|
491
|
+
const failures = results.results.filter((r) => !r.success);
|
|
492
|
+
for (const f of failures) {
|
|
493
|
+
logger.error(`â Pre-processor ${f.processorName} failed: ${f.error}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
for (const r of results.results) {
|
|
498
|
+
logger.log(`â
Pre-processor ${r.processorName}: ${r.success ? "OK" : "FAILED"}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function logPostProcessorResults(results, logger) {
|
|
503
|
+
const allSuccess = results.every((r) => r.success);
|
|
504
|
+
logger.log(`đ Post-processor result: ${allSuccess ? "SUCCESS" : "FAILED"} (${results.length} processors)`);
|
|
505
|
+
for (const r of results) {
|
|
506
|
+
if (r.success) {
|
|
507
|
+
logger.log(`â
Post-processor ${r.processorName}: OK`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
logger.error(`â Post-processor ${r.processorName} failed: ${r.error}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function logTestAutoCreationResult(results, logger) {
|
|
515
|
+
const testAutoResult = results.find((r) => r.processorName === "testAutoCreation");
|
|
516
|
+
if (testAutoResult) {
|
|
517
|
+
const data = testAutoResult.data;
|
|
518
|
+
if (testAutoResult.success && data?.testCreated && data?.testFile) {
|
|
519
|
+
logger.log(`â
TEST AUTO-CREATION: Created ${data.testFile}`);
|
|
520
|
+
}
|
|
521
|
+
else if (!testAutoResult.success) {
|
|
522
|
+
logger.log(`âšī¸ TEST AUTO-CREATION: ${testAutoResult.error ?? "skipped - no new files"}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// Main plugin function
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
506
529
|
export default async function strrayCodexPlugin(input) {
|
|
507
530
|
const { directory: inputDirectory } = input;
|
|
508
531
|
const directory = inputDirectory || process.cwd();
|
|
509
532
|
return {
|
|
510
533
|
"experimental.chat.system.transform": async (_input, output) => {
|
|
511
534
|
try {
|
|
512
|
-
// Use lean system prompt generator for token efficiency
|
|
513
535
|
await importSystemPromptGenerator();
|
|
514
536
|
let leanPrompt = getFrameworkIdentity();
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
leanPrompt = await SystemPromptGenerator({
|
|
537
|
+
if (_systemPromptGenerator) {
|
|
538
|
+
leanPrompt = await _systemPromptGenerator({
|
|
518
539
|
showWelcomeBanner: true,
|
|
519
|
-
showCodexContext: false,
|
|
540
|
+
showCodexContext: false,
|
|
520
541
|
enableTokenOptimization: true,
|
|
521
|
-
maxTokenBudget:
|
|
542
|
+
maxTokenBudget: 8192,
|
|
522
543
|
showCriticalTermsOnly: true,
|
|
523
|
-
showEssentialLinks: true
|
|
544
|
+
showEssentialLinks: true,
|
|
524
545
|
});
|
|
525
546
|
}
|
|
526
547
|
if (output.system && Array.isArray(output.system)) {
|
|
527
|
-
// Replace verbose system prompt with lean version
|
|
528
548
|
output.system = [leanPrompt];
|
|
529
549
|
}
|
|
530
550
|
}
|
|
531
551
|
catch (error) {
|
|
532
|
-
// Critical failure - log error but don't break the plugin
|
|
533
552
|
const logger = await getOrCreateLogger(directory);
|
|
534
553
|
logger.error("System prompt injection failed:", error);
|
|
535
|
-
// Fallback to minimal prompt
|
|
536
554
|
const fallback = getFrameworkIdentity();
|
|
537
555
|
if (output.system && Array.isArray(output.system)) {
|
|
538
556
|
output.system = [fallback];
|
|
@@ -543,13 +561,15 @@ export default async function strrayCodexPlugin(input) {
|
|
|
543
561
|
const logger = await getOrCreateLogger(directory);
|
|
544
562
|
logger.log(`đ TOOL EXECUTE BEFORE HOOK FIRED: ${input.tool}`);
|
|
545
563
|
logger.log(`đĨ Full input: ${JSON.stringify(input)}`);
|
|
546
|
-
await
|
|
547
|
-
if (
|
|
564
|
+
await loadStringRayComponents();
|
|
565
|
+
if (_featuresConfigLoader && _detectTaskType) {
|
|
548
566
|
try {
|
|
549
|
-
const config =
|
|
567
|
+
const config = _featuresConfigLoader.loadConfig();
|
|
550
568
|
if (config.model_routing?.enabled) {
|
|
551
|
-
const taskType =
|
|
552
|
-
const routing =
|
|
569
|
+
const taskType = _detectTaskType(input.tool);
|
|
570
|
+
const routing = taskType !== "unknown"
|
|
571
|
+
? config.model_routing.task_routing?.[taskType]
|
|
572
|
+
: undefined;
|
|
553
573
|
if (routing?.model) {
|
|
554
574
|
output.model = routing.model;
|
|
555
575
|
logger.log(`Model routed: ${input.tool} â ${taskType} â ${routing.model}`);
|
|
@@ -561,122 +581,61 @@ export default async function strrayCodexPlugin(input) {
|
|
|
561
581
|
}
|
|
562
582
|
}
|
|
563
583
|
const { tool, args } = input;
|
|
564
|
-
// ENFORCER QUALITY GATE CHECK - Block on violations
|
|
565
584
|
const qualityGateResult = await runEnforcerQualityGate(input, logger);
|
|
566
585
|
if (!qualityGateResult.passed) {
|
|
567
586
|
logger.error(`đĢ Quality gate failed: ${qualityGateResult.violations.join(", ")}`);
|
|
568
587
|
throw new Error(`ENFORCER BLOCKED: ${qualityGateResult.violations.join("; ")}`);
|
|
569
588
|
}
|
|
570
589
|
logger.log(`â
Quality gate passed for ${tool}`);
|
|
571
|
-
if (
|
|
572
|
-
if (!
|
|
590
|
+
if (isWriteEditOperation(tool)) {
|
|
591
|
+
if (!_ProcessorManager || !_StrRayStateManager) {
|
|
573
592
|
logger.error("ProcessorManager or StrRayStateManager not loaded");
|
|
574
593
|
return;
|
|
575
594
|
}
|
|
576
|
-
// PHASE 1: Connect to booted framework or boot if needed
|
|
577
595
|
let stateManager;
|
|
578
596
|
let processorManager;
|
|
579
|
-
// Check if framework is already booted (global state exists)
|
|
580
597
|
const globalState = globalThis.strRayStateManager;
|
|
581
598
|
if (globalState) {
|
|
582
|
-
logger.log("đ Connecting to booted
|
|
599
|
+
logger.log("đ Connecting to booted 0xRay framework");
|
|
583
600
|
stateManager = globalState;
|
|
584
601
|
}
|
|
585
602
|
else {
|
|
586
|
-
logger.log("đ
|
|
587
|
-
|
|
588
|
-
stateManager = new StrRayStateManager(await resolveStateDir(directory));
|
|
589
|
-
// Store globally for future use
|
|
603
|
+
logger.log("đ 0xRay framework not booted, initializing...");
|
|
604
|
+
stateManager = new _StrRayStateManager(await resolveStateDir(directory));
|
|
590
605
|
globalThis.strRayStateManager = stateManager;
|
|
591
606
|
}
|
|
592
|
-
|
|
593
|
-
processorManager = stateManager.get("processor:manager");
|
|
607
|
+
processorManager = stateManager.get("processor:manager") ?? null;
|
|
594
608
|
if (!processorManager) {
|
|
595
609
|
logger.log("âī¸ Creating and registering processors...");
|
|
596
|
-
processorManager = new
|
|
597
|
-
|
|
598
|
-
processorManager.registerProcessor({
|
|
599
|
-
name: "preValidate",
|
|
600
|
-
type: "pre",
|
|
601
|
-
priority: 10,
|
|
602
|
-
enabled: true,
|
|
603
|
-
});
|
|
604
|
-
processorManager.registerProcessor({
|
|
605
|
-
name: "codexCompliance",
|
|
606
|
-
type: "pre",
|
|
607
|
-
priority: 20,
|
|
608
|
-
enabled: true,
|
|
609
|
-
});
|
|
610
|
-
processorManager.registerProcessor({
|
|
611
|
-
name: "versionCompliance",
|
|
612
|
-
type: "pre",
|
|
613
|
-
priority: 25,
|
|
614
|
-
enabled: true,
|
|
615
|
-
});
|
|
616
|
-
processorManager.registerProcessor({
|
|
617
|
-
name: "testAutoCreation",
|
|
618
|
-
type: "post",
|
|
619
|
-
priority: 5, // FIX: Run BEFORE testExecution so tests exist when we run them
|
|
620
|
-
enabled: true,
|
|
621
|
-
});
|
|
622
|
-
processorManager.registerProcessor({
|
|
623
|
-
name: "testExecution",
|
|
624
|
-
type: "post",
|
|
625
|
-
priority: 10,
|
|
626
|
-
enabled: true,
|
|
627
|
-
});
|
|
628
|
-
processorManager.registerProcessor({
|
|
629
|
-
name: "coverageAnalysis",
|
|
630
|
-
type: "post",
|
|
631
|
-
priority: 20,
|
|
632
|
-
enabled: true,
|
|
633
|
-
});
|
|
634
|
-
// Store for future use
|
|
610
|
+
processorManager = new _ProcessorManager(stateManager);
|
|
611
|
+
registerAllProcessors(processorManager);
|
|
635
612
|
stateManager.set("processor:manager", processorManager);
|
|
636
613
|
logger.log("â
Processors registered successfully");
|
|
637
614
|
}
|
|
638
615
|
else {
|
|
639
616
|
logger.log("â
Using existing processor manager");
|
|
640
617
|
}
|
|
641
|
-
// PHASE 2: Execute pre-processors with detailed logging
|
|
642
618
|
try {
|
|
643
619
|
logger.log(`âļī¸ Executing pre-processors for ${tool}...`);
|
|
644
|
-
const
|
|
620
|
+
const preProcessorInput = {
|
|
645
621
|
tool,
|
|
646
|
-
args,
|
|
622
|
+
args: args,
|
|
647
623
|
context: {
|
|
648
624
|
directory,
|
|
649
625
|
operation: "tool_execution",
|
|
650
626
|
filePath: args?.filePath,
|
|
651
627
|
},
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const failures = result.results?.filter((r) => !r.success) || [];
|
|
656
|
-
failures.forEach((f) => {
|
|
657
|
-
logger.error(`â Pre-processor ${f.processorName} failed: ${f.error}`);
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
result.results?.forEach((r) => {
|
|
662
|
-
logger.log(`â
Pre-processor ${r.processorName}: ${r.success ? "OK" : "FAILED"}`);
|
|
663
|
-
});
|
|
664
|
-
}
|
|
628
|
+
};
|
|
629
|
+
const result = await processorManager.executePreProcessors(preProcessorInput);
|
|
630
|
+
logPreProcessorResults(result, logger);
|
|
665
631
|
}
|
|
666
632
|
catch (error) {
|
|
667
633
|
logger.error(`đĨ Pre-processor execution error`, error);
|
|
668
634
|
}
|
|
669
|
-
// PHASE 3: Execute post-processors after tool completion
|
|
670
635
|
try {
|
|
671
636
|
logger.log(`âļī¸ Executing post-processors for ${tool}...`);
|
|
672
637
|
logger.log(`đ Post-processor args: ${JSON.stringify(args)}`);
|
|
673
|
-
|
|
674
|
-
const isPublishOperation = tool === "publish" || tool === "release" || tool === "npm-publish" || tool === "strray-release";
|
|
675
|
-
// Get agent info from session state if available
|
|
676
|
-
const agentName = global.currentAgent?.agentType ||
|
|
677
|
-
global.currentAgent?.type ||
|
|
678
|
-
input.agentType ||
|
|
679
|
-
"orchestrator";
|
|
638
|
+
const agentName = resolveAgentName(input);
|
|
680
639
|
const postProcessorContext = {
|
|
681
640
|
directory,
|
|
682
641
|
operation: tool,
|
|
@@ -684,7 +643,7 @@ export default async function strrayCodexPlugin(input) {
|
|
|
684
643
|
success: true,
|
|
685
644
|
agentName,
|
|
686
645
|
metadata: {
|
|
687
|
-
isPublishing: isPublishOperation,
|
|
646
|
+
isPublishing: isPublishOperation(tool),
|
|
688
647
|
hook: "tool_execution",
|
|
689
648
|
toolName: tool,
|
|
690
649
|
timestamp: Date.now(),
|
|
@@ -692,32 +651,17 @@ export default async function strrayCodexPlugin(input) {
|
|
|
692
651
|
},
|
|
693
652
|
};
|
|
694
653
|
const postResults = await processorManager.executePostProcessors(tool, postProcessorContext, []);
|
|
695
|
-
|
|
696
|
-
const allSuccess = postResults.every((r) => r.success);
|
|
697
|
-
logger.log(`đ Post-processor result: ${allSuccess ? "SUCCESS" : "FAILED"} (${postResults.length} processors)`);
|
|
698
|
-
// Log each post-processor result for debugging
|
|
699
|
-
for (const r of postResults) {
|
|
700
|
-
if (r.success) {
|
|
701
|
-
logger.log(`â
Post-processor ${r.processorName}: OK`);
|
|
702
|
-
}
|
|
703
|
-
else {
|
|
704
|
-
logger.error(`â Post-processor ${r.processorName} failed: ${r.error}`);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
654
|
+
logPostProcessorResults(postResults, logger);
|
|
707
655
|
}
|
|
708
656
|
catch (error) {
|
|
709
657
|
logger.error(`đĨ Post-processor execution error`, error);
|
|
710
658
|
}
|
|
711
659
|
}
|
|
712
660
|
},
|
|
713
|
-
// Execute POST-processors AFTER tool completes (this is the correct place!)
|
|
714
661
|
"tool.execute.after": async (input, _output) => {
|
|
715
662
|
const logger = await getOrCreateLogger(directory);
|
|
716
|
-
await
|
|
663
|
+
await loadStringRayComponents();
|
|
717
664
|
const { tool, args, result } = input;
|
|
718
|
-
// Record routing outcome for analytics pipeline.
|
|
719
|
-
// This feeds the inference tuner with real tool usage data so it
|
|
720
|
-
// can refine keyword mappings and improve predictive analytics.
|
|
721
665
|
try {
|
|
722
666
|
const { routingOutcomeTracker } = await import("../delegation/analytics/outcome-tracker.js");
|
|
723
667
|
const mapping = TOOL_AGENT_MAP[tool];
|
|
@@ -746,82 +690,37 @@ export default async function strrayCodexPlugin(input) {
|
|
|
746
690
|
catch {
|
|
747
691
|
// Outcome tracker not available â skip silently
|
|
748
692
|
}
|
|
749
|
-
// Debug: log full input
|
|
750
693
|
logger.log(`đĨ After hook input: ${JSON.stringify({ tool, hasArgs: !!args, args, hasResult: !!result }).slice(0, 200)}`);
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (!ProcessorManager || !StrRayStateManager)
|
|
694
|
+
if (isWriteEditOperation(tool)) {
|
|
695
|
+
if (!_ProcessorManager || !_StrRayStateManager)
|
|
754
696
|
return;
|
|
755
|
-
const stateManager = new
|
|
756
|
-
const processorManager = new
|
|
757
|
-
|
|
758
|
-
processorManager.registerProcessor({
|
|
759
|
-
name: "testAutoCreation",
|
|
760
|
-
type: "post",
|
|
761
|
-
priority: 50,
|
|
762
|
-
enabled: true,
|
|
763
|
-
});
|
|
764
|
-
processorManager.registerProcessor({
|
|
765
|
-
name: "testExecution",
|
|
766
|
-
type: "post",
|
|
767
|
-
priority: 10,
|
|
768
|
-
enabled: true,
|
|
769
|
-
});
|
|
770
|
-
processorManager.registerProcessor({
|
|
771
|
-
name: "coverageAnalysis",
|
|
772
|
-
type: "post",
|
|
773
|
-
priority: 20,
|
|
774
|
-
enabled: true,
|
|
775
|
-
});
|
|
697
|
+
const stateManager = new _StrRayStateManager(await resolveStateDir(directory));
|
|
698
|
+
const processorManager = new _ProcessorManager(stateManager);
|
|
699
|
+
registerAfterPostProcessors(processorManager);
|
|
776
700
|
try {
|
|
777
|
-
// Execute post-processors AFTER tool - with actual filePath for testAutoCreation
|
|
778
701
|
logger.log(`đ Post-processor tool: ${tool}`);
|
|
779
702
|
logger.log(`đ Post-processor args: ${JSON.stringify(args)}`);
|
|
780
703
|
logger.log(`đ Post-processor directory: ${directory}`);
|
|
781
|
-
// Determine operation type and enrich context with metadata for processors
|
|
782
|
-
const isPublishOperation = tool === "publish" || tool === "release" || tool === "npm-publish" || tool === "strray-release";
|
|
783
704
|
const postProcessorContext = {
|
|
784
705
|
directory,
|
|
785
706
|
operation: tool,
|
|
786
707
|
filePath: args?.filePath,
|
|
787
708
|
success: result?.success !== false,
|
|
788
709
|
metadata: {
|
|
789
|
-
isPublishing: isPublishOperation,
|
|
710
|
+
isPublishing: isPublishOperation(tool),
|
|
790
711
|
hook: "tool_execution",
|
|
791
712
|
toolName: tool,
|
|
792
713
|
timestamp: Date.now(),
|
|
793
714
|
},
|
|
794
715
|
};
|
|
795
716
|
const postResults = await processorManager.executePostProcessors(tool, postProcessorContext, []);
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
logger.log(`đ Post-processor result: ${allSuccess ? "SUCCESS" : "FAILED"} (${postResults.length} processors)`);
|
|
799
|
-
// Log each post-processor result for debugging
|
|
800
|
-
for (const r of postResults) {
|
|
801
|
-
if (r.success) {
|
|
802
|
-
logger.log(`â
Post-processor ${r.processorName}: OK`);
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
logger.error(`â Post-processor ${r.processorName} failed: ${r.error}`);
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
// Log testAutoCreation results specifically
|
|
809
|
-
const testAutoResult = postResults.find((r) => r.processorName === "testAutoCreation");
|
|
810
|
-
if (testAutoResult) {
|
|
811
|
-
if (testAutoResult.success && testAutoResult.testCreated) {
|
|
812
|
-
logger.log(`â
TEST AUTO-CREATION: Created ${testAutoResult.testFile}`);
|
|
813
|
-
}
|
|
814
|
-
else if (!testAutoResult.success) {
|
|
815
|
-
logger.log(`âšī¸ TEST AUTO-CREATION: ${testAutoResult.message || "skipped - no new files"}`);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
717
|
+
logPostProcessorResults(postResults, logger);
|
|
718
|
+
logTestAutoCreationResult(postResults, logger);
|
|
818
719
|
}
|
|
819
720
|
catch (error) {
|
|
820
721
|
logger.error(`đĨ Post-processor error`, error);
|
|
821
722
|
}
|
|
822
723
|
}
|
|
823
|
-
// Auto inference tuning: every INFERENCE_TUNE_INTERVAL tool calls,
|
|
824
|
-
// run a single tuning cycle to close the feedback loop.
|
|
825
724
|
_openCodeToolCallCount++;
|
|
826
725
|
if (_openCodeToolCallCount - _lastTuneToolCallCount >= INFERENCE_TUNE_INTERVAL) {
|
|
827
726
|
_lastTuneToolCallCount = _openCodeToolCallCount;
|
|
@@ -841,17 +740,71 @@ export default async function strrayCodexPlugin(input) {
|
|
|
841
740
|
}
|
|
842
741
|
}
|
|
843
742
|
},
|
|
743
|
+
"chat.message": async (input, output) => {
|
|
744
|
+
const logger = await getOrCreateLogger(directory);
|
|
745
|
+
if (!output.parts) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const textContent = input.parts?.find(p => p.type === "text")?.text ?? "";
|
|
749
|
+
if (!textContent) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const agentMentionRegex = /@(\w+)(?:\s+(.+?))?(?=$|\n\n|\r\r)/g;
|
|
753
|
+
let match;
|
|
754
|
+
let hasAgentMention = false;
|
|
755
|
+
let transformedText = textContent;
|
|
756
|
+
const knownAgents = {
|
|
757
|
+
"architect": "architect",
|
|
758
|
+
"strategist": "strategist",
|
|
759
|
+
"testing-lead": "testing-lead",
|
|
760
|
+
"bug-triage-specialist": "bug-triage-specialist",
|
|
761
|
+
"code-reviewer": "code-reviewer",
|
|
762
|
+
"security-auditor": "security-auditor",
|
|
763
|
+
"refactorer": "refactorer",
|
|
764
|
+
"researcher": "researcher",
|
|
765
|
+
"code-analyzer": "code-analyzer",
|
|
766
|
+
"frontend-engineer": "frontend-engineer",
|
|
767
|
+
"frontend-ui-ux-engineer": "frontend-ui-ux-engineer",
|
|
768
|
+
"backend-engineer": "backend-engineer",
|
|
769
|
+
"database-engineer": "database-engineer",
|
|
770
|
+
"devops-engineer": "devops-engineer",
|
|
771
|
+
"performance-engineer": "performance-engineer",
|
|
772
|
+
"mobile-developer": "mobile-developer",
|
|
773
|
+
"content-creator": "content-creator",
|
|
774
|
+
"growth-strategist": "growth-strategist",
|
|
775
|
+
"seo-consultant": "seo-consultant",
|
|
776
|
+
"tech-writer": "tech-writer",
|
|
777
|
+
"multimodal-looker": "multimodal-looker",
|
|
778
|
+
"log-monitor": "log-monitor",
|
|
779
|
+
};
|
|
780
|
+
while ((match = agentMentionRegex.exec(textContent)) !== null) {
|
|
781
|
+
const agentName = match[1].toLowerCase().replace(/-/g, "");
|
|
782
|
+
const taskPart = match[2]?.trim() ?? "";
|
|
783
|
+
if (knownAgents[agentName]) {
|
|
784
|
+
hasAgentMention = true;
|
|
785
|
+
const canonicalAgent = knownAgents[agentName];
|
|
786
|
+
logger.log(`đ¯ Agent mention detected: @${canonicalAgent}`);
|
|
787
|
+
const prefix = `\n[DELEGATE TO AGENT: ${canonicalAgent}]\n`;
|
|
788
|
+
transformedText = prefix + (taskPart || textContent.replace(`@${match[1]}`, "").trim());
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (hasAgentMention) {
|
|
793
|
+
const textPart = output.parts.find(p => p.type === "text");
|
|
794
|
+
if (textPart) {
|
|
795
|
+
textPart.text = transformedText;
|
|
796
|
+
logger.log(`â
Transformed prompt for agent routing`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
},
|
|
844
800
|
config: async (_config) => {
|
|
845
|
-
// Guard: only run init.sh once per startup session
|
|
846
|
-
// OpenCode may fire the config hook multiple times in fresh contexts
|
|
847
|
-
// where globalThis is reset, so we use a TTL lockfile
|
|
848
801
|
const lockFile = path.join(directory, ".opencode", "logs", ".strray-init.lock");
|
|
849
802
|
const now = Date.now();
|
|
850
803
|
try {
|
|
851
804
|
if (fs.existsSync(lockFile)) {
|
|
852
805
|
const stat = fs.statSync(lockFile);
|
|
853
806
|
if (now - stat.mtimeMs < 15000) {
|
|
854
|
-
return;
|
|
807
|
+
return;
|
|
855
808
|
}
|
|
856
809
|
}
|
|
857
810
|
fs.writeFileSync(lockFile, String(now));
|
|
@@ -860,10 +813,7 @@ export default async function strrayCodexPlugin(input) {
|
|
|
860
813
|
// lock check failed â proceed anyway
|
|
861
814
|
}
|
|
862
815
|
const logger = await getOrCreateLogger(directory);
|
|
863
|
-
logger.log("đ§ Plugin config hook triggered - initializing
|
|
864
|
-
// Initialize StrRay framework
|
|
865
|
-
// Primary: project .opencode/init.sh (copied by postinstall)
|
|
866
|
-
// Fallback: package .opencode/init.sh (works when postinstall skips for Hermes consumers)
|
|
816
|
+
logger.log("đ§ Plugin config hook triggered - initializing 0xRay integration");
|
|
867
817
|
let initScriptPath = path.join(directory, ".opencode", "init.sh");
|
|
868
818
|
const pkgInitPath = path.join(directory, "node_modules", "strray-ai", ".opencode", "init.sh");
|
|
869
819
|
if (!fs.existsSync(initScriptPath) && fs.existsSync(pkgInitPath)) {
|
|
@@ -876,7 +826,7 @@ export default async function strrayCodexPlugin(input) {
|
|
|
876
826
|
logger.error(`Framework init error: ${stderr}`);
|
|
877
827
|
}
|
|
878
828
|
else {
|
|
879
|
-
logger.log("â
|
|
829
|
+
logger.log("â
0xRay Framework initialized successfully");
|
|
880
830
|
}
|
|
881
831
|
}
|
|
882
832
|
catch (error) {
|