0xray 4.0.41 → 4.0.42

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 (115) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/orchestrator/SKILL.md +12 -0
  3. package/.xray/features.json +1 -1
  4. package/.xray/features.schema.json +1 -1
  5. package/AGENTS-consumer.md +2 -0
  6. package/AGENTS.md +2 -0
  7. package/README.md +52 -12
  8. package/SKILLS.md +2 -0
  9. package/dist/AGENTS.md +2 -0
  10. package/dist/CHANGELOG.md +13 -0
  11. package/dist/README.md +52 -12
  12. package/dist/SKILLS.md +2 -0
  13. package/dist/cli/commands/openclaw-install.js +1 -1
  14. package/dist/cli/index.js +12 -0
  15. package/dist/enforcement/index.d.ts +4 -0
  16. package/dist/enforcement/index.js +4 -0
  17. package/dist/enforcement/validators/code-quality-validators.js +6 -4
  18. package/dist/governance/llm-governance-provider.d.ts +7 -1
  19. package/dist/governance/llm-governance-provider.js +72 -14
  20. package/dist/governance/local-confer.d.ts +4 -1
  21. package/dist/governance/local-confer.js +22 -1
  22. package/dist/integrations/grok/grok-cli.js +10 -37
  23. package/dist/integrations/grok/hooks/grok-hook-utils.js +8 -1
  24. package/dist/integrations/hooks/confer-hook-runtime.mjs +23 -1
  25. package/dist/integrations/hooks/goggles-planes.json +4 -1
  26. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  27. package/dist/mcps/architect-tools.server.d.ts +6 -0
  28. package/dist/mcps/architect-tools.server.js +19 -2
  29. package/dist/mcps/config/server-config-registry.js +8 -6
  30. package/dist/mcps/governance.server.d.ts +1 -0
  31. package/dist/mcps/governance.server.js +46 -0
  32. package/dist/mcps/knowledge-skills/code-review.server.js +7 -0
  33. package/dist/mcps/knowledge-skills/security-audit.server.js +5 -3
  34. package/dist/mcps/mcp-client.js +1 -1
  35. package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
  36. package/dist/mcps/researcher.server.js +12 -3
  37. package/dist/nucleus/autonomy-kernel.d.ts +2 -0
  38. package/dist/nucleus/autonomy-kernel.js +17 -13
  39. package/dist/nucleus/confer-host.d.ts +35 -0
  40. package/dist/nucleus/confer-host.js +55 -0
  41. package/dist/nucleus/confer.d.ts +19 -3
  42. package/dist/nucleus/confer.js +111 -55
  43. package/dist/nucleus/lead-dev-plan-persistence.js +4 -2
  44. package/dist/nucleus/suit-temperament.d.ts +1 -1
  45. package/dist/nucleus/suit-temperament.js +2 -2
  46. package/dist/nucleus/synthesis-consult-receipt.d.ts +7 -1
  47. package/dist/nucleus/synthesis-consult-receipt.js +86 -35
  48. package/dist/reporting/framework-reporting-system.js +2 -5
  49. package/dist/reporting/log-parser.d.ts +2 -0
  50. package/dist/reporting/log-parser.js +7 -6
  51. package/dist/skills/orchestrator/SKILL.md +12 -0
  52. package/docs-site/docs/plates/activity-log.md +1 -1
  53. package/docs-site/docs/plates/boot.md +1 -1
  54. package/docs-site/docs/plates/centrifuge.md +1 -1
  55. package/docs-site/docs/plates/cursor-compact.md +1 -1
  56. package/docs-site/docs/plates/glossary.md +1 -1
  57. package/docs-site/docs/plates/goggles.md +1 -1
  58. package/docs-site/docs/plates/governance.md +1 -1
  59. package/docs-site/docs/plates/grok-compact.md +1 -1
  60. package/docs-site/docs/plates/grokbot.md +1 -1
  61. package/docs-site/docs/plates/host-pack.md +1 -1
  62. package/docs-site/docs/plates/house.md +1 -1
  63. package/docs-site/docs/plates/index.md +1 -1
  64. package/docs-site/docs/plates/inference-files.md +1 -1
  65. package/docs-site/docs/plates/kept-line.md +1 -1
  66. package/docs-site/docs/plates/kits.md +1 -1
  67. package/docs-site/docs/plates/lens-gate.md +1 -1
  68. package/docs-site/docs/plates/lens-page.md +1 -1
  69. package/docs-site/docs/plates/live-mesh.md +1 -1
  70. package/docs-site/docs/plates/memory-recall.md +1 -1
  71. package/docs-site/docs/plates/notes-page.md +1 -1
  72. package/docs-site/docs/plates/orchestration.md +1 -1
  73. package/docs-site/docs/plates/package-face.md +1 -1
  74. package/docs-site/docs/plates/payload-heat.md +1 -1
  75. package/docs-site/docs/plates/pickup-stamp.md +1 -1
  76. package/docs-site/docs/plates/pre-tool.md +1 -1
  77. package/docs-site/docs/plates/processor.md +1 -1
  78. package/docs-site/docs/plates/record-map.md +1 -1
  79. package/docs-site/docs/plates/reflection-page.md +1 -1
  80. package/docs-site/docs/plates/reporting.md +1 -1
  81. package/docs-site/docs/plates/review.md +1 -1
  82. package/docs-site/docs/plates/routing.md +1 -1
  83. package/docs-site/docs/plates/session-capture.md +1 -1
  84. package/docs-site/docs/plates/site-manual.md +1 -1
  85. package/docs-site/docs/plates/stamp-plate.md +1 -1
  86. package/docs-site/docs/plates/station-card.md +1 -1
  87. package/docs-site/docs/plates/station-heat.md +1 -1
  88. package/docs-site/docs/plates/suit-organs.md +1 -1
  89. package/docs-site/docs/plates/suit-settings.md +1 -1
  90. package/docs-site/docs/plates/suit-wear.md +1 -1
  91. package/docs-site/docs/plates/suit.md +1 -1
  92. package/docs-site/docs/plates/trail-state.md +1 -1
  93. package/docs-site/docs/plates/work-fresh.md +1 -1
  94. package/docs-site/docs/plates/write-home.md +1 -1
  95. package/llms.txt +2 -0
  96. package/package.json +2 -2
  97. package/scripts/mjs/verify-confer-core.mjs +153 -29
  98. package/scripts/mjs/verify-user-aside-core.mjs +3 -4
  99. package/scripts/node/bridge-mcp-wiring.cjs +236 -5
  100. package/scripts/node/confer-switch.cjs +66 -0
  101. package/scripts/node/install-bridges.cjs +16 -41
  102. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  103. package/src/mcps/architect-tools.server.ts +26 -2
  104. package/src/mcps/config/__tests__/server-config-registry.test.ts +15 -6
  105. package/src/mcps/config/server-config-registry.ts +9 -6
  106. package/src/mcps/governance.server.ts +49 -0
  107. package/src/mcps/knowledge-skills/code-review.server.ts +8 -0
  108. package/src/mcps/knowledge-skills/security-audit.server.ts +11 -12
  109. package/src/mcps/mcp-client.ts +1 -1
  110. package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
  111. package/src/mcps/researcher.server.ts +19 -10
  112. package/src/skills/orchestrator/SKILL.md +12 -0
  113. package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +5 -51
  114. package/xray/features.json +1 -1
  115. package/xray/features.schema.json +1 -1
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Confer quorum fixture — 3-agent synthesis consult completes via fixture mode.
3
+ * Confer quorum fixture.
4
+ * PASS completes. FAIL and no-LLM UNREVIEWED leave the todo open and the checkpoint due.
5
+ * Temp projects opt into synthesis so a due checkpoint can be observed. The repo default stays off.
4
6
  */
5
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
6
8
  import { tmpdir } from 'node:os';
7
9
  import { dirname, join } from 'node:path';
8
10
  import { fileURLToPath } from 'node:url';
@@ -10,10 +12,14 @@ import { fileURLToPath } from 'node:url';
10
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
13
  const packageRoot = join(__dirname, '../..');
12
14
 
15
+ // Fixture PASS is refused unless this process is a test. The live orchestrate-task
16
+ // flag and XRAY_CONFER_FIXTURE stay unreachable when NODE_ENV is not test.
17
+ process.env.NODE_ENV = 'test';
18
+
13
19
  const { buildSynthesisCheckpointPlan } = await import(
14
20
  join(packageRoot, 'dist/nucleus/autonomy-kernel.js')
15
21
  );
16
- const { savePersistedLeadDevPlan, areSynthesisConsultTodosComplete } = await import(
22
+ const { savePersistedLeadDevPlan, areSynthesisConsultTodosComplete, loadPersistedLeadDevPlan } = await import(
17
23
  join(packageRoot, 'dist/nucleus/lead-dev-plan-persistence.js')
18
24
  );
19
25
  const { recordExecutionSlice, isSynthesisCheckpointDue } = await import(
@@ -22,46 +28,103 @@ const { recordExecutionSlice, isSynthesisCheckpointDue } = await import(
22
28
  const { runConferQuorum, loadConferCheckpoint, CONFER_AGENTS } = await import(
23
29
  join(packageRoot, 'dist/nucleus/confer.js')
24
30
  );
31
+ const { loadSynthesisConsultReceipt } = await import(
32
+ join(packageRoot, 'dist/nucleus/synthesis-consult-receipt.js')
33
+ );
34
+ const { readConferReviewHint } = await import(
35
+ join(packageRoot, 'src/integrations/hooks/confer-hook-runtime.mjs')
36
+ );
37
+ const { buildSessionBootPayload } = await import(
38
+ join(packageRoot, 'src/integrations/grok/hooks/grok-hook-utils.js')
39
+ );
25
40
 
26
- const sessionId = 'verify-confer-core';
27
41
  let failed = 0;
42
+ let checks = 0;
28
43
  function pass(n) {
44
+ checks++;
29
45
  console.log(`✅ ${n}`);
30
46
  }
31
47
  function fail(n, d = '') {
32
48
  failed++;
49
+ checks++;
33
50
  console.error(`❌ ${n}${d ? ` — ${d}` : ''}`);
34
51
  }
35
52
 
36
- const tmp = mkdtempSync(join(tmpdir(), 'xray-confer-verify-'));
37
- mkdirSync(join(tmp, '.xray', 'state'), { recursive: true });
38
- writeFileSync(
39
- join(tmp, '.xray', 'features.json'),
40
- JSON.stringify({
41
- multi_agent_orchestration: { lead_dev_mode: true, confer_on_synthesis: true },
42
- synthesis: { enabled: true, every_n_gates: 1, every_n_turns: 0, every_n_todos_completed: 0 },
43
- }),
44
- );
53
+ function assertRepoSynthesisStaysOff() {
54
+ const source = readFileSync(join(packageRoot, 'src/nucleus/synthesis.ts'), 'utf8');
55
+ const defaults = source.match(/function defaultSynthesisConfig\(\)[\s\S]*?return \{([\s\S]*?)\n \};/);
56
+ if (defaults && /enabled:\s*false/.test(defaults[1])) pass('repo defaultSynthesisConfig stays enabled: false');
57
+ else fail('repo defaultSynthesisConfig stays enabled: false');
45
58
 
46
- try {
47
- recordExecutionSlice('gate', { projectRoot: tmp, sessionId });
48
- if (!isSynthesisCheckpointDue(tmp, sessionId)) fail('step 1: synthesis due');
49
- else pass('step 1: synthesis checkpoint due');
59
+ for (const rel of ['xray/features.json', '.xray/features.json']) {
60
+ const data = JSON.parse(readFileSync(join(packageRoot, rel), 'utf8'));
61
+ if (data.synthesis?.enabled === false) pass(`${rel} synthesis.enabled stays false`);
62
+ else fail(`${rel} synthesis.enabled stays false`, JSON.stringify(data.synthesis));
63
+ }
64
+ }
50
65
 
66
+ function makeProject() {
67
+ const tmp = mkdtempSync(join(tmpdir(), 'xray-confer-verify-'));
68
+ mkdirSync(join(tmp, '.xray', 'state'), { recursive: true });
69
+ writeFileSync(
70
+ join(tmp, '.xray', 'features.json'),
71
+ JSON.stringify({
72
+ multi_agent_orchestration: { lead_dev_mode: true, confer_on_synthesis: true },
73
+ synthesis: { enabled: true, every_n_gates: 1, every_n_turns: 0, every_n_todos_completed: 0 },
74
+ }),
75
+ );
76
+ return tmp;
77
+ }
78
+
79
+ function seedDuePlan(tmp, sessionId) {
80
+ recordExecutionSlice('gate', { projectRoot: tmp, sessionId });
81
+ const due = isSynthesisCheckpointDue(tmp, sessionId);
51
82
  const plan = buildSynthesisCheckpointPlan('gate threshold (1/1)', tmp);
52
- if (!plan) fail('step 2: synthesis plan');
53
- else {
83
+ if (plan) {
54
84
  savePersistedLeadDevPlan(
55
85
  { ...plan, persistedAt: new Date().toISOString(), sessionId },
56
86
  tmp,
57
87
  );
58
- pass('step 2: synthesis plan persisted');
59
88
  }
89
+ return { due, plan };
90
+ }
91
+
92
+ function blocksCheckpoint(label, tmp, sessionId, result, verdict) {
93
+ const receipt = loadSynthesisConsultReceipt('s.1', tmp);
94
+ if (result.status === 'partial' && result.agents[0]?.verdict === verdict && result.agents[0]?.todoCompleted === false) {
95
+ pass(`${label}: quorum stopped on ${verdict}`);
96
+ } else {
97
+ fail(`${label}: quorum stopped on ${verdict}`, JSON.stringify(result));
98
+ }
99
+ if (receipt?.verdict === verdict) pass(`${label}: receipt verdict ${verdict}`);
100
+ else fail(`${label}: receipt verdict ${verdict}`, JSON.stringify(receipt));
101
+ if (!areSynthesisConsultTodosComplete(loadPersistedLeadDevPlan(tmp))) {
102
+ pass(`${label}: consult todo stays open`);
103
+ } else {
104
+ fail(`${label}: consult todo stays open`);
105
+ }
106
+ if (isSynthesisCheckpointDue(tmp, sessionId)) pass(`${label}: synthesis checkpoint stays due`);
107
+ else fail(`${label}: synthesis checkpoint stays due`);
108
+ const state = loadConferCheckpoint(tmp);
109
+ if (state?.status === 'failed') pass(`${label}: confer checkpoint not cleared`);
110
+ else fail(`${label}: confer checkpoint not cleared`, JSON.stringify(state));
111
+ }
112
+
113
+ assertRepoSynthesisStaysOff();
114
+
115
+ const passRoot = makeProject();
116
+ const passSession = 'verify-confer-core-pass';
117
+ try {
118
+ const seeded = seedDuePlan(passRoot, passSession);
119
+ if (!seeded.due) fail('step 1: synthesis due');
120
+ else pass('step 1: synthesis checkpoint due');
121
+ if (!seeded.plan) fail('step 2: synthesis plan');
122
+ else pass('step 2: synthesis plan persisted');
60
123
 
61
124
  if (CONFER_AGENTS.length !== 3) fail('step 3: confer agent count', String(CONFER_AGENTS.length));
62
125
  else pass('step 3: three confer agents defined');
63
126
 
64
- const result = await runConferQuorum(tmp, sessionId, {
127
+ const result = await runConferQuorum(passRoot, passSession, {
65
128
  collocatedText: 'verify confer fixture context',
66
129
  dueReason: 'gate threshold',
67
130
  fixture: true,
@@ -73,31 +136,92 @@ try {
73
136
  fail('step 4: confer quorum', JSON.stringify(result));
74
137
  }
75
138
 
76
- const state = loadConferCheckpoint(tmp);
139
+ const state = loadConferCheckpoint(passRoot);
77
140
  if (state?.status === 'completed' && state.completedAgents.length === 3) {
78
141
  pass('step 5: confer checkpoint state completed');
79
142
  } else {
80
143
  fail('step 5: confer state', JSON.stringify(state));
81
144
  }
82
145
 
83
- if (areSynthesisConsultTodosComplete(
84
- (await import(join(packageRoot, 'dist/nucleus/lead-dev-plan-persistence.js'))).loadPersistedLeadDevPlan(tmp),
85
- )) {
146
+ if (areSynthesisConsultTodosComplete(loadPersistedLeadDevPlan(passRoot))) {
86
147
  pass('step 6: synthesis consult todos complete after confer');
87
148
  } else {
88
149
  fail('step 6: consult todos incomplete');
89
150
  }
90
151
 
91
- if (!isSynthesisCheckpointDue(tmp, sessionId)) pass('step 7: synthesis checkpoint cleared');
152
+ if (!isSynthesisCheckpointDue(passRoot, passSession)) pass('step 7: synthesis checkpoint cleared');
92
153
  else fail('step 7: checkpoint still due');
93
154
  } finally {
94
- rmSync(tmp, { recursive: true, force: true });
155
+ rmSync(passRoot, { recursive: true, force: true });
156
+ }
157
+
158
+ const failRoot = makeProject();
159
+ const failSession = 'verify-confer-core-fail';
160
+ try {
161
+ seedDuePlan(failRoot, failSession);
162
+ const result = await runConferQuorum(failRoot, failSession, {
163
+ fixture: true,
164
+ fixtureVerdict: 'FAIL',
165
+ });
166
+ blocksCheckpoint('FAIL', failRoot, failSession, result, 'FAIL');
167
+ } finally {
168
+ rmSync(failRoot, { recursive: true, force: true });
169
+ }
170
+
171
+ const unreviewedRoot = makeProject();
172
+ const unreviewedSession = 'verify-confer-core-unreviewed';
173
+ try {
174
+ seedDuePlan(unreviewedRoot, unreviewedSession);
175
+ const result = await runConferQuorum(unreviewedRoot, unreviewedSession, {
176
+ fixture: true,
177
+ fixtureNoLlm: true,
178
+ });
179
+ blocksCheckpoint('no-LLM', unreviewedRoot, unreviewedSession, result, 'UNREVIEWED');
180
+ if (readConferReviewHint(unreviewedRoot) === 'UNREVIEWED') {
181
+ pass('no-LLM: confer review hint is UNREVIEWED');
182
+ } else {
183
+ fail('no-LLM: confer review hint is UNREVIEWED');
184
+ }
185
+ const boot = buildSessionBootPayload(unreviewedRoot, 'verify/session-start');
186
+ if (boot.conferReview === 'UNREVIEWED' && String(boot.conferReviewHint).includes('UNREVIEWED')) {
187
+ pass('no-LLM: session-boot hint records UNREVIEWED');
188
+ } else {
189
+ fail('no-LLM: session-boot hint records UNREVIEWED', JSON.stringify({
190
+ conferReview: boot.conferReview,
191
+ conferReviewHint: boot.conferReviewHint,
192
+ }));
193
+ }
194
+ } finally {
195
+ rmSync(unreviewedRoot, { recursive: true, force: true });
196
+ }
197
+
198
+ const conditionalRoot = makeProject();
199
+ const conditionalSession = 'verify-confer-core-conditional';
200
+ try {
201
+ seedDuePlan(conditionalRoot, conditionalSession);
202
+ const result = await runConferQuorum(conditionalRoot, conditionalSession, {
203
+ fixture: true,
204
+ fixtureVerdict: 'CONDITIONAL',
205
+ });
206
+ const verdicts = result.agents.map((agent) => agent.verdict);
207
+ if (result.status === 'partial' && verdicts[0] === 'CONDITIONAL' && result.agents[0]?.todoCompleted === false) {
208
+ pass('CONDITIONAL: explicit conditional does not complete');
209
+ } else {
210
+ fail('CONDITIONAL: explicit conditional does not complete', JSON.stringify(result));
211
+ }
212
+ if (isSynthesisCheckpointDue(conditionalRoot, conditionalSession)) {
213
+ pass('CONDITIONAL: checkpoint stays due');
214
+ } else {
215
+ fail('CONDITIONAL: checkpoint stays due');
216
+ }
217
+ } finally {
218
+ rmSync(conditionalRoot, { recursive: true, force: true });
95
219
  }
96
220
 
97
221
  console.log(
98
222
  '\n' +
99
223
  (failed === 0
100
- ? '🎉 Confer verify passed (7/7).'
224
+ ? `🎉 Confer verify passed (${checks}/${checks}).`
101
225
  : `⚠️ ${failed} confer check(s) failed.`),
102
226
  );
103
- process.exit(failed === 0 ? 0 : 1);
227
+ process.exit(failed === 0 ? 0 : 1);
@@ -75,16 +75,15 @@ try {
75
75
  35,
76
76
  tmp,
77
77
  );
78
- if (aside?.plan.phases[0]?.todos[0]?.id !== 'suit-nft.a.1.1') {
79
- fail('step 3: build aside plan', aside?.plan.phases[0]?.todos[0]?.id);
78
+ const todo = aside?.plan.phases.flatMap((phase) => phase.todos)[0];
79
+ if (todo?.id !== 'suit-nft.a.2.1') {
80
+ fail('step 3: build aside plan', todo?.id);
80
81
  } else pass('step 3: namespaced aside todos');
81
82
 
82
83
  saveUserAside(aside, tmp);
83
84
  setActiveAsideId('suit-nft', tmp, 'verify-session');
84
85
  if (getActiveAsideId(tmp, 'verify-session') !== 'suit-nft') fail('step 4: active pointer');
85
86
  else pass('step 4: active aside pointer with session');
86
-
87
- const todo = aside.plan.phases[0].todos[0];
88
87
  const resolved = resolveSpawnPlan({ planTodoId: todo.id }, tmp, 'verify-session');
89
88
  if (resolved.source !== 'aside') fail('step 5: resolve spawn plan', JSON.stringify(resolved));
90
89
  else pass('step 5: spawn plan resolves to aside');
@@ -869,7 +869,125 @@ function resolveOpenClawPluginDir(packageRoot) {
869
869
  ].find((p) => fs.existsSync(path.join(p, "index.js"))) || null;
870
870
  }
871
871
 
872
- function installOpenClawHostWear(packageRoot) {
872
+ function isCheckoutOpenClawPluginPath(pluginPath) {
873
+ const normalized = String(pluginPath || "").replace(/\\/g, "/");
874
+ return (
875
+ normalized.includes("/src/integrations/openclaw/plugin/xray-pre-tool") &&
876
+ !normalized.includes("/node_modules/")
877
+ );
878
+ }
879
+
880
+ function isInstalledOpenClawPluginPath(pluginPath) {
881
+ const normalized = String(pluginPath || "").replace(/\\/g, "/");
882
+ return (
883
+ normalized.includes("/node_modules/") &&
884
+ normalized.includes("/integrations/openclaw/plugin/xray-pre-tool")
885
+ );
886
+ }
887
+
888
+ /** Installed package first. A checkout src path drops once any installed plugin is present. */
889
+ function settleOpenClawPluginPaths(paths, wornPluginDir) {
890
+ const list = (Array.isArray(paths) ? paths : []).map((entry) => String(entry));
891
+ const wornIsInstalled = Boolean(wornPluginDir && isInstalledOpenClawPluginPath(wornPluginDir));
892
+ const dropCheckout =
893
+ wornIsInstalled || list.some((entry) => isInstalledOpenClawPluginPath(entry));
894
+ const next = [];
895
+ const seen = new Set();
896
+ const push = (entry) => {
897
+ if (!entry) return;
898
+ const resolved = path.resolve(String(entry));
899
+ if (dropCheckout && isCheckoutOpenClawPluginPath(resolved)) return;
900
+ if (seen.has(resolved)) return;
901
+ seen.add(resolved);
902
+ next.push(resolved);
903
+ };
904
+ if (wornIsInstalled) push(wornPluginDir);
905
+ for (const entry of list) push(entry);
906
+ return next;
907
+ }
908
+
909
+ function resolveWornOpenClawPlugin(targetDir, packageRoot) {
910
+ const candidates = [];
911
+ if (targetDir) {
912
+ candidates.push(
913
+ path.join(targetDir, "node_modules", "0xray", "dist", "integrations", "openclaw", "plugin", "xray-pre-tool"),
914
+ path.join(targetDir, "node_modules", "0xray", "src", "integrations", "openclaw", "plugin", "xray-pre-tool"),
915
+ );
916
+ }
917
+ const fromPackage = resolveOpenClawPluginDir(packageRoot);
918
+ if (fromPackage) candidates.push(fromPackage);
919
+ return candidates.find((dir) => fs.existsSync(path.join(dir, "index.js"))) || null;
920
+ }
921
+
922
+ function replaceJsonPathsArray(raw, before, next) {
923
+ const keyAt = raw.indexOf('"paths"');
924
+ if (keyAt < 0) return null;
925
+ const bracket = raw.indexOf("[", keyAt);
926
+ if (bracket < 0) return null;
927
+ let depth = 0;
928
+ let end = -1;
929
+ for (let i = bracket; i < raw.length; i += 1) {
930
+ const ch = raw[i];
931
+ if (ch === "[") depth += 1;
932
+ else if (ch === "]") {
933
+ depth -= 1;
934
+ if (depth === 0) {
935
+ end = i;
936
+ break;
937
+ }
938
+ }
939
+ }
940
+ if (end < 0) return null;
941
+ let parsed;
942
+ try {
943
+ parsed = JSON.parse(raw.slice(bracket, end + 1));
944
+ } catch {
945
+ return null;
946
+ }
947
+ if (JSON.stringify(parsed) !== JSON.stringify(before)) return null;
948
+ const lineStart = raw.lastIndexOf("\n", keyAt) + 1;
949
+ const indent = (raw.slice(lineStart, keyAt).match(/^\s*/) || [""])[0];
950
+ const itemIndent = `${indent} `;
951
+ const body = next
952
+ .map((entry) => `${itemIndent}${JSON.stringify(entry)}`)
953
+ .join(",\n");
954
+ const array = `[\n${body}\n${indent}]`;
955
+ return `${raw.slice(0, bracket)}${array}${raw.slice(end + 1)}`;
956
+ }
957
+
958
+ /** Rewrite plugins.load.paths in place. Leaves the rest of the config file alone. */
959
+ function pinOpenClawPluginLoad(configPath, wornPluginDir) {
960
+ if (!configPath || !fs.existsSync(configPath)) return null;
961
+ const raw = fs.readFileSync(configPath, "utf8");
962
+ const config = JSON.parse(raw);
963
+ if (!config.plugins || typeof config.plugins !== "object") return null;
964
+ if (!config.plugins.load || typeof config.plugins.load !== "object") config.plugins.load = {};
965
+ const before = Array.isArray(config.plugins.load.paths) ? config.plugins.load.paths : [];
966
+ const next = settleOpenClawPluginPaths(before, wornPluginDir);
967
+ if (JSON.stringify(before) === JSON.stringify(next)) return next;
968
+ const replaced = replaceJsonPathsArray(raw, before, next);
969
+ if (replaced == null) return null;
970
+ fs.writeFileSync(configPath, replaced);
971
+ return next;
972
+ }
973
+
974
+ function recordOpenClawProjectPlugin(targetDir, packageRoot) {
975
+ const pluginDir = resolveWornOpenClawPlugin(targetDir, packageRoot);
976
+ if (!pluginDir || !isInstalledOpenClawPluginPath(pluginDir)) return null;
977
+ const configPath = path.join(targetDir, ".xray", "config", "openclaw.json");
978
+ if (!fs.existsSync(configPath)) return null;
979
+ let config;
980
+ try {
981
+ config = JSON.parse(fs.readFileSync(configPath, "utf8"));
982
+ } catch {
983
+ return null;
984
+ }
985
+ config.pluginPath = path.resolve(pluginDir);
986
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
987
+ return config.pluginPath;
988
+ }
989
+
990
+ function installOpenClawHostWear(packageRoot, targetDir) {
873
991
  if (millSuit.isIsolatedHome()) return null;
874
992
  const source = resolveOpenClawPreToolHookSource(packageRoot);
875
993
  if (!source) return null;
@@ -878,6 +996,11 @@ function installOpenClawHostWear(packageRoot) {
878
996
  fs.mkdirSync(hookDir, { recursive: true });
879
997
  const dest = path.join(hookDir, "xray-pre-tool.mjs");
880
998
  fs.copyFileSync(source, dest);
999
+ const worn = resolveWornOpenClawPlugin(targetDir, packageRoot);
1000
+ const hookRoot =
1001
+ worn && isInstalledOpenClawPluginPath(worn) && targetDir
1002
+ ? path.join(path.resolve(targetDir), "node_modules", "0xray")
1003
+ : packageRoot;
881
1004
  fs.writeFileSync(
882
1005
  path.join(hookDir, "xray-pre-tool.json"),
883
1006
  `${JSON.stringify(
@@ -887,17 +1010,16 @@ function installOpenClawHostWear(packageRoot) {
887
1010
  args: [dest],
888
1011
  stdin: "json",
889
1012
  blockExitCode: 2,
890
- env: { XRAY_AI_PATH: packageRoot },
1013
+ env: { XRAY_AI_PATH: hookRoot },
891
1014
  },
892
1015
  null,
893
1016
  2,
894
1017
  )}\n`,
895
1018
  );
896
1019
 
897
- const pluginSrc = resolveOpenClawPluginDir(packageRoot);
898
- if (pluginSrc) {
1020
+ if (worn && isInstalledOpenClawPluginPath(worn)) {
899
1021
  try {
900
- execFileSync("openclaw", ["plugins", "install", "-l", pluginSrc], {
1022
+ execFileSync("openclaw", ["plugins", "install", "-l", path.resolve(worn)], {
901
1023
  stdio: "pipe",
902
1024
  encoding: "utf8",
903
1025
  timeout: 60000,
@@ -915,6 +1037,13 @@ function installOpenClawHostWear(packageRoot) {
915
1037
  /* enable is best-effort */
916
1038
  }
917
1039
  }
1040
+ const configPath = resolveOpenClawConfigPath();
1041
+ if (fs.existsSync(configPath)) {
1042
+ pinOpenClawPluginLoad(
1043
+ configPath,
1044
+ worn && isInstalledOpenClawPluginPath(worn) ? worn : null,
1045
+ );
1046
+ }
918
1047
  return dest;
919
1048
  }
920
1049
 
@@ -979,6 +1108,102 @@ function wireOpenClawBridge(targetDir) {
979
1108
  return syncOpenClawMcpRegistry(targetDir);
980
1109
  }
981
1110
 
1111
+ function tomlString(value) {
1112
+ return JSON.stringify(String(value));
1113
+ }
1114
+
1115
+ function suitServerNames() {
1116
+ return new Set(XRAY_MCP_SERVERS.map((server) => server.name));
1117
+ }
1118
+
1119
+ function isSuitMcpHeader(line, names) {
1120
+ const match = /^\[([^\]]+)\]\s*$/.exec(line.trim());
1121
+ if (!match) return false;
1122
+ const parts = match[1].split(".");
1123
+ return parts[0] === "mcp_servers" && names.has(parts[1]);
1124
+ }
1125
+
1126
+ function stripSuitMcpBlocks(toml, names) {
1127
+ const kept = [];
1128
+ let skip = false;
1129
+ for (const line of String(toml || "").split(/\r?\n/)) {
1130
+ if (/^\s*\[/.test(line)) skip = isSuitMcpHeader(line, names);
1131
+ if (!skip) kept.push(line);
1132
+ }
1133
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
1134
+ }
1135
+
1136
+ function suitMcpBlock(server, launch) {
1137
+ const args = launch.args.map((arg) => ` ${tomlString(arg)},`).join("\n");
1138
+ const lines = [
1139
+ `[mcp_servers.${server.name}]`,
1140
+ `command = ${tomlString(launch.command)}`,
1141
+ "args = [",
1142
+ args,
1143
+ "]",
1144
+ "enabled = true",
1145
+ ];
1146
+ const env = launch.env && typeof launch.env === "object" ? launch.env : {};
1147
+ const envKeys = Object.keys(env);
1148
+ if (envKeys.length > 0) {
1149
+ lines.push("", `[mcp_servers.${server.name}.env]`);
1150
+ for (const key of envKeys) lines.push(`${key} = ${tomlString(env[key])}`);
1151
+ }
1152
+ return lines.join("\n");
1153
+ }
1154
+
1155
+ /**
1156
+ * Project `.grok/config.toml` replaces a same-named user server.
1157
+ * The launch is the worn package, not `npx -y 0xray`.
1158
+ */
1159
+ function writeProjectSuitMcp(targetDir) {
1160
+ const root = path.resolve(targetDir);
1161
+ const names = suitServerNames();
1162
+ const blocks = XRAY_MCP_SERVERS.map((server) =>
1163
+ suitMcpBlock(server, scopedMcpLaunch(root, server.mcpCmd, server.env)),
1164
+ );
1165
+ const grokDir = path.join(root, ".grok");
1166
+ fs.mkdirSync(grokDir, { recursive: true });
1167
+ const tomlPath = path.join(grokDir, "config.toml");
1168
+ const existing = fs.existsSync(tomlPath) ? fs.readFileSync(tomlPath, "utf8") : "";
1169
+ const kept = stripSuitMcpBlocks(existing, names);
1170
+ const body = [kept, blocks.join("\n\n")].filter((part) => part && part.trim()).join("\n\n");
1171
+ fs.writeFileSync(tomlPath, `${body.trim()}\n`);
1172
+ return tomlPath;
1173
+ }
1174
+
1175
+ function grokConfigHome(env) {
1176
+ const source = env || process.env;
1177
+ const home = source.HOME || source.USERPROFILE;
1178
+ return home ? path.resolve(home) : os.homedir();
1179
+ }
1180
+
1181
+ /** Grok 1.0.50 has no --trust flag. An untrusted folder skips project MCP. */
1182
+ function trustGrokFolder(targetDir, env) {
1183
+ const home = grokConfigHome(env);
1184
+ const grokDir = path.join(home, ".grok");
1185
+ fs.mkdirSync(grokDir, { recursive: true });
1186
+ const file = path.join(grokDir, "trusted_folders.toml");
1187
+ let text = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
1188
+ const folders = [path.resolve(targetDir)];
1189
+ try {
1190
+ const real = fs.realpathSync(folders[0]);
1191
+ if (real !== folders[0]) folders.push(real);
1192
+ } catch {
1193
+ /* the path is the one Grok will see */
1194
+ }
1195
+ const now = Math.floor(Date.now() / 1000);
1196
+ let added = 0;
1197
+ for (const folder of folders) {
1198
+ const header = `[folders.${tomlString(folder)}]`;
1199
+ if (text.includes(header)) continue;
1200
+ text = `${text.replace(/\s*$/, "")}\n${header}\ntrusted = true\ndecided_at = ${now}\n`;
1201
+ added += 1;
1202
+ }
1203
+ if (added > 0) fs.writeFileSync(file, text.endsWith("\n") ? text : `${text}\n`);
1204
+ return file;
1205
+ }
1206
+
982
1207
  module.exports = {
983
1208
  XRAY_MCP_SERVERS,
984
1209
  HERMES_CONFIG_PATH,
@@ -1025,5 +1250,11 @@ module.exports = {
1025
1250
  resolveOpenClawPreToolHookSource,
1026
1251
  resolveOpenClawPluginDir,
1027
1252
  installOpenClawHostWear,
1253
+ settleOpenClawPluginPaths,
1254
+ pinOpenClawPluginLoad,
1255
+ recordOpenClawProjectPlugin,
1256
+ resolveWornOpenClawPlugin,
1028
1257
  maybeWriteOpenClawCliBackend,
1258
+ writeProjectSuitMcp,
1259
+ trustGrokFolder,
1029
1260
  };
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Turn confer on or off in the worn .xray/features.json.
4
+ * A missing key stays off until this runs. Synthesis stays untouched.
5
+ */
6
+ "use strict";
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+
11
+ function fail(message) {
12
+ process.stderr.write(`${message}\n`);
13
+ process.exit(1);
14
+ }
15
+
16
+ function orchestrationBlock(data) {
17
+ const current = data.multi_agent_orchestration;
18
+ if (current && typeof current === "object" && !Array.isArray(current)) return current;
19
+ const created = {};
20
+ data.multi_agent_orchestration = created;
21
+ return created;
22
+ }
23
+
24
+ function conferBlock(orch) {
25
+ const current = orch.confer;
26
+ if (current && typeof current === "object" && !Array.isArray(current)) return current;
27
+ const created = {};
28
+ orch.confer = created;
29
+ return created;
30
+ }
31
+
32
+ function main() {
33
+ const state = process.argv[2];
34
+ if (state !== "on" && state !== "off") fail("confer: say on or off");
35
+
36
+ const featuresPath = path.join(process.cwd(), ".xray", "features.json");
37
+ if (!fs.existsSync(featuresPath)) {
38
+ fail("confer: .xray/features.json is missing. Run npx 0xray wear first.");
39
+ }
40
+
41
+ let data;
42
+ try {
43
+ data = JSON.parse(fs.readFileSync(featuresPath, "utf8"));
44
+ } catch {
45
+ fail("confer: .xray/features.json is not valid JSON");
46
+ }
47
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
48
+ fail("confer: .xray/features.json is not valid JSON");
49
+ }
50
+
51
+ const orch = orchestrationBlock(data);
52
+ const confer = conferBlock(orch);
53
+ if (state === "on") {
54
+ confer.enabled = true;
55
+ if (confer.on_synthesis !== false) confer.on_synthesis = true;
56
+ } else {
57
+ confer.enabled = false;
58
+ confer.on_synthesis = false;
59
+ orch.confer_on_synthesis = false;
60
+ }
61
+
62
+ fs.writeFileSync(featuresPath, `${JSON.stringify(data, null, 2)}\n`);
63
+ process.stdout.write(`confer: ${state}\n`);
64
+ }
65
+
66
+ if (require.main === module) main();