0xray 4.0.30 → 4.0.32
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/.mcp.json +8 -0
- package/.opencode/skills/orchestrator/SKILL.md +12 -0
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +17 -0
- package/dist/cli/commands/status.d.ts +4 -2
- package/dist/cli/commands/status.js +6 -3
- package/dist/cli/commands/validate.js +9 -0
- package/dist/cli/goggles-status.d.ts +13 -0
- package/dist/cli/goggles-status.js +76 -0
- package/dist/cli/index.js +41 -2
- package/dist/integrations/grok/grok-cli.d.ts +3 -0
- package/dist/integrations/grok/grok-cli.js +41 -1
- package/dist/integrations/grok/hooks/grok-hook-utils.js +24 -4
- package/dist/integrations/grok/hooks/post-tool-use.js +2 -1
- package/dist/integrations/grok/hooks/pre-tool-use.js +90 -5
- package/dist/integrations/grok/hooks/session-start.js +7 -2
- package/dist/integrations/hooks/goggles-mcp.mjs +371 -0
- package/dist/integrations/hooks/goggles-pipeline.mjs +632 -0
- package/dist/integrations/hooks/goggles-planes.json +28 -0
- package/dist/integrations/hooks/plates.cjs +23 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +40 -11
- package/dist/integrations/hooks/station-memory-ingest.mjs +3 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/orchestrator/config/agent-capabilities.js +7 -0
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +5 -0
- package/dist/memory-routing/provider-loader.js +2 -0
- package/dist/nucleus/thin-dispatch.d.ts +1 -0
- package/dist/nucleus/thin-dispatch.js +4 -2
- package/dist/skills/orchestrator/SKILL.md +12 -0
- package/docs-site/docs/plates/glossary.md +27 -0
- package/docs-site/docs/plates/goggles.md +18 -0
- package/docs-site/docs/plates/grokbot.md +41 -0
- package/docs-site/docs/plates/host-pack.md +32 -0
- package/docs-site/docs/plates/house.md +42 -39
- package/docs-site/docs/plates/index.md +13 -1
- package/docs-site/docs/plates/kits.md +28 -0
- package/docs-site/docs/plates/suit.md +40 -0
- package/package.json +2 -1
- package/scripts/foundry/assert-packed-dist-cli.mjs +9 -0
- package/scripts/mjs/run-goggles-mcp.mjs +41 -0
- package/scripts/node/bridge-mcp-wiring.cjs +61 -21
- package/scripts/node/consumer-install-smoke.mjs +5 -0
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/orchestrator/config/agent-capabilities.ts +8 -0
- package/src/skills/orchestrator/SKILL.md +12 -0
- package/vendor/@0xray/repertoire/dist/paths.js +4 -0
- package/xray/features.json +1 -1
package/.grok-plugin/plugin.json
CHANGED
package/.mcp.json
CHANGED
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
"repertoire": {
|
|
35
35
|
"command": "node",
|
|
36
36
|
"args": ["scripts/mjs/run-repertoire-mcp.mjs"]
|
|
37
|
+
},
|
|
38
|
+
"goggles": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["scripts/mjs/run-goggles-mcp.mjs"],
|
|
41
|
+
"env": {
|
|
42
|
+
"GOGGLES_ROOT": ".",
|
|
43
|
+
"GOGGLES_PLANES_PATH": ""
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
47
|
}
|
|
@@ -53,6 +53,18 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
|
|
|
53
53
|
| Test failures | bug-triage |
|
|
54
54
|
| Review | code-review |
|
|
55
55
|
|
|
56
|
+
## Goggles
|
|
57
|
+
|
|
58
|
+
The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
|
|
59
|
+
|
|
60
|
+
Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
|
|
61
|
+
|
|
62
|
+
Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
|
|
63
|
+
|
|
64
|
+
`digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
|
|
65
|
+
|
|
66
|
+
The hold lasts for the work that named the plane. It does not stop one tool and then forget. Work that says goggles clears the hold and stays quiet. The suit does not invent the plane.
|
|
67
|
+
|
|
56
68
|
## Hooks (rewired, not new MCPs)
|
|
57
69
|
|
|
58
70
|
- **SessionStart** → `session-start.js` boots lead_dev_mode
|
package/.xray/features.json
CHANGED
package/dist/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,7 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
9
|
### Features
|
|
10
|
+
- **Goggles.** Proposed plates for goggles, suit, kits, host pack, and glossary. Goggles views one plane. Recall opens a plate when speech names that plate. Plates only. Not a new skill.
|
|
10
11
|
- **Plates.** Pipeline schematics live in `docs-site/docs/plates/` (routing, governance, boot, orchestration, processor, reporting, memory-recall). Station keeps one pointer line. Pre-compact and the `MEMORY_ROUTING` inject carry that one schematic. A lesson is an episode. A plate is the machine. Not a new skill.
|
|
11
12
|
- **Vendored Repertoire 0.2.8.** `vendor/@0xray/repertoire` is that package. Lesson lines and retained ids stay at 20. Speech that names nothing mints a learned signal, and only those mints evict past 24. Not a new skill.
|
|
12
13
|
- **Feat rebase.** When the mandated work turns into mantra, or the job has split across planes that no longer share one present, stop and answer the original message again from this seat. On `LEAD-CADENCE.md`, the orchestrator skill, and the ship-ready mill gate. Project dest grows the name `feat-rebase` from the stack overlay. Not a new skill.
|
|
@@ -41,6 +42,22 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
|
|
|
41
42
|
- Archive root `integrations/` costume SKILL dump → `docs/archive/integrations-costume/`. Live wear is `src/integrations`.
|
|
42
43
|
- Archive root `commands/` stale copy → `docs/archive/commands-copy/`. Live wear is `src/opencode/commands`.
|
|
43
44
|
|
|
45
|
+
## [4.0.32] - 2026-10-01
|
|
46
|
+
|
|
47
|
+
Next patch after npm `4.0.31`. Not published from this commit.
|
|
48
|
+
|
|
49
|
+
Organ bolster for goggles-pipeline after #167. `0xray look` (and `goggles`) opens one plane through the organ. Digest and triage return the card pane. Status and health show the organ worn and Kind 0 quiet/match. Bot Plugin MCP `goggles` exposes lean tools `look` and `status_lens` over stdio on the same organ. Kind 0 name-checks the six outers and stays quiet on match; reading stays a dichotomy; digest/triage stay the card. Calling and confer stay off. Companion `@0xray/grok-bot` is unchanged at 0.1.8.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## [4.0.31] - 2026-10-01
|
|
54
|
+
|
|
55
|
+
Next patch after npm `4.0.30`. Not published from this commit.
|
|
56
|
+
|
|
57
|
+
Goggles looks at one pipeline. Kind 0 name-checks the six outer names and stays quiet when the worn list matches the map. Digest and triage return one card. A leave is denied. Session boot hands the card through `handCard` and normalizes the workspace root, including a trailing slash. The mill path is `scripts/foundry`. Run `node dist/integrations/hooks/goggles-pipeline.mjs`. This cut also carries the plate docs and the live project-file signal count that landed on main after 4.0.30. Calling and confer stay off. Companion `@0xray/grok-bot` is unchanged at 0.1.8.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
44
61
|
## [4.0.30] - 2026-09-28
|
|
45
62
|
|
|
46
63
|
Next patch after npm `4.0.29`. Released 2026-09-28.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage: npx 0xray status
|
|
12
12
|
*/
|
|
13
|
+
import type { GogglesStatus } from "../goggles-status.js";
|
|
13
14
|
interface StatusReport {
|
|
14
15
|
opencode: {
|
|
15
16
|
installed: boolean;
|
|
@@ -34,8 +35,9 @@ interface StatusReport {
|
|
|
34
35
|
outcomesCount: number;
|
|
35
36
|
patternsCount: number;
|
|
36
37
|
};
|
|
38
|
+
goggles: GogglesStatus;
|
|
37
39
|
}
|
|
38
|
-
export declare function getStatusReport(cwd?: string): StatusReport;
|
|
40
|
+
export declare function getStatusReport(cwd?: string, packageRoot?: string): StatusReport;
|
|
39
41
|
export declare function printStatus(report: StatusReport): void;
|
|
40
|
-
export declare function statusCommand(): Promise<void>;
|
|
42
|
+
export declare function statusCommand(packageRoot?: string): Promise<void>;
|
|
41
43
|
export default statusCommand;
|
|
@@ -14,6 +14,7 @@ import { readdirSync, existsSync, readFileSync } from "fs";
|
|
|
14
14
|
import { join } from "path";
|
|
15
15
|
import { getConfigDir } from "../../core/config-paths.js";
|
|
16
16
|
import { featuresConfigLoader } from "../../core/features-config.js";
|
|
17
|
+
import { formatGogglesStatus, probeGoggles } from "../goggles-status.js";
|
|
17
18
|
function getSkillsList(cwd) {
|
|
18
19
|
const skills = [];
|
|
19
20
|
const configDir = getConfigDir(cwd);
|
|
@@ -119,7 +120,7 @@ function getInferenceStatus(cwd) {
|
|
|
119
120
|
catch { /* ignore */ }
|
|
120
121
|
return { active, lastTuning, outcomesCount, patternsCount };
|
|
121
122
|
}
|
|
122
|
-
export function getStatusReport(cwd = process.cwd()) {
|
|
123
|
+
export function getStatusReport(cwd = process.cwd(), packageRoot = cwd) {
|
|
123
124
|
const opencodeConfigPath = join(cwd, "opencode.json");
|
|
124
125
|
const cwdSkills = getSkillsList(cwd);
|
|
125
126
|
const agents = getAgentsList(cwd);
|
|
@@ -134,6 +135,7 @@ export function getStatusReport(cwd = process.cwd()) {
|
|
|
134
135
|
agents,
|
|
135
136
|
health,
|
|
136
137
|
inference,
|
|
138
|
+
goggles: probeGoggles(packageRoot, cwd),
|
|
137
139
|
};
|
|
138
140
|
}
|
|
139
141
|
export function printStatus(report) {
|
|
@@ -166,6 +168,7 @@ export function printStatus(report) {
|
|
|
166
168
|
console.log(`⚙️ Inference: ${inferenceStatus} (${report.inference.outcomesCount} outcomes, ${report.inference.patternsCount} patterns)`);
|
|
167
169
|
console.log(`📊 Health: ${healthStatus}`);
|
|
168
170
|
console.log("");
|
|
171
|
+
process.stdout.write(`${formatGogglesStatus(report.goggles).join("\n")}\n`);
|
|
169
172
|
if (report.skills.names.length > 0) {
|
|
170
173
|
console.log("Installed Skills:");
|
|
171
174
|
const cols = 4;
|
|
@@ -191,9 +194,9 @@ export function printStatus(report) {
|
|
|
191
194
|
console.log("");
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
|
-
export async function statusCommand() {
|
|
197
|
+
export async function statusCommand(packageRoot = process.cwd()) {
|
|
195
198
|
const cwd = process.cwd();
|
|
196
|
-
const report = getStatusReport(cwd);
|
|
199
|
+
const report = getStatusReport(cwd, packageRoot);
|
|
197
200
|
printStatus(report);
|
|
198
201
|
}
|
|
199
202
|
export default statusCommand;
|
|
@@ -23,8 +23,17 @@ export const VALIDATE_PACK_PATHS = [
|
|
|
23
23
|
"dist/integrations/cursor/hooks/pre-compact.sh",
|
|
24
24
|
"dist/integrations/cursor/hooks/after-file-edit.sh",
|
|
25
25
|
"dist/integrations/hooks/plates.cjs",
|
|
26
|
+
"dist/integrations/hooks/goggles-pipeline.mjs",
|
|
27
|
+
"dist/integrations/hooks/goggles-planes.json",
|
|
28
|
+
"dist/integrations/hooks/goggles-mcp.mjs",
|
|
29
|
+
"scripts/mjs/run-goggles-mcp.mjs",
|
|
26
30
|
"docs-site/docs/plates/memory-recall.md",
|
|
27
31
|
"docs-site/docs/plates/processor.md",
|
|
32
|
+
"docs-site/docs/plates/goggles.md",
|
|
33
|
+
"docs-site/docs/plates/suit.md",
|
|
34
|
+
"docs-site/docs/plates/kits.md",
|
|
35
|
+
"docs-site/docs/plates/host-pack.md",
|
|
36
|
+
"docs-site/docs/plates/glossary.md",
|
|
28
37
|
];
|
|
29
38
|
export const CONSUMER_WEAR_PATHS = [
|
|
30
39
|
"AGENTS.md",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goggles on status and health.
|
|
3
|
+
* Organ present is not Kind 0. Quiet is a match. No phone-home.
|
|
4
|
+
*/
|
|
5
|
+
export type GogglesStatus = {
|
|
6
|
+
organPresent: boolean;
|
|
7
|
+
kind0Text: string;
|
|
8
|
+
kind0Quiet: boolean;
|
|
9
|
+
kind0Label: string;
|
|
10
|
+
organLine: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function probeGoggles(packageRoot: string, cwd: string): GogglesStatus;
|
|
13
|
+
export declare function formatGogglesStatus(probe: GogglesStatus): string[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goggles on status and health.
|
|
3
|
+
* Organ present is not Kind 0. Quiet is a match. No phone-home.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
const WORN_PLANES = ["dichotomy", "syncopate", "synthesis", "digest", "triage", "loop"];
|
|
8
|
+
const ORGAN_PAIRS = [
|
|
9
|
+
["dist/integrations/hooks/goggles-pipeline.mjs", "dist/integrations/hooks/goggles-planes.json"],
|
|
10
|
+
["src/integrations/hooks/goggles-pipeline.mjs", "src/integrations/hooks/goggles-planes.json"],
|
|
11
|
+
];
|
|
12
|
+
function organPair(packageRoot) {
|
|
13
|
+
for (const pair of ORGAN_PAIRS) {
|
|
14
|
+
if (existsSync(join(packageRoot, pair[0])) && existsSync(join(packageRoot, pair[1]))) {
|
|
15
|
+
return pair;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function lensText(cwd) {
|
|
21
|
+
const file = join(cwd, ".xray", "state", "LENS.md");
|
|
22
|
+
if (!existsSync(file))
|
|
23
|
+
return { found: false, text: "" };
|
|
24
|
+
try {
|
|
25
|
+
return { found: true, text: readFileSync(file, "utf8").trim() };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { found: false, text: "" };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function mapDrift(packageRoot, planesRel) {
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(readFileSync(join(packageRoot, planesRel), "utf8"));
|
|
34
|
+
const map = Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
|
|
35
|
+
const same = map.length === WORN_PLANES.length && map.every((name, index) => name === WORN_PLANES[index]);
|
|
36
|
+
return same ? "" : "Actuality. Drift: worn is not the map.";
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function probeGoggles(packageRoot, cwd) {
|
|
43
|
+
const pair = organPair(packageRoot);
|
|
44
|
+
const organPresent = pair !== null;
|
|
45
|
+
const lens = lensText(cwd);
|
|
46
|
+
let kind0Text = "";
|
|
47
|
+
let kind0Label = "(no LENS yet)";
|
|
48
|
+
let kind0Quiet = false;
|
|
49
|
+
if (lens.found) {
|
|
50
|
+
kind0Text = lens.text;
|
|
51
|
+
if (!lens.text) {
|
|
52
|
+
kind0Quiet = true;
|
|
53
|
+
kind0Label = "quiet (match)";
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
kind0Label = lens.text;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (pair) {
|
|
60
|
+
const drift = mapDrift(packageRoot, pair[1]);
|
|
61
|
+
if (drift) {
|
|
62
|
+
kind0Text = drift;
|
|
63
|
+
kind0Label = drift;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
organPresent,
|
|
68
|
+
kind0Text,
|
|
69
|
+
kind0Quiet,
|
|
70
|
+
kind0Label,
|
|
71
|
+
organLine: organPresent ? "Goggles: worn" : "Goggles: not found",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function formatGogglesStatus(probe) {
|
|
75
|
+
return [probe.organLine, `Kind 0: ${probe.kind0Label}`];
|
|
76
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provides commands for installing and managing xray framework
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { execSync, spawn } from "child_process";
|
|
8
|
+
import { execSync, spawn, spawnSync } from "child_process";
|
|
9
9
|
import { join, resolve } from "path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -76,6 +76,43 @@ program
|
|
|
76
76
|
process.exit(1);
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
+
const LOOK_HELP = "Kind 0 is quiet on a match. One outer is a reading. digest and triage return a card.";
|
|
80
|
+
function gogglesScript() {
|
|
81
|
+
const built = join(packageRoot, "dist", "integrations", "hooks", "goggles-pipeline.mjs");
|
|
82
|
+
const source = join(packageRoot, "src", "integrations", "hooks", "goggles-pipeline.mjs");
|
|
83
|
+
return existsSync(built) ? built : source;
|
|
84
|
+
}
|
|
85
|
+
function runGoggles(args) {
|
|
86
|
+
const script = gogglesScript();
|
|
87
|
+
if (!existsSync(script)) {
|
|
88
|
+
process.stderr.write("Goggles: not found\n");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
if (process.stdout.isTTY) {
|
|
92
|
+
const result = spawnSync(process.execPath, [script, ...(args ?? [])], { stdio: "inherit" });
|
|
93
|
+
process.exit(result.status ?? 1);
|
|
94
|
+
}
|
|
95
|
+
const result = spawnSync(process.execPath, [script, ...(args ?? [])], { encoding: "utf8" });
|
|
96
|
+
if (result.stdout)
|
|
97
|
+
process.stdout.write(result.stdout);
|
|
98
|
+
if (result.stderr)
|
|
99
|
+
process.stderr.write(result.stderr);
|
|
100
|
+
process.exit(result.status ?? 1);
|
|
101
|
+
}
|
|
102
|
+
program
|
|
103
|
+
.command("look")
|
|
104
|
+
.argument("[args...]", "kind 0, one outer, or digest/triage and one card plane")
|
|
105
|
+
.description(LOOK_HELP)
|
|
106
|
+
.action((args) => {
|
|
107
|
+
runGoggles(args);
|
|
108
|
+
});
|
|
109
|
+
program
|
|
110
|
+
.command("goggles")
|
|
111
|
+
.argument("[args...]", "same words as look")
|
|
112
|
+
.description(`Same organ as look. ${LOOK_HELP}`)
|
|
113
|
+
.action((args) => {
|
|
114
|
+
runGoggles(args);
|
|
115
|
+
});
|
|
79
116
|
program
|
|
80
117
|
.command("wear")
|
|
81
118
|
.description("Wear the 0xray suit in this git checkout")
|
|
@@ -115,7 +152,7 @@ program
|
|
|
115
152
|
.description("Show comprehensive xray framework status")
|
|
116
153
|
.action(async () => {
|
|
117
154
|
const { statusCommand } = await import("./commands/status.js");
|
|
118
|
-
await statusCommand();
|
|
155
|
+
await statusCommand(packageRoot);
|
|
119
156
|
});
|
|
120
157
|
program
|
|
121
158
|
.command("validate")
|
|
@@ -252,6 +289,8 @@ program
|
|
|
252
289
|
}
|
|
253
290
|
}
|
|
254
291
|
console.log("");
|
|
292
|
+
const { probeGoggles, formatGogglesStatus } = await import("./goggles-status.js");
|
|
293
|
+
process.stdout.write(`${formatGogglesStatus(probeGoggles(packageRoot, process.cwd())).join("\n")}\n\n`);
|
|
255
294
|
if (allHealthy) {
|
|
256
295
|
console.log("🎉 Framework is healthy and ready to use!");
|
|
257
296
|
console.log("");
|
|
@@ -24,8 +24,11 @@ export interface GrokInstallOptions {
|
|
|
24
24
|
export declare function installForGrokCLI(options?: GrokInstallOptions): Promise<void>;
|
|
25
25
|
/** Grok TUI reads <project>/.grok/config.toml, not the package copy. */
|
|
26
26
|
export declare function writeProjectRepertoireMcp(projectRoot: string): string | null;
|
|
27
|
+
/** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
|
|
28
|
+
export declare function writeProjectGogglesMcp(projectRoot: string): string | null;
|
|
27
29
|
declare const _default: {
|
|
28
30
|
installForGrokCLI: typeof installForGrokCLI;
|
|
29
31
|
writeProjectRepertoireMcp: typeof writeProjectRepertoireMcp;
|
|
32
|
+
writeProjectGogglesMcp: typeof writeProjectGogglesMcp;
|
|
30
33
|
};
|
|
31
34
|
export default _default;
|
|
@@ -27,7 +27,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
27
27
|
const packageRoot = path.resolve(__dirname, '..', '..', '..');
|
|
28
28
|
const requireCjs = createRequire(import.meta.url);
|
|
29
29
|
const { resolveConsumerTargetDir, patchGrokHooks } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
|
|
30
|
-
const { XRAY_MCP_SERVERS, resolveRepertoireMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
|
|
30
|
+
const { XRAY_MCP_SERVERS, resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
|
|
31
31
|
function registerGrokMcpServers(targetDir) {
|
|
32
32
|
try {
|
|
33
33
|
execSync('which grok', { stdio: 'ignore' });
|
|
@@ -113,6 +113,7 @@ export async function installForGrokCLI(options = {}) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
writeProjectRepertoireMcp(targetDir);
|
|
116
|
+
writeProjectGogglesMcp(targetDir);
|
|
116
117
|
mintAfterWear(targetDir);
|
|
117
118
|
const primary = dests[0];
|
|
118
119
|
try {
|
|
@@ -177,6 +178,7 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
const repertoireMcp = resolveRepertoireMcp(targetDir);
|
|
181
|
+
const gogglesMcp = resolveGogglesMcp(targetDir);
|
|
180
182
|
if (mcp.mcpServers) {
|
|
181
183
|
if (repertoireMcp) {
|
|
182
184
|
mcp.mcpServers.repertoire = {
|
|
@@ -187,6 +189,16 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
|
|
|
187
189
|
else {
|
|
188
190
|
delete mcp.mcpServers.repertoire;
|
|
189
191
|
}
|
|
192
|
+
if (gogglesMcp) {
|
|
193
|
+
mcp.mcpServers.goggles = {
|
|
194
|
+
command: 'node',
|
|
195
|
+
args: [gogglesMcp],
|
|
196
|
+
env: { GOGGLES_ROOT: targetDir, GOGGLES_PLANES_PATH: '' },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
delete mcp.mcpServers.goggles;
|
|
201
|
+
}
|
|
190
202
|
}
|
|
191
203
|
fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
|
|
192
204
|
}
|
|
@@ -217,7 +229,35 @@ enabled = true
|
|
|
217
229
|
}
|
|
218
230
|
return tomlPath;
|
|
219
231
|
}
|
|
232
|
+
/** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
|
|
233
|
+
export function writeProjectGogglesMcp(projectRoot) {
|
|
234
|
+
const gogglesMcp = resolveGogglesMcp(projectRoot);
|
|
235
|
+
if (!gogglesMcp)
|
|
236
|
+
return null;
|
|
237
|
+
const grokDir = path.join(projectRoot, '.grok');
|
|
238
|
+
fs.mkdirSync(grokDir, { recursive: true });
|
|
239
|
+
const tomlPath = path.join(grokDir, 'config.toml');
|
|
240
|
+
const block = `[mcp_servers.goggles]
|
|
241
|
+
command = "node"
|
|
242
|
+
args = [${JSON.stringify(gogglesMcp)}]
|
|
243
|
+
enabled = true
|
|
244
|
+
`;
|
|
245
|
+
let existing = '';
|
|
246
|
+
if (fs.existsSync(tomlPath)) {
|
|
247
|
+
existing = fs.readFileSync(tomlPath, 'utf8');
|
|
248
|
+
}
|
|
249
|
+
if (/\[mcp_servers\.goggles\]/.test(existing)) {
|
|
250
|
+
existing = existing.replace(/\[mcp_servers\.goggles\][\s\S]*?(?=\n\[|$)/, block.trim());
|
|
251
|
+
fs.writeFileSync(tomlPath, existing.endsWith('\n') ? existing : `${existing}\n`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const prefix = existing.trim() ? `${existing.trim()}\n\n` : '';
|
|
255
|
+
fs.writeFileSync(tomlPath, `${prefix}${block}`);
|
|
256
|
+
}
|
|
257
|
+
return tomlPath;
|
|
258
|
+
}
|
|
220
259
|
export default {
|
|
221
260
|
installForGrokCLI,
|
|
222
261
|
writeProjectRepertoireMcp,
|
|
262
|
+
writeProjectGogglesMcp,
|
|
223
263
|
};
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
retainCompactFields,
|
|
29
29
|
writeStationMarkdown,
|
|
30
30
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
31
|
+
import { handCard } from '../../hooks/goggles-pipeline.mjs';
|
|
31
32
|
|
|
32
33
|
export {
|
|
33
34
|
checkPendingDelegationGate,
|
|
@@ -86,12 +87,18 @@ const WRITE_TOOLS = new Set([
|
|
|
86
87
|
'EditNotebook',
|
|
87
88
|
]);
|
|
88
89
|
|
|
90
|
+
export function normalizeWorkspaceRoot(root) {
|
|
91
|
+
const raw = String(root || '').trim();
|
|
92
|
+
if (!raw) return raw;
|
|
93
|
+
return path.resolve(raw);
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
export function workspaceRoot() {
|
|
90
|
-
return (
|
|
97
|
+
return normalizeWorkspaceRoot(
|
|
91
98
|
process.env.GROK_WORKSPACE_ROOT ||
|
|
92
99
|
process.env.CLAUDE_PROJECT_DIR ||
|
|
93
100
|
process.env.XRAY_ROOT ||
|
|
94
|
-
process.cwd()
|
|
101
|
+
process.cwd(),
|
|
95
102
|
);
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -198,6 +205,7 @@ export function extractToolContext(event) {
|
|
|
198
205
|
|
|
199
206
|
if (toolInput.path) paths.push(String(toolInput.path));
|
|
200
207
|
if (toolInput.file_path) paths.push(String(toolInput.file_path));
|
|
208
|
+
if (toolInput.target_file) paths.push(String(toolInput.target_file));
|
|
201
209
|
if (toolInput.target_notebook) paths.push(String(toolInput.target_notebook));
|
|
202
210
|
if (Array.isArray(toolInput.paths)) paths.push(...toolInput.paths.map(String));
|
|
203
211
|
|
|
@@ -301,7 +309,7 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
|
|
|
301
309
|
? 'xray-orchestrator analyze-complexity optional on frontier (spawn warns, does not deny)'
|
|
302
310
|
: 'xray-orchestrator → analyze-complexity (required before spawn_subagent)',
|
|
303
311
|
enforcement: 'PreToolUse hook — Codex constitution always on; ceremony scales by suit_temperament',
|
|
304
|
-
workspaceRoot: root,
|
|
312
|
+
workspaceRoot: normalizeWorkspaceRoot(root),
|
|
305
313
|
...heat,
|
|
306
314
|
...(siblingRoots.length > 0 ? { siblingWorkspaceRoots: siblingRoots } : {}),
|
|
307
315
|
...(conferPending ? { conferPending: true, conferTrigger: 'analyze-complexity at synthesis checkpoint' } : {}),
|
|
@@ -322,6 +330,11 @@ export function writeSessionBoot(root, payload) {
|
|
|
322
330
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
323
331
|
fs.writeFileSync(sessionBootPath(root), JSON.stringify(payload, null, 2));
|
|
324
332
|
writeStationMarkdown(root, payload);
|
|
333
|
+
try {
|
|
334
|
+
handCard(root, payload && (payload.cardText || payload.intent));
|
|
335
|
+
} catch {
|
|
336
|
+
/* a missed card must not fail the boot */
|
|
337
|
+
}
|
|
325
338
|
return sessionBootPath(root);
|
|
326
339
|
} catch {
|
|
327
340
|
return null;
|
|
@@ -333,9 +346,16 @@ export function sessionBootNeedsRefresh(existing, root) {
|
|
|
333
346
|
if (existing.lead_dev_mode === undefined) return true;
|
|
334
347
|
if (existing.host !== 'grok') return true;
|
|
335
348
|
if (!existing.suit_profile) return true;
|
|
336
|
-
if (
|
|
349
|
+
if (
|
|
350
|
+
existing.workspaceRoot &&
|
|
351
|
+
normalizeWorkspaceRoot(existing.workspaceRoot) !== normalizeWorkspaceRoot(root)
|
|
352
|
+
) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
337
355
|
if (!existing.repertoireResume) return true;
|
|
338
356
|
if (!existing.stationLine) return true;
|
|
357
|
+
const liveResume = buildRepertoireResume(root);
|
|
358
|
+
if (liveResume && existing.repertoireResume !== liveResume) return true;
|
|
339
359
|
return false;
|
|
340
360
|
}
|
|
341
361
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isSubagentTool,
|
|
11
11
|
isWriteTool,
|
|
12
12
|
loadFeatures,
|
|
13
|
+
normalizeWorkspaceRoot,
|
|
13
14
|
readStdinJson,
|
|
14
15
|
resolveSessionId,
|
|
15
16
|
workspaceRoot,
|
|
@@ -33,7 +34,7 @@ function extractToolOutput(event) {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export function handlePostToolUse(event, root = workspaceRoot()) {
|
|
36
|
-
const eventRoot = event.workspaceRoot || event.cwd || root;
|
|
37
|
+
const eventRoot = normalizeWorkspaceRoot(event.workspaceRoot || event.cwd || root);
|
|
37
38
|
const sessionId = resolveSessionId(event);
|
|
38
39
|
const { toolName, toolInput } = extractToolContext(event);
|
|
39
40
|
|
|
@@ -15,11 +15,72 @@ import {
|
|
|
15
15
|
isShellTool,
|
|
16
16
|
isWriteTool,
|
|
17
17
|
loadFeatures,
|
|
18
|
+
normalizeWorkspaceRoot,
|
|
18
19
|
readStdinJson,
|
|
19
20
|
resolveSessionId,
|
|
20
21
|
workspaceRoot,
|
|
21
22
|
} from './grok-hook-utils.js';
|
|
22
23
|
import { appendHookActivity } from './grok-hook-activity.js';
|
|
24
|
+
import { cardStop } from '../../hooks/goggles-pipeline.mjs';
|
|
25
|
+
import { createRequire } from 'node:module';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import { dirname, join } from 'node:path';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
|
|
30
|
+
const hookDir = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
let scoreAndRouteFn;
|
|
32
|
+
|
|
33
|
+
function loadScoreAndRoute() {
|
|
34
|
+
if (scoreAndRouteFn !== undefined) return scoreAndRouteFn;
|
|
35
|
+
scoreAndRouteFn = null;
|
|
36
|
+
const require = createRequire(import.meta.url);
|
|
37
|
+
const candidates = [
|
|
38
|
+
join(hookDir, '../../../nucleus/thin-dispatch.js'),
|
|
39
|
+
join(hookDir, '../../../../dist/nucleus/thin-dispatch.js'),
|
|
40
|
+
];
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
if (!existsSync(candidate)) continue;
|
|
43
|
+
try {
|
|
44
|
+
const loaded = require(candidate);
|
|
45
|
+
if (typeof loaded.scoreAndRoute === 'function') {
|
|
46
|
+
scoreAndRouteFn = loaded.scoreAndRoute;
|
|
47
|
+
return scoreAndRouteFn;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
/* next built copy */
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Speak only when the organ changes the agent. A match that leaves the agent is quiet. */
|
|
57
|
+
export function repertoireDecisionFields(routed) {
|
|
58
|
+
if (!routed?.memoryRouting?.overridden || !routed.agent) return null;
|
|
59
|
+
const signals = Array.isArray(routed.memoryRouting.signals)
|
|
60
|
+
? routed.memoryRouting.signals.slice(0, 4).filter(Boolean)
|
|
61
|
+
: [];
|
|
62
|
+
const why = signals.length ? ` on ${signals.join(', ')}` : '';
|
|
63
|
+
return {
|
|
64
|
+
agent: routed.agent,
|
|
65
|
+
gate: 'repertoire',
|
|
66
|
+
hookSpecificOutput: {
|
|
67
|
+
hookEventName: 'PreToolUse',
|
|
68
|
+
additionalContext: `Repertoire changed the route to ${routed.agent}${why}. Follow that agent for this work.`,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function repertoireRoute(operation) {
|
|
74
|
+
const text = String(operation || '').slice(0, 500).trim();
|
|
75
|
+
if (!text) return null;
|
|
76
|
+
try {
|
|
77
|
+
const scoreAndRoute = loadScoreAndRoute();
|
|
78
|
+
if (!scoreAndRoute) return null;
|
|
79
|
+
return repertoireDecisionFields(scoreAndRoute(text, {}));
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
23
84
|
|
|
24
85
|
function finish(root, decision, reason, hint, toolName, extra = {}) {
|
|
25
86
|
const out = { decision, ...extra };
|
|
@@ -30,6 +91,7 @@ function finish(root, decision, reason, hint, toolName, extra = {}) {
|
|
|
30
91
|
tool: toolName,
|
|
31
92
|
reason: reason || hint || null,
|
|
32
93
|
gate: extra.gate || null,
|
|
94
|
+
agent: extra.agent || null,
|
|
33
95
|
livePath: true,
|
|
34
96
|
});
|
|
35
97
|
process.exit(0);
|
|
@@ -41,7 +103,7 @@ async function main() {
|
|
|
41
103
|
|
|
42
104
|
try {
|
|
43
105
|
const event = await readStdinJson();
|
|
44
|
-
const eventRoot = event.workspaceRoot || event.cwd || root;
|
|
106
|
+
const eventRoot = normalizeWorkspaceRoot(event.workspaceRoot || event.cwd || root);
|
|
45
107
|
ensureSessionBoot(eventRoot, '0xray/grok-pre-tool-use-boot');
|
|
46
108
|
|
|
47
109
|
const features = loadFeatures(eventRoot);
|
|
@@ -75,9 +137,28 @@ async function main() {
|
|
|
75
137
|
if (extraBlock) finish(eventRoot, 'deny', extraBlock, null, toolName);
|
|
76
138
|
}
|
|
77
139
|
|
|
140
|
+
const stop = cardStop(
|
|
141
|
+
eventRoot,
|
|
142
|
+
toolName,
|
|
143
|
+
[cmd, content, toolInput.pattern, toolInput.query, ...(ctx.paths || [])].filter(Boolean).join('\n'),
|
|
144
|
+
ctx.paths || [],
|
|
145
|
+
toolInput,
|
|
146
|
+
);
|
|
147
|
+
if (stop?.decision === 'deny') {
|
|
148
|
+
finish(eventRoot, 'deny', stop.reason, null, toolName, { gate: stop.gate });
|
|
149
|
+
}
|
|
150
|
+
if (stop?.decision === 'allow') {
|
|
151
|
+
finish(eventRoot, 'allow', null, null, toolName, {
|
|
152
|
+
gate: stop.gate,
|
|
153
|
+
hookSpecificOutput: stop.hookSpecificOutput,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const route = repertoireRoute([toolName, cmd, content].filter(Boolean).join(' '));
|
|
158
|
+
|
|
78
159
|
if (isShellTool(toolName) && cmd) {
|
|
79
160
|
const testHint = checkFullTestSuite(cmd, features);
|
|
80
|
-
if (testHint) finish(eventRoot, 'allow', null, testHint, toolName);
|
|
161
|
+
if (testHint) finish(eventRoot, 'allow', null, testHint, toolName, route || {});
|
|
81
162
|
}
|
|
82
163
|
|
|
83
164
|
if (gateBlock.reason) {
|
|
@@ -87,11 +168,11 @@ async function main() {
|
|
|
87
168
|
gateBlock.reason,
|
|
88
169
|
gateBlock.hint,
|
|
89
170
|
toolName,
|
|
90
|
-
{ gate: gateBlock.gate, warn: true },
|
|
171
|
+
{ gate: gateBlock.gate, warn: true, ...(route || {}) },
|
|
91
172
|
);
|
|
92
173
|
}
|
|
93
174
|
|
|
94
|
-
finish(eventRoot, 'allow', null, null, toolName);
|
|
175
|
+
finish(eventRoot, 'allow', null, null, toolName, route || {});
|
|
95
176
|
} catch (err) {
|
|
96
177
|
appendHookActivity(root, 'grok-pre-tool-use', 'hook-error', 'error', {
|
|
97
178
|
tool: toolName,
|
|
@@ -116,6 +197,7 @@ function extractFromEvent(event) {
|
|
|
116
197
|
|
|
117
198
|
if (toolInput.path) paths.push(String(toolInput.path));
|
|
118
199
|
if (toolInput.file_path) paths.push(String(toolInput.file_path));
|
|
200
|
+
if (toolInput.target_file) paths.push(String(toolInput.target_file));
|
|
119
201
|
if (toolInput.target_notebook) paths.push(String(toolInput.target_notebook));
|
|
120
202
|
if (Array.isArray(toolInput.paths)) paths.push(...toolInput.paths.map(String));
|
|
121
203
|
|
|
@@ -133,4 +215,7 @@ function extractFromEvent(event) {
|
|
|
133
215
|
};
|
|
134
216
|
}
|
|
135
217
|
|
|
136
|
-
|
|
218
|
+
const launchedAsCli = Boolean(process.argv[1] && process.argv[1].endsWith('pre-tool-use.js'));
|
|
219
|
+
if (launchedAsCli) {
|
|
220
|
+
main();
|
|
221
|
+
}
|