0xray 4.0.41 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/orchestrator/SKILL.md +12 -0
  3. package/.plugin/plugin.json +1 -1
  4. package/.xray/features.json +5 -5
  5. package/.xray/features.schema.json +1 -1
  6. package/AGENTS-consumer.md +10 -6
  7. package/AGENTS.md +19 -8
  8. package/LICENSE +1 -1
  9. package/README.md +65 -19
  10. package/SKILLS.md +5 -1
  11. package/dist/AGENTS.md +19 -8
  12. package/dist/CHANGELOG.md +85 -0
  13. package/dist/LICENSE +1 -1
  14. package/dist/README.md +65 -19
  15. package/dist/SKILLS.md +5 -1
  16. package/dist/cli/commands/openclaw-install.js +1 -1
  17. package/dist/cli/commands/plugin-commands.d.ts +4 -0
  18. package/dist/cli/commands/plugin-commands.js +84 -41
  19. package/dist/cli/index.js +12 -0
  20. package/dist/core/config-loader.js +4 -4
  21. package/dist/core/features-config.d.ts +1 -1
  22. package/dist/core/features-config.js +3 -3
  23. package/dist/enforcement/index.d.ts +4 -0
  24. package/dist/enforcement/index.js +4 -0
  25. package/dist/enforcement/validators/code-quality-validators.js +6 -4
  26. package/dist/governance/llm-governance-provider.d.ts +7 -1
  27. package/dist/governance/llm-governance-provider.js +72 -14
  28. package/dist/governance/local-confer.d.ts +4 -1
  29. package/dist/governance/local-confer.js +22 -1
  30. package/dist/integrations/cursor/hooks/hooks.json +6 -3
  31. package/dist/integrations/cursor/hooks/pre-tool-use.js +100 -37
  32. package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +9 -1
  33. package/dist/integrations/grok/grok-cli.js +10 -37
  34. package/dist/integrations/grok/hooks/grok-hook-utils.js +8 -1
  35. package/dist/integrations/grok/hooks/pre-tool-use.js +2 -58
  36. package/dist/integrations/hermes-agent/bridge.mjs +14 -1
  37. package/dist/integrations/hooks/confer-hook-runtime.mjs +23 -1
  38. package/dist/integrations/hooks/delegation-gate-runtime.mjs +3 -2
  39. package/dist/integrations/hooks/goggles-mcp.mjs +35 -17
  40. package/dist/integrations/hooks/goggles-pipeline.mjs +46 -1
  41. package/dist/integrations/hooks/goggles-planes.json +11 -7
  42. package/dist/integrations/hooks/pre-tool-speak.mjs +76 -0
  43. package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +21 -4
  44. package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +17 -2
  45. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  46. package/dist/mcps/architect-tools.server.d.ts +6 -0
  47. package/dist/mcps/architect-tools.server.js +19 -2
  48. package/dist/mcps/config/server-config-registry.js +8 -6
  49. package/dist/mcps/governance.server.d.ts +1 -0
  50. package/dist/mcps/governance.server.js +46 -0
  51. package/dist/mcps/knowledge-skills/code-review.server.js +7 -0
  52. package/dist/mcps/knowledge-skills/security-audit.server.js +5 -3
  53. package/dist/mcps/mcp-client.js +1 -1
  54. package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
  55. package/dist/mcps/researcher.server.js +12 -3
  56. package/dist/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
  57. package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
  58. package/dist/nucleus/autonomy-kernel.d.ts +2 -0
  59. package/dist/nucleus/autonomy-kernel.js +17 -13
  60. package/dist/nucleus/confer-host.d.ts +35 -0
  61. package/dist/nucleus/confer-host.js +55 -0
  62. package/dist/nucleus/confer.d.ts +19 -3
  63. package/dist/nucleus/confer.js +111 -55
  64. package/dist/nucleus/lead-dev-plan-persistence.js +4 -2
  65. package/dist/nucleus/suit-temperament.d.ts +1 -1
  66. package/dist/nucleus/suit-temperament.js +2 -2
  67. package/dist/nucleus/synthesis-consult-receipt.d.ts +7 -1
  68. package/dist/nucleus/synthesis-consult-receipt.js +86 -35
  69. package/dist/plugin/xray-codex-injection.js +18 -0
  70. package/dist/processors/implementations/nudge-processor.d.ts +1 -1
  71. package/dist/processors/implementations/nudge-processor.js +4 -4
  72. package/dist/reporting/framework-reporting-system.js +2 -5
  73. package/dist/reporting/log-parser.d.ts +2 -0
  74. package/dist/reporting/log-parser.js +7 -6
  75. package/dist/skills/orchestrator/SKILL.md +12 -0
  76. package/docs-site/docs/plates/activity-log.md +1 -1
  77. package/docs-site/docs/plates/bifrost.md +48 -0
  78. package/docs-site/docs/plates/boot.md +2 -2
  79. package/docs-site/docs/plates/centrifuge.md +1 -1
  80. package/docs-site/docs/plates/cursor-compact.md +1 -1
  81. package/docs-site/docs/plates/glossary.md +7 -3
  82. package/docs-site/docs/plates/goggles.md +13 -2
  83. package/docs-site/docs/plates/governance.md +1 -1
  84. package/docs-site/docs/plates/grok-compact.md +1 -1
  85. package/docs-site/docs/plates/grokbot.md +3 -1
  86. package/docs-site/docs/plates/host-pack.md +1 -1
  87. package/docs-site/docs/plates/house.md +1 -1
  88. package/docs-site/docs/plates/index.md +4 -2
  89. package/docs-site/docs/plates/inference-files.md +1 -1
  90. package/docs-site/docs/plates/kept-line.md +1 -1
  91. package/docs-site/docs/plates/kits.md +1 -1
  92. package/docs-site/docs/plates/lens-gate.md +3 -1
  93. package/docs-site/docs/plates/lens-page.md +1 -1
  94. package/docs-site/docs/plates/memory-recall.md +1 -1
  95. package/docs-site/docs/plates/notes-page.md +2 -2
  96. package/docs-site/docs/plates/orchestration.md +1 -1
  97. package/docs-site/docs/plates/package-face.md +1 -1
  98. package/docs-site/docs/plates/payload-heat.md +1 -1
  99. package/docs-site/docs/plates/pickup-stamp.md +1 -1
  100. package/docs-site/docs/plates/pre-tool.md +1 -1
  101. package/docs-site/docs/plates/processor.md +1 -1
  102. package/docs-site/docs/plates/record-map.md +1 -1
  103. package/docs-site/docs/plates/reflection-page.md +1 -1
  104. package/docs-site/docs/plates/reporting.md +1 -1
  105. package/docs-site/docs/plates/review.md +1 -1
  106. package/docs-site/docs/plates/routing.md +1 -1
  107. package/docs-site/docs/plates/session-capture.md +1 -1
  108. package/docs-site/docs/plates/site-manual.md +1 -1
  109. package/docs-site/docs/plates/stamp-plate.md +1 -1
  110. package/docs-site/docs/plates/station-card.md +1 -1
  111. package/docs-site/docs/plates/station-heat.md +1 -1
  112. package/docs-site/docs/plates/suit-organs.md +1 -1
  113. package/docs-site/docs/plates/suit-settings.md +1 -1
  114. package/docs-site/docs/plates/suit-wear.md +1 -1
  115. package/docs-site/docs/plates/suit.md +3 -3
  116. package/docs-site/docs/plates/trail-state.md +1 -1
  117. package/docs-site/docs/plates/work-fresh.md +1 -1
  118. package/docs-site/docs/plates/write-home.md +1 -1
  119. package/llms.txt +16 -6
  120. package/package.json +4 -4
  121. package/scripts/foundry/README.md +2 -2
  122. package/scripts/foundry/release-gate.mjs +111 -15
  123. package/scripts/foundry/release.mjs +8 -0
  124. package/scripts/mjs/verify-confer-core.mjs +153 -29
  125. package/scripts/mjs/verify-pipeline-facets.mjs +1 -1
  126. package/scripts/mjs/verify-user-aside-core.mjs +3 -4
  127. package/scripts/node/bridge-mcp-wiring.cjs +236 -5
  128. package/scripts/node/confer-switch.cjs +66 -0
  129. package/scripts/node/install-bridges.cjs +44 -53
  130. package/scripts/node/pack-tmp-suit-proof.mjs +14 -6
  131. package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +17 -2
  132. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  133. package/src/mcps/architect-tools.server.ts +26 -2
  134. package/src/mcps/config/__tests__/server-config-registry.test.ts +15 -6
  135. package/src/mcps/config/server-config-registry.ts +9 -6
  136. package/src/mcps/governance.server.ts +49 -0
  137. package/src/mcps/knowledge-skills/code-review.server.ts +8 -0
  138. package/src/mcps/knowledge-skills/security-audit.server.ts +11 -12
  139. package/src/mcps/mcp-client.ts +1 -1
  140. package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
  141. package/src/mcps/researcher.server.ts +19 -10
  142. package/src/skills/orchestrator/SKILL.md +12 -0
  143. package/vendor/@0xray/repertoire/README.md +1 -1
  144. package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +5 -51
  145. package/xray/agents_template.md +2 -2
  146. package/xray/features.json +5 -5
  147. package/xray/features.schema.json +1 -1
  148. package/docs-site/docs/plates/live-mesh.md +0 -41
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'node:url';
18
18
  const FIELD_ORDER = ['plate', 'entry', 'exit', 'files', 'skills', 'setup', 'teardown', 'worn'];
19
19
  const HERE = dirname(fileURLToPath(import.meta.url));
20
20
  const WORN_PLANES = ['dichotomy', 'syncopate', 'synthesis', 'digest', 'triage', 'loop'];
21
- const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory-recall', 'orchestration', 'processor', 'reporting', 'stamp-plate', 'record-map', 'write-home', 'activity-log', 'session-capture', 'suit-wear', 'suit-organs', 'station-card', 'notes-page', 'reflection-page', 'site-manual', 'package-face', 'suit-settings', 'trail-state', 'inference-files', 'grok-compact', 'payload-heat', 'station-heat', 'pickup-stamp', 'cursor-compact', 'work-fresh', 'kept-line', 'lens-gate', 'pre-tool', 'lens-page', 'centrifuge'];
21
+ export const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory-recall', 'orchestration', 'processor', 'reporting', 'stamp-plate', 'record-map', 'write-home', 'activity-log', 'session-capture', 'suit-wear', 'suit-organs', 'station-card', 'notes-page', 'reflection-page', 'site-manual', 'package-face', 'suit-settings', 'trail-state', 'inference-files', 'grok-compact', 'payload-heat', 'station-heat', 'pickup-stamp', 'cursor-compact', 'work-fresh', 'kept-line', 'lens-gate', 'pre-tool', 'lens-page', 'centrifuge'];
22
22
  const CARD_FLAVORS = ['digest', 'triage'];
23
23
  const SCOPE_ZOOM = ['ecosystem', 'part', 'one flow', 'one artifact'];
24
24
 
@@ -1065,6 +1065,51 @@ export function growPlane(id, platesDir, root) {
1065
1065
  return { ...base, ...grown };
1066
1066
  }
1067
1067
 
1068
+ /** Paths the package ships: package.json files, plus what npm always packs. */
1069
+ function shippedSet(root) {
1070
+ try {
1071
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
1072
+ if (!Array.isArray(pkg.files)) return null;
1073
+ return pkg.files
1074
+ .map(String)
1075
+ .filter((entry) => entry && !entry.startsWith('!'))
1076
+ .map((entry) => entry.replace(/\/+$/, ''))
1077
+ .concat(['package.json', 'README.md', 'LICENSE']);
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
1082
+
1083
+ function ships(shipped, rel) {
1084
+ if (!shipped) return true;
1085
+ const path = String(rel || '').replace(/\/+$/, '');
1086
+ if (!path) return false;
1087
+ return shipped.some((entry) => path === entry || path.startsWith(`${entry}/`) || entry.startsWith(`${path}/`));
1088
+ }
1089
+
1090
+ /**
1091
+ * Kind 0 card sweep: triage's file check on every card plane, limited to
1092
+ * paths the package ships, so a packed install and the repo read the same.
1093
+ * Returns the first drift line, or '' when every card holds.
1094
+ */
1095
+ export function shippedDrift(projectRoot) {
1096
+ const platesDir = findPlatesDir(HERE);
1097
+ const root = sourceRoot(platesDir);
1098
+ const shipped = shippedSet(root);
1099
+ for (const id of CARD_PLANES) {
1100
+ const plane = growPlane(id, platesDir, projectRoot || stateRoot());
1101
+ const kept = {
1102
+ ...plane,
1103
+ files: (Array.isArray(plane.files) ? plane.files : []).filter((rel) => ships(shipped, rel)),
1104
+ skills: ships(shipped, plane.skills) ? plane.skills : '',
1105
+ worn: ships(shipped, plane.worn) ? plane.worn : '',
1106
+ };
1107
+ const exam = examinePlane(kept, root);
1108
+ if (exam.kind === 'drift') return `Actuality. ${id}: ${exam.text}`;
1109
+ }
1110
+ return '';
1111
+ }
1112
+
1068
1113
  function repoRootFrom(platesDir) {
1069
1114
  return dirname(dirname(dirname(platesDir)));
1070
1115
  }
@@ -7,6 +7,7 @@
7
7
  "files": [
8
8
  "src",
9
9
  "grok-bot/OP-PROC.md",
10
+ "grok-bot/LEXICON.md",
10
11
  "src/opencode/agents",
11
12
  "AGENTS.md",
12
13
  "src/integrations",
@@ -28,7 +29,7 @@
28
29
  "plates": ["orchestration", "processor"]
29
30
  },
30
31
  "house": {
31
- "files": ["grok-bot/lib/seat-doctor.cjs"],
32
+ "files": ["grok-bot/lib/seat-doctor.cjs", "grok-bot/templates/house/fleet/WAKE-ROUTINE.md", "grok-bot/templates/house/HOUSE.md", "src/__tests__/unit/grok-bot-house-template.test.ts", "src/__tests__/unit/grok-bot-house-migrate.test.ts"],
32
33
  "skills": ".agents/skills/house/SKILL.md",
33
34
  "plates": ["suit-wear"]
34
35
  },
@@ -38,11 +39,14 @@
38
39
  "plates": ["suit-wear"]
39
40
  },
40
41
  "governance": {
41
- "files": ["src/governance/governance-service.ts"],
42
+ "files": [
43
+ "src/governance/governance-service.ts",
44
+ "src/governance/llm-governance-provider.ts"
45
+ ],
42
46
  "skills": ".agents/skills/governance/SKILL.md"
43
47
  },
44
48
  "memory-recall": {
45
- "files": ["src/integrations/hooks/plates.cjs"],
49
+ "files": ["src/integrations/hooks/plates.cjs", "src/__tests__/unit/plates.test.ts"],
46
50
  "skills": ".agents/skills/memory-recall/SKILL.md",
47
51
  "plates": ["record-map"]
48
52
  },
@@ -91,7 +95,7 @@
91
95
  "plates": ["write-home", "inference-files", "suit-organs"]
92
96
  },
93
97
  "suit-wear": {
94
- "files": ["src/core/features-config.ts"],
98
+ "files": ["src/core/features-config.ts", "scripts/node/install-bridges.cjs", "scripts/node/setup.cjs", "src/integrations/cursor/hooks/xray-cloud-hook.sh"],
95
99
  "mark": "log_path",
96
100
  "skills": ".agents/skills/suit-wear/SKILL.md",
97
101
  "plates": ["suit-settings", "write-home", "package-face"]
@@ -121,13 +125,13 @@
121
125
  "plates": ["record-map"]
122
126
  },
123
127
  "site-manual": {
124
- "files": ["docs-site/docs/introduction.md"],
128
+ "files": ["docs-site/docs/introduction.md", "docs-site/docs/index.md", "docs-site/docs/full-reference.md", "docs-site/docs/guides/features-since-3.1.md"],
125
129
  "mark": "survives the context window",
126
130
  "skills": ".agents/skills/site-manual/SKILL.md",
127
131
  "plates": ["record-map"]
128
132
  },
129
133
  "package-face": {
130
- "files": ["package.json"],
134
+ "files": ["package.json", "grok-bot/README.md", "grok-bot/llms.txt", "grok-bot/CHANGELOG.md", "src/cli/index.ts", "src/cli/commands/plugin-commands.ts", "src/__tests__/unit/plugin-commands.test.ts"],
131
135
  "mark": "\"name\": \"0xray\"",
132
136
  "skills": ".agents/skills/package-face/SKILL.md",
133
137
  "plates": ["record-map", "suit-wear"]
@@ -194,7 +198,7 @@
194
198
  "plates": ["station-card", "work-fresh"]
195
199
  },
196
200
  "lens-gate": {
197
- "files": ["src/integrations/hooks/goggles-pipeline.mjs"],
201
+ "files": ["src/integrations/hooks/goggles-pipeline.mjs", "src/__tests__/unit/goggles-pipeline.test.ts"],
198
202
  "mark": "export function lensBeforeResearch",
199
203
  "skills": ".agents/skills/lens-gate/SKILL.md",
200
204
  "plates": ["pre-tool"]
@@ -0,0 +1,76 @@
1
+ /**
2
+ * One pre-tool speak for every host.
3
+ * The lens denies an unnamed plane. The route speaks only when Repertoire changes the agent.
4
+ */
5
+ import { createRequire } from 'node:module';
6
+ import { existsSync } from 'node:fs';
7
+ import { dirname, join } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { lensBeforeResearch } from './goggles-pipeline.mjs';
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ let scoreAndRouteFn;
13
+
14
+ function loadScoreAndRoute() {
15
+ if (scoreAndRouteFn !== undefined) return scoreAndRouteFn;
16
+ scoreAndRouteFn = null;
17
+ const require = createRequire(import.meta.url);
18
+ const candidates = [
19
+ join(here, '../../nucleus/thin-dispatch.js'),
20
+ join(here, '../../../dist/nucleus/thin-dispatch.js'),
21
+ join(here, '../../../nucleus/thin-dispatch.js'),
22
+ ];
23
+ for (const candidate of candidates) {
24
+ if (!existsSync(candidate)) continue;
25
+ try {
26
+ const loaded = require(candidate);
27
+ if (typeof loaded.scoreAndRoute === 'function') {
28
+ scoreAndRouteFn = loaded.scoreAndRoute;
29
+ return scoreAndRouteFn;
30
+ }
31
+ } catch {
32
+ /* next built copy */
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /** Speak only when the organ changes the agent. A match that leaves the agent is quiet. */
39
+ export function repertoireDecisionFields(routed) {
40
+ if (!routed?.memoryRouting?.overridden || !routed.agent) return null;
41
+ const signals = Array.isArray(routed.memoryRouting.signals)
42
+ ? routed.memoryRouting.signals.slice(0, 4).filter(Boolean)
43
+ : [];
44
+ const why = signals.length ? ` on ${signals.join(', ')}` : '';
45
+ return {
46
+ agent: routed.agent,
47
+ gate: 'repertoire',
48
+ hookSpecificOutput: {
49
+ hookEventName: 'PreToolUse',
50
+ additionalContext: `Repertoire changed the route to ${routed.agent}${why}. Follow that agent for this work.`,
51
+ },
52
+ };
53
+ }
54
+
55
+ export function repertoireRoute(operation) {
56
+ const text = String(operation || '').slice(0, 500).trim();
57
+ if (!text) return null;
58
+ try {
59
+ const scoreAndRoute = loadScoreAndRoute();
60
+ if (!scoreAndRoute) return null;
61
+ return repertoireDecisionFields(scoreAndRoute(text, {}));
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /** Deny when the lens stops the tool. Any other lens result is quiet. */
68
+ export function lensDecision(root, toolName, spoken) {
69
+ try {
70
+ const stop = lensBeforeResearch(root, toolName, spoken);
71
+ if (stop?.decision === 'deny') return stop;
72
+ return null;
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
@@ -23,7 +23,7 @@ function resolveGate() {
23
23
  ].filter(Boolean);
24
24
  const found = candidates.find((p) => existsSync(p));
25
25
  if (!found) {
26
- throw new Error('delegation-gate.js missing — build 0xray or npm install 0xray');
26
+ throw Object.assign(new Error('delegation-gate.js missing — build 0xray or npm install 0xray'), { code: 'ERR_MODULE_NOT_FOUND' });
27
27
  }
28
28
  return require(found);
29
29
  }
@@ -49,7 +49,7 @@ function readStdin() {
49
49
  });
50
50
  }
51
51
 
52
- function decide(event, gate) {
52
+ async function decide(event, gate) {
53
53
  const projectRoot =
54
54
  event.cwd || event.workspaceRoot || event.projectRoot || process.cwd();
55
55
  const sessionId = event.sessionId || event.session_id || 'openclaw';
@@ -70,20 +70,37 @@ function decide(event, gate) {
70
70
  gate: outcome.gate,
71
71
  };
72
72
  }
73
- return {
73
+
74
+ const spoken = `${toolName} ${JSON.stringify(args)}`.slice(0, 500);
75
+ const { lensDecision, repertoireRoute } = await import('../../hooks/pre-tool-speak.mjs');
76
+ const lens = lensDecision(projectRoot, toolName, spoken);
77
+ if (lens) {
78
+ return {
79
+ action: 'block',
80
+ allow: false,
81
+ reason: lens.reason,
82
+ gate: lens.gate || 'lens',
83
+ };
84
+ }
85
+ const route = repertoireRoute(spoken);
86
+ const allowed = {
74
87
  action: 'allow',
75
88
  allow: true,
76
89
  reason: outcome.reason,
77
90
  gate: outcome.gate,
78
91
  warn: Boolean(outcome.reason),
79
92
  };
93
+ if (route?.hookSpecificOutput) {
94
+ allowed.hookSpecificOutput = route.hookSpecificOutput;
95
+ }
96
+ return allowed;
80
97
  }
81
98
 
82
99
  async function main() {
83
100
  try {
84
101
  const event = await readStdin();
85
102
  const gate = resolveGate();
86
- const decision = decide(event, gate);
103
+ const decision = await decide(event, gate);
87
104
  console.log(JSON.stringify(decision));
88
105
  process.exit(decision.action === 'block' ? 2 : 0);
89
106
  } catch (err) {
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { dirname, join } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
7
7
 
8
8
  const require = createRequire(import.meta.url);
@@ -39,7 +39,7 @@ export default definePluginEntry({
39
39
  register(api) {
40
40
  api.on(
41
41
  'before_tool_call',
42
- (event, ctx) => {
42
+ async (event, ctx) => {
43
43
  try {
44
44
  const xrayRoot = resolveXrayRoot();
45
45
  if (!xrayRoot) {
@@ -61,6 +61,21 @@ export default definePluginEntry({
61
61
  if (!outcome.allow) {
62
62
  return { block: true, blockReason: outcome.reason || '0xRay constitution' };
63
63
  }
64
+ const spoken = `${event.toolName} ${JSON.stringify(event.params || {})}`.slice(0, 500);
65
+ const speakPath = [
66
+ join(xrayRoot, 'dist/integrations/hooks/pre-tool-speak.mjs'),
67
+ join(xrayRoot, 'src/integrations/hooks/pre-tool-speak.mjs'),
68
+ ].find((candidate) => existsSync(candidate));
69
+ if (!speakPath) return undefined;
70
+ const speak = await import(pathToFileURL(speakPath).href);
71
+ const lens = speak.lensDecision(projectRoot, event.toolName, spoken);
72
+ if (lens) {
73
+ return { block: true, blockReason: lens.reason };
74
+ }
75
+ const route = speak.repertoireRoute(spoken);
76
+ if (route?.hookSpecificOutput) {
77
+ return { block: false, hookSpecificOutput: route.hookSpecificOutput };
78
+ }
64
79
  return undefined;
65
80
  } catch (err) {
66
81
  return {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.41",
3
+ "version": "4.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -5,6 +5,12 @@
5
5
  * Provides contextual analysis capabilities via MCP protocol
6
6
  */
7
7
  import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
8
+ import { type GovernanceVote } from "../governance/llm-governance-provider.js";
9
+ import { type ArchitectureAssessment } from "../architect/architect-tools.js";
10
+ export type ConferModelVerdict = "PASS" | "CONDITIONAL" | "FAIL" | "UNREVIEWED";
11
+ /** A confer verdict counts only when a model vote exists. Metrics alone are UNREVIEWED. */
12
+ export declare function conferVerdictFromModelVote(vote: Pick<GovernanceVote, "decision"> | null): ConferModelVerdict;
13
+ export declare function formatArchitectureAssessmentConferText(conferPrompt: string, assessment: ArchitectureAssessment, verdict?: ConferModelVerdict): string;
8
14
  export declare function renderArchitectureAssessment(args: Record<string, unknown> | undefined): Promise<{
9
15
  content: Array<{
10
16
  type: "text";
@@ -8,7 +8,24 @@ import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
8
  import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
9
9
  import * as fs from "fs";
10
10
  import { frameworkLogger } from "../core/framework-logger.js";
11
- import { contextAnalysis as architectContextAnalysis, codebaseStructure as architectCodebaseStructure, dependencyAnalysis as architectDependencyAnalysis, architectureAssessment as architectArchitectureAssessment, formatConferArchitectureText, } from "../architect/architect-tools.js";
11
+ import { contextAnalysis as architectContextAnalysis, codebaseStructure as architectCodebaseStructure, dependencyAnalysis as architectDependencyAnalysis, architectureAssessment as architectArchitectureAssessment, } from "../architect/architect-tools.js";
12
+ /** A confer verdict counts only when a model vote exists. Metrics alone are UNREVIEWED. */
13
+ export function conferVerdictFromModelVote(vote) {
14
+ if (!vote)
15
+ return "UNREVIEWED";
16
+ if (vote.decision === "reject")
17
+ return "FAIL";
18
+ if (vote.decision === "approve")
19
+ return "PASS";
20
+ return "CONDITIONAL";
21
+ }
22
+ export function formatArchitectureAssessmentConferText(conferPrompt, assessment, verdict = "UNREVIEWED") {
23
+ const assessmentJson = JSON.stringify(assessment, null, 2);
24
+ const trimmed = conferPrompt.trim();
25
+ if (!trimmed)
26
+ return assessmentJson;
27
+ return `${trimmed}\n\n## Architecture assessment\n${assessmentJson}\n\nVerdict: ${verdict}\nTop risks: review assessment metrics above\nHardening: address high-complexity or coupling findings before resuming`;
28
+ }
12
29
  export async function renderArchitectureAssessment(args) {
13
30
  const projectRoot = typeof args?.projectRoot === "string" ? args.projectRoot : "";
14
31
  const assessmentType = args?.assessmentType === "quick" || args?.assessmentType === "comprehensive"
@@ -21,7 +38,7 @@ export async function renderArchitectureAssessment(args) {
21
38
  content: [
22
39
  {
23
40
  type: "text",
24
- text: formatConferArchitectureText(conferPrompt, result),
41
+ text: formatArchitectureAssessmentConferText(conferPrompt, result, "UNREVIEWED"),
25
42
  },
26
43
  ],
27
44
  };
@@ -10,6 +10,8 @@ import { frameworkLogger } from '../../core/framework-logger.js';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { dirname, join } from 'path';
12
12
  import fs from 'fs';
13
+ /** Must outlast the 120s governance model call so a real approve can return. */
14
+ const GOVERNANCE_MODEL_TIMEOUT_MS = 150_000;
13
15
  /**
14
16
  * Registry for managing MCP server configurations
15
17
  */
@@ -85,14 +87,14 @@ export class ServerConfigRegistry {
85
87
  serverName: 'code-review',
86
88
  command: 'node',
87
89
  args: [`${basePath}/knowledge-skills/code-review.server.js`],
88
- timeout: 30000,
90
+ timeout: GOVERNANCE_MODEL_TIMEOUT_MS,
89
91
  });
90
92
  // Security Audit Server
91
93
  this.register({
92
94
  serverName: 'security-audit',
93
95
  command: 'node',
94
96
  args: [`${basePath}/knowledge-skills/security-audit.server.js`],
95
- timeout: 45000,
97
+ timeout: GOVERNANCE_MODEL_TIMEOUT_MS,
96
98
  });
97
99
  // Performance Optimization Server
98
100
  this.register({
@@ -113,7 +115,7 @@ export class ServerConfigRegistry {
113
115
  serverName: 'researcher',
114
116
  command: 'node',
115
117
  args: [`${basePath}/researcher.server.js`],
116
- timeout: 60000,
118
+ timeout: GOVERNANCE_MODEL_TIMEOUT_MS,
117
119
  });
118
120
  // Governance Service (meta-MCP that orchestrates the three skill servers + required external Dynamo)
119
121
  this.register({
@@ -176,7 +178,7 @@ export class ServerConfigRegistry {
176
178
  serverName: 'architect',
177
179
  command: 'node',
178
180
  args: [`${basePath}/architect-tools.server.js`],
179
- timeout: 45000,
181
+ timeout: GOVERNANCE_MODEL_TIMEOUT_MS,
180
182
  });
181
183
  // Bug Triage Specialist Server
182
184
  this.register({
@@ -198,14 +200,14 @@ export class ServerConfigRegistry {
198
200
  serverName: 'code-reviewer',
199
201
  command: 'node',
200
202
  args: [`${basePath}/knowledge-skills/code-review.server.js`],
201
- timeout: 30000,
203
+ timeout: GOVERNANCE_MODEL_TIMEOUT_MS,
202
204
  });
203
205
  // Security Auditor (alias for security-audit)
204
206
  this.register({
205
207
  serverName: 'security-auditor',
206
208
  command: 'node',
207
209
  args: [`${basePath}/knowledge-skills/security-audit.server.js`],
208
- timeout: 45000,
210
+ timeout: GOVERNANCE_MODEL_TIMEOUT_MS,
209
211
  });
210
212
  // Refactorer Server
211
213
  this.register({
@@ -24,6 +24,7 @@ declare class GovernanceServer extends XrayKnowledgeSkillBase {
24
24
  private armRequiredDynamo;
25
25
  private handleGovernProposals;
26
26
  private handleGovernReflection;
27
+ private handleRecordConferReceipt;
27
28
  private handleGetActiveCodex;
28
29
  private parseCodexTermsFromReflection;
29
30
  private initializeGovernance;
@@ -20,6 +20,7 @@ import { featuresConfigLoader } from "../core/features-config.js";
20
20
  import * as fs from "fs";
21
21
  import { fileURLToPath } from "url";
22
22
  import { randomUUID } from "crypto";
23
+ import { applyConferConsultResult } from "../nucleus/confer.js";
23
24
  import { getGovernanceService } from "../governance/governance-service.js";
24
25
  import { getCodexPolicyService } from "../governance/codex-policy.service.js";
25
26
  import { initializeGovernanceIntegration } from "../integrations/governance/index.js";
@@ -95,6 +96,23 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
95
96
  required: [],
96
97
  },
97
98
  },
99
+ {
100
+ name: "record_confer_receipt",
101
+ description: "Record a confer verdict written by the wearing TUI. " +
102
+ "The TUI already called the seat's MCP server. This tool stores the receipt. " +
103
+ "Only Verdict: PASS completes the consult todo.",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ todoId: { type: "string", description: "Consult todo id, such as s.1" },
108
+ subagent: { type: "string", description: "researcher, architect-tools, or code-review" },
109
+ sessionId: { type: "string", description: "Plan session id" },
110
+ outputText: { type: "string", description: "Host verdict text, including a Verdict line" },
111
+ projectRoot: { type: "string", description: "Checkout root. Defaults to XRAY_ROOT or the process cwd." },
112
+ },
113
+ required: ["todoId", "subagent", "sessionId", "outputText"],
114
+ },
115
+ },
98
116
  {
99
117
  name: "get_active_codex",
100
118
  description: "Get the currently active Codex (SSOT) — returns metadata about the loaded codex terms, " +
@@ -117,6 +135,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
117
135
  "govern_proposals": async (args) => this.handleGovernProposals(this.validateGovernProposalsArgs(args)),
118
136
  "govern_reflection": async (args) => this.handleGovernReflection(this.validateGovernReflectionArgs(args)),
119
137
  "get_active_codex": async (args) => this.handleGetActiveCodex(args),
138
+ "record_confer_receipt": async (args) => this.handleRecordConferReceipt(args),
120
139
  };
121
140
  this.setupToolHandlers();
122
141
  }
@@ -163,6 +182,8 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
163
182
  return await this.handleGovernReflection(this.validateGovernReflectionArgs(args));
164
183
  case "get_active_codex":
165
184
  return await this.handleGetActiveCodex(args);
185
+ case "record_confer_receipt":
186
+ return await this.handleRecordConferReceipt(args);
166
187
  default:
167
188
  throw new Error(`Unknown tool: ${name}`);
168
189
  }
@@ -272,6 +293,31 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
272
293
  options: { require_external: true },
273
294
  });
274
295
  }
296
+ async handleRecordConferReceipt(args) {
297
+ if (!args || typeof args !== "object") {
298
+ throw new Error("record_confer_receipt requires an object argument");
299
+ }
300
+ const obj = args;
301
+ const todoId = typeof obj.todoId === "string" ? obj.todoId.trim() : "";
302
+ const subagent = typeof obj.subagent === "string" ? obj.subagent.trim() : "";
303
+ const sessionId = typeof obj.sessionId === "string" ? obj.sessionId.trim() : "";
304
+ const outputText = typeof obj.outputText === "string" ? obj.outputText : "";
305
+ if (!todoId || !subagent || !sessionId || !outputText.trim()) {
306
+ throw new Error("record_confer_receipt requires todoId, subagent, sessionId, and outputText");
307
+ }
308
+ const projectRoot = typeof obj.projectRoot === "string" && obj.projectRoot.trim()
309
+ ? obj.projectRoot
310
+ : process.env.XRAY_ROOT || process.cwd();
311
+ const result = applyConferConsultResult(todoId, subagent, sessionId, outputText, projectRoot);
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: JSON.stringify(result),
317
+ },
318
+ ],
319
+ };
320
+ }
275
321
  async handleGetActiveCodex(args) {
276
322
  const includeRaw = args?.includeRaw === true;
277
323
  const snapshot = await getCodexPolicyService().getCurrentCodex(includeRaw);
@@ -9,6 +9,7 @@ import * as fs from "fs";
9
9
  import * as path from "path";
10
10
  import { isGovernanceLlmConfigured, tryLLMGovernance, } from "../../governance/llm-governance-provider.js";
11
11
  import { formatGovernanceVoteText, localConferVote } from "../../governance/local-confer.js";
12
+ import { buildHostConferEvidence } from "../../nucleus/confer-host.js";
12
13
  class XrayCodeReviewServer extends XrayKnowledgeSkillBase {
13
14
  constructor() {
14
15
  super("code-review", "3.1.0");
@@ -316,6 +317,12 @@ class XrayCodeReviewServer extends XrayKnowledgeSkillBase {
316
317
  */
317
318
  async analyzeProposal(args) {
318
319
  const { proposalTitle = "", proposalDescription = "", evidence = [], proposalType = "" } = args;
320
+ if (proposalType === "synthesis-confer") {
321
+ const packet = [proposalTitle, proposalDescription, ...(evidence || [])].filter(Boolean).join("\n\n");
322
+ return {
323
+ content: [{ type: "text", text: buildHostConferEvidence("code-review", packet) }],
324
+ };
325
+ }
319
326
  const vote = (await tryLLMGovernance("code-review", proposalTitle, proposalDescription, evidence, proposalType)) ??
320
327
  localConferVote({
321
328
  role: "code-review",
@@ -7,7 +7,7 @@
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
9
  import { XrayKnowledgeSkillBase } from "../shared/knowledge-skill-base.js";
10
- import { isGovernanceLlmConfigured, tryLLMGovernance, } from "../../governance/llm-governance-provider.js";
10
+ import { attemptLLMGovernance } from "../../governance/llm-governance-provider.js";
11
11
  import { formatGovernanceVoteText, localConferVote } from "../../governance/local-confer.js";
12
12
  class XraySecurityAuditServer extends XrayKnowledgeSkillBase {
13
13
  constructor() {
@@ -661,10 +661,12 @@ class XraySecurityAuditServer extends XrayKnowledgeSkillBase {
661
661
  */
662
662
  async analyzeProposal(args) {
663
663
  const { proposalTitle = "", proposalDescription = "", evidence = [], proposalType = "" } = args;
664
- const vote = (await tryLLMGovernance("security-audit", proposalTitle, proposalDescription, evidence, proposalType)) ??
664
+ const attempt = await attemptLLMGovernance("security-audit", proposalTitle, proposalDescription, evidence, proposalType);
665
+ const vote = attempt.vote ??
665
666
  localConferVote({
666
667
  role: "security-audit",
667
- llmConfigured: isGovernanceLlmConfigured(),
668
+ llmConfigured: attempt.miss !== "not-configured",
669
+ ...(attempt.miss ? { miss: attempt.miss } : {}),
668
670
  });
669
671
  return {
670
672
  content: [
@@ -319,7 +319,7 @@ export class MCPClient extends EventEmitter {
319
319
  };
320
320
  this.emit('tool.before', beforeEvent);
321
321
  const serverName = this.config.serverName;
322
- const isGovernanceServer = ['code-review', 'security-audit', 'researcher', 'governance'].includes(serverName);
322
+ const isGovernanceServer = ['code-review', 'security-audit', 'researcher', 'architect', 'governance'].includes(serverName);
323
323
  const isGovernanceTool = toolName === 'analyze_proposal';
324
324
  const preferReal = this.isPureMcpMode || isGovernanceServer || isGovernanceTool;
325
325
  try {
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { frameworkLogger } from '../../../core/framework-logger.js';
7
7
  import { buildLeadDevPlan, buildSynthesisCheckpointPlan, isLeadDevModeActive, persistLeadDevPlan, } from '../../../nucleus/autonomy-kernel.js';
8
- import { formatConferQuorumReport, isConferEnabled, runConferQuorum, } from '../../../nucleus/confer.js';
8
+ import { conferFixtureAllowed, formatConferQuorumReport, isConferEnabled, runConferQuorum, } from '../../../nucleus/confer.js';
9
9
  import { bindPlanToSession } from '../../../nucleus/lead-dev-plan-persistence.js';
10
10
  import { clearPendingDelegations } from '../../../nucleus/pending-delegations.js';
11
11
  import { isAutoProvisionWorktreeEnabled, provisionGitWorktree, } from '../../../nucleus/aside-worktree.js';
@@ -177,7 +177,7 @@ export class ComplexityHandler {
177
177
  args.sessionId) {
178
178
  const conferOpts = {
179
179
  dueReason: args.synthesisDueReason ?? null,
180
- fixture: args.conferFixture === true,
180
+ fixture: conferFixtureAllowed() && args.conferFixture === true,
181
181
  };
182
182
  if (args.collocatedText)
183
183
  conferOpts.collocatedText = args.collocatedText;
@@ -10,10 +10,11 @@
10
10
  import * as fs from "fs";
11
11
  import * as path from "path";
12
12
  import { frameworkLogger } from "../core/framework-logger.js";
13
- import { isGovernanceLlmConfigured, tryLLMGovernance, } from "../governance/llm-governance-provider.js";
13
+ import { attemptLLMGovernance, } from "../governance/llm-governance-provider.js";
14
14
  import { formatGovernanceVoteText, localConferVote } from "../governance/local-confer.js";
15
15
  import { initializeMemoryRouting } from "../memory-routing/index.js";
16
16
  import { buildMemoryRoutingEvidence, formatMemoryRoutingBlock, resolveResearcherMemoryContext, } from "./researcher-confidence.js";
17
+ import { buildHostConferEvidence } from "../nucleus/confer-host.js";
17
18
  import { XrayKnowledgeSkillBase } from "./shared/knowledge-skill-base.js";
18
19
  class XrayLibrarianServer extends XrayKnowledgeSkillBase {
19
20
  constructor() {
@@ -382,6 +383,12 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
382
383
  */
383
384
  async analyzeProposal(args) {
384
385
  const { proposalTitle = "", proposalDescription = "", evidence = [], proposalType = "" } = args || {};
386
+ if (proposalType === "synthesis-confer") {
387
+ const packet = [proposalTitle, proposalDescription, ...(evidence || [])].filter(Boolean).join("\n\n");
388
+ return {
389
+ content: [{ type: "text", text: buildHostConferEvidence("researcher", packet) }],
390
+ };
391
+ }
385
392
  const memoryContext = await resolveResearcherMemoryContext({
386
393
  proposalTitle,
387
394
  proposalDescription,
@@ -391,12 +398,14 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
391
398
  if (memoryContext) {
392
399
  enrichedEvidence.push(...buildMemoryRoutingEvidence(memoryContext));
393
400
  }
394
- const vote = (await tryLLMGovernance("researcher", proposalTitle || "", proposalDescription || "", enrichedEvidence, proposalType || "")) ??
401
+ const attempt = await attemptLLMGovernance("researcher", proposalTitle || "", proposalDescription || "", enrichedEvidence, proposalType || "");
402
+ const vote = attempt.vote ??
395
403
  localConferVote({
396
404
  role: "researcher",
397
- llmConfigured: isGovernanceLlmConfigured(),
405
+ llmConfigured: attempt.miss !== "not-configured",
398
406
  highConfidenceTrapPresent: memoryContext?.confidence.highConfidenceTrapPresent,
399
407
  recommendedAgent: memoryContext?.recommendedAgent,
408
+ ...(attempt.miss ? { miss: attempt.miss } : {}),
400
409
  });
401
410
  const memoryRoutingBlock = memoryContext
402
411
  ? `\n${formatMemoryRoutingBlock(memoryContext)}`