0xray 4.0.41 → 4.1.0
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/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +12 -0
- package/.plugin/plugin.json +1 -1
- package/.xray/features.json +5 -5
- package/.xray/features.schema.json +1 -1
- package/AGENTS-consumer.md +10 -6
- package/AGENTS.md +19 -8
- package/LICENSE +1 -1
- package/README.md +65 -19
- package/SKILLS.md +5 -1
- package/dist/AGENTS.md +19 -8
- package/dist/CHANGELOG.md +85 -0
- package/dist/LICENSE +1 -1
- package/dist/README.md +65 -19
- package/dist/SKILLS.md +5 -1
- package/dist/cli/commands/openclaw-install.js +1 -1
- package/dist/cli/commands/plugin-commands.d.ts +4 -0
- package/dist/cli/commands/plugin-commands.js +84 -41
- package/dist/cli/index.js +12 -0
- package/dist/core/config-loader.js +4 -4
- package/dist/core/features-config.d.ts +1 -1
- package/dist/core/features-config.js +3 -3
- package/dist/enforcement/index.d.ts +4 -0
- package/dist/enforcement/index.js +4 -0
- package/dist/enforcement/validators/code-quality-validators.js +6 -4
- package/dist/governance/llm-governance-provider.d.ts +7 -1
- package/dist/governance/llm-governance-provider.js +72 -14
- package/dist/governance/local-confer.d.ts +4 -1
- package/dist/governance/local-confer.js +22 -1
- package/dist/integrations/cursor/hooks/hooks.json +6 -3
- package/dist/integrations/cursor/hooks/pre-tool-use.js +100 -37
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +9 -1
- package/dist/integrations/grok/grok-cli.js +10 -37
- package/dist/integrations/grok/hooks/grok-hook-utils.js +8 -1
- package/dist/integrations/grok/hooks/pre-tool-use.js +2 -58
- package/dist/integrations/hermes-agent/bridge.mjs +14 -1
- package/dist/integrations/hooks/confer-hook-runtime.mjs +23 -1
- package/dist/integrations/hooks/delegation-gate-runtime.mjs +3 -2
- package/dist/integrations/hooks/goggles-mcp.mjs +35 -17
- package/dist/integrations/hooks/goggles-pipeline.mjs +46 -1
- package/dist/integrations/hooks/goggles-planes.json +11 -7
- package/dist/integrations/hooks/pre-tool-speak.mjs +76 -0
- package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +21 -4
- package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +17 -2
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/architect-tools.server.d.ts +6 -0
- package/dist/mcps/architect-tools.server.js +19 -2
- package/dist/mcps/config/server-config-registry.js +8 -6
- package/dist/mcps/governance.server.d.ts +1 -0
- package/dist/mcps/governance.server.js +46 -0
- package/dist/mcps/knowledge-skills/code-review.server.js +7 -0
- package/dist/mcps/knowledge-skills/security-audit.server.js +5 -3
- package/dist/mcps/mcp-client.js +1 -1
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
- package/dist/mcps/researcher.server.js +12 -3
- package/dist/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
- package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
- package/dist/nucleus/autonomy-kernel.d.ts +2 -0
- package/dist/nucleus/autonomy-kernel.js +17 -13
- package/dist/nucleus/confer-host.d.ts +35 -0
- package/dist/nucleus/confer-host.js +55 -0
- package/dist/nucleus/confer.d.ts +19 -3
- package/dist/nucleus/confer.js +111 -55
- package/dist/nucleus/lead-dev-plan-persistence.js +4 -2
- package/dist/nucleus/suit-temperament.d.ts +1 -1
- package/dist/nucleus/suit-temperament.js +2 -2
- package/dist/nucleus/synthesis-consult-receipt.d.ts +7 -1
- package/dist/nucleus/synthesis-consult-receipt.js +86 -35
- package/dist/plugin/xray-codex-injection.js +18 -0
- package/dist/processors/implementations/nudge-processor.d.ts +1 -1
- package/dist/processors/implementations/nudge-processor.js +4 -4
- package/dist/reporting/framework-reporting-system.js +2 -5
- package/dist/reporting/log-parser.d.ts +2 -0
- package/dist/reporting/log-parser.js +7 -6
- package/dist/skills/orchestrator/SKILL.md +12 -0
- package/docs-site/docs/plates/activity-log.md +1 -1
- package/docs-site/docs/plates/bifrost.md +48 -0
- package/docs-site/docs/plates/boot.md +2 -2
- package/docs-site/docs/plates/centrifuge.md +1 -1
- package/docs-site/docs/plates/cursor-compact.md +1 -1
- package/docs-site/docs/plates/glossary.md +7 -3
- package/docs-site/docs/plates/goggles.md +13 -2
- package/docs-site/docs/plates/governance.md +1 -1
- package/docs-site/docs/plates/grok-compact.md +1 -1
- package/docs-site/docs/plates/grokbot.md +3 -1
- package/docs-site/docs/plates/host-pack.md +1 -1
- package/docs-site/docs/plates/house.md +1 -1
- package/docs-site/docs/plates/index.md +4 -2
- package/docs-site/docs/plates/inference-files.md +1 -1
- package/docs-site/docs/plates/kept-line.md +1 -1
- package/docs-site/docs/plates/kits.md +1 -1
- package/docs-site/docs/plates/lens-gate.md +3 -1
- package/docs-site/docs/plates/lens-page.md +1 -1
- package/docs-site/docs/plates/memory-recall.md +1 -1
- package/docs-site/docs/plates/notes-page.md +2 -2
- package/docs-site/docs/plates/orchestration.md +1 -1
- package/docs-site/docs/plates/package-face.md +1 -1
- package/docs-site/docs/plates/payload-heat.md +1 -1
- package/docs-site/docs/plates/pickup-stamp.md +1 -1
- package/docs-site/docs/plates/pre-tool.md +1 -1
- package/docs-site/docs/plates/processor.md +1 -1
- package/docs-site/docs/plates/record-map.md +1 -1
- package/docs-site/docs/plates/reflection-page.md +1 -1
- package/docs-site/docs/plates/reporting.md +1 -1
- package/docs-site/docs/plates/review.md +1 -1
- package/docs-site/docs/plates/routing.md +1 -1
- package/docs-site/docs/plates/session-capture.md +1 -1
- package/docs-site/docs/plates/site-manual.md +1 -1
- package/docs-site/docs/plates/stamp-plate.md +1 -1
- package/docs-site/docs/plates/station-card.md +1 -1
- package/docs-site/docs/plates/station-heat.md +1 -1
- package/docs-site/docs/plates/suit-organs.md +1 -1
- package/docs-site/docs/plates/suit-settings.md +1 -1
- package/docs-site/docs/plates/suit-wear.md +1 -1
- package/docs-site/docs/plates/suit.md +3 -3
- package/docs-site/docs/plates/trail-state.md +1 -1
- package/docs-site/docs/plates/work-fresh.md +1 -1
- package/docs-site/docs/plates/write-home.md +1 -1
- package/llms.txt +16 -6
- package/package.json +4 -4
- package/scripts/foundry/README.md +2 -2
- package/scripts/foundry/release-gate.mjs +111 -15
- package/scripts/foundry/release.mjs +8 -0
- package/scripts/mjs/verify-confer-core.mjs +153 -29
- package/scripts/mjs/verify-pipeline-facets.mjs +1 -1
- package/scripts/mjs/verify-user-aside-core.mjs +3 -4
- package/scripts/node/bridge-mcp-wiring.cjs +236 -5
- package/scripts/node/confer-switch.cjs +66 -0
- package/scripts/node/install-bridges.cjs +44 -53
- package/scripts/node/pack-tmp-suit-proof.mjs +14 -6
- package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +17 -2
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/architect-tools.server.ts +26 -2
- package/src/mcps/config/__tests__/server-config-registry.test.ts +15 -6
- package/src/mcps/config/server-config-registry.ts +9 -6
- package/src/mcps/governance.server.ts +49 -0
- package/src/mcps/knowledge-skills/code-review.server.ts +8 -0
- package/src/mcps/knowledge-skills/security-audit.server.ts +11 -12
- package/src/mcps/mcp-client.ts +1 -1
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
- package/src/mcps/researcher.server.ts +19 -10
- package/src/skills/orchestrator/SKILL.md +12 -0
- package/vendor/@0xray/repertoire/README.md +1 -1
- package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +5 -51
- package/xray/agents_template.md +2 -2
- package/xray/features.json +5 -5
- package/xray/features.schema.json +1 -1
- package/docs-site/docs/plates/live-mesh.md +0 -41
|
@@ -869,7 +869,125 @@ function resolveOpenClawPluginDir(packageRoot) {
|
|
|
869
869
|
].find((p) => fs.existsSync(path.join(p, "index.js"))) || null;
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
-
function
|
|
872
|
+
function isCheckoutOpenClawPluginPath(pluginPath) {
|
|
873
|
+
const normalized = String(pluginPath || "").replace(/\\/g, "/");
|
|
874
|
+
return (
|
|
875
|
+
normalized.includes("/src/integrations/openclaw/plugin/xray-pre-tool") &&
|
|
876
|
+
!normalized.includes("/node_modules/")
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function isInstalledOpenClawPluginPath(pluginPath) {
|
|
881
|
+
const normalized = String(pluginPath || "").replace(/\\/g, "/");
|
|
882
|
+
return (
|
|
883
|
+
normalized.includes("/node_modules/") &&
|
|
884
|
+
normalized.includes("/integrations/openclaw/plugin/xray-pre-tool")
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** Installed package first. A checkout src path drops once any installed plugin is present. */
|
|
889
|
+
function settleOpenClawPluginPaths(paths, wornPluginDir) {
|
|
890
|
+
const list = (Array.isArray(paths) ? paths : []).map((entry) => String(entry));
|
|
891
|
+
const wornIsInstalled = Boolean(wornPluginDir && isInstalledOpenClawPluginPath(wornPluginDir));
|
|
892
|
+
const dropCheckout =
|
|
893
|
+
wornIsInstalled || list.some((entry) => isInstalledOpenClawPluginPath(entry));
|
|
894
|
+
const next = [];
|
|
895
|
+
const seen = new Set();
|
|
896
|
+
const push = (entry) => {
|
|
897
|
+
if (!entry) return;
|
|
898
|
+
const resolved = path.resolve(String(entry));
|
|
899
|
+
if (dropCheckout && isCheckoutOpenClawPluginPath(resolved)) return;
|
|
900
|
+
if (seen.has(resolved)) return;
|
|
901
|
+
seen.add(resolved);
|
|
902
|
+
next.push(resolved);
|
|
903
|
+
};
|
|
904
|
+
if (wornIsInstalled) push(wornPluginDir);
|
|
905
|
+
for (const entry of list) push(entry);
|
|
906
|
+
return next;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function resolveWornOpenClawPlugin(targetDir, packageRoot) {
|
|
910
|
+
const candidates = [];
|
|
911
|
+
if (targetDir) {
|
|
912
|
+
candidates.push(
|
|
913
|
+
path.join(targetDir, "node_modules", "0xray", "dist", "integrations", "openclaw", "plugin", "xray-pre-tool"),
|
|
914
|
+
path.join(targetDir, "node_modules", "0xray", "src", "integrations", "openclaw", "plugin", "xray-pre-tool"),
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
const fromPackage = resolveOpenClawPluginDir(packageRoot);
|
|
918
|
+
if (fromPackage) candidates.push(fromPackage);
|
|
919
|
+
return candidates.find((dir) => fs.existsSync(path.join(dir, "index.js"))) || null;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function replaceJsonPathsArray(raw, before, next) {
|
|
923
|
+
const keyAt = raw.indexOf('"paths"');
|
|
924
|
+
if (keyAt < 0) return null;
|
|
925
|
+
const bracket = raw.indexOf("[", keyAt);
|
|
926
|
+
if (bracket < 0) return null;
|
|
927
|
+
let depth = 0;
|
|
928
|
+
let end = -1;
|
|
929
|
+
for (let i = bracket; i < raw.length; i += 1) {
|
|
930
|
+
const ch = raw[i];
|
|
931
|
+
if (ch === "[") depth += 1;
|
|
932
|
+
else if (ch === "]") {
|
|
933
|
+
depth -= 1;
|
|
934
|
+
if (depth === 0) {
|
|
935
|
+
end = i;
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (end < 0) return null;
|
|
941
|
+
let parsed;
|
|
942
|
+
try {
|
|
943
|
+
parsed = JSON.parse(raw.slice(bracket, end + 1));
|
|
944
|
+
} catch {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
if (JSON.stringify(parsed) !== JSON.stringify(before)) return null;
|
|
948
|
+
const lineStart = raw.lastIndexOf("\n", keyAt) + 1;
|
|
949
|
+
const indent = (raw.slice(lineStart, keyAt).match(/^\s*/) || [""])[0];
|
|
950
|
+
const itemIndent = `${indent} `;
|
|
951
|
+
const body = next
|
|
952
|
+
.map((entry) => `${itemIndent}${JSON.stringify(entry)}`)
|
|
953
|
+
.join(",\n");
|
|
954
|
+
const array = `[\n${body}\n${indent}]`;
|
|
955
|
+
return `${raw.slice(0, bracket)}${array}${raw.slice(end + 1)}`;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/** Rewrite plugins.load.paths in place. Leaves the rest of the config file alone. */
|
|
959
|
+
function pinOpenClawPluginLoad(configPath, wornPluginDir) {
|
|
960
|
+
if (!configPath || !fs.existsSync(configPath)) return null;
|
|
961
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
962
|
+
const config = JSON.parse(raw);
|
|
963
|
+
if (!config.plugins || typeof config.plugins !== "object") return null;
|
|
964
|
+
if (!config.plugins.load || typeof config.plugins.load !== "object") config.plugins.load = {};
|
|
965
|
+
const before = Array.isArray(config.plugins.load.paths) ? config.plugins.load.paths : [];
|
|
966
|
+
const next = settleOpenClawPluginPaths(before, wornPluginDir);
|
|
967
|
+
if (JSON.stringify(before) === JSON.stringify(next)) return next;
|
|
968
|
+
const replaced = replaceJsonPathsArray(raw, before, next);
|
|
969
|
+
if (replaced == null) return null;
|
|
970
|
+
fs.writeFileSync(configPath, replaced);
|
|
971
|
+
return next;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function recordOpenClawProjectPlugin(targetDir, packageRoot) {
|
|
975
|
+
const pluginDir = resolveWornOpenClawPlugin(targetDir, packageRoot);
|
|
976
|
+
if (!pluginDir || !isInstalledOpenClawPluginPath(pluginDir)) return null;
|
|
977
|
+
const configPath = path.join(targetDir, ".xray", "config", "openclaw.json");
|
|
978
|
+
if (!fs.existsSync(configPath)) return null;
|
|
979
|
+
let config;
|
|
980
|
+
try {
|
|
981
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
982
|
+
} catch {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
config.pluginPath = path.resolve(pluginDir);
|
|
986
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
987
|
+
return config.pluginPath;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function installOpenClawHostWear(packageRoot, targetDir) {
|
|
873
991
|
if (millSuit.isIsolatedHome()) return null;
|
|
874
992
|
const source = resolveOpenClawPreToolHookSource(packageRoot);
|
|
875
993
|
if (!source) return null;
|
|
@@ -878,6 +996,11 @@ function installOpenClawHostWear(packageRoot) {
|
|
|
878
996
|
fs.mkdirSync(hookDir, { recursive: true });
|
|
879
997
|
const dest = path.join(hookDir, "xray-pre-tool.mjs");
|
|
880
998
|
fs.copyFileSync(source, dest);
|
|
999
|
+
const worn = resolveWornOpenClawPlugin(targetDir, packageRoot);
|
|
1000
|
+
const hookRoot =
|
|
1001
|
+
worn && isInstalledOpenClawPluginPath(worn) && targetDir
|
|
1002
|
+
? path.join(path.resolve(targetDir), "node_modules", "0xray")
|
|
1003
|
+
: packageRoot;
|
|
881
1004
|
fs.writeFileSync(
|
|
882
1005
|
path.join(hookDir, "xray-pre-tool.json"),
|
|
883
1006
|
`${JSON.stringify(
|
|
@@ -887,17 +1010,16 @@ function installOpenClawHostWear(packageRoot) {
|
|
|
887
1010
|
args: [dest],
|
|
888
1011
|
stdin: "json",
|
|
889
1012
|
blockExitCode: 2,
|
|
890
|
-
env: { XRAY_AI_PATH:
|
|
1013
|
+
env: { XRAY_AI_PATH: hookRoot },
|
|
891
1014
|
},
|
|
892
1015
|
null,
|
|
893
1016
|
2,
|
|
894
1017
|
)}\n`,
|
|
895
1018
|
);
|
|
896
1019
|
|
|
897
|
-
|
|
898
|
-
if (pluginSrc) {
|
|
1020
|
+
if (worn && isInstalledOpenClawPluginPath(worn)) {
|
|
899
1021
|
try {
|
|
900
|
-
execFileSync("openclaw", ["plugins", "install", "-l",
|
|
1022
|
+
execFileSync("openclaw", ["plugins", "install", "-l", path.resolve(worn)], {
|
|
901
1023
|
stdio: "pipe",
|
|
902
1024
|
encoding: "utf8",
|
|
903
1025
|
timeout: 60000,
|
|
@@ -915,6 +1037,13 @@ function installOpenClawHostWear(packageRoot) {
|
|
|
915
1037
|
/* enable is best-effort */
|
|
916
1038
|
}
|
|
917
1039
|
}
|
|
1040
|
+
const configPath = resolveOpenClawConfigPath();
|
|
1041
|
+
if (fs.existsSync(configPath)) {
|
|
1042
|
+
pinOpenClawPluginLoad(
|
|
1043
|
+
configPath,
|
|
1044
|
+
worn && isInstalledOpenClawPluginPath(worn) ? worn : null,
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
918
1047
|
return dest;
|
|
919
1048
|
}
|
|
920
1049
|
|
|
@@ -979,6 +1108,102 @@ function wireOpenClawBridge(targetDir) {
|
|
|
979
1108
|
return syncOpenClawMcpRegistry(targetDir);
|
|
980
1109
|
}
|
|
981
1110
|
|
|
1111
|
+
function tomlString(value) {
|
|
1112
|
+
return JSON.stringify(String(value));
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function suitServerNames() {
|
|
1116
|
+
return new Set(XRAY_MCP_SERVERS.map((server) => server.name));
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function isSuitMcpHeader(line, names) {
|
|
1120
|
+
const match = /^\[([^\]]+)\]\s*$/.exec(line.trim());
|
|
1121
|
+
if (!match) return false;
|
|
1122
|
+
const parts = match[1].split(".");
|
|
1123
|
+
return parts[0] === "mcp_servers" && names.has(parts[1]);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function stripSuitMcpBlocks(toml, names) {
|
|
1127
|
+
const kept = [];
|
|
1128
|
+
let skip = false;
|
|
1129
|
+
for (const line of String(toml || "").split(/\r?\n/)) {
|
|
1130
|
+
if (/^\s*\[/.test(line)) skip = isSuitMcpHeader(line, names);
|
|
1131
|
+
if (!skip) kept.push(line);
|
|
1132
|
+
}
|
|
1133
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function suitMcpBlock(server, launch) {
|
|
1137
|
+
const args = launch.args.map((arg) => ` ${tomlString(arg)},`).join("\n");
|
|
1138
|
+
const lines = [
|
|
1139
|
+
`[mcp_servers.${server.name}]`,
|
|
1140
|
+
`command = ${tomlString(launch.command)}`,
|
|
1141
|
+
"args = [",
|
|
1142
|
+
args,
|
|
1143
|
+
"]",
|
|
1144
|
+
"enabled = true",
|
|
1145
|
+
];
|
|
1146
|
+
const env = launch.env && typeof launch.env === "object" ? launch.env : {};
|
|
1147
|
+
const envKeys = Object.keys(env);
|
|
1148
|
+
if (envKeys.length > 0) {
|
|
1149
|
+
lines.push("", `[mcp_servers.${server.name}.env]`);
|
|
1150
|
+
for (const key of envKeys) lines.push(`${key} = ${tomlString(env[key])}`);
|
|
1151
|
+
}
|
|
1152
|
+
return lines.join("\n");
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Project `.grok/config.toml` replaces a same-named user server.
|
|
1157
|
+
* The launch is the worn package, not `npx -y 0xray`.
|
|
1158
|
+
*/
|
|
1159
|
+
function writeProjectSuitMcp(targetDir) {
|
|
1160
|
+
const root = path.resolve(targetDir);
|
|
1161
|
+
const names = suitServerNames();
|
|
1162
|
+
const blocks = XRAY_MCP_SERVERS.map((server) =>
|
|
1163
|
+
suitMcpBlock(server, scopedMcpLaunch(root, server.mcpCmd, server.env)),
|
|
1164
|
+
);
|
|
1165
|
+
const grokDir = path.join(root, ".grok");
|
|
1166
|
+
fs.mkdirSync(grokDir, { recursive: true });
|
|
1167
|
+
const tomlPath = path.join(grokDir, "config.toml");
|
|
1168
|
+
const existing = fs.existsSync(tomlPath) ? fs.readFileSync(tomlPath, "utf8") : "";
|
|
1169
|
+
const kept = stripSuitMcpBlocks(existing, names);
|
|
1170
|
+
const body = [kept, blocks.join("\n\n")].filter((part) => part && part.trim()).join("\n\n");
|
|
1171
|
+
fs.writeFileSync(tomlPath, `${body.trim()}\n`);
|
|
1172
|
+
return tomlPath;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function grokConfigHome(env) {
|
|
1176
|
+
const source = env || process.env;
|
|
1177
|
+
const home = source.HOME || source.USERPROFILE;
|
|
1178
|
+
return home ? path.resolve(home) : os.homedir();
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/** Grok 1.0.50 has no --trust flag. An untrusted folder skips project MCP. */
|
|
1182
|
+
function trustGrokFolder(targetDir, env) {
|
|
1183
|
+
const home = grokConfigHome(env);
|
|
1184
|
+
const grokDir = path.join(home, ".grok");
|
|
1185
|
+
fs.mkdirSync(grokDir, { recursive: true });
|
|
1186
|
+
const file = path.join(grokDir, "trusted_folders.toml");
|
|
1187
|
+
let text = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
1188
|
+
const folders = [path.resolve(targetDir)];
|
|
1189
|
+
try {
|
|
1190
|
+
const real = fs.realpathSync(folders[0]);
|
|
1191
|
+
if (real !== folders[0]) folders.push(real);
|
|
1192
|
+
} catch {
|
|
1193
|
+
/* the path is the one Grok will see */
|
|
1194
|
+
}
|
|
1195
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1196
|
+
let added = 0;
|
|
1197
|
+
for (const folder of folders) {
|
|
1198
|
+
const header = `[folders.${tomlString(folder)}]`;
|
|
1199
|
+
if (text.includes(header)) continue;
|
|
1200
|
+
text = `${text.replace(/\s*$/, "")}\n${header}\ntrusted = true\ndecided_at = ${now}\n`;
|
|
1201
|
+
added += 1;
|
|
1202
|
+
}
|
|
1203
|
+
if (added > 0) fs.writeFileSync(file, text.endsWith("\n") ? text : `${text}\n`);
|
|
1204
|
+
return file;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
982
1207
|
module.exports = {
|
|
983
1208
|
XRAY_MCP_SERVERS,
|
|
984
1209
|
HERMES_CONFIG_PATH,
|
|
@@ -1025,5 +1250,11 @@ module.exports = {
|
|
|
1025
1250
|
resolveOpenClawPreToolHookSource,
|
|
1026
1251
|
resolveOpenClawPluginDir,
|
|
1027
1252
|
installOpenClawHostWear,
|
|
1253
|
+
settleOpenClawPluginPaths,
|
|
1254
|
+
pinOpenClawPluginLoad,
|
|
1255
|
+
recordOpenClawProjectPlugin,
|
|
1256
|
+
resolveWornOpenClawPlugin,
|
|
1028
1257
|
maybeWriteOpenClawCliBackend,
|
|
1258
|
+
writeProjectSuitMcp,
|
|
1259
|
+
trustGrokFolder,
|
|
1029
1260
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Turn confer on or off in the worn .xray/features.json.
|
|
4
|
+
* A missing key stays off until this runs. Synthesis stays untouched.
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
11
|
+
function fail(message) {
|
|
12
|
+
process.stderr.write(`${message}\n`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function orchestrationBlock(data) {
|
|
17
|
+
const current = data.multi_agent_orchestration;
|
|
18
|
+
if (current && typeof current === "object" && !Array.isArray(current)) return current;
|
|
19
|
+
const created = {};
|
|
20
|
+
data.multi_agent_orchestration = created;
|
|
21
|
+
return created;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function conferBlock(orch) {
|
|
25
|
+
const current = orch.confer;
|
|
26
|
+
if (current && typeof current === "object" && !Array.isArray(current)) return current;
|
|
27
|
+
const created = {};
|
|
28
|
+
orch.confer = created;
|
|
29
|
+
return created;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
const state = process.argv[2];
|
|
34
|
+
if (state !== "on" && state !== "off") fail("confer: say on or off");
|
|
35
|
+
|
|
36
|
+
const featuresPath = path.join(process.cwd(), ".xray", "features.json");
|
|
37
|
+
if (!fs.existsSync(featuresPath)) {
|
|
38
|
+
fail("confer: .xray/features.json is missing. Run npx 0xray wear first.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let data;
|
|
42
|
+
try {
|
|
43
|
+
data = JSON.parse(fs.readFileSync(featuresPath, "utf8"));
|
|
44
|
+
} catch {
|
|
45
|
+
fail("confer: .xray/features.json is not valid JSON");
|
|
46
|
+
}
|
|
47
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
48
|
+
fail("confer: .xray/features.json is not valid JSON");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const orch = orchestrationBlock(data);
|
|
52
|
+
const confer = conferBlock(orch);
|
|
53
|
+
if (state === "on") {
|
|
54
|
+
confer.enabled = true;
|
|
55
|
+
if (confer.on_synthesis !== false) confer.on_synthesis = true;
|
|
56
|
+
} else {
|
|
57
|
+
confer.enabled = false;
|
|
58
|
+
confer.on_synthesis = false;
|
|
59
|
+
orch.confer_on_synthesis = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(featuresPath, `${JSON.stringify(data, null, 2)}\n`);
|
|
63
|
+
process.stdout.write(`confer: ${state}\n`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (require.main === module) main();
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require("fs");
|
|
9
9
|
const path = require("path");
|
|
10
|
-
const { execFileSync
|
|
10
|
+
const { execFileSync } = require("child_process");
|
|
11
11
|
const {
|
|
12
12
|
wantsCostume,
|
|
13
13
|
isIsolatedHome,
|
|
@@ -22,18 +22,19 @@ const {
|
|
|
22
22
|
wireOpencodeBridge,
|
|
23
23
|
wireOpenClawBridge,
|
|
24
24
|
deployPortableProjectMcpJson,
|
|
25
|
-
scopedMcpLaunch,
|
|
26
25
|
mergeMcpMap,
|
|
27
26
|
mergeNamedRecords,
|
|
28
27
|
jsonDeepEqual,
|
|
29
28
|
copyHermesFindProjectRootHelper,
|
|
30
29
|
copyHermesHookRuntimes,
|
|
31
30
|
installOpenClawHostWear,
|
|
31
|
+
recordOpenClawProjectPlugin,
|
|
32
32
|
maybeWriteOpenClawCliBackend,
|
|
33
33
|
isEphemeralInstallRoot,
|
|
34
34
|
enableMemoryRoutingIfResolves,
|
|
35
35
|
pinLauncherPackageRoot,
|
|
36
|
-
|
|
36
|
+
writeProjectSuitMcp,
|
|
37
|
+
trustGrokFolder,
|
|
37
38
|
} = require("./bridge-mcp-wiring.cjs");
|
|
38
39
|
|
|
39
40
|
const wearIo = { writes: 0, skips: 0 };
|
|
@@ -484,42 +485,11 @@ function findGrokPluginSource(packageRoot) {
|
|
|
484
485
|
return candidates.find((p) => fs.existsSync(p));
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
function registerGrokMcpServers(targetDir, log,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
for (const s of XRAY_MCP_SERVERS) {
|
|
496
|
-
try {
|
|
497
|
-
const launch = scopedMcpLaunch(targetDir, s.mcpCmd, s.env);
|
|
498
|
-
const argv = ["mcp", "add", s.name, "--command", launch.command, "--args", launch.args[0], ...launch.args.slice(1)];
|
|
499
|
-
for (const [key, value] of Object.entries(launch.env)) {
|
|
500
|
-
argv.push("--env", `${key}=${value}`);
|
|
501
|
-
}
|
|
502
|
-
execFileSync("grok", argv, { stdio: "pipe" });
|
|
503
|
-
log("grok-bridge", `registered ${s.name} (mcp-launch)`, "info");
|
|
504
|
-
} catch {
|
|
505
|
-
// already registered or grok config conflict — non-blocking
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const dirs =
|
|
510
|
-
Array.isArray(pluginDirs) && pluginDirs.length > 0
|
|
511
|
-
? pluginDirs
|
|
512
|
-
: [path.join(targetDir, ".grok", "plugins", "0xray")];
|
|
513
|
-
for (const pluginDir of dirs) {
|
|
514
|
-
if (!fs.existsSync(pluginDir)) continue;
|
|
515
|
-
try {
|
|
516
|
-
execSync(`grok plugins trust "${pluginDir}"`, { stdio: "ignore" });
|
|
517
|
-
log("grok-bridge", "plugin trusted", "info", { path: pluginDir });
|
|
518
|
-
break;
|
|
519
|
-
} catch {
|
|
520
|
-
// best-effort
|
|
521
|
-
}
|
|
522
|
-
}
|
|
488
|
+
function registerGrokMcpServers(targetDir, log, env) {
|
|
489
|
+
const tomlPath = writeProjectSuitMcp(targetDir);
|
|
490
|
+
log("grok-bridge", "project mcp config written", "info", { path: tomlPath });
|
|
491
|
+
const trustPath = trustGrokFolder(targetDir, env || process.env);
|
|
492
|
+
log("grok-bridge", "folder trusted", "info", { path: trustPath });
|
|
523
493
|
}
|
|
524
494
|
|
|
525
495
|
|
|
@@ -742,8 +712,8 @@ function installGrokBridge(targetDir, packageRoot, log, opts) {
|
|
|
742
712
|
if (globalCopied > 0) {
|
|
743
713
|
log("grok-bridge", `global skills synced (${globalCopied})`, "info", { path: grokGlobalSkills });
|
|
744
714
|
}
|
|
745
|
-
registerGrokMcpServers(targetDir, log, targets);
|
|
746
715
|
}
|
|
716
|
+
registerGrokMcpServers(targetDir, log, env);
|
|
747
717
|
}
|
|
748
718
|
|
|
749
719
|
function installHermesBridge(targetDir, packageRoot, log) {
|
|
@@ -881,7 +851,7 @@ function installOpenclawBridge(targetDir, packageRoot, log) {
|
|
|
881
851
|
}
|
|
882
852
|
|
|
883
853
|
if (!ephemeralOpenclaw && !isolatedOpenclaw) {
|
|
884
|
-
const hook = installOpenClawHostWear(packageRoot);
|
|
854
|
+
const hook = installOpenClawHostWear(packageRoot, targetDir);
|
|
885
855
|
if (hook) log("openclaw-bridge", "PreToolUse hook installed", "info", { path: hook });
|
|
886
856
|
if (maybeWriteOpenClawCliBackend()) {
|
|
887
857
|
log("openclaw-bridge", "opencode-cli backend written", "info");
|
|
@@ -895,6 +865,11 @@ function installOpenclawBridge(targetDir, packageRoot, log) {
|
|
|
895
865
|
"info",
|
|
896
866
|
);
|
|
897
867
|
}
|
|
868
|
+
|
|
869
|
+
const recorded = recordOpenClawProjectPlugin(targetDir, packageRoot);
|
|
870
|
+
if (recorded) {
|
|
871
|
+
log("openclaw-bridge", "project plugin path recorded", "info", { path: recorded });
|
|
872
|
+
}
|
|
898
873
|
}
|
|
899
874
|
|
|
900
875
|
const XRAY_CONFIG_FILES = ["codex.json", "features.json", "features.schema.json", "config.json"];
|
|
@@ -1041,16 +1016,18 @@ const CURSOR_HOOK_EVENTS = [
|
|
|
1041
1016
|
["beforeReadFile", "before-read-file.sh"],
|
|
1042
1017
|
];
|
|
1043
1018
|
|
|
1019
|
+
const CURSOR_GATE_EVENTS = new Set(["preToolUse", "beforeShellExecution", "beforeReadFile"]);
|
|
1020
|
+
|
|
1044
1021
|
const CURSOR_WEAR_BACKUP = "hooks.json.xray-before";
|
|
1045
1022
|
|
|
1046
1023
|
const CLOUD_SAFE_CURSOR_HOOKS = {
|
|
1047
1024
|
version: 1,
|
|
1048
1025
|
hooks: {
|
|
1049
|
-
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh" }],
|
|
1026
|
+
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh", failClosed: true }],
|
|
1050
1027
|
preCompact: [{ command: ".cursor/hooks/pre-compact.sh" }],
|
|
1051
1028
|
afterFileEdit: [{ command: ".cursor/hooks/after-file-edit.sh" }],
|
|
1052
|
-
beforeShellExecution: [{ command: ".cursor/hooks/before-shell-execution.sh" }],
|
|
1053
|
-
beforeReadFile: [{ command: ".cursor/hooks/before-read-file.sh" }],
|
|
1029
|
+
beforeShellExecution: [{ command: ".cursor/hooks/before-shell-execution.sh", failClosed: true }],
|
|
1030
|
+
beforeReadFile: [{ command: ".cursor/hooks/before-read-file.sh", failClosed: true }],
|
|
1054
1031
|
},
|
|
1055
1032
|
};
|
|
1056
1033
|
|
|
@@ -1096,6 +1073,9 @@ function mergeCloudSafeCursorHooks(existing) {
|
|
|
1096
1073
|
!Array.isArray(current) || !current.some((h) => isRelativeCursorHookCommand(h && h.command));
|
|
1097
1074
|
if (!current || leftover || missingRelative) {
|
|
1098
1075
|
next.hooks[name] = entries;
|
|
1076
|
+
} else if (entries[0].failClosed) {
|
|
1077
|
+
// Gate events fail closed: our relative entry gets failClosed even on an existing hooks.json (re-wear upgrades it).
|
|
1078
|
+
next.hooks[name] = current.map((h) => (h && isRelativeCursorHookCommand(h.command) ? { ...h, failClosed: true } : h));
|
|
1099
1079
|
}
|
|
1100
1080
|
}
|
|
1101
1081
|
return next;
|
|
@@ -1304,7 +1284,10 @@ function installedHookCommand(fromDir, packageRoot, scriptName) {
|
|
|
1304
1284
|
function installedCursorHookEntries(fromDir, packageRoot) {
|
|
1305
1285
|
const hooks = {};
|
|
1306
1286
|
for (const [event, script] of CURSOR_HOOK_EVENTS) {
|
|
1307
|
-
|
|
1287
|
+
const entry = { command: installedHookCommand(fromDir, packageRoot, script) };
|
|
1288
|
+
// Gate events fail closed: Cursor denies when the hook crashes or prints no JSON.
|
|
1289
|
+
if (CURSOR_GATE_EVENTS.has(event)) entry.failClosed = true;
|
|
1290
|
+
hooks[event] = [entry];
|
|
1308
1291
|
}
|
|
1309
1292
|
return hooks;
|
|
1310
1293
|
}
|
|
@@ -1635,8 +1618,10 @@ function ownedDeletionEdits(tokens, elements) {
|
|
|
1635
1618
|
return edits;
|
|
1636
1619
|
}
|
|
1637
1620
|
|
|
1638
|
-
function renderEventEntry(command) {
|
|
1639
|
-
return
|
|
1621
|
+
function renderEventEntry(command, failClosed) {
|
|
1622
|
+
return failClosed
|
|
1623
|
+
? `{ "command": ${JSON.stringify(command)}, "failClosed": true }`
|
|
1624
|
+
: `{ "command": ${JSON.stringify(command)} }`;
|
|
1640
1625
|
}
|
|
1641
1626
|
|
|
1642
1627
|
function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
@@ -1652,7 +1637,7 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1652
1637
|
const hooksPair = root.pairs.find((pair) => pair.key === "hooks");
|
|
1653
1638
|
if (!hooksPair) {
|
|
1654
1639
|
const inner = Object.entries(xrayByEvent)
|
|
1655
|
-
.map(([event, entries]) => `"${event}": [${renderEventEntry(entries[0].command)}]`)
|
|
1640
|
+
.map(([event, entries]) => `"${event}": [${renderEventEntry(entries[0].command, entries[0].failClosed)}]`)
|
|
1656
1641
|
.join(", ");
|
|
1657
1642
|
const needsComma = root.pairs.length > 0;
|
|
1658
1643
|
edits.push({
|
|
@@ -1667,9 +1652,10 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1667
1652
|
const missing = [];
|
|
1668
1653
|
for (const [event, entries] of Object.entries(xrayByEvent)) {
|
|
1669
1654
|
const command = entries[0].command;
|
|
1655
|
+
const failClosed = entries[0].failClosed === true;
|
|
1670
1656
|
const pair = hooksObj.pairs.find((item) => item.key === event);
|
|
1671
1657
|
if (!pair) {
|
|
1672
|
-
missing.push([event, command]);
|
|
1658
|
+
missing.push([event, command, failClosed]);
|
|
1673
1659
|
continue;
|
|
1674
1660
|
}
|
|
1675
1661
|
if (tokens[pair.valIdx].type !== "[") throw new Error(`${event} is not an array`);
|
|
@@ -1691,7 +1677,7 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1691
1677
|
edits.push({
|
|
1692
1678
|
start: tokens[pair.valEnd].start,
|
|
1693
1679
|
end: tokens[pair.valEnd].start,
|
|
1694
|
-
insert: `${elements.length > 0 ? ", " : ""}${renderEventEntry(command)}`,
|
|
1680
|
+
insert: `${elements.length > 0 ? ", " : ""}${renderEventEntry(command, failClosed)}`,
|
|
1695
1681
|
});
|
|
1696
1682
|
continue;
|
|
1697
1683
|
}
|
|
@@ -1703,13 +1689,17 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1703
1689
|
insert: JSON.stringify(command),
|
|
1704
1690
|
});
|
|
1705
1691
|
}
|
|
1692
|
+
if (failClosed && !objectPairs(tokens, first.el.startIdx).pairs.some((p) => p.key === "failClosed")) {
|
|
1693
|
+
// Re-wear upgrades an older entry: gate events fail closed.
|
|
1694
|
+
edits.push({ start: first.cmd.token.end, end: first.cmd.token.end, insert: ', "failClosed": true' });
|
|
1695
|
+
}
|
|
1706
1696
|
if (owned.length > 1) {
|
|
1707
1697
|
edits.push(...ownedDeletionEdits(tokens, owned.slice(1).map((item) => item.el)));
|
|
1708
1698
|
}
|
|
1709
1699
|
}
|
|
1710
1700
|
if (missing.length > 0) {
|
|
1711
1701
|
const rendered = missing
|
|
1712
|
-
.map(([event, command]) => `"${event}": [${renderEventEntry(command)}]`)
|
|
1702
|
+
.map(([event, command, failClosed]) => `"${event}": [${renderEventEntry(command, failClosed)}]`)
|
|
1713
1703
|
.join(", ");
|
|
1714
1704
|
edits.push({
|
|
1715
1705
|
start: tokens[hooksObj.closeIdx].start,
|
|
@@ -1803,20 +1793,21 @@ function mergeInstalledCursorHooks(existing, xrayByEvent) {
|
|
|
1803
1793
|
continue;
|
|
1804
1794
|
}
|
|
1805
1795
|
const command = shipped[0].command;
|
|
1796
|
+
const extra = shipped[0].failClosed ? { failClosed: true } : {};
|
|
1806
1797
|
let replaced = false;
|
|
1807
1798
|
const next = [];
|
|
1808
1799
|
for (const entry of current) {
|
|
1809
1800
|
const cmd = entry && typeof entry === "object" ? entry.command : "";
|
|
1810
1801
|
if (isXrayHookCommand(cmd)) {
|
|
1811
1802
|
if (!replaced) {
|
|
1812
|
-
next.push({ ...entry, command });
|
|
1803
|
+
next.push({ ...entry, command, ...extra });
|
|
1813
1804
|
replaced = true;
|
|
1814
1805
|
}
|
|
1815
1806
|
continue;
|
|
1816
1807
|
}
|
|
1817
1808
|
next.push(entry);
|
|
1818
1809
|
}
|
|
1819
|
-
if (!replaced) next.push({ command });
|
|
1810
|
+
if (!replaced) next.push({ command, ...extra });
|
|
1820
1811
|
hooks[name] = next;
|
|
1821
1812
|
}
|
|
1822
1813
|
const doc = {};
|
|
@@ -39,17 +39,25 @@ export function foundryCliPath(millRoot) {
|
|
|
39
39
|
return path.join(millRoot, "cli.js");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
/** Clean-room foundry env. The machine home is empty so a plugin on the real machine cannot fail the seat inspect. */
|
|
43
|
+
export function proofFoundryEnv(consumerRoot, baseEnv = process.env) {
|
|
43
44
|
const isolatedHome = path.join(consumerRoot, ".proof-home");
|
|
45
|
+
const machineHome = path.join(consumerRoot, ".proof-machine");
|
|
44
46
|
fs.mkdirSync(isolatedHome, { recursive: true });
|
|
47
|
+
fs.mkdirSync(machineHome, { recursive: true });
|
|
48
|
+
return {
|
|
49
|
+
...baseEnv,
|
|
50
|
+
FOUNDRY_ROOT: consumerRoot,
|
|
51
|
+
HOME: isolatedHome,
|
|
52
|
+
FOUNDRY_MACHINE_HOME: machineHome,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function runFoundryCli(millRoot, consumerRoot, args) {
|
|
45
57
|
return spawnSync(process.execPath, [foundryCliPath(millRoot), ...args], {
|
|
46
58
|
cwd: consumerRoot,
|
|
47
59
|
encoding: "utf8",
|
|
48
|
-
env:
|
|
49
|
-
...process.env,
|
|
50
|
-
FOUNDRY_ROOT: consumerRoot,
|
|
51
|
-
HOME: isolatedHome,
|
|
52
|
-
},
|
|
60
|
+
env: proofFoundryEnv(consumerRoot),
|
|
53
61
|
});
|
|
54
62
|
}
|
|
55
63
|
|
|
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
|
|
7
7
|
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
@@ -39,7 +39,7 @@ export default definePluginEntry({
|
|
|
39
39
|
register(api) {
|
|
40
40
|
api.on(
|
|
41
41
|
'before_tool_call',
|
|
42
|
-
(event, ctx) => {
|
|
42
|
+
async (event, ctx) => {
|
|
43
43
|
try {
|
|
44
44
|
const xrayRoot = resolveXrayRoot();
|
|
45
45
|
if (!xrayRoot) {
|
|
@@ -61,6 +61,21 @@ export default definePluginEntry({
|
|
|
61
61
|
if (!outcome.allow) {
|
|
62
62
|
return { block: true, blockReason: outcome.reason || '0xRay constitution' };
|
|
63
63
|
}
|
|
64
|
+
const spoken = `${event.toolName} ${JSON.stringify(event.params || {})}`.slice(0, 500);
|
|
65
|
+
const speakPath = [
|
|
66
|
+
join(xrayRoot, 'dist/integrations/hooks/pre-tool-speak.mjs'),
|
|
67
|
+
join(xrayRoot, 'src/integrations/hooks/pre-tool-speak.mjs'),
|
|
68
|
+
].find((candidate) => existsSync(candidate));
|
|
69
|
+
if (!speakPath) return undefined;
|
|
70
|
+
const speak = await import(pathToFileURL(speakPath).href);
|
|
71
|
+
const lens = speak.lensDecision(projectRoot, event.toolName, spoken);
|
|
72
|
+
if (lens) {
|
|
73
|
+
return { block: true, blockReason: lens.reason };
|
|
74
|
+
}
|
|
75
|
+
const route = speak.repertoireRoute(spoken);
|
|
76
|
+
if (route?.hookSpecificOutput) {
|
|
77
|
+
return { block: false, hookSpecificOutput: route.hookSpecificOutput };
|
|
78
|
+
}
|
|
64
79
|
return undefined;
|
|
65
80
|
} catch (err) {
|
|
66
81
|
return {
|