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.
- package/.gitignore.default +4 -1
- package/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +1 -7
- package/.plugin/plugin.json +1 -1
- package/.xray/features.json +8 -5
- package/.xray/features.schema.json +8 -0
- package/AGENTS-consumer.md +9 -7
- package/AGENTS.md +19 -10
- package/LICENSE +1 -1
- package/README.md +15 -9
- package/SKILLS.md +3 -1
- package/dist/.gitignore.default +4 -1
- package/dist/AGENTS.md +19 -10
- package/dist/CHANGELOG.md +109 -11
- package/dist/LICENSE +1 -1
- package/dist/README.md +15 -9
- package/dist/SKILLS.md +3 -1
- package/dist/cli/commands/opencode-install.js +3 -9
- package/dist/cli/commands/plugin-commands.d.ts +4 -0
- package/dist/cli/commands/plugin-commands.js +84 -41
- package/dist/cli/commands/skill-install.js +10 -5
- package/dist/cli/goggles-status.d.ts +2 -5
- package/dist/cli/goggles-status.js +5 -53
- package/dist/cli/index.js +2 -2
- package/dist/core/config-loader.js +4 -4
- package/dist/core/features-config.d.ts +1 -1
- package/dist/core/features-config.js +3 -3
- package/dist/enforcement/enforcer-tools.d.ts +1 -1
- package/dist/enforcement/enforcer-tools.js +1 -1
- package/dist/integrations/cursor/README.md +1 -1
- package/dist/integrations/cursor/hooks/hooks.json +6 -3
- package/dist/integrations/cursor/hooks/pre-tool-use.js +104 -37
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +9 -1
- package/dist/integrations/grok/grok-cli.js +5 -7
- package/dist/integrations/grok/hooks/grok-hook-utils.js +4 -1
- package/dist/integrations/grok/hooks/pre-tool-use.js +6 -76
- package/dist/integrations/grok/hooks/session-start.js +4 -2
- package/dist/integrations/hermes-agent/bridge.mjs +17 -1
- package/dist/integrations/hooks/delegation-gate-runtime.mjs +3 -2
- package/dist/integrations/hooks/goggles-mcp.mjs +23 -181
- package/dist/integrations/hooks/goggles-pipeline.mjs +606 -571
- package/dist/integrations/hooks/goggles-planes.json +95 -46
- package/dist/integrations/hooks/plates.cjs +84 -0
- package/dist/integrations/hooks/pre-tool-speak.mjs +74 -0
- package/dist/integrations/hooks/skill-seek.mjs +115 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +4 -1
- package/dist/integrations/openclaw/hooks/pre-tool-gate-runtime.mjs +24 -7
- package/dist/integrations/openclaw/plugin/xray-pre-tool/index.js +26 -2
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +14 -0
- package/dist/monitoring/{nudge-watchdog.d.ts → nudge-monitor.d.ts} +14 -5
- package/dist/monitoring/{nudge-watchdog.js → nudge-monitor.js} +42 -17
- package/dist/nucleus/thin-dispatch.d.ts +1 -1
- package/dist/nucleus/thin-dispatch.js +1 -1
- package/dist/nucleus/work-freshness.mjs +10 -0
- package/dist/plugin/xray-codex-injection.js +20 -0
- package/dist/processors/implementations/nudge-processor.d.ts +1 -1
- package/dist/processors/implementations/nudge-processor.js +4 -4
- package/dist/skills/orchestrator/SKILL.md +1 -7
- package/docs-site/docs/plates/activity-log.md +1 -1
- package/docs-site/docs/plates/asides.md +35 -0
- package/docs-site/docs/plates/bifrost.md +48 -0
- package/docs-site/docs/plates/boot.md +2 -2
- package/docs-site/docs/plates/centrifuge.md +1 -1
- package/docs-site/docs/plates/confer.md +35 -0
- package/docs-site/docs/plates/consumer-mcp.md +35 -0
- package/docs-site/docs/plates/cursor-compact.md +1 -1
- package/docs-site/docs/plates/delegation-gate.md +35 -0
- package/docs-site/docs/plates/dynamo.md +35 -0
- package/docs-site/docs/plates/foundry.md +35 -0
- package/docs-site/docs/plates/glossary.md +7 -3
- package/docs-site/docs/plates/goggles.md +12 -1
- package/docs-site/docs/plates/governance.md +1 -1
- package/docs-site/docs/plates/grok-compact.md +1 -1
- package/docs-site/docs/plates/grokbot.md +3 -1
- package/docs-site/docs/plates/ground.md +35 -0
- package/docs-site/docs/plates/hermes.md +35 -0
- package/docs-site/docs/plates/host-pack.md +1 -1
- package/docs-site/docs/plates/house.md +1 -1
- package/docs-site/docs/plates/index.md +16 -7
- package/docs-site/docs/plates/inference-cycle.md +35 -0
- package/docs-site/docs/plates/inference-files.md +1 -1
- package/docs-site/docs/plates/kept-line.md +1 -1
- package/docs-site/docs/plates/kits.md +1 -1
- package/docs-site/docs/plates/lens-gate.md +8 -6
- package/docs-site/docs/plates/lens-page.md +1 -1
- package/docs-site/docs/plates/memory-recall.md +1 -1
- package/docs-site/docs/plates/notes-page.md +2 -2
- package/docs-site/docs/plates/openclaw.md +35 -0
- package/docs-site/docs/plates/opencode.md +35 -0
- package/docs-site/docs/plates/orchestration.md +1 -1
- package/docs-site/docs/plates/package-face.md +1 -1
- package/docs-site/docs/plates/payload-heat.md +1 -1
- package/docs-site/docs/plates/pickup-stamp.md +1 -1
- package/docs-site/docs/plates/pre-tool.md +1 -1
- package/docs-site/docs/plates/processor.md +1 -1
- package/docs-site/docs/plates/record-map.md +1 -1
- package/docs-site/docs/plates/reflection-page.md +1 -1
- package/docs-site/docs/plates/repertoire.md +35 -0
- package/docs-site/docs/plates/reporting.md +1 -1
- package/docs-site/docs/plates/review.md +1 -1
- package/docs-site/docs/plates/routing.md +1 -1
- package/docs-site/docs/plates/session-capture.md +1 -1
- package/docs-site/docs/plates/site-manual.md +1 -1
- package/docs-site/docs/plates/stamp-plate.md +1 -1
- package/docs-site/docs/plates/station-card.md +1 -1
- package/docs-site/docs/plates/station-heat.md +1 -1
- package/docs-site/docs/plates/suit-organs.md +1 -1
- package/docs-site/docs/plates/suit-settings.md +1 -1
- package/docs-site/docs/plates/suit-wear.md +1 -1
- package/docs-site/docs/plates/suit.md +3 -3
- package/docs-site/docs/plates/synthesis.md +35 -0
- package/docs-site/docs/plates/temperament.md +35 -0
- package/docs-site/docs/plates/trail-state.md +2 -2
- package/docs-site/docs/plates/work-fresh.md +1 -1
- package/docs-site/docs/plates/write-home.md +1 -1
- package/llms.txt +14 -6
- package/package.json +3 -3
- package/scripts/foundry/README.md +5 -3
- package/scripts/foundry/mint-suit.cjs +120 -2
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/foundry/reconcile-version.mjs +10 -3
- package/scripts/foundry/release-gate.mjs +111 -15
- package/scripts/foundry/release.mjs +9 -1
- package/scripts/foundry/validate-release-docs.mjs +2 -2
- package/scripts/foundry/version-manager.mjs +12 -4
- package/scripts/mjs/verify-pipeline-facets.mjs +1 -1
- package/scripts/node/install-bridges.cjs +91 -46
- package/scripts/node/pack-tmp-suit-proof.mjs +37 -10
- package/src/integrations/openclaw/plugin/xray-pre-tool/index.js +26 -2
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +1 -7
- package/vendor/@0xray/repertoire/README.md +1 -1
- package/xray/agents_template.md +2 -2
- package/xray/features.json +8 -5
- package/xray/features.schema.json +8 -0
- 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
|
-
|
|
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,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
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
|
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-
|
|
114
|
+
this.log("nudge-monitor", "started", "info", { config: this.config });
|
|
90
115
|
}
|
|
91
116
|
stop() {
|
|
92
|
-
this.log("nudge-
|
|
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-
|
|
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
|
|
356
|
+
export const nudgeMonitor = new NudgeMonitor();
|
|
332
357
|
export function recordThinkTag() {
|
|
333
|
-
|
|
358
|
+
nudgeMonitor.recordThinkTag();
|
|
334
359
|
}
|
|
335
360
|
export function recordCodeChange() {
|
|
336
|
-
|
|
361
|
+
nudgeMonitor.recordCodeChange();
|
|
337
362
|
}
|
|
338
363
|
export function recordToolCall(tool, args) {
|
|
339
|
-
|
|
364
|
+
nudgeMonitor.recordToolCall(tool, args);
|
|
340
365
|
}
|
|
341
366
|
export function recordFixAttempt(filePath, error) {
|
|
342
|
-
|
|
367
|
+
nudgeMonitor.recordFixAttempt(filePath, error);
|
|
343
368
|
}
|
|
344
369
|
export function recordExplanation() {
|
|
345
|
-
|
|
370
|
+
nudgeMonitor.recordExplanation();
|
|
346
371
|
}
|
|
347
372
|
export function getNudgeSuggestion(type, context) {
|
|
348
|
-
return
|
|
373
|
+
return nudgeMonitor.getNudgeSuggestion(type, context);
|
|
349
374
|
}
|
|
350
|
-
export function
|
|
351
|
-
return
|
|
375
|
+
export function getNudgeMonitor() {
|
|
376
|
+
return nudgeMonitor;
|
|
352
377
|
}
|
|
353
378
|
export function recordProcessorFix(filePath, fixed, errorPersisted) {
|
|
354
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|