0xray 4.0.39 → 4.0.41
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/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +29 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +21 -1
- package/dist/integrations/grok/hooks/session-start.js +2 -20
- package/dist/integrations/hooks/goggles-mcp.mjs +36 -4
- package/dist/integrations/hooks/goggles-pipeline.mjs +55 -15
- package/dist/integrations/hooks/goggles-planes.json +10 -4
- package/dist/integrations/hooks/plates.cjs +22 -0
- package/dist/integrations/hooks/session-note-index.cjs +298 -0
- package/dist/integrations/hooks/session-note-index.mjs +22 -0
- package/dist/integrations/hooks/sleeve.cjs +335 -0
- package/dist/integrations/hooks/sleeve.mjs +18 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +205 -9
- package/dist/integrations/hooks/wake-cascade.cjs +102 -0
- package/dist/integrations/hooks/wake-cascade.mjs +16 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +1 -0
- package/docs-site/docs/plates/activity-log.md +1 -1
- package/docs-site/docs/plates/boot.md +2 -0
- package/docs-site/docs/plates/centrifuge.md +30 -0
- package/docs-site/docs/plates/cursor-compact.md +1 -1
- package/docs-site/docs/plates/glossary.md +1 -2
- package/docs-site/docs/plates/goggles.md +4 -0
- package/docs-site/docs/plates/governance.md +2 -0
- package/docs-site/docs/plates/grok-compact.md +1 -1
- package/docs-site/docs/plates/grokbot.md +1 -1
- package/docs-site/docs/plates/host-pack.md +2 -0
- 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 +2 -0
- package/docs-site/docs/plates/lens-gate.md +1 -1
- package/docs-site/docs/plates/lens-page.md +1 -1
- package/docs-site/docs/plates/live-mesh.md +41 -0
- package/docs-site/docs/plates/memory-recall.md +4 -0
- package/docs-site/docs/plates/notes-page.md +2 -1
- package/docs-site/docs/plates/orchestration.md +2 -0
- 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 +2 -0
- package/docs-site/docs/plates/record-map.md +2 -1
- package/docs-site/docs/plates/reflection-page.md +1 -1
- package/docs-site/docs/plates/reporting.md +2 -0
- package/docs-site/docs/plates/review.md +1 -1
- package/docs-site/docs/plates/routing.md +2 -0
- 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 +4 -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 +3 -1
- package/docs-site/docs/plates/suit.md +2 -0
- 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/package.json +2 -1
- package/scripts/node/bridge-mcp-wiring.cjs +37 -4
- package/scripts/node/install-bridges.cjs +172 -19
- package/scripts/node/playwright-smoke-server.cjs +27 -0
- package/scripts/node/setup.cjs +2 -46
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js +4 -1
- package/vendor/@0xray/repertoire/dist/paths.js +3 -0
- package/xray/features.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
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.41
|
|
@@ -38,3 +38,7 @@ Intent names one plate. Equal top scores recall nothing. A missing worn copy is
|
|
|
38
38
|
│ └────────────────────┘ └────────────────────────┘ │
|
|
39
39
|
└──────────────────────────────────────────────────────────────┘
|
|
40
40
|
```
|
|
41
|
+
|
|
42
|
+
A look returns the named plane, the matched law names, and the current lines from the session-note index. It does not write a law.
|
|
43
|
+
|
|
44
|
+
stamped · 0xray 4.0.41
|
|
@@ -32,4 +32,5 @@ Notes are the body the station card cannot hold.
|
|
|
32
32
|
|
|
33
33
|
The page is `.xray/state/NOTES.md`. The pickup line equals the station intent. The default depth is the working notes: the point, the coverage, the library, the open pull requests, the rituals, and the next cut. A pickup line alone does not survive a compact. PreCompact calls `stampNotesPickup`, which updates only that line and leaves the body alone. The hook does not check the body. The seat writes it before the compact. Do not commit this file. Do not feed it to Repertoire.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
The station module writes this page. Heat keeps the current lines in `.xray/state/repertoire/session-notes.json` beside the laws. The wake cascade reads that index and does not write a law. A replaced note stays in that index as history. The short index returns the current line.
|
|
36
|
+
stamped · 0xray 4.0.41
|
|
@@ -32,4 +32,4 @@ stampNotesPickup writes one pickup line and leaves the rest of NOTES.md alone.
|
|
|
32
32
|
|
|
33
33
|
The comment on the function is one pickup line. There is no check that the working notes are present. The plate that belongs here is notes-page.
|
|
34
34
|
|
|
35
|
-
stamped · 0xray 4.0.
|
|
35
|
+
stamped · 0xray 4.0.41
|
|
@@ -32,4 +32,4 @@ The tool hook is where the lens gate runs.
|
|
|
32
32
|
|
|
33
33
|
pre-tool-use.js joins the command, the written content, and the paths, then calls lensBeforeResearch. A deny ends the hook. The plate that belongs here is lens-gate.
|
|
34
34
|
|
|
35
|
-
stamped · 0xray 4.0.
|
|
35
|
+
stamped · 0xray 4.0.41
|
|
@@ -38,4 +38,5 @@ Trail: state and logs under `.xray`. Codex is already loaded. This plate is the
|
|
|
38
38
|
|
|
39
39
|
The published manual is `docs-site/docs/`. The rule sheet is `grok-bot/OP-PROC.md`. Neither is one of the five pages.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Goggles plane `record-map` is the lens over this map. Station and the notes page belong to the station module. Repertoire laws stay in the law file. The session-note index sits beside that file.
|
|
42
|
+
stamped · 0xray 4.0.41
|
|
@@ -54,4 +54,4 @@ A state flow plate: one critic pass, one fix, one re-review, then stop.
|
|
|
54
54
|
|
|
55
55
|
A FAIL names the file and a test that fails on the old code. One author fixes that hole. A second session waits while the verdict is open. Re-review that new head. A hole the new test did not lock returns to ONE FIX. A PASS ends the plate. A note stays a note and does not open another lap.
|
|
56
56
|
|
|
57
|
-
stamped · 0xray 4.0.
|
|
57
|
+
stamped · 0xray 4.0.41
|
|
@@ -35,4 +35,4 @@ Today that file is `docs/inference/latest-session.json`. The home is `.xray/infe
|
|
|
35
35
|
|
|
36
36
|
Station reads the file for heat. The inference accumulator reads `session-*.json` beside it. Do not mint a `session-<date>-<sha>` file for the station note.
|
|
37
37
|
|
|
38
|
-
stamped · 0xray 4.0.
|
|
38
|
+
stamped · 0xray 4.0.41
|
|
@@ -32,4 +32,7 @@ The station card is the one-line ticket at .xray/state/STATION.md.
|
|
|
32
32
|
|
|
33
33
|
Read it after a compact. Leave Intent alone. The plan is the next cut. The long body belongs in notes.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
The station module writes this card. Heat adds `Cascade: record-map · notes N current`. The count is the current index. The body stays on the notes page.
|
|
36
|
+
|
|
37
|
+
The card keeps the named plane. `Sleeve: on` means the previous ticket came back, the worn plate matched the package, the previous ticket matched the working record, and inspect passed. Judgement is continue, retry N, stop, or start. The same miss keeps its retry. A different ticket stops. Payments is on only when the ticket is a payment.
|
|
38
|
+
stamped · 0xray 4.0.41
|
|
@@ -32,4 +32,4 @@ On a compact event applyStationHeat stamps the notes pickup line.
|
|
|
32
32
|
|
|
33
33
|
`isCompactHook` is true when the hook name contains compact. The body of NOTES.md is left alone. The plates that belong here are pickup-stamp and notes-page.
|
|
34
34
|
|
|
35
|
-
stamped · 0xray 4.0.
|
|
35
|
+
stamped · 0xray 4.0.41
|
|
@@ -35,4 +35,4 @@ Reporting reads the activity log and may write a report. Station reads `latest-s
|
|
|
35
35
|
|
|
36
36
|
Repertoire already keeps `inference-state.json` and the day log under `.xray/state/repertoire/`. Goggles does not write these files. It reads a plate when one plane is named.
|
|
37
37
|
|
|
38
|
-
stamped · 0xray 4.0.
|
|
38
|
+
stamped · 0xray 4.0.41
|
|
@@ -34,4 +34,6 @@ Wear copies hooks, features.json, and the consumer gitignore into the project, a
|
|
|
34
34
|
|
|
35
35
|
A worn project is a consumer. Its writes go to its own `.xray/logs` and `.xray/inference`, not into `docs/` and not into `.opencode/logs`.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
A fresh wear fastens mill and inspect. Grok's Goggles launcher stays on that install: the checkout script when the package name is `0xray`, and `node_modules/0xray` for every other project.
|
|
38
|
+
|
|
39
|
+
stamped · 0xray 4.0.41
|
|
@@ -32,4 +32,4 @@ Refresh writes one Fresh line from git and the published package.
|
|
|
32
32
|
|
|
33
33
|
refreshFreshness in src/nucleus/work-freshness.mjs fetches origin, reads npm view 0xray version, and returns that one line. A compact hook does not call it. The plate that belongs here is station-card.
|
|
34
34
|
|
|
35
|
-
stamped · 0xray 4.0.
|
|
35
|
+
stamped · 0xray 4.0.41
|
|
@@ -35,4 +35,4 @@ The path table is `src/integrations/hooks/write-home.cjs`. Writers use it. Reade
|
|
|
35
35
|
|
|
36
36
|
Still on the old path until each caller is rewired: the activity logger, the framework logger, the pipeline hook, the Grok hook, the Hermes bridge, station capture, `saveSessionInference`, reporting, pulse, monitor, and `features.json` `log_path`.
|
|
37
37
|
|
|
38
|
-
stamped · 0xray 4.0.
|
|
38
|
+
stamped · 0xray 4.0.41
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0xray",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.41",
|
|
4
4
|
"description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
|
|
5
5
|
"readme": "README.md",
|
|
6
6
|
"license": "MIT",
|
|
@@ -226,6 +226,7 @@
|
|
|
226
226
|
},
|
|
227
227
|
"devDependencies": {
|
|
228
228
|
"@eslint/js": "^9.39.2",
|
|
229
|
+
"@playwright/test": "^1.55.0",
|
|
229
230
|
"@types/express": "^5.0.0",
|
|
230
231
|
"@types/jsonwebtoken": "^9.0.7",
|
|
231
232
|
"@types/node": "^22.10.2",
|
|
@@ -139,11 +139,32 @@ function enableMemoryRoutingIfResolves(features, targetDir) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function isDogfoodXrayPackage(targetDir) {
|
|
143
|
+
try {
|
|
144
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
145
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
146
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
147
|
+
return pkg && pkg.name === "0xray";
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
142
153
|
function resolveGogglesMcp(targetDir) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
const installed = path.join(
|
|
155
|
+
targetDir,
|
|
156
|
+
"node_modules",
|
|
157
|
+
"0xray",
|
|
158
|
+
"scripts",
|
|
159
|
+
"mjs",
|
|
160
|
+
"run-goggles-mcp.mjs",
|
|
161
|
+
);
|
|
162
|
+
const checkout = path.join(targetDir, "scripts", "mjs", "run-goggles-mcp.mjs");
|
|
163
|
+
// The factory package is not installed into its own node_modules. npm upgrade
|
|
164
|
+
// removes that folder, and a recorded node_modules path then fails the handshake.
|
|
165
|
+
const candidates = isDogfoodXrayPackage(targetDir)
|
|
166
|
+
? [checkout, installed]
|
|
167
|
+
: [installed, checkout];
|
|
147
168
|
return candidates.find((file) => fs.existsSync(file)) || null;
|
|
148
169
|
}
|
|
149
170
|
|
|
@@ -315,7 +336,18 @@ function pinnedMcpLaunch(targetDir, mcpCmd) {
|
|
|
315
336
|
*/
|
|
316
337
|
const LIVE_ENV_NAMES = ["PATH", "HOME"];
|
|
317
338
|
|
|
339
|
+
let launcherPackageRoot = "";
|
|
340
|
+
|
|
341
|
+
/** Wear sets this so a /tmp checkout is not the path written into a real project. */
|
|
342
|
+
function pinLauncherPackageRoot(packageRoot) {
|
|
343
|
+
launcherPackageRoot = packageRoot ? path.resolve(packageRoot) : "";
|
|
344
|
+
}
|
|
345
|
+
|
|
318
346
|
function mcpLauncherPath() {
|
|
347
|
+
if (launcherPackageRoot) {
|
|
348
|
+
const candidate = path.join(launcherPackageRoot, "scripts", "node", "mcp-launch.cjs");
|
|
349
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
350
|
+
}
|
|
319
351
|
return path.join(__dirname, "mcp-launch.cjs");
|
|
320
352
|
}
|
|
321
353
|
|
|
@@ -988,6 +1020,7 @@ module.exports = {
|
|
|
988
1020
|
enableMemoryRoutingIfResolves,
|
|
989
1021
|
isEphemeralInstallRoot,
|
|
990
1022
|
isIsolatedHome,
|
|
1023
|
+
pinLauncherPackageRoot,
|
|
991
1024
|
copyHermesHookRuntimes,
|
|
992
1025
|
resolveOpenClawPreToolHookSource,
|
|
993
1026
|
resolveOpenClawPluginDir,
|
|
@@ -32,6 +32,7 @@ const {
|
|
|
32
32
|
maybeWriteOpenClawCliBackend,
|
|
33
33
|
isEphemeralInstallRoot,
|
|
34
34
|
enableMemoryRoutingIfResolves,
|
|
35
|
+
pinLauncherPackageRoot,
|
|
35
36
|
XRAY_MCP_SERVERS,
|
|
36
37
|
} = require("./bridge-mcp-wiring.cjs");
|
|
37
38
|
|
|
@@ -334,6 +335,34 @@ function mergeOpencodeJson(targetDir, packageRoot, log) {
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
|
|
338
|
+
function settlePackageRoot(packageRoot, targetDir) {
|
|
339
|
+
const resolved = path.resolve(packageRoot || ".");
|
|
340
|
+
if (!targetDir || !isEphemeralInstallRoot(resolved) || isEphemeralInstallRoot(path.resolve(targetDir))) {
|
|
341
|
+
return resolved;
|
|
342
|
+
}
|
|
343
|
+
const dest = path.join(machineHome(), ".grok", "wears", "0xray");
|
|
344
|
+
if (resolved === dest) return dest;
|
|
345
|
+
// Copy beside the live wear. A hook pointed at dest keeps loading until the swap.
|
|
346
|
+
const staging = `${dest}.next`;
|
|
347
|
+
const backup = `${dest}.prev`;
|
|
348
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
349
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
350
|
+
fs.cpSync(resolved, staging, {
|
|
351
|
+
recursive: true,
|
|
352
|
+
filter(src) {
|
|
353
|
+
const rel = path.relative(resolved, src);
|
|
354
|
+
if (!rel) return true;
|
|
355
|
+
const top = rel.split(path.sep)[0];
|
|
356
|
+
return top !== "node_modules" && top !== ".git";
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
360
|
+
if (fs.existsSync(dest)) fs.renameSync(dest, backup);
|
|
361
|
+
fs.renameSync(staging, dest);
|
|
362
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
363
|
+
return dest;
|
|
364
|
+
}
|
|
365
|
+
|
|
337
366
|
function grokHookShellCommand(packageRoot, scriptName, extraArgs) {
|
|
338
367
|
const script = path.join(packageRoot, "scripts", "mjs", "run-grok-hook.mjs");
|
|
339
368
|
const extra = extraArgs ? ` ${extraArgs}` : "";
|
|
@@ -365,6 +394,7 @@ function ensureGrokHookEvent(hooks, eventName) {
|
|
|
365
394
|
}
|
|
366
395
|
|
|
367
396
|
function patchGrokHooks(pluginDir, packageRoot, targetDir, log, label) {
|
|
397
|
+
packageRoot = settlePackageRoot(packageRoot, targetDir);
|
|
368
398
|
const hooksDir = path.join(pluginDir, "hooks");
|
|
369
399
|
const hooksPath = path.join(hooksDir, "hooks.json");
|
|
370
400
|
const hookScript = path.join(packageRoot, "scripts", "mjs", "run-grok-hook.mjs");
|
|
@@ -580,6 +610,83 @@ function grokPluginSettled(sourceDir, dest, packageRoot, targetDir) {
|
|
|
580
610
|
return true;
|
|
581
611
|
}
|
|
582
612
|
|
|
613
|
+
/** Absolute paths under a temp checkout. Longest match wins so a script path is retargeted before its root. */
|
|
614
|
+
function retargetEphemeralText(text, durableRoot) {
|
|
615
|
+
if (typeof text !== "string" || !isEphemeralInstallRoot(text)) return text;
|
|
616
|
+
const re = /(?:\/private\/var\/folders\/|\/var\/folders\/|\/private\/tmp\/|\/tmp\/|\/Temp\/)[^\s"']*/g;
|
|
617
|
+
const tokens = [...new Set(text.match(re) || [])].sort((a, b) => b.length - a.length);
|
|
618
|
+
let out = text;
|
|
619
|
+
for (const token of tokens) {
|
|
620
|
+
const scriptsAt = token.indexOf("/scripts/");
|
|
621
|
+
if (scriptsAt > 0) {
|
|
622
|
+
const repl = path.join(durableRoot, token.slice(scriptsAt + 1));
|
|
623
|
+
if (fs.existsSync(repl)) out = out.split(token).join(repl);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
const runner = path.join(durableRoot, "scripts", "mjs", "run-grok-hook.mjs");
|
|
627
|
+
if (
|
|
628
|
+
!fs.existsSync(token) &&
|
|
629
|
+
fs.existsSync(runner) &&
|
|
630
|
+
(out.includes("run-grok-hook") || out.includes("mcp-launch.cjs"))
|
|
631
|
+
) {
|
|
632
|
+
out = out.split(token).join(durableRoot);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return out;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function scrubEphemeralPins(node, durableRoot) {
|
|
639
|
+
if (Array.isArray(node)) return node.map((item) => scrubEphemeralPins(item, durableRoot));
|
|
640
|
+
if (!node || typeof node !== "object") {
|
|
641
|
+
return typeof node === "string" ? retargetEphemeralText(node, durableRoot) : node;
|
|
642
|
+
}
|
|
643
|
+
const out = {};
|
|
644
|
+
for (const [key, value] of Object.entries(node)) {
|
|
645
|
+
if ((key === "env" || key === "environment") && value && typeof value === "object" && !Array.isArray(value)) {
|
|
646
|
+
const env = {};
|
|
647
|
+
for (const [envKey, envValue] of Object.entries(value)) {
|
|
648
|
+
if (typeof envValue === "string" && isEphemeralInstallRoot(envValue)) {
|
|
649
|
+
if (envKey === "XRAY_AI_PATH") env[envKey] = durableRoot;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
env[envKey] = scrubEphemeralPins(envValue, durableRoot);
|
|
653
|
+
}
|
|
654
|
+
out[key] = env;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
out[key] = scrubEphemeralPins(value, durableRoot);
|
|
658
|
+
}
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Shared HOME does not wear the machine plugin, but Grok still loads a stale
|
|
664
|
+
* `~/.grok/plugins/0xray`. Drop temp XRAY_ROOT pins and retarget dead launch
|
|
665
|
+
* paths onto the durable wear. A real project root on that plugin is left alone.
|
|
666
|
+
*/
|
|
667
|
+
function scrubEphemeralMachineGrokPins(machine, durablePackageRoot) {
|
|
668
|
+
if (!machine || !durablePackageRoot) return 0;
|
|
669
|
+
const runner = path.join(durablePackageRoot, "scripts", "mjs", "run-grok-hook.mjs");
|
|
670
|
+
if (!fs.existsSync(runner)) return 0;
|
|
671
|
+
const plugin = path.join(machine, ".grok", "plugins", "0xray");
|
|
672
|
+
const files = [path.join(plugin, "hooks", "hooks.json"), path.join(plugin, ".mcp.json")];
|
|
673
|
+
let changed = 0;
|
|
674
|
+
for (const file of files) {
|
|
675
|
+
if (!fs.existsSync(file)) continue;
|
|
676
|
+
let data;
|
|
677
|
+
try {
|
|
678
|
+
data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
679
|
+
} catch {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const next = scrubEphemeralPins(data, durablePackageRoot);
|
|
683
|
+
if (JSON.stringify(data) === JSON.stringify(next)) continue;
|
|
684
|
+
writeFileIfChanged(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
685
|
+
changed += 1;
|
|
686
|
+
}
|
|
687
|
+
return changed;
|
|
688
|
+
}
|
|
689
|
+
|
|
583
690
|
function installGrokBridge(targetDir, packageRoot, log, opts) {
|
|
584
691
|
const sourceDir = findGrokPluginSource(packageRoot);
|
|
585
692
|
if (!sourceDir) {
|
|
@@ -599,6 +706,7 @@ function installGrokBridge(targetDir, packageRoot, log, opts) {
|
|
|
599
706
|
log("grok-bridge", "skip machine ~/.grok plugin — isolated HOME", "info");
|
|
600
707
|
} else {
|
|
601
708
|
log("grok-bridge", "skip machine ~/.grok plugin — project-scoped wear", "info");
|
|
709
|
+
scrubEphemeralMachineGrokPins(machine, settlePackageRoot(packageRoot, targetDir));
|
|
602
710
|
}
|
|
603
711
|
|
|
604
712
|
for (const dest of targets) {
|
|
@@ -1846,26 +1954,29 @@ function syncSetupSkillsAndRootLinks(packageRoot, targetDir) {
|
|
|
1846
1954
|
require("./setup.cjs").syncSetupSkillsAndRootLinks(packageRoot, targetDir);
|
|
1847
1955
|
}
|
|
1848
1956
|
|
|
1849
|
-
|
|
1850
|
-
|
|
1957
|
+
/** A byte-identical package skill is the old mirror. A different body still throws. */
|
|
1958
|
+
function wornSkillMatchesPackage(packageRoot, targetDir, name) {
|
|
1959
|
+
const source = fs.existsSync(path.join(packageRoot, "src", "skills"))
|
|
1851
1960
|
? path.join(packageRoot, "src", "skills")
|
|
1852
1961
|
: path.join(packageRoot, "dist", "skills");
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1962
|
+
const packaged = path.join(source, name, "SKILL.md");
|
|
1963
|
+
if (!fs.existsSync(packaged)) return false;
|
|
1964
|
+
const packagedBytes = fs.readFileSync(packaged);
|
|
1965
|
+
let saw = false;
|
|
1966
|
+
for (const parts of ROLE_SKILL_DIRS) {
|
|
1967
|
+
const worn = path.join(targetDir, ...parts, name, "SKILL.md");
|
|
1968
|
+
if (!fs.existsSync(worn)) continue;
|
|
1969
|
+
saw = true;
|
|
1970
|
+
if (!packagedBytes.equals(fs.readFileSync(worn))) return false;
|
|
1858
1971
|
}
|
|
1859
|
-
return
|
|
1972
|
+
return saw;
|
|
1860
1973
|
}
|
|
1861
1974
|
|
|
1862
|
-
|
|
1863
|
-
function isSetupSkillMirrorDump(err, packageRoot) {
|
|
1975
|
+
function isSetupSkillMirrorDump(err, packageRoot, targetDir) {
|
|
1864
1976
|
if (!err || err.code !== "FOUNDRY_COSTUME_DUMP") return false;
|
|
1865
1977
|
if (!Array.isArray(err.extraSkills) || err.extraSkills.length === 0) return false;
|
|
1866
1978
|
if (Array.isArray(err.extraAgents) && err.extraAgents.length > 0) return false;
|
|
1867
|
-
|
|
1868
|
-
return err.extraSkills.every((name) => mirrored.has(name));
|
|
1979
|
+
return err.extraSkills.every((name) => wornSkillMatchesPackage(packageRoot, targetDir, name));
|
|
1869
1980
|
}
|
|
1870
1981
|
|
|
1871
1982
|
function packageShipsConsumerSuit(packageRoot) {
|
|
@@ -1873,11 +1984,41 @@ function packageShipsConsumerSuit(packageRoot) {
|
|
|
1873
1984
|
}
|
|
1874
1985
|
|
|
1875
1986
|
/**
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
*
|
|
1987
|
+
* Consumer wear files: AGENTS.md, .gitignore, repertoire link, .xray config,
|
|
1988
|
+
* bridges, mill plant (mill + inspect), and the scripts and dist links.
|
|
1989
|
+
* The role-skill mirror stays in the package.
|
|
1990
|
+
* Does not install Cursor hooks or git hooks.
|
|
1879
1991
|
* Existing user files follow the 4.0.28 guards inside those functions.
|
|
1880
1992
|
*/
|
|
1993
|
+
|
|
1994
|
+
const ROLE_SKILL_KEEP = new Set(["mill", "inspect"]);
|
|
1995
|
+
const ROLE_SKILL_DIRS = [
|
|
1996
|
+
[".opencode", "skills"],
|
|
1997
|
+
[".grok", "plugins", "0xray", "skills"],
|
|
1998
|
+
[".hermes", "plugins", "xray-hermes", "skills"],
|
|
1999
|
+
[".openclaw", "skills"],
|
|
2000
|
+
];
|
|
2001
|
+
|
|
2002
|
+
function removeCopiedRoleSkills(packageRoot, targetDir) {
|
|
2003
|
+
const source = fs.existsSync(path.join(packageRoot, "src", "skills"))
|
|
2004
|
+
? path.join(packageRoot, "src", "skills")
|
|
2005
|
+
: path.join(packageRoot, "dist", "skills");
|
|
2006
|
+
if (!fs.existsSync(source)) return;
|
|
2007
|
+
for (const parts of ROLE_SKILL_DIRS) {
|
|
2008
|
+
const dir = path.join(targetDir, ...parts);
|
|
2009
|
+
if (!fs.existsSync(dir)) continue;
|
|
2010
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
2011
|
+
if (!entry.isDirectory() || ROLE_SKILL_KEEP.has(entry.name)) continue;
|
|
2012
|
+
const packaged = path.join(source, entry.name, "SKILL.md");
|
|
2013
|
+
const worn = path.join(dir, entry.name, "SKILL.md");
|
|
2014
|
+
if (!fs.existsSync(packaged) || !fs.existsSync(worn)) continue;
|
|
2015
|
+
const same = fs.readFileSync(packaged).equals(fs.readFileSync(worn));
|
|
2016
|
+
if (!same) continue;
|
|
2017
|
+
fs.rmSync(path.join(dir, entry.name), { recursive: true, force: true });
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1881
2022
|
function installConsumerProjectFiles(packageRoot, targetDir, log) {
|
|
1882
2023
|
const write = typeof log === "function" ? log : () => {};
|
|
1883
2024
|
if (!isConsumerInstall(packageRoot, targetDir) || !packageShipsConsumerSuit(packageRoot)) {
|
|
@@ -1896,10 +2037,11 @@ function installConsumerProjectFiles(packageRoot, targetDir, log) {
|
|
|
1896
2037
|
installGrokBridge(targetDir, packageRoot, write);
|
|
1897
2038
|
installHermesBridge(targetDir, packageRoot, write);
|
|
1898
2039
|
installOpenclawBridge(targetDir, packageRoot, write);
|
|
2040
|
+
removeCopiedRoleSkills(packageRoot, targetDir);
|
|
1899
2041
|
try {
|
|
1900
2042
|
mintConsumerSuit(packageRoot, targetDir, write);
|
|
1901
2043
|
} catch (err) {
|
|
1902
|
-
if (!isSetupSkillMirrorDump(err, packageRoot)) throw err;
|
|
2044
|
+
if (!isSetupSkillMirrorDump(err, packageRoot, targetDir)) throw err;
|
|
1903
2045
|
}
|
|
1904
2046
|
syncSetupSkillsAndRootLinks(packageRoot, targetDir);
|
|
1905
2047
|
return true;
|
|
@@ -2085,7 +2227,9 @@ function wearCursorHooks(targetDir, packageRoot, log, opts) {
|
|
|
2085
2227
|
const write = typeof log === "function" ? log : () => {};
|
|
2086
2228
|
const options = opts || {};
|
|
2087
2229
|
const resolvedTarget = path.resolve(targetDir);
|
|
2088
|
-
const resolvedPackage = path.resolve(packageRoot);
|
|
2230
|
+
const resolvedPackage = settlePackageRoot(path.resolve(packageRoot), resolvedTarget);
|
|
2231
|
+
pinLauncherPackageRoot(resolvedPackage);
|
|
2232
|
+
try {
|
|
2089
2233
|
if (classifyCursorWearGit(resolvedTarget) === "not-work-tree") {
|
|
2090
2234
|
wearSuitSkippingCursorHooks(resolvedPackage, resolvedTarget, write);
|
|
2091
2235
|
return;
|
|
@@ -2137,6 +2281,9 @@ function wearCursorHooks(targetDir, packageRoot, log, opts) {
|
|
|
2137
2281
|
}
|
|
2138
2282
|
write("cursor-bridge", "hooks.json wired to installed dist", "info", { path: hooksPath });
|
|
2139
2283
|
return hooksPath;
|
|
2284
|
+
} finally {
|
|
2285
|
+
pinLauncherPackageRoot("");
|
|
2286
|
+
}
|
|
2140
2287
|
}
|
|
2141
2288
|
|
|
2142
2289
|
/**
|
|
@@ -2344,15 +2491,16 @@ function installFrameworkDogfoodWear(packageRoot, log) {
|
|
|
2344
2491
|
* @param {{ targetDir: string, packageRoot: string, log?: Function }} opts
|
|
2345
2492
|
*/
|
|
2346
2493
|
function installAllBridges(opts) {
|
|
2347
|
-
const packageRoot = path.resolve(opts.packageRoot);
|
|
2348
2494
|
const targetDir = path.resolve(opts.targetDir);
|
|
2495
|
+
const packageRoot = settlePackageRoot(path.resolve(opts.packageRoot), targetDir);
|
|
2496
|
+
pinLauncherPackageRoot(packageRoot);
|
|
2349
2497
|
const logFn = opts.log;
|
|
2350
2498
|
const log =
|
|
2351
2499
|
logFn ||
|
|
2352
2500
|
((_component, _action, _status, _details) => {
|
|
2353
2501
|
/* noop */
|
|
2354
2502
|
});
|
|
2355
|
-
|
|
2503
|
+
try {
|
|
2356
2504
|
wearVendoredRepertoire(packageRoot, targetDir, log);
|
|
2357
2505
|
|
|
2358
2506
|
if (!isConsumerInstall(packageRoot, targetDir)) {
|
|
@@ -2373,6 +2521,9 @@ function installAllBridges(opts) {
|
|
|
2373
2521
|
installCursorBridge(targetDir, packageRoot, log);
|
|
2374
2522
|
|
|
2375
2523
|
log("install-bridges", "4-platform + cursor wear complete", "success");
|
|
2524
|
+
} finally {
|
|
2525
|
+
pinLauncherPackageRoot("");
|
|
2526
|
+
}
|
|
2376
2527
|
}
|
|
2377
2528
|
|
|
2378
2529
|
module.exports = {
|
|
@@ -2393,6 +2544,8 @@ module.exports = {
|
|
|
2393
2544
|
MERGE_CONFIG_FILES,
|
|
2394
2545
|
patchGrokHooks,
|
|
2395
2546
|
grokHookShellCommand,
|
|
2547
|
+
settlePackageRoot,
|
|
2548
|
+
scrubEphemeralMachineGrokPins,
|
|
2396
2549
|
writeGrokDiscoveredHooks,
|
|
2397
2550
|
isEphemeralInstallRoot,
|
|
2398
2551
|
isIsolatedHome,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Minimal static server for the Arch1 Playwright release-gate battery.
|
|
4
|
+
* Serves one HTML page with a tiny script so chromium coverage has a target.
|
|
5
|
+
*/
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const port = Number(process.env.PLAYWRIGHT_SMOKE_PORT || 3000);
|
|
8
|
+
const html = `<!doctype html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head><meta charset="utf-8"><title>0xray release gate</title></head>
|
|
11
|
+
<body>
|
|
12
|
+
<main id="gate">release-gate-ok</main>
|
|
13
|
+
<script>
|
|
14
|
+
window.__XRAY_RELEASE_GATE__ = function mark() {
|
|
15
|
+
return document.getElementById('gate').textContent;
|
|
16
|
+
};
|
|
17
|
+
window.__XRAY_RELEASE_GATE__();
|
|
18
|
+
</script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>`;
|
|
21
|
+
const server = http.createServer((req, res) => {
|
|
22
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
23
|
+
res.end(html);
|
|
24
|
+
});
|
|
25
|
+
server.listen(port, '127.0.0.1', () => {
|
|
26
|
+
process.stdout.write(`playwright-smoke-server listening on ${port}\n`);
|
|
27
|
+
});
|