0xray 4.0.42 → 4.1.1

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 (138) hide show
  1. package/.gitignore.default +4 -1
  2. package/.grok-plugin/plugin.json +1 -1
  3. package/.opencode/skills/orchestrator/SKILL.md +1 -7
  4. package/.plugin/plugin.json +1 -1
  5. package/.xray/features.json +8 -5
  6. package/.xray/features.schema.json +8 -0
  7. package/AGENTS-consumer.md +9 -7
  8. package/AGENTS.md +19 -10
  9. package/LICENSE +1 -1
  10. package/README.md +15 -9
  11. package/SKILLS.md +3 -1
  12. package/dist/.gitignore.default +4 -1
  13. package/dist/AGENTS.md +19 -10
  14. package/dist/CHANGELOG.md +109 -11
  15. package/dist/LICENSE +1 -1
  16. package/dist/README.md +15 -9
  17. package/dist/SKILLS.md +3 -1
  18. package/dist/cli/commands/opencode-install.js +3 -9
  19. package/dist/cli/commands/plugin-commands.d.ts +4 -0
  20. package/dist/cli/commands/plugin-commands.js +84 -41
  21. package/dist/cli/commands/skill-install.js +10 -5
  22. package/dist/cli/goggles-status.d.ts +2 -5
  23. package/dist/cli/goggles-status.js +5 -53
  24. package/dist/cli/index.js +2 -2
  25. package/dist/core/config-loader.js +4 -4
  26. package/dist/core/features-config.d.ts +1 -1
  27. package/dist/core/features-config.js +3 -3
  28. package/dist/enforcement/enforcer-tools.d.ts +1 -1
  29. package/dist/enforcement/enforcer-tools.js +1 -1
  30. package/dist/integrations/cursor/README.md +1 -1
  31. package/dist/integrations/cursor/hooks/hooks.json +6 -3
  32. package/dist/integrations/cursor/hooks/pre-tool-use.js +104 -37
  33. package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +9 -1
  34. package/dist/integrations/grok/grok-cli.js +5 -7
  35. package/dist/integrations/grok/hooks/grok-hook-utils.js +4 -1
  36. package/dist/integrations/grok/hooks/pre-tool-use.js +6 -76
  37. package/dist/integrations/grok/hooks/session-start.js +4 -2
  38. package/dist/integrations/hermes-agent/bridge.mjs +17 -1
  39. package/dist/integrations/hooks/delegation-gate-runtime.mjs +3 -2
  40. package/dist/integrations/hooks/goggles-mcp.mjs +23 -181
  41. package/dist/integrations/hooks/goggles-pipeline.mjs +606 -571
  42. package/dist/integrations/hooks/goggles-planes.json +95 -46
  43. package/dist/integrations/hooks/plates.cjs +84 -0
  44. package/dist/integrations/hooks/pre-tool-speak.mjs +74 -0
  45. package/dist/integrations/hooks/skill-seek.mjs +115 -0
  46. package/dist/integrations/hooks/station-hook-runtime.cjs +4 -1
  47. package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +24 -7
  48. package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +26 -2
  49. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  50. package/dist/memory-routing/plates.d.ts +1 -1
  51. package/dist/memory-routing/plates.js +14 -0
  52. package/dist/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
  53. package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
  54. package/dist/nucleus/thin-dispatch.d.ts +1 -1
  55. package/dist/nucleus/thin-dispatch.js +1 -1
  56. package/dist/nucleus/work-freshness.mjs +10 -0
  57. package/dist/plugin/xray-codex-injection.js +20 -0
  58. package/dist/processors/implementations/nudge-processor.d.ts +1 -1
  59. package/dist/processors/implementations/nudge-processor.js +4 -4
  60. package/dist/skills/orchestrator/SKILL.md +1 -7
  61. package/docs-site/docs/plates/activity-log.md +1 -1
  62. package/docs-site/docs/plates/asides.md +35 -0
  63. package/docs-site/docs/plates/bifrost.md +48 -0
  64. package/docs-site/docs/plates/boot.md +2 -2
  65. package/docs-site/docs/plates/centrifuge.md +1 -1
  66. package/docs-site/docs/plates/confer.md +35 -0
  67. package/docs-site/docs/plates/consumer-mcp.md +35 -0
  68. package/docs-site/docs/plates/cursor-compact.md +1 -1
  69. package/docs-site/docs/plates/delegation-gate.md +35 -0
  70. package/docs-site/docs/plates/dynamo.md +35 -0
  71. package/docs-site/docs/plates/foundry.md +35 -0
  72. package/docs-site/docs/plates/glossary.md +7 -3
  73. package/docs-site/docs/plates/goggles.md +12 -1
  74. package/docs-site/docs/plates/governance.md +1 -1
  75. package/docs-site/docs/plates/grok-compact.md +1 -1
  76. package/docs-site/docs/plates/grokbot.md +3 -1
  77. package/docs-site/docs/plates/ground.md +35 -0
  78. package/docs-site/docs/plates/hermes.md +35 -0
  79. package/docs-site/docs/plates/host-pack.md +1 -1
  80. package/docs-site/docs/plates/house.md +1 -1
  81. package/docs-site/docs/plates/index.md +16 -7
  82. package/docs-site/docs/plates/inference-cycle.md +35 -0
  83. package/docs-site/docs/plates/inference-files.md +1 -1
  84. package/docs-site/docs/plates/kept-line.md +1 -1
  85. package/docs-site/docs/plates/kits.md +1 -1
  86. package/docs-site/docs/plates/lens-gate.md +8 -6
  87. package/docs-site/docs/plates/lens-page.md +1 -1
  88. package/docs-site/docs/plates/memory-recall.md +1 -1
  89. package/docs-site/docs/plates/notes-page.md +2 -2
  90. package/docs-site/docs/plates/openclaw.md +35 -0
  91. package/docs-site/docs/plates/opencode.md +35 -0
  92. package/docs-site/docs/plates/orchestration.md +1 -1
  93. package/docs-site/docs/plates/package-face.md +1 -1
  94. package/docs-site/docs/plates/payload-heat.md +1 -1
  95. package/docs-site/docs/plates/pickup-stamp.md +1 -1
  96. package/docs-site/docs/plates/pre-tool.md +1 -1
  97. package/docs-site/docs/plates/processor.md +1 -1
  98. package/docs-site/docs/plates/record-map.md +1 -1
  99. package/docs-site/docs/plates/reflection-page.md +1 -1
  100. package/docs-site/docs/plates/repertoire.md +35 -0
  101. package/docs-site/docs/plates/reporting.md +1 -1
  102. package/docs-site/docs/plates/review.md +1 -1
  103. package/docs-site/docs/plates/routing.md +1 -1
  104. package/docs-site/docs/plates/session-capture.md +1 -1
  105. package/docs-site/docs/plates/site-manual.md +1 -1
  106. package/docs-site/docs/plates/stamp-plate.md +1 -1
  107. package/docs-site/docs/plates/station-card.md +1 -1
  108. package/docs-site/docs/plates/station-heat.md +1 -1
  109. package/docs-site/docs/plates/suit-organs.md +1 -1
  110. package/docs-site/docs/plates/suit-settings.md +1 -1
  111. package/docs-site/docs/plates/suit-wear.md +1 -1
  112. package/docs-site/docs/plates/suit.md +3 -3
  113. package/docs-site/docs/plates/synthesis.md +35 -0
  114. package/docs-site/docs/plates/temperament.md +35 -0
  115. package/docs-site/docs/plates/trail-state.md +2 -2
  116. package/docs-site/docs/plates/work-fresh.md +1 -1
  117. package/docs-site/docs/plates/write-home.md +1 -1
  118. package/llms.txt +14 -6
  119. package/package.json +3 -3
  120. package/scripts/foundry/README.md +5 -3
  121. package/scripts/foundry/mint-suit.cjs +120 -2
  122. package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
  123. package/scripts/foundry/reconcile-version.mjs +10 -3
  124. package/scripts/foundry/release-gate.mjs +111 -15
  125. package/scripts/foundry/release.mjs +9 -1
  126. package/scripts/foundry/validate-release-docs.mjs +2 -2
  127. package/scripts/foundry/version-manager.mjs +12 -4
  128. package/scripts/mjs/verify-pipeline-facets.mjs +1 -1
  129. package/scripts/node/install-bridges.cjs +91 -46
  130. package/scripts/node/pack-tmp-suit-proof.mjs +37 -10
  131. package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +26 -2
  132. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  133. package/src/skills/orchestrator/SKILL.md +1 -7
  134. package/vendor/@0xray/repertoire/README.md +1 -1
  135. package/xray/agents_template.md +2 -2
  136. package/xray/features.json +8 -5
  137. package/xray/features.schema.json +8 -0
  138. package/docs-site/docs/plates/live-mesh.md +0 -41
@@ -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?.decision === 'deny') {
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
- reason: outcome.reason,
77
- gate: outcome.gate,
78
- warn: Boolean(outcome.reason),
89
+ reason: lens?.reason || outcome.reason,
90
+ gate: lens?.reason ? 'lens' : outcome.gate,
91
+ warn: Boolean(lens?.reason || 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,30 @@ 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?.decision === 'deny') {
73
+ return { block: true, blockReason: lens.reason };
74
+ }
75
+ if (lens?.reason) {
76
+ return {
77
+ block: false,
78
+ hookSpecificOutput: {
79
+ hookEventName: 'PreToolUse',
80
+ additionalContext: lens.reason,
81
+ },
82
+ };
83
+ }
84
+ const route = speak.repertoireRoute(spoken);
85
+ if (route?.hookSpecificOutput) {
86
+ return { block: false, hookSpecificOutput: route.hookSpecificOutput };
87
+ }
64
88
  return undefined;
65
89
  } catch (err) {
66
90
  return {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.42",
3
+ "version": "4.1.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -1,4 +1,4 @@
1
- export declare const PLATE_IDS: readonly ["routing", "governance", "boot", "orchestration", "processor", "reporting", "memory-recall", "stamp-plate", "house", "review", "goggles", "suit", "kits", "host-pack", "glossary", "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"];
1
+ export declare const PLATE_IDS: readonly ["routing", "governance", "boot", "orchestration", "processor", "reporting", "memory-recall", "stamp-plate", "house", "review", "goggles", "suit", "kits", "host-pack", "glossary", "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", "ground", "synthesis", "repertoire", "dynamo", "foundry", "delegation-gate", "temperament", "asides", "inference-cycle", "hermes", "openclaw", "opencode", "consumer-mcp", "confer"];
2
2
  export type PlateId = (typeof PLATE_IDS)[number];
3
3
  export interface PlateStamp {
4
4
  id: PlateId;
@@ -46,6 +46,20 @@ export const PLATE_IDS = [
46
46
  'pre-tool',
47
47
  'lens-page',
48
48
  'centrifuge',
49
+ 'ground',
50
+ 'synthesis',
51
+ 'repertoire',
52
+ 'dynamo',
53
+ 'foundry',
54
+ 'delegation-gate',
55
+ 'temperament',
56
+ 'asides',
57
+ 'inference-cycle',
58
+ 'hermes',
59
+ 'openclaw',
60
+ 'opencode',
61
+ 'consumer-mcp',
62
+ 'confer',
49
63
  ];
50
64
  function isPlateId(value) {
51
65
  return PLATE_IDS.includes(value);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Nudge Watchdog - Detects and breaks stuck AI patterns
2
+ * Nudge Monitor - Detects and breaks stuck AI patterns
3
3
  *
4
4
  * Monitors agent actions and detects common failure patterns:
5
5
  * - think-loop: AI keeps outputting thinking tags but no progress
@@ -8,7 +8,7 @@
8
8
  * - tool-loop: Same tool called with similar args repeatedly
9
9
  * - repair-failure: Processor indicates fix but error persists
10
10
  *
11
- * @module monitoring/nudge-watchdog
11
+ * @module monitoring/nudge-monitor
12
12
  */
13
13
  import { EventEmitter } from "events";
14
14
  export interface NudgeConfig {
@@ -33,7 +33,16 @@ export interface NudgeAction {
33
33
  suggestion: string;
34
34
  priority: number;
35
35
  }
36
- declare class NudgeWatchdog extends EventEmitter {
36
+ /** Old config key from before the rename to nudge_monitor; still read as a deprecated alias. */
37
+ export declare const LEGACY_NUDGE_CONFIG_KEY = "nudge_watchdog";
38
+ /**
39
+ * features.nudge_monitor, or the deprecated features.nudge_watchdog when nudge_monitor is unset (the new key wins
40
+ * when both are set). Using the old key logs a one-time deprecation warning.
41
+ */
42
+ export declare function resolveNudgeConfig(features: Record<string, unknown> | undefined | null, warn?: (message: string) => void): Record<string, unknown> | undefined;
43
+ /** Test hook: let the one-time deprecation warning fire again. */
44
+ export declare function resetLegacyNudgeWarning(): void;
45
+ declare class NudgeMonitor extends EventEmitter {
37
46
  private config;
38
47
  private patterns;
39
48
  private lastNudgeTime;
@@ -75,13 +84,13 @@ declare class NudgeWatchdog extends EventEmitter {
75
84
  };
76
85
  private log;
77
86
  }
78
- export declare const nudgeWatchdog: NudgeWatchdog;
87
+ export declare const nudgeMonitor: NudgeMonitor;
79
88
  export declare function recordThinkTag(): void;
80
89
  export declare function recordCodeChange(): void;
81
90
  export declare function recordToolCall(tool: string, args: string): void;
82
91
  export declare function recordFixAttempt(filePath: string, error: string): void;
83
92
  export declare function recordExplanation(): void;
84
93
  export declare function getNudgeSuggestion(type: NudgeType, context?: Record<string, unknown>): string;
85
- export declare function getNudgeWatchdog(): NudgeWatchdog;
94
+ export declare function getNudgeMonitor(): NudgeMonitor;
86
95
  export declare function recordProcessorFix(filePath: string, fixed: boolean, errorPersisted: boolean): void;
87
96
  export {};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Nudge Watchdog - Detects and breaks stuck AI patterns
2
+ * Nudge Monitor - Detects and breaks stuck AI patterns
3
3
  *
4
4
  * Monitors agent actions and detects common failure patterns:
5
5
  * - think-loop: AI keeps outputting thinking tags but no progress
@@ -8,7 +8,7 @@
8
8
  * - tool-loop: Same tool called with similar args repeatedly
9
9
  * - repair-failure: Processor indicates fix but error persists
10
10
  *
11
- * @module monitoring/nudge-watchdog
11
+ * @module monitoring/nudge-monitor
12
12
  */
13
13
  import { EventEmitter } from "events";
14
14
  import { frameworkLogger } from "../core/framework-logger.js";
@@ -53,7 +53,32 @@ const NUDGE_MESSAGES = {
53
53
  priority: 5,
54
54
  },
55
55
  };
56
- class NudgeWatchdog extends EventEmitter {
56
+ /** Old config key from before the rename to nudge_monitor; still read as a deprecated alias. */
57
+ export const LEGACY_NUDGE_CONFIG_KEY = "nudge_watchdog";
58
+ let legacyKeyWarned = false;
59
+ /**
60
+ * features.nudge_monitor, or the deprecated features.nudge_watchdog when nudge_monitor is unset (the new key wins
61
+ * when both are set). Using the old key logs a one-time deprecation warning.
62
+ */
63
+ export function resolveNudgeConfig(features, warn = (m) => frameworkLogger.log("nudge-monitor", "deprecated-config-key", "warning", { message: m })) {
64
+ const current = features?.nudge_monitor;
65
+ if (current !== undefined && current !== null)
66
+ return current;
67
+ const legacy = features?.[LEGACY_NUDGE_CONFIG_KEY];
68
+ if (legacy !== undefined && legacy !== null) {
69
+ if (!legacyKeyWarned) {
70
+ legacyKeyWarned = true;
71
+ warn(`features.${LEGACY_NUDGE_CONFIG_KEY} is deprecated; rename it to features.nudge_monitor`);
72
+ }
73
+ return legacy;
74
+ }
75
+ return undefined;
76
+ }
77
+ /** Test hook: let the one-time deprecation warning fire again. */
78
+ export function resetLegacyNudgeWarning() {
79
+ legacyKeyWarned = false;
80
+ }
81
+ class NudgeMonitor extends EventEmitter {
57
82
  config;
58
83
  patterns = new Map();
59
84
  lastNudgeTime = 0;
@@ -73,7 +98,7 @@ class NudgeWatchdog extends EventEmitter {
73
98
  loadConfigFromFeatures() {
74
99
  try {
75
100
  const features = featuresConfigLoader.loadConfig();
76
- const nudgeConfig = features?.nudge_watchdog;
101
+ const nudgeConfig = resolveNudgeConfig(features);
77
102
  if (nudgeConfig) {
78
103
  this.config = { ...this.config, ...nudgeConfig };
79
104
  }
@@ -86,10 +111,10 @@ class NudgeWatchdog extends EventEmitter {
86
111
  if (!this.config.enabled) {
87
112
  return;
88
113
  }
89
- this.log("nudge-watchdog", "started", "info", { config: this.config });
114
+ this.log("nudge-monitor", "started", "info", { config: this.config });
90
115
  }
91
116
  stop() {
92
- this.log("nudge-watchdog", "stopped", "info");
117
+ this.log("nudge-monitor", "stopped", "info");
93
118
  }
94
119
  recordThinkTag() {
95
120
  const now = Date.now();
@@ -256,7 +281,7 @@ class NudgeWatchdog extends EventEmitter {
256
281
  if (!action)
257
282
  return null;
258
283
  this.emit("nudge", action);
259
- this.log("nudge-watchdog", "nudge-triggered", "warning", {
284
+ this.log("nudge-monitor", "nudge-triggered", "warning", {
260
285
  type: action.type,
261
286
  message: action.message,
262
287
  suggestion: action.suggestion,
@@ -328,28 +353,28 @@ class NudgeWatchdog extends EventEmitter {
328
353
  }
329
354
  }
330
355
  }
331
- export const nudgeWatchdog = new NudgeWatchdog();
356
+ export const nudgeMonitor = new NudgeMonitor();
332
357
  export function recordThinkTag() {
333
- nudgeWatchdog.recordThinkTag();
358
+ nudgeMonitor.recordThinkTag();
334
359
  }
335
360
  export function recordCodeChange() {
336
- nudgeWatchdog.recordCodeChange();
361
+ nudgeMonitor.recordCodeChange();
337
362
  }
338
363
  export function recordToolCall(tool, args) {
339
- nudgeWatchdog.recordToolCall(tool, args);
364
+ nudgeMonitor.recordToolCall(tool, args);
340
365
  }
341
366
  export function recordFixAttempt(filePath, error) {
342
- nudgeWatchdog.recordFixAttempt(filePath, error);
367
+ nudgeMonitor.recordFixAttempt(filePath, error);
343
368
  }
344
369
  export function recordExplanation() {
345
- nudgeWatchdog.recordExplanation();
370
+ nudgeMonitor.recordExplanation();
346
371
  }
347
372
  export function getNudgeSuggestion(type, context) {
348
- return nudgeWatchdog.getNudgeSuggestion(type, context);
373
+ return nudgeMonitor.getNudgeSuggestion(type, context);
349
374
  }
350
- export function getNudgeWatchdog() {
351
- return nudgeWatchdog;
375
+ export function getNudgeMonitor() {
376
+ return nudgeMonitor;
352
377
  }
353
378
  export function recordProcessorFix(filePath, fixed, errorPersisted) {
354
- nudgeWatchdog.recordProcessorFix(filePath, fixed, errorPersisted);
379
+ nudgeMonitor.recordProcessorFix(filePath, fixed, errorPersisted);
355
380
  }
@@ -22,7 +22,7 @@ export declare function scoreComplexity(operation: string, context: unknown, thr
22
22
  export declare function routeToAgent(scoreOrLevel: number | ComplexityScore, level?: ComplexityLevel): string;
23
23
  /**
24
24
  * Convenience: score first, then route — one call.
25
- * One named plane: the route is that file, and the agent is not the researcher.
25
+ * One of the four plane names: the route is that card's first file, and the agent is not the researcher.
26
26
  * A look that names no plane stops. Repertoire may still change the agent when a law matches.
27
27
  */
28
28
  export declare function scoreAndRoute(operation: string, context: unknown, thresholds?: ComplexityThresholds): {
@@ -55,7 +55,7 @@ export function routeToAgent(scoreOrLevel, level) {
55
55
  }
56
56
  /**
57
57
  * Convenience: score first, then route — one call.
58
- * One named plane: the route is that file, and the agent is not the researcher.
58
+ * One of the four plane names: the route is that card's first file, and the agent is not the researcher.
59
59
  * A look that names no plane stops. Repertoire may still change the agent when a law matches.
60
60
  */
61
61
  export function scoreAndRoute(operation, context, thresholds) {
@@ -135,11 +135,21 @@ function freshnessFile(projectRoot) {
135
135
  return join(projectRoot, '.xray', 'state', 'freshness.json');
136
136
  }
137
137
 
138
+ export const FRESHNESS_MAX_AGE_MS = 60 * 60 * 1000;
139
+
138
140
  export function readSavedFreshness(projectRoot) {
139
141
  const saved = readJson(freshnessFile(projectRoot));
140
142
  return saved ? describeFreshness(saved) : null;
141
143
  }
142
144
 
145
+ /** A missing stamp or a stamp older than an hour is due. */
146
+ export function freshnessNeedsRefresh(projectRoot, now = Date.now()) {
147
+ const saved = readJson(freshnessFile(projectRoot));
148
+ const at = saved && saved.checkedAt ? Date.parse(String(saved.checkedAt)) : NaN;
149
+ if (!Number.isFinite(at)) return true;
150
+ return now - at >= FRESHNESS_MAX_AGE_MS;
151
+ }
152
+
143
153
  export function probeFreshness(projectRoot) {
144
154
  const saved = readJson(freshnessFile(projectRoot));
145
155
  if (saved && saved.behind != null) return decideFreshness(saved);
@@ -114,6 +114,26 @@ export default async function xrayCodexPlugin(input) {
114
114
  logger.error(`🚫 Delegation gate: ${delegationGate.reason}`);
115
115
  throw new Error(`DELEGATION GATE: ${delegationGate.reason}`);
116
116
  }
117
+ let speak = null;
118
+ try {
119
+ speak = (await import(new URL("../integrations/hooks/pre-tool-speak.mjs", import.meta.url).href));
120
+ }
121
+ catch {
122
+ speak = null;
123
+ }
124
+ if (speak) {
125
+ const spoken = `${tool} ${JSON.stringify(args ?? {})}`.slice(0, 500);
126
+ const lens = speak.lensDecision(directory, tool, spoken);
127
+ if (lens?.decision === "deny") {
128
+ throw new Error(`DELEGATION GATE: ${lens.reason ?? "lens"}`);
129
+ }
130
+ if (lens?.reason)
131
+ logger.log(lens.reason);
132
+ const additional = speak.repertoireRoute(spoken)?.hookSpecificOutput?.additionalContext;
133
+ if (additional) {
134
+ logger.log(additional);
135
+ }
136
+ }
117
137
  if (isWriteEditOperation(tool)) {
118
138
  const _ProcessorManager = getProcessorManager();
119
139
  const _XrayStateManager = getXrayStateManager();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Nudge Processor - Integrates Nudge Watchdog with processor system
2
+ * Nudge Processor - Integrates Nudge Monitor with processor system
3
3
  *
4
4
  * Post-processor that monitors agent actions and triggers nudges
5
5
  * when stuck patterns are detected.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Nudge Processor - Integrates Nudge Watchdog with processor system
2
+ * Nudge Processor - Integrates Nudge Monitor with processor system
3
3
  *
4
4
  * Post-processor that monitors agent actions and triggers nudges
5
5
  * when stuck patterns are detected.
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { PostProcessor } from "../processor-interfaces.js";
10
10
  import { frameworkLogger } from "../../core/framework-logger.js";
11
- import { nudgeWatchdog, recordThinkTag, recordCodeChange, recordToolCall, recordFixAttempt, recordExplanation, getNudgeSuggestion, } from "../../monitoring/nudge-watchdog.js";
11
+ import { nudgeMonitor, resolveNudgeConfig, recordThinkTag, recordCodeChange, recordToolCall, recordFixAttempt, recordExplanation, getNudgeSuggestion, } from "../../monitoring/nudge-monitor.js";
12
12
  import { featuresConfigLoader } from "../../core/features-config.js";
13
13
  export class NudgeProcessor extends PostProcessor {
14
14
  name = "nudge";
@@ -21,7 +21,7 @@ export class NudgeProcessor extends PostProcessor {
21
21
  loadConfig() {
22
22
  try {
23
23
  const features = featuresConfigLoader.loadConfig();
24
- const nudgeConfig = features?.nudge_watchdog;
24
+ const nudgeConfig = resolveNudgeConfig(features);
25
25
  if (nudgeConfig) {
26
26
  this.nudgeEnabled = nudgeConfig.enabled !== false;
27
27
  }
@@ -71,7 +71,7 @@ export class NudgeProcessor extends PostProcessor {
71
71
  }
72
72
  }
73
73
  }
74
- const stats = nudgeWatchdog.getStats();
74
+ const stats = nudgeMonitor.getStats();
75
75
  if (stats.activePatterns > 0) {
76
76
  const nudge = this.detectMostUrgentNudge(stats);
77
77
  if (nudge) {
@@ -57,13 +57,7 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
57
57
 
58
58
  The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
59
59
 
60
- Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet.
61
-
62
- Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
63
-
64
- Naming a plane, `digest`, or `triage` returns that plane's view. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Each field has a pipe. The pipe writes what it can see from the plate or the tree, and the view keeps that field. A field the pipe cannot see yet stays on the view for the next look. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. The mill path is `scripts/foundry`. `triage` names the fields still open and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact.
65
-
66
- The hold lasts for the work that named the plane. A sentence that does not say look or name does not arm it and does not drop it. Work that says goggles clears the hold and stays quiet. When a tool names one card plane, the suit adds that plane's file as a hint.
60
+ A session starts on dev. The boot payload includes that card from `.xray/state/LENS.md`. The planes are dev, integration, domain, and ecosystem. A search inside the current card runs. Any other search returns the current card and does not run. Opening a file on the current card adds nothing. Opening a file on another plane switches to that card and the open proceeds. A file on no card stays on the current plane and returns that card. Compact writes that same card back. A thing on that plane has its own card. One card has its own plate and can name more. A missing plate names the stamp-plate skill. Stamp one plate for that card. The cards and plates that stay are what the next agent reads. Saying goggles clears a stale card line on the station.
67
61
 
68
62
  ## Hooks (rewired, not new MCPs)
69
63
 
@@ -35,4 +35,4 @@ Today those writers append `logs/framework/activity.log`. The features default s
35
35
 
36
36
  When: on the tool hook, on framework logger flush, and on boot when file logging is on. Why: the report and the pulse read one tail, not a pile of copies.
37
37
 
38
- stamped · 0xray 4.0.42
38
+ stamped · 0xray 4.1.0
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Asides
3
+ sidebar_label: Asides
4
+ ---
5
+
6
+ # Asides
7
+
8
+ A user aside is one parallel track.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ ┌────────────────────────────────────────────────────┐ │
14
+ │ │ a named track │ │
15
+ │ └──────────────────────────┬─────────────────────────┘ │
16
+ └──────────────────────────────┼─────────────────────────────┘
17
+ v
18
+ ┌────────────┼───────────────────────────────────────────────┐
19
+ │ PROCESSING LAYER │
20
+ │ ┌────────────────────────────────────────────────────┐ │
21
+ │ │ read the aside file │ │
22
+ │ └──────────────────────────┬─────────────────────────┘ │
23
+ └──────────────────────────────┼─────────────────────────────┘
24
+ v
25
+ ┌──────────────────────────────┼─────────────────────────────┐
26
+ │ OUTPUT LAYER v │
27
+ │ ┌────────────────────────────────────────────────────┐ │
28
+ │ │ that track, or the main line │ │
29
+ │ └────────────────────────────────────────────────────┘ │
30
+ └────────────────────────────────────────────────────────────┘
31
+ ```
32
+
33
+ State is `.xray/state/asides/{id}.json` and `_active.json`. A todo id looks like `{asideId}.a.1.1`. The code is `src/nucleus/user-aside.ts`. The guides aside-context, user-asides, and parallel-work-tracks describe this one thing.
34
+
35
+ stamped · 0xray 4.1.0
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: Bifrost
3
+ sidebar_label: Bifrost
4
+ plate_type: pipeline
5
+ ---
6
+
7
+ # Bifrost
8
+
9
+ Bifrost is an autonomous AI fleet management system. Every PR, issue and comment becomes a card on one board and a line in the right seat's inbox, and the seat is woken to act on it.
10
+
11
+ ```
12
+ ┌────────────────────────────────────────────────────────────┐
13
+ │ INPUT LAYER │
14
+ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │
15
+ │ │ GitHub │ │ seat │ │ Railway deploys, │ │
16
+ │ │ PR / issue / │ │ presence + │ │ /healthz probes │ │
17
+ │ │ comment │ │ activity │ │ │ │
18
+ │ └──────┬───────┘ └──────┬───────┘ └─────────┬─────────┘ │
19
+ │ └────────────────┴───────────────────┘ │
20
+ └─────────────────────────────┬──────────────────────────────┘
21
+ v
22
+ ┌─────────────────────────────┼──────────────────────────────┐
23
+ │ PROCESSING LAYER v │
24
+ │ ┌────────────────────────────────────────────────────┐ │
25
+ │ │ collector board (live_mesh.py) on Railway │ │
26
+ │ │ PR, issue, review, push, and merge → beats │ │
27
+ │ │ inbox lines use routing/inbox-roles.json │ │
28
+ │ └──────────────────────────┬─────────────────────────┘ │
29
+ └──────────────────────────────┼─────────────────────────────┘
30
+ v
31
+ ┌──────────────────────────────┼─────────────────────────────┐
32
+ │ OUTPUT LAYER v │
33
+ │ ┌──────────────┐ ┌──────────────────┐ ┌───────────────┐ │
34
+ │ │ the board │ │ seat inboxes + │ │ seat presence │ │
35
+ │ │ (cards) │ │ wake webhooks │ │ + archive │ │
36
+ │ └──────────────┘ └──────────────────┘ └───────────────┘ │
37
+ └────────────────────────────────────────────────────────────┘
38
+ ```
39
+
40
+ The governor runs inside the server (`house/live/app/server.mjs`). When nothing on the board moves for `BIFROST_TRIPWIRE_MINUTES` (default 30), the tripwire starts a timed run. On a stalled board, every seat with open work is woken with a deadline, and the CoS role is told. On an empty board, the CoS role is woken to pull the next work. The CoS role can start the same run with `POST /clock`. A seat's move clears the clock.
41
+
42
+ `evalNudges` is the one nudge path. A live card with no beat for `NUDGE_L1_S` (default 3600) sends L1 to the next owner, or else the card owner. L2 goes to the CoS role after `NUDGE_L2_S` (default 1800). L3 repeats to that L1 seat after `NUDGE_L3_S` (default 7200), and the CoS role is woken once to reassign. An unacked inbox line steps L1, L2, and L3 on that same seat, `NUDGE_LINE_S` apart (default 900), and the CoS role is woken at L3. A missing handoff nudges the owing seat at L1, again at L2, and again at L3, and the CoS role is woken once at L3. Seats with no route, and seats whose wake has gone quiet, each hold one nudge to the CoS role.
43
+
44
+ A cloud agent is its own board row (`cloud_agents`, also copied onto `board.cloud_agents`): `bcId`, owning seat, linked card, and state `running`, `finished`, or `failed`. The Cursor webhook and seat activity `kind: cloud_agent` feed that row.
45
+
46
+ Code lives under `house/live/` (collector, board, inbox routing, server). `house/live/feed/live_mesh.py` runs the collector and the board. `house/live-mesh/fetch_feed.py` is the upstream feed the deploy assembles. Deploy only via `house/live/app/deploy.sh`. A Railway build straight from git stops in the Dockerfile with `ERROR: collector/ is not assembled`. The plate that belongs here is house.
47
+
48
+ stamped · 0xray 4.1.0
@@ -5,7 +5,7 @@ sidebar_label: Boot
5
5
 
6
6
  # Boot
7
7
 
8
- `BootOrchestrator.executeBootSequence` brings the framework up. Processor registration here is the legacy boot list. The live tool gate is the suit pipeline, drawn on the processor plate.
8
+ `BootOrchestrator.executeBootSequence` brings the engine up. Processor registration here is the legacy boot list. The live tool gate is the suit pipeline, drawn on the processor plate.
9
9
 
10
10
  ```
11
11
  ┌────────────────────────────────────────────────────────────┐
@@ -66,4 +66,4 @@ sidebar_label: Boot
66
66
  └────────────────────────────────────────────────────────────┘
67
67
  ```
68
68
 
69
- stamped · 0xray 4.0.42
69
+ stamped · 0xray 4.1.0
@@ -27,4 +27,4 @@ JUDGEMENT│ LOOP STATE │PAYMENTS
27
27
 
28
28
  Loop, domain, state, foundry, and memory ride that turn. Judgement and payments stay off the rotor. Goggles looks on and does not spin. The next turn waits for the next session or compact.
29
29
 
30
- stamped · 0xray 4.0.42
30
+ stamped · 0xray 4.1.0
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Confer
3
+ sidebar_label: Confer
4
+ ---
5
+
6
+ # Confer
7
+
8
+ Confer stays off unless it is enabled.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ ┌────────────────────────────────────────────────────┐ │
14
+ │ │ a synthesis checkpoint │ │
15
+ │ └──────────────────────────┬─────────────────────────┘ │
16
+ └──────────────────────────────┼─────────────────────────────┘
17
+ v
18
+ ┌────────────┼───────────────────────────────────────────────┐
19
+ │ PROCESSING LAYER │
20
+ │ ┌────────────────────────────────────────────────────┐ │
21
+ │ │ stay off │ │
22
+ │ └──────────────────────────┬─────────────────────────┘ │
23
+ └──────────────────────────────┼─────────────────────────────┘
24
+ v
25
+ ┌──────────────────────────────┼─────────────────────────────┐
26
+ │ OUTPUT LAYER v │
27
+ │ ┌────────────────────────────────────────────────────┐ │
28
+ │ │ skipped, unless it is enabled │ │
29
+ │ └────────────────────────────────────────────────────┘ │
30
+ └────────────────────────────────────────────────────────────┘
31
+ ```
32
+
33
+ `src/nucleus/confer.ts` defaults to enabled false and on_synthesis false. A missing key stays off. A confer todo completes only on a real model PASS.
34
+
35
+ stamped · 0xray 4.1.0
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Consumer MCP
3
+ sidebar_label: Consumer MCP
4
+ ---
5
+
6
+ # Consumer MCP
7
+
8
+ The worn checkout lists seven MCP servers.
9
+
10
+ ```
11
+ ┌────────────────────────────────────────────────────────────┐
12
+ │ INPUT LAYER │
13
+ │ ┌────────────────────────────────────────────────────┐ │
14
+ │ │ a worn checkout │ │
15
+ │ └──────────────────────────┬─────────────────────────┘ │
16
+ └──────────────────────────────┼─────────────────────────────┘
17
+ v
18
+ ┌────────────┼───────────────────────────────────────────────┐
19
+ │ PROCESSING LAYER │
20
+ │ ┌────────────────────────────────────────────────────┐ │
21
+ │ │ read .mcp.json │ │
22
+ │ └──────────────────────────┬─────────────────────────┘ │
23
+ └──────────────────────────────┼─────────────────────────────┘
24
+ v
25
+ ┌──────────────────────────────┼─────────────────────────────┐
26
+ │ OUTPUT LAYER v │
27
+ │ ┌────────────────────────────────────────────────────┐ │
28
+ │ │ the seven servers │ │
29
+ │ └────────────────────────────────────────────────────┘ │
30
+ └────────────────────────────────────────────────────────────┘
31
+ ```
32
+
33
+ The card owns `src/mcps`. A longer card still wins. The seven servers are xray-governance, xray-skills, xray-enforcer, xray-orchestrator, xray-researcher, xray-code-review, and xray-architect-tools. Repertoire and goggles sit beside them in `.mcp.json`. `src/mcps/registry.json` is the community catalog. Orchestrator and governance keep their own cards.
34
+
35
+ stamped · 0xray 4.1.0