0xray 4.0.42 → 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/.plugin/plugin.json +1 -1
- package/.xray/features.json +5 -5
- package/AGENTS-consumer.md +8 -6
- package/AGENTS.md +17 -8
- package/LICENSE +1 -1
- package/README.md +15 -9
- package/SKILLS.md +3 -1
- package/dist/AGENTS.md +17 -8
- package/dist/CHANGELOG.md +83 -11
- package/dist/LICENSE +1 -1
- package/dist/README.md +15 -9
- package/dist/SKILLS.md +3 -1
- package/dist/cli/commands/plugin-commands.d.ts +4 -0
- package/dist/cli/commands/plugin-commands.js +84 -41
- 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/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/hooks/pre-tool-use.js +2 -58
- package/dist/integrations/hermes-agent/bridge.mjs +14 -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 +7 -6
- 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/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
- package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
- 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/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 +14 -6
- package/package.json +3 -3
- 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-pipeline-facets.mjs +1 -1
- package/scripts/node/install-bridges.cjs +28 -12
- 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/vendor/@0xray/repertoire/README.md +1 -1
- package/xray/agents_template.md +2 -2
- package/xray/features.json +5 -5
- package/docs-site/docs/plates/live-mesh.md +0 -41
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* release-gate.mjs — Single release checkpoint.
|
|
4
4
|
*
|
|
5
5
|
* Modes:
|
|
6
|
-
* (default) build + test + consumer smoke (fresh + upgrade merge)
|
|
6
|
+
* (default) build + test + docs + consumer smoke (fresh + upgrade merge)
|
|
7
7
|
* --verify-only git + reconcile + release docs + consumer smoke — after push, before publish
|
|
8
8
|
* --skip-smoke skip consumer install smoke
|
|
9
|
-
* --
|
|
9
|
+
* --prove-published Gate D only: npm lists this version, the annotated tag points at HEAD,
|
|
10
|
+
* and the GitHub Release exists. This command does not publish.
|
|
11
|
+
* --skip-docs refused. Gate C runs on every gate.
|
|
10
12
|
*
|
|
11
13
|
* Usage:
|
|
12
14
|
* npx @0xray/foundry gate
|
|
@@ -21,7 +23,7 @@ import { isXrayExoRepo, millScript, readRootPackage, resolveMillRoot } from "./m
|
|
|
21
23
|
const rootDir = resolveMillRoot();
|
|
22
24
|
const verifyOnly = process.argv.includes("--verify-only");
|
|
23
25
|
const skipSmoke = process.argv.includes("--skip-smoke");
|
|
24
|
-
const
|
|
26
|
+
const provePublished = process.argv.includes("--prove-published");
|
|
25
27
|
|
|
26
28
|
function step(label, cmd) {
|
|
27
29
|
console.log(`\n${"─".repeat(60)}\n🔄 ${label}\n${"─".repeat(60)}\n`);
|
|
@@ -32,18 +34,114 @@ function docsCheckCmd() {
|
|
|
32
34
|
return `${JSON.stringify(process.execPath)} ${JSON.stringify(millScript("validate-release-docs.mjs"))}`;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
|
|
38
|
+
/** Gate C is the docs check. A ship gate that skips it is not a pass. */
|
|
39
|
+
export function gateCRefusal(argv = process.argv) {
|
|
40
|
+
if (argv.includes("--skip-docs")) {
|
|
41
|
+
return "Gate C cannot be skipped. Remove --skip-docs. The docs check runs on every gate.";
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Gate D facts. listed, tagAnnotated, and tagMatchesHead are booleans.
|
|
48
|
+
* releaseUrl is the GitHub Release page, or empty when that page is missing.
|
|
49
|
+
*/
|
|
50
|
+
export function provePublishedRelease(proof) {
|
|
51
|
+
const version = proof.version;
|
|
52
|
+
const errors = [];
|
|
53
|
+
if (proof.listed !== true) errors.push(`npm does not list ${version}`);
|
|
54
|
+
if (proof.tagAnnotated !== true) errors.push(`annotated tag v${version} is missing`);
|
|
55
|
+
if (proof.tagMatchesHead !== true) errors.push(`annotated tag v${version} does not point at HEAD`);
|
|
56
|
+
if (typeof proof.releaseUrl !== "string" || proof.releaseUrl.length === 0) {
|
|
57
|
+
errors.push(`GitHub Release v${version} is missing`);
|
|
58
|
+
}
|
|
59
|
+
return errors;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function gitOut(cmd) {
|
|
63
|
+
return execSync(cmd, { cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function originSlug() {
|
|
67
|
+
const url = gitOut("git remote get-url origin");
|
|
68
|
+
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
|
|
69
|
+
if (!match) throw new Error(`origin is not a GitHub repo: ${url}`);
|
|
70
|
+
return { owner: match[1], repo: match[2] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function collectPublishedProof() {
|
|
74
|
+
const pkg = readRootPackage(rootDir);
|
|
75
|
+
const version = pkg.version;
|
|
76
|
+
const name = pkg.name;
|
|
77
|
+
let listed = false;
|
|
78
|
+
try {
|
|
79
|
+
const view = gitOut(`npm view ${name}@${version} version`);
|
|
80
|
+
listed = view.split(/\s+/).includes(version);
|
|
81
|
+
} catch {
|
|
82
|
+
listed = false;
|
|
83
|
+
}
|
|
84
|
+
let tagAnnotated = false;
|
|
85
|
+
let tagMatchesHead = false;
|
|
86
|
+
try {
|
|
87
|
+
tagAnnotated = gitOut(`git cat-file -t v${version}`) === "tag";
|
|
88
|
+
const tagged = gitOut(`git rev-parse v${version}^{}`);
|
|
89
|
+
const head = gitOut("git rev-parse HEAD");
|
|
90
|
+
tagMatchesHead = tagAnnotated && tagged === head;
|
|
91
|
+
} catch {
|
|
92
|
+
tagAnnotated = false;
|
|
93
|
+
tagMatchesHead = false;
|
|
94
|
+
}
|
|
95
|
+
let releaseUrl = "";
|
|
96
|
+
try {
|
|
97
|
+
const { owner, repo } = originSlug();
|
|
98
|
+
const body = gitOut(
|
|
99
|
+
`curl -fsS -H "Accept: application/vnd.github+json" -H "User-Agent: 0xray-release-gate" https://api.github.com/repos/${owner}/${repo}/releases/tags/v${version}`,
|
|
100
|
+
);
|
|
101
|
+
const parsed = JSON.parse(body);
|
|
102
|
+
releaseUrl = typeof parsed.html_url === "string" ? parsed.html_url : "";
|
|
103
|
+
} catch {
|
|
104
|
+
releaseUrl = "";
|
|
105
|
+
}
|
|
106
|
+
return { version, name, listed, tagAnnotated, tagMatchesHead, releaseUrl };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runProvePublished() {
|
|
110
|
+
const proof = collectPublishedProof();
|
|
111
|
+
const errors = provePublishedRelease(proof);
|
|
112
|
+
process.stdout.write(`\nGate D — ${proof.name}@${proof.version}\n`);
|
|
113
|
+
if (errors.length) {
|
|
114
|
+
for (const error of errors) process.stderr.write(`❌ ${error}\n`);
|
|
115
|
+
process.stderr.write("\n❌ GATE D FAILED — npm listing, annotated tag at HEAD, and GitHub Release are all required\n\n");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
process.stdout.write(`✅ npm lists ${proof.version}\n`);
|
|
119
|
+
process.stdout.write(`✅ annotated tag v${proof.version} points at HEAD\n`);
|
|
120
|
+
process.stdout.write(`✅ GitHub Release ${proof.releaseUrl}\n`);
|
|
121
|
+
process.stdout.write("\n✅ GATE D PROVEN\n\n");
|
|
122
|
+
}
|
|
123
|
+
|
|
35
124
|
function runLightGate() {
|
|
36
125
|
const pkg = readRootPackage(rootDir);
|
|
37
126
|
if (verifyOnly) {
|
|
38
|
-
|
|
127
|
+
step("1/1 Release docs (light)", docsCheckCmd());
|
|
39
128
|
return;
|
|
40
129
|
}
|
|
41
130
|
if (pkg.scripts.build) step("1/3 Build", "npm run build");
|
|
42
131
|
if (pkg.scripts.test) step("2/3 Tests", "npm test");
|
|
43
|
-
|
|
132
|
+
step("3/3 Release docs (light)", docsCheckCmd());
|
|
44
133
|
}
|
|
45
134
|
|
|
46
135
|
function main() {
|
|
136
|
+
const refused = gateCRefusal();
|
|
137
|
+
if (refused) {
|
|
138
|
+
process.stderr.write(`\n❌ ${refused}\n\n`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
if (provePublished) {
|
|
142
|
+
runProvePublished();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
47
145
|
const mode = verifyOnly ? "verify" : "full";
|
|
48
146
|
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
49
147
|
console.log(`║ 🛡️ 0xRay Release Gate (${mode})`.padEnd(57) + "║");
|
|
@@ -54,9 +152,7 @@ function main() {
|
|
|
54
152
|
runLightGate();
|
|
55
153
|
} else if (verifyOnly) {
|
|
56
154
|
step("1/6 Git + reconcile", "node scripts/node/pre-publish-guard.js --verify-only");
|
|
57
|
-
|
|
58
|
-
step("2/6 Release docs", docsCheckCmd());
|
|
59
|
-
}
|
|
155
|
+
step("2/6 Release docs", docsCheckCmd());
|
|
60
156
|
step(
|
|
61
157
|
"3/6 Plugin infrastructure",
|
|
62
158
|
"node scripts/test/validate-plugins-e2e.cjs --structural-only",
|
|
@@ -75,9 +171,7 @@ function main() {
|
|
|
75
171
|
`${JSON.stringify(process.execPath)} ${JSON.stringify(millScript("assert-packed-dist-cli.mjs"))}`,
|
|
76
172
|
);
|
|
77
173
|
step("3/8 Tests", "npm run test:comprehensive");
|
|
78
|
-
|
|
79
|
-
step("4/8 Release docs", docsCheckCmd());
|
|
80
|
-
}
|
|
174
|
+
step("4/8 Release docs", docsCheckCmd());
|
|
81
175
|
step(
|
|
82
176
|
"5/8 Consumer hook verifiers",
|
|
83
177
|
"npm run verify:pre-commit-diff && npm run verify:pre-push-diff && node scripts/mjs/verify-delegation-gate-core.mjs --host=grok && node scripts/mjs/verify-delegation-gate-core.mjs --host=hermes && node scripts/mjs/verify-delegation-gate-core.mjs --host=opencode && node scripts/mjs/verify-hermes-session-start.mjs && node scripts/mjs/verify-confer-core.mjs && npm run verify:user-aside && npm run build && node scripts/mjs/verify-pipeline-facets.mjs --package-only",
|
|
@@ -91,9 +185,11 @@ function main() {
|
|
|
91
185
|
}
|
|
92
186
|
}
|
|
93
187
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
188
|
+
process.stdout.write("\n╔════════════════════════════════════════════════════════╗\n");
|
|
189
|
+
process.stdout.write("║ ✅ GATE A–C LOCAL PASS ║\n");
|
|
190
|
+
process.stdout.write("║ Gate D is not proven by this command. ║\n");
|
|
191
|
+
process.stdout.write("╚════════════════════════════════════════════════════════╝\n");
|
|
192
|
+
process.stdout.write("Prove D after publish: npx @0xray/foundry gate --prove-published\n\n");
|
|
97
193
|
} catch {
|
|
98
194
|
console.error("\n❌ RELEASE GATE FAILED — do not tag or publish\n");
|
|
99
195
|
process.exit(1);
|
|
@@ -105,4 +201,4 @@ const isMain =
|
|
|
105
201
|
|
|
106
202
|
if (isMain) {
|
|
107
203
|
main();
|
|
108
|
-
}
|
|
204
|
+
}
|
|
@@ -191,6 +191,8 @@ async function main() {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
run(`git push origin v${version}`, "push tag");
|
|
194
|
+
say("Step 8: Gate D");
|
|
195
|
+
runMill("release-gate.mjs", ["--prove-published"], "gate D");
|
|
194
196
|
return;
|
|
195
197
|
}
|
|
196
198
|
|
|
@@ -281,6 +283,12 @@ async function main() {
|
|
|
281
283
|
console.log(` would: git tag -a v${newVersion}`);
|
|
282
284
|
}
|
|
283
285
|
run(`git push origin v${newVersion}`, "push tag");
|
|
286
|
+
say("Step 8: Gate D");
|
|
287
|
+
runMill("release-gate.mjs", ["--prove-published"], "gate D");
|
|
288
|
+
if (dryRun) {
|
|
289
|
+
say("Dry run did not prove Gate D.");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
284
292
|
|
|
285
293
|
console.log("\n╔════════════════════════════════════════════════════════╗");
|
|
286
294
|
console.log("║ ✅ Release Complete! ║");
|
|
@@ -110,7 +110,7 @@ if (packageOnly) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// P1 — autonomous reporting
|
|
113
|
-
if (features?.autonomous_reporting?.enabled ===
|
|
113
|
+
if (features?.autonomous_reporting?.enabled === false) {
|
|
114
114
|
pass('autonomous_reporting.enabled');
|
|
115
115
|
} else {
|
|
116
116
|
fail('autonomous_reporting.enabled');
|
|
@@ -1016,16 +1016,18 @@ const CURSOR_HOOK_EVENTS = [
|
|
|
1016
1016
|
["beforeReadFile", "before-read-file.sh"],
|
|
1017
1017
|
];
|
|
1018
1018
|
|
|
1019
|
+
const CURSOR_GATE_EVENTS = new Set(["preToolUse", "beforeShellExecution", "beforeReadFile"]);
|
|
1020
|
+
|
|
1019
1021
|
const CURSOR_WEAR_BACKUP = "hooks.json.xray-before";
|
|
1020
1022
|
|
|
1021
1023
|
const CLOUD_SAFE_CURSOR_HOOKS = {
|
|
1022
1024
|
version: 1,
|
|
1023
1025
|
hooks: {
|
|
1024
|
-
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh" }],
|
|
1026
|
+
preToolUse: [{ command: ".cursor/hooks/pre-tool-use.sh", failClosed: true }],
|
|
1025
1027
|
preCompact: [{ command: ".cursor/hooks/pre-compact.sh" }],
|
|
1026
1028
|
afterFileEdit: [{ command: ".cursor/hooks/after-file-edit.sh" }],
|
|
1027
|
-
beforeShellExecution: [{ command: ".cursor/hooks/before-shell-execution.sh" }],
|
|
1028
|
-
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 }],
|
|
1029
1031
|
},
|
|
1030
1032
|
};
|
|
1031
1033
|
|
|
@@ -1071,6 +1073,9 @@ function mergeCloudSafeCursorHooks(existing) {
|
|
|
1071
1073
|
!Array.isArray(current) || !current.some((h) => isRelativeCursorHookCommand(h && h.command));
|
|
1072
1074
|
if (!current || leftover || missingRelative) {
|
|
1073
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));
|
|
1074
1079
|
}
|
|
1075
1080
|
}
|
|
1076
1081
|
return next;
|
|
@@ -1279,7 +1284,10 @@ function installedHookCommand(fromDir, packageRoot, scriptName) {
|
|
|
1279
1284
|
function installedCursorHookEntries(fromDir, packageRoot) {
|
|
1280
1285
|
const hooks = {};
|
|
1281
1286
|
for (const [event, script] of CURSOR_HOOK_EVENTS) {
|
|
1282
|
-
|
|
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];
|
|
1283
1291
|
}
|
|
1284
1292
|
return hooks;
|
|
1285
1293
|
}
|
|
@@ -1610,8 +1618,10 @@ function ownedDeletionEdits(tokens, elements) {
|
|
|
1610
1618
|
return edits;
|
|
1611
1619
|
}
|
|
1612
1620
|
|
|
1613
|
-
function renderEventEntry(command) {
|
|
1614
|
-
return
|
|
1621
|
+
function renderEventEntry(command, failClosed) {
|
|
1622
|
+
return failClosed
|
|
1623
|
+
? `{ "command": ${JSON.stringify(command)}, "failClosed": true }`
|
|
1624
|
+
: `{ "command": ${JSON.stringify(command)} }`;
|
|
1615
1625
|
}
|
|
1616
1626
|
|
|
1617
1627
|
function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
@@ -1627,7 +1637,7 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1627
1637
|
const hooksPair = root.pairs.find((pair) => pair.key === "hooks");
|
|
1628
1638
|
if (!hooksPair) {
|
|
1629
1639
|
const inner = Object.entries(xrayByEvent)
|
|
1630
|
-
.map(([event, entries]) => `"${event}": [${renderEventEntry(entries[0].command)}]`)
|
|
1640
|
+
.map(([event, entries]) => `"${event}": [${renderEventEntry(entries[0].command, entries[0].failClosed)}]`)
|
|
1631
1641
|
.join(", ");
|
|
1632
1642
|
const needsComma = root.pairs.length > 0;
|
|
1633
1643
|
edits.push({
|
|
@@ -1642,9 +1652,10 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1642
1652
|
const missing = [];
|
|
1643
1653
|
for (const [event, entries] of Object.entries(xrayByEvent)) {
|
|
1644
1654
|
const command = entries[0].command;
|
|
1655
|
+
const failClosed = entries[0].failClosed === true;
|
|
1645
1656
|
const pair = hooksObj.pairs.find((item) => item.key === event);
|
|
1646
1657
|
if (!pair) {
|
|
1647
|
-
missing.push([event, command]);
|
|
1658
|
+
missing.push([event, command, failClosed]);
|
|
1648
1659
|
continue;
|
|
1649
1660
|
}
|
|
1650
1661
|
if (tokens[pair.valIdx].type !== "[") throw new Error(`${event} is not an array`);
|
|
@@ -1666,7 +1677,7 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1666
1677
|
edits.push({
|
|
1667
1678
|
start: tokens[pair.valEnd].start,
|
|
1668
1679
|
end: tokens[pair.valEnd].start,
|
|
1669
|
-
insert: `${elements.length > 0 ? ", " : ""}${renderEventEntry(command)}`,
|
|
1680
|
+
insert: `${elements.length > 0 ? ", " : ""}${renderEventEntry(command, failClosed)}`,
|
|
1670
1681
|
});
|
|
1671
1682
|
continue;
|
|
1672
1683
|
}
|
|
@@ -1678,13 +1689,17 @@ function editJsoncHooks(text, xrayByEvent, targetDir) {
|
|
|
1678
1689
|
insert: JSON.stringify(command),
|
|
1679
1690
|
});
|
|
1680
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
|
+
}
|
|
1681
1696
|
if (owned.length > 1) {
|
|
1682
1697
|
edits.push(...ownedDeletionEdits(tokens, owned.slice(1).map((item) => item.el)));
|
|
1683
1698
|
}
|
|
1684
1699
|
}
|
|
1685
1700
|
if (missing.length > 0) {
|
|
1686
1701
|
const rendered = missing
|
|
1687
|
-
.map(([event, command]) => `"${event}": [${renderEventEntry(command)}]`)
|
|
1702
|
+
.map(([event, command, failClosed]) => `"${event}": [${renderEventEntry(command, failClosed)}]`)
|
|
1688
1703
|
.join(", ");
|
|
1689
1704
|
edits.push({
|
|
1690
1705
|
start: tokens[hooksObj.closeIdx].start,
|
|
@@ -1778,20 +1793,21 @@ function mergeInstalledCursorHooks(existing, xrayByEvent) {
|
|
|
1778
1793
|
continue;
|
|
1779
1794
|
}
|
|
1780
1795
|
const command = shipped[0].command;
|
|
1796
|
+
const extra = shipped[0].failClosed ? { failClosed: true } : {};
|
|
1781
1797
|
let replaced = false;
|
|
1782
1798
|
const next = [];
|
|
1783
1799
|
for (const entry of current) {
|
|
1784
1800
|
const cmd = entry && typeof entry === "object" ? entry.command : "";
|
|
1785
1801
|
if (isXrayHookCommand(cmd)) {
|
|
1786
1802
|
if (!replaced) {
|
|
1787
|
-
next.push({ ...entry, command });
|
|
1803
|
+
next.push({ ...entry, command, ...extra });
|
|
1788
1804
|
replaced = true;
|
|
1789
1805
|
}
|
|
1790
1806
|
continue;
|
|
1791
1807
|
}
|
|
1792
1808
|
next.push(entry);
|
|
1793
1809
|
}
|
|
1794
|
-
if (!replaced) next.push({ command });
|
|
1810
|
+
if (!replaced) next.push({ command, ...extra });
|
|
1795
1811
|
hooks[name] = next;
|
|
1796
1812
|
}
|
|
1797
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 {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Factory memory organ for [0xRay](https://github.com/0xRayAI/xray): seed primitives, in-process memory routing, extra MCP (not an 8th `xray-*` server).
|
|
4
4
|
|
|
5
|
-
**v0.2.9** · 0xRay **4.
|
|
5
|
+
**v0.2.9** · 0xRay **4.1** wear. Runtime does **not** depend on `0xray` (optional peer for suit-verify scripts).
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
package/xray/agents_template.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 0xRay AI Agents
|
|
2
2
|
|
|
3
|
-
Quick reference for the 0xRay
|
|
3
|
+
Quick reference for the 0xRay suit (**4.1** — 7 consumer MCP servers). 0xRay is a three-subsystem autonomous engine. The suit is the wear.
|
|
4
4
|
|
|
5
5
|
## Available MCP Servers
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ All seven use `npx -y 0xray mcp <cmd>`:
|
|
|
28
28
|
|
|
29
29
|
## Governance
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
0xRay is a three-subsystem autonomous engine: Inference + External Governance (Dynamo Solar SSOT) + Autonomous Engine (thinDispatch 7-flow in MCP orchestrator). The suit is the wear. All actions are validated against the Universal Development Codex before execution.
|
|
32
32
|
|
|
33
33
|
**Codex**: `.xray/codex.json` — **70 terms** across all agent interactions.
|
|
34
34
|
|
package/xray/features.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./features.schema.json",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "0xRay Framework - Unified Feature Configuration",
|
|
5
5
|
"suit_temperament": {
|
|
6
6
|
"profile": "auto",
|
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
"auto_provision_worktree": false
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
|
-
"grok_postprocessor_light":
|
|
57
|
+
"grok_postprocessor_light": false,
|
|
58
58
|
"inference_session_capture": {
|
|
59
59
|
"enabled": true,
|
|
60
60
|
"min_commits": 3,
|
|
61
61
|
"lookback_commits": 20
|
|
62
62
|
},
|
|
63
63
|
"autonomous_reporting": {
|
|
64
|
-
"enabled":
|
|
64
|
+
"enabled": false,
|
|
65
65
|
"interval_minutes": 60,
|
|
66
|
-
"auto_schedule":
|
|
66
|
+
"auto_schedule": false,
|
|
67
67
|
"include_health_assessment": true,
|
|
68
68
|
"include_agent_activities": true,
|
|
69
69
|
"include_pipeline_operations": true,
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
"every_n_todos_completed": 0
|
|
175
175
|
},
|
|
176
176
|
"inference": {
|
|
177
|
-
"description": "
|
|
177
|
+
"description": "The cycle runs from the CLI command inference:run and the session wake does not start it.",
|
|
178
178
|
"enabled": true,
|
|
179
179
|
"workflow_dir": ".xray/inference",
|
|
180
180
|
"reports_dir": ".xray/reports",
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Live mesh
|
|
3
|
-
sidebar_label: Live mesh
|
|
4
|
-
plate_type: pipeline
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Live mesh
|
|
8
|
-
|
|
9
|
-
Ping-pong eng events become one ordered JSON feed, then a mesh render. Markdown is never the source.
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
┌────────────────────────────────────────────────────────────┐
|
|
13
|
-
│ INPUT LAYER │
|
|
14
|
-
│ ┌──────────────┐ ┌──────────┐ ┌─────────┐ ┌───────────┐ │
|
|
15
|
-
│ │ GitHub REST │ │ deploy │ │ /health │ │ X ledger │ │
|
|
16
|
-
│ │ PR/issue │ │ muse │ │ probe │ │ herald │ │
|
|
17
|
-
│ └──────┬───────┘ └────┬─────┘ └────┬────┘ └─────┬─────┘ │
|
|
18
|
-
│ └──────────────┴────────────┴────────────┘ │
|
|
19
|
-
└─────────────────────────────┬──────────────────────────────┘
|
|
20
|
-
v
|
|
21
|
-
┌─────────────────────────────┼──────────────────────────────┐
|
|
22
|
-
│ PROCESSING LAYER v │
|
|
23
|
-
│ ┌────────────────────────────────────────────────────┐ │
|
|
24
|
-
│ │ fetch_feed.py │ │
|
|
25
|
-
│ │ poll → dedupe on id → rewrite live-events.json │ │
|
|
26
|
-
│ │ --watch keeps the feed warm │ │
|
|
27
|
-
│ └──────────────────────────┬─────────────────────────┘ │
|
|
28
|
-
└──────────────────────────────┼────────────────────────────┘
|
|
29
|
-
v
|
|
30
|
-
┌──────────────────────────────┼────────────────────────────┐
|
|
31
|
-
│ OUTPUT LAYER v │
|
|
32
|
-
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
|
|
33
|
-
│ │ live-events.json │ │ render_mesh.py │ │
|
|
34
|
-
│ │ (+ jsonl append) │ │ mp4 / --follow │ │
|
|
35
|
-
│ └──────────────────────┘ └──────────────────────────┘ │
|
|
36
|
-
└────────────────────────────────────────────────────────────┘
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Paths live under `house/live-mesh/`. Schema is `schema.json`. The plate that belongs here is house.
|
|
40
|
-
|
|
41
|
-
stamped · 0xray 4.0.42
|