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.
Files changed (71) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.xray/features.json +1 -1
  3. package/dist/CHANGELOG.md +29 -0
  4. package/dist/integrations/grok/hooks/grok-hook-utils.js +21 -1
  5. package/dist/integrations/grok/hooks/session-start.js +2 -20
  6. package/dist/integrations/hooks/goggles-mcp.mjs +36 -4
  7. package/dist/integrations/hooks/goggles-pipeline.mjs +55 -15
  8. package/dist/integrations/hooks/goggles-planes.json +10 -4
  9. package/dist/integrations/hooks/plates.cjs +22 -0
  10. package/dist/integrations/hooks/session-note-index.cjs +298 -0
  11. package/dist/integrations/hooks/session-note-index.mjs +22 -0
  12. package/dist/integrations/hooks/sleeve.cjs +335 -0
  13. package/dist/integrations/hooks/sleeve.mjs +18 -0
  14. package/dist/integrations/hooks/station-hook-runtime.cjs +205 -9
  15. package/dist/integrations/hooks/wake-cascade.cjs +102 -0
  16. package/dist/integrations/hooks/wake-cascade.mjs +16 -0
  17. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  18. package/dist/memory-routing/plates.d.ts +1 -1
  19. package/dist/memory-routing/plates.js +1 -0
  20. package/docs-site/docs/plates/activity-log.md +1 -1
  21. package/docs-site/docs/plates/boot.md +2 -0
  22. package/docs-site/docs/plates/centrifuge.md +30 -0
  23. package/docs-site/docs/plates/cursor-compact.md +1 -1
  24. package/docs-site/docs/plates/glossary.md +1 -2
  25. package/docs-site/docs/plates/goggles.md +4 -0
  26. package/docs-site/docs/plates/governance.md +2 -0
  27. package/docs-site/docs/plates/grok-compact.md +1 -1
  28. package/docs-site/docs/plates/grokbot.md +1 -1
  29. package/docs-site/docs/plates/host-pack.md +2 -0
  30. package/docs-site/docs/plates/house.md +1 -1
  31. package/docs-site/docs/plates/index.md +4 -2
  32. package/docs-site/docs/plates/inference-files.md +1 -1
  33. package/docs-site/docs/plates/kept-line.md +1 -1
  34. package/docs-site/docs/plates/kits.md +2 -0
  35. package/docs-site/docs/plates/lens-gate.md +1 -1
  36. package/docs-site/docs/plates/lens-page.md +1 -1
  37. package/docs-site/docs/plates/live-mesh.md +41 -0
  38. package/docs-site/docs/plates/memory-recall.md +4 -0
  39. package/docs-site/docs/plates/notes-page.md +2 -1
  40. package/docs-site/docs/plates/orchestration.md +2 -0
  41. package/docs-site/docs/plates/package-face.md +1 -1
  42. package/docs-site/docs/plates/payload-heat.md +1 -1
  43. package/docs-site/docs/plates/pickup-stamp.md +1 -1
  44. package/docs-site/docs/plates/pre-tool.md +1 -1
  45. package/docs-site/docs/plates/processor.md +2 -0
  46. package/docs-site/docs/plates/record-map.md +2 -1
  47. package/docs-site/docs/plates/reflection-page.md +1 -1
  48. package/docs-site/docs/plates/reporting.md +2 -0
  49. package/docs-site/docs/plates/review.md +1 -1
  50. package/docs-site/docs/plates/routing.md +2 -0
  51. package/docs-site/docs/plates/session-capture.md +1 -1
  52. package/docs-site/docs/plates/site-manual.md +1 -1
  53. package/docs-site/docs/plates/stamp-plate.md +1 -1
  54. package/docs-site/docs/plates/station-card.md +4 -1
  55. package/docs-site/docs/plates/station-heat.md +1 -1
  56. package/docs-site/docs/plates/suit-organs.md +1 -1
  57. package/docs-site/docs/plates/suit-settings.md +1 -1
  58. package/docs-site/docs/plates/suit-wear.md +3 -1
  59. package/docs-site/docs/plates/suit.md +2 -0
  60. package/docs-site/docs/plates/trail-state.md +1 -1
  61. package/docs-site/docs/plates/work-fresh.md +1 -1
  62. package/docs-site/docs/plates/write-home.md +1 -1
  63. package/package.json +2 -1
  64. package/scripts/node/bridge-mcp-wiring.cjs +37 -4
  65. package/scripts/node/install-bridges.cjs +172 -19
  66. package/scripts/node/playwright-smoke-server.cjs +27 -0
  67. package/scripts/node/setup.cjs +2 -46
  68. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  69. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js +4 -1
  70. package/vendor/@0xray/repertoire/dist/paths.js +3 -0
  71. 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
- stamped · 0xray 4.0.36
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
@@ -57,3 +57,5 @@ Each tool is its own call. `spawnAside` opens around `orchestrate-task`, `analyz
57
57
  │ └─────────────────────┘ └─────────────────────────┘ │
58
58
  └────────────────────────────────────────────────────────────┘
59
59
  ```
60
+
61
+ stamped · 0xray 4.0.41
@@ -32,4 +32,4 @@ A release writes the package face, and the agent reads it.
32
32
 
33
33
  The face is `README.md`, `package.json`, `AGENTS.md`, `llms.txt`, `CHANGELOG.md`, and `SKILLS.md`. Do not hand-bump the version.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ stamped · 0xray 4.0.41
@@ -32,4 +32,4 @@ buildSessionBootPayload calls applyStationHeat and writes the station card.
32
32
 
33
33
  The function is in `src/integrations/grok/hooks/grok-hook-utils.js`. The plates that belong here are station-heat and pickup-stamp.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ 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.36
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.36
35
+ stamped · 0xray 4.0.41
@@ -42,3 +42,5 @@ sidebar_label: Processor
42
42
  │ └──────────────────────────┘ └───────────────────────┘ │
43
43
  └──────────────────────────────────────────────────────────────┘
44
44
  ```
45
+
46
+ 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
- stamped · 0xray 4.0.36
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
@@ -32,4 +32,4 @@ A reflection is one episode written under docs/reflections.
32
32
 
33
33
  The loader is `loadReflectionInferences` in `src/inference/inference-accumulator.ts`. A reflection is not a repertoire law.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ stamped · 0xray 4.0.41
@@ -35,3 +35,5 @@ Logs become a report. A valid cache returns the formatted report and does not wr
35
35
  │ └──────────────────────────┘ └─────────────────────┘ │
36
36
  └────────────────────────────────────────────────────────────┘
37
37
  ```
38
+
39
+ 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.36
57
+ stamped · 0xray 4.0.41
@@ -58,3 +58,5 @@ Task text becomes an agent. `scoreAndRoute` scores the work, then `routeToAgent`
58
58
  │ └────────────┘ │
59
59
  └──────────────────────────────────────────────────────────────┘
60
60
  ```
61
+
62
+ 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.36
38
+ stamped · 0xray 4.0.41
@@ -32,4 +32,4 @@ The published manual is docs-site/docs.
32
32
 
33
33
  Guides, architecture, and introduction live here. Factory notes under `docs/` are a different set. Reflections stay in `docs/reflections/`.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ stamped · 0xray 4.0.41
@@ -32,4 +32,4 @@ A stamped plate is one drawing for one plane.
32
32
 
33
33
  The file is `docs-site/docs/plates/<id>.md`. Each opened segue gets its own plate. The hook does not author this file. The plate that belongs here is review.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ 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
- stamped · 0xray 4.0.36
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.36
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.36
38
+ stamped · 0xray 4.0.41
@@ -32,4 +32,4 @@ Suit settings are features, config, and codex.
32
32
 
33
33
  There is no settings.json. The files are `.xray/features.json`, `.xray/config.json`, and `.xray/codex.json`. The package copies live under `xray/`.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ 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
- stamped · 0xray 4.0.36
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
@@ -38,3 +38,5 @@ Give your agent a suit. The rules stay on, you add powers, and it runs in the to
38
38
  ```
39
39
 
40
40
  Same rules in the repo. You are not locked into one app.
41
+
42
+ stamped · 0xray 4.0.41
@@ -32,4 +32,4 @@ The suit writes its trail under .xray.
32
32
 
33
33
  Session boot, the lens match line, and the station card are state. The agent reads them. Do not commit `.xray/state`.
34
34
 
35
- stamped · 0xray 4.0.36
35
+ 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.36
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.36
38
+ stamped · 0xray 4.0.41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.39",
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 candidates = [
144
- path.join(targetDir, "node_modules", "0xray", "scripts", "mjs", "run-goggles-mcp.mjs"),
145
- path.join(targetDir, "scripts", "mjs", "run-goggles-mcp.mjs"),
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
- function setupSkillMirrorNames(packageRoot) {
1850
- const skillsSource = fs.existsSync(path.join(packageRoot, "src", "skills"))
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
- if (!fs.existsSync(skillsSource)) return new Set();
1854
- const names = new Set();
1855
- for (const entry of fs.readdirSync(skillsSource, { withFileTypes: true })) {
1856
- if (!entry.isDirectory()) continue;
1857
- if (fs.existsSync(path.join(skillsSource, entry.name, "SKILL.md"))) names.add(entry.name);
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 names;
1972
+ return saw;
1860
1973
  }
1861
1974
 
1862
- /** The 4.0.28 skill mirror is not a consumer costume. Any other extra name still throws. */
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
- const mirrored = setupSkillMirrorNames(packageRoot);
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
- * 4.0.28 consumer postinstall files: AGENTS.md, .gitignore, repertoire link,
1877
- * .xray config, bridges, mill plant, then the setup.cjs skill mirror and
1878
- * dist/scripts links. Does not install Cursor hooks or git hooks.
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
+ });