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
|
@@ -5,23 +5,40 @@
|
|
|
5
5
|
* Does not rely on sessionStart (unavailable on managed cloud).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
8
|
+
// Gate modules load inside main()'s try (dynamic import): a missing or broken module must deny, not crash
|
|
9
|
+
// before the handler with no JSON (Cursor would then fail open). Nothing here may import them statically.
|
|
10
|
+
let appendHookActivity = () => {};
|
|
11
|
+
|
|
12
|
+
/** Deny JSON on stdout and exit 0. Used when the gate cannot run at all. */
|
|
13
|
+
function denyHard(reason) {
|
|
14
|
+
try {
|
|
15
|
+
process.stdout.write(`${JSON.stringify({ permission: 'deny', user_message: reason, agent_message: reason })}\n`);
|
|
16
|
+
} finally {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function cursorHookErrorPermission(err) {
|
|
22
|
+
if (err?.code === 'EACCES') return 'allow'; // code only: a message mentioning EACCES is not a permission error
|
|
23
|
+
return 'deny';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Bootstrap: a fresh checkout has no build yet, so gate modules can be missing.
|
|
27
|
+
// Only the exact build commands may run then, so the hook cannot block the build that creates it.
|
|
28
|
+
const BOOTSTRAP_CMDS = new Set(['npm ci', 'npm run build', 'npm ci && npm run build', 'npm install && npm run build']);
|
|
29
|
+
export function cursorBootstrapPermission(err, event) {
|
|
30
|
+
if (err?.code !== 'ERR_MODULE_NOT_FOUND') return 'deny';
|
|
31
|
+
const cmd = String(event?.tool_input?.command ?? event?.command ?? '').trim().replace(/\s+/g, ' ');
|
|
32
|
+
return BOOTSTRAP_CMDS.has(cmd) ? 'allow' : 'deny';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function readRawEvent() {
|
|
36
|
+
try {
|
|
37
|
+
let raw = '';
|
|
38
|
+
for await (const c of process.stdin) raw += c;
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
} catch { return null; }
|
|
41
|
+
}
|
|
25
42
|
|
|
26
43
|
function finish(root, permission, reason, hint, toolName, extra = {}) {
|
|
27
44
|
const out = { permission };
|
|
@@ -31,23 +48,40 @@ function finish(root, permission, reason, hint, toolName, extra = {}) {
|
|
|
31
48
|
}
|
|
32
49
|
if (hint && permission === 'allow') {
|
|
33
50
|
out.user_message = hint;
|
|
51
|
+
out.agent_message = hint;
|
|
34
52
|
}
|
|
53
|
+
if (extra.hookSpecificOutput) out.hookSpecificOutput = extra.hookSpecificOutput;
|
|
35
54
|
console.log(JSON.stringify(out));
|
|
36
|
-
|
|
37
|
-
tool:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
try {
|
|
56
|
+
appendHookActivity(root, 'cursor-pre-tool-use', permission, permission === 'deny' ? 'error' : 'info', {
|
|
57
|
+
tool: toolName,
|
|
58
|
+
reason: reason || hint || null,
|
|
59
|
+
gate: extra.gate || null,
|
|
60
|
+
livePath: true,
|
|
61
|
+
});
|
|
62
|
+
} catch { /* the verdict is already on stdout; activity logging must not change it */ }
|
|
42
63
|
process.exit(0);
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
async function main() {
|
|
46
|
-
|
|
67
|
+
let fallbackRoot = process.cwd();
|
|
47
68
|
let toolName = 'unknown';
|
|
48
69
|
|
|
49
70
|
try {
|
|
71
|
+
const u = await import('./cursor-hook-utils.js');
|
|
72
|
+
const { lensDecision, repertoireRoute } = await import('../../hooks/pre-tool-speak.mjs');
|
|
73
|
+
const {
|
|
74
|
+
checkCodexPatterns, checkFullTestSuite, cursorGateRoot, cursorHeatRoots, cursorSessionId, cursorToolContext,
|
|
75
|
+
cursorWorkspaceRoot, ensureCursorSessionBoot, evaluatePreToolGate, isShellTool, isWriteTool,
|
|
76
|
+
loadDelegationGateFeatures, loadFeatures, readStdinJson,
|
|
77
|
+
} = u;
|
|
78
|
+
appendHookActivity = u.appendHookActivity;
|
|
79
|
+
fallbackRoot = cursorWorkspaceRoot();
|
|
50
80
|
const event = await readStdinJson();
|
|
81
|
+
// readStdinJson returns {} for empty or malformed stdin; Cursor always sends a JSON object, so deny those.
|
|
82
|
+
if (!event || typeof event !== 'object' || Array.isArray(event) || Object.keys(event).length === 0) {
|
|
83
|
+
finish(fallbackRoot, 'deny', 'preToolUse: empty or malformed hook input — denied', null, toolName, { gate: 'bad-input' });
|
|
84
|
+
}
|
|
51
85
|
const eventRoot = cursorWorkspaceRoot(event);
|
|
52
86
|
const sessionId = cursorSessionId(event);
|
|
53
87
|
const heatRoots = cursorHeatRoots(event);
|
|
@@ -60,7 +94,7 @@ async function main() {
|
|
|
60
94
|
const gateFeatures = loadDelegationGateFeatures(gateRoot, 'cursor');
|
|
61
95
|
const ctx = cursorToolContext(event);
|
|
62
96
|
toolName = ctx.toolName;
|
|
63
|
-
const { content, cmd, toolInput } = ctx;
|
|
97
|
+
const { content, cmd, toolInput, paths } = ctx;
|
|
64
98
|
|
|
65
99
|
const gateBlock = evaluatePreToolGate(toolName, toolInput, {
|
|
66
100
|
projectRoot: gateRoot,
|
|
@@ -77,35 +111,68 @@ async function main() {
|
|
|
77
111
|
if (extraBlock) finish(gateRoot, 'deny', extraBlock, null, toolName);
|
|
78
112
|
}
|
|
79
113
|
|
|
114
|
+
const spoken = [content, cmd, toolInput.pattern, toolInput.query, ...(paths || [])].filter(Boolean).join('\n');
|
|
115
|
+
const lensStop = lensDecision(gateRoot, toolName, spoken);
|
|
116
|
+
if (lensStop?.decision === 'deny') {
|
|
117
|
+
finish(gateRoot, 'deny', lensStop.reason, null, toolName, { gate: lensStop.gate });
|
|
118
|
+
}
|
|
119
|
+
if (lensStop?.decision === 'allow' && lensStop.reason) {
|
|
120
|
+
finish(gateRoot, 'allow', lensStop.reason, lensStop.reason, toolName, { gate: lensStop.gate });
|
|
121
|
+
}
|
|
122
|
+
const route = repertoireRoute([toolName, cmd, content].filter(Boolean).join(' '));
|
|
123
|
+
|
|
80
124
|
if (isShellTool(toolName) && cmd) {
|
|
81
125
|
const testHint = checkFullTestSuite(cmd, features);
|
|
82
|
-
if (testHint) finish(gateRoot, 'allow', null, testHint, toolName);
|
|
126
|
+
if (testHint) finish(gateRoot, 'allow', null, testHint, toolName, { ...(route || {}) });
|
|
83
127
|
}
|
|
84
128
|
|
|
85
129
|
if (gateBlock.reason) {
|
|
86
130
|
finish(gateRoot, 'allow', gateBlock.reason, gateBlock.hint, toolName, {
|
|
87
131
|
gate: gateBlock.gate,
|
|
88
132
|
warn: true,
|
|
133
|
+
...(route || {}),
|
|
89
134
|
});
|
|
90
135
|
}
|
|
91
136
|
|
|
92
|
-
finish(gateRoot, 'allow', null, null, toolName);
|
|
137
|
+
finish(gateRoot, 'allow', null, null, toolName, { ...(route || {}) });
|
|
93
138
|
} catch (err) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
139
|
+
if (err?.code === 'ERR_MODULE_NOT_FOUND' && cursorBootstrapPermission(err, await readRawEvent()) === 'allow') {
|
|
140
|
+
console.log(JSON.stringify({ permission: 'allow', user_message: 'preToolUse: build not present — bootstrap build allowed' }));
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
const failOpen = cursorHookErrorPermission(err) === 'allow';
|
|
144
|
+
try {
|
|
145
|
+
appendHookActivity(fallbackRoot, 'cursor-pre-tool-use', 'hook-error', 'error', {
|
|
146
|
+
tool: toolName,
|
|
147
|
+
error: err.message,
|
|
148
|
+
failOpen,
|
|
149
|
+
});
|
|
150
|
+
} catch { /* logging must not turn a deny into a crash */ }
|
|
151
|
+
if (failOpen) {
|
|
152
|
+
// Dest EACCES on a Cloud workspace wrapper must not deny every tool.
|
|
153
|
+
finish(
|
|
154
|
+
fallbackRoot,
|
|
155
|
+
'allow',
|
|
156
|
+
`preToolUse hook error — fail open: ${err.message}`,
|
|
157
|
+
null,
|
|
158
|
+
toolName,
|
|
159
|
+
{ gate: 'hook-error-fail-open' },
|
|
160
|
+
);
|
|
161
|
+
}
|
|
100
162
|
finish(
|
|
101
163
|
fallbackRoot,
|
|
102
|
-
'
|
|
103
|
-
`preToolUse hook error
|
|
164
|
+
'deny',
|
|
165
|
+
`preToolUse hook error blocked the tool: ${err.message}`,
|
|
104
166
|
null,
|
|
105
167
|
toolName,
|
|
106
|
-
{ gate: 'hook-error
|
|
168
|
+
{ gate: 'hook-error' },
|
|
107
169
|
);
|
|
108
170
|
}
|
|
109
171
|
}
|
|
110
172
|
|
|
111
|
-
|
|
173
|
+
const launchedAsCli = Boolean(process.argv[1] && process.argv[1].endsWith('pre-tool-use.js'));
|
|
174
|
+
if (launchedAsCli) {
|
|
175
|
+
process.on('uncaughtException', (err) => denyHard(`preToolUse hook crashed — denied: ${err?.message || err}`));
|
|
176
|
+
process.on('unhandledRejection', (err) => denyHard(`preToolUse hook crashed — denied: ${err?.message || err}`));
|
|
177
|
+
main().catch((err) => denyHard(`preToolUse hook crashed — denied: ${err?.message || err}`));
|
|
178
|
+
}
|
|
@@ -110,7 +110,15 @@ if [ -n "$MILL" ]; then
|
|
|
110
110
|
fi
|
|
111
111
|
|
|
112
112
|
if [ -z "$NODE_BIN" ] || [ -z "$MILL" ] || [ ! -f "${MILL}/${REL}" ]; then
|
|
113
|
-
|
|
113
|
+
# Gate events fail closed: no node or no hook file means the gate cannot run, so the tool is denied.
|
|
114
|
+
case "$EVENT" in
|
|
115
|
+
preToolUse|beforeShellExecution|beforeReadFile)
|
|
116
|
+
printf '{"permission":"deny","user_message":"xray-cloud-hook: node or hook file missing — denied (fail closed)","agent_message":"xray-cloud-hook: node or hook file missing — denied (fail closed)"}\n'
|
|
117
|
+
;;
|
|
118
|
+
*)
|
|
119
|
+
printf '{"permission":"allow","user_message":"xray-cloud-hook: node or hook file missing — %s skipped"}\n' "$EVENT"
|
|
120
|
+
;;
|
|
121
|
+
esac
|
|
114
122
|
exit 0
|
|
115
123
|
fi
|
|
116
124
|
|
|
@@ -80,17 +80,15 @@ export async function installForGrokCLI(options = {}) {
|
|
|
80
80
|
frameworkLogger.log('grok-integration', 'plugin-copied', 'info', { destination: dest });
|
|
81
81
|
console.log(`\x1b[32m✓ Copied Grok plugin to ${dest}\x1b[0m`);
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
console.log(`\x1b[32m✓ Synced ${skillsCopied} builtin skills to Grok plugin\x1b[0m`);
|
|
87
|
-
}
|
|
88
|
-
frameworkLogger.log('grok-integration', 'skills-synced', 'info', { count: skillsCopied });
|
|
83
|
+
const skillsCopied = syncBuiltinSkills(path.join(dest, 'skills'));
|
|
84
|
+
if (skillsCopied > 0) {
|
|
85
|
+
console.log(`\x1b[32m✓ Synced ${skillsCopied} builtin skills to Grok plugin\x1b[0m`);
|
|
89
86
|
}
|
|
87
|
+
frameworkLogger.log('grok-integration', 'skills-synced', 'info', { count: skillsCopied });
|
|
90
88
|
pinGrokPluginToInstalledDist(dest, packageRoot, targetDir);
|
|
91
89
|
wearGrokHookCommands(dest, packageRoot, targetDir);
|
|
92
90
|
}
|
|
93
|
-
if (isolated
|
|
91
|
+
if (isolated) {
|
|
94
92
|
const home = millSuit.processHome(env);
|
|
95
93
|
const globalCopied = syncBuiltinSkills(path.join(home, '.grok', 'skills'));
|
|
96
94
|
if (globalCopied > 0) {
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
retainCompactFields,
|
|
29
29
|
writeStationMarkdown,
|
|
30
30
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
31
|
-
import { handCard } from '../../hooks/goggles-pipeline.mjs';
|
|
31
|
+
import { handCard, planeCard } from '../../hooks/goggles-pipeline.mjs';
|
|
32
32
|
|
|
33
33
|
export {
|
|
34
34
|
checkPendingDelegationGate,
|
|
@@ -315,6 +315,7 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
|
|
|
315
315
|
const existing = readExistingBoot(root);
|
|
316
316
|
const heat = applyStationHeat(root, extra.host || 'grok', extra, existing);
|
|
317
317
|
const compactHold = retainCompactFields(existing, extra);
|
|
318
|
+
const shown = planeCard(root);
|
|
318
319
|
return {
|
|
319
320
|
lead_dev_mode: features.lead_dev_mode,
|
|
320
321
|
no_new_surface: features.no_new_surface,
|
|
@@ -348,6 +349,8 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
|
|
|
348
349
|
hook: source,
|
|
349
350
|
source,
|
|
350
351
|
timestamp: new Date().toISOString(),
|
|
352
|
+
plane: shown.plane,
|
|
353
|
+
card: shown.card,
|
|
351
354
|
};
|
|
352
355
|
}
|
|
353
356
|
|
|
@@ -21,66 +21,10 @@ import {
|
|
|
21
21
|
workspaceRoot,
|
|
22
22
|
} from './grok-hook-utils.js';
|
|
23
23
|
import { appendHookActivity } from './grok-hook-activity.js';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { existsSync } from 'node:fs';
|
|
27
|
-
import { dirname, join } from 'node:path';
|
|
28
|
-
import { fileURLToPath } from 'node:url';
|
|
29
|
-
|
|
30
|
-
const hookDir = dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
let scoreAndRouteFn;
|
|
32
|
-
|
|
33
|
-
function loadScoreAndRoute() {
|
|
34
|
-
if (scoreAndRouteFn !== undefined) return scoreAndRouteFn;
|
|
35
|
-
scoreAndRouteFn = null;
|
|
36
|
-
const require = createRequire(import.meta.url);
|
|
37
|
-
const candidates = [
|
|
38
|
-
join(hookDir, '../../../nucleus/thin-dispatch.js'),
|
|
39
|
-
join(hookDir, '../../../../dist/nucleus/thin-dispatch.js'),
|
|
40
|
-
];
|
|
41
|
-
for (const candidate of candidates) {
|
|
42
|
-
if (!existsSync(candidate)) continue;
|
|
43
|
-
try {
|
|
44
|
-
const loaded = require(candidate);
|
|
45
|
-
if (typeof loaded.scoreAndRoute === 'function') {
|
|
46
|
-
scoreAndRouteFn = loaded.scoreAndRoute;
|
|
47
|
-
return scoreAndRouteFn;
|
|
48
|
-
}
|
|
49
|
-
} catch {
|
|
50
|
-
/* next built copy */
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Speak only when the organ changes the agent. A match that leaves the agent is quiet. */
|
|
57
|
-
export function repertoireDecisionFields(routed) {
|
|
58
|
-
if (!routed?.memoryRouting?.overridden || !routed.agent) return null;
|
|
59
|
-
const signals = Array.isArray(routed.memoryRouting.signals)
|
|
60
|
-
? routed.memoryRouting.signals.slice(0, 4).filter(Boolean)
|
|
61
|
-
: [];
|
|
62
|
-
const why = signals.length ? ` on ${signals.join(', ')}` : '';
|
|
63
|
-
return {
|
|
64
|
-
agent: routed.agent,
|
|
65
|
-
gate: 'repertoire',
|
|
66
|
-
hookSpecificOutput: {
|
|
67
|
-
hookEventName: 'PreToolUse',
|
|
68
|
-
additionalContext: `Repertoire changed the route to ${routed.agent}${why}. Follow that agent for this work.`,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
24
|
+
import { lensBeforeResearch } from '../../hooks/goggles-pipeline.mjs';
|
|
25
|
+
import { repertoireDecisionFields, repertoireRoute } from '../../hooks/pre-tool-speak.mjs';
|
|
72
26
|
|
|
73
|
-
|
|
74
|
-
const text = String(operation || '').slice(0, 500).trim();
|
|
75
|
-
if (!text) return null;
|
|
76
|
-
try {
|
|
77
|
-
const scoreAndRoute = loadScoreAndRoute();
|
|
78
|
-
if (!scoreAndRoute) return null;
|
|
79
|
-
return repertoireDecisionFields(scoreAndRoute(text, {}));
|
|
80
|
-
} catch {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
27
|
+
export { repertoireDecisionFields };
|
|
84
28
|
|
|
85
29
|
function finish(root, decision, reason, hint, toolName, extra = {}) {
|
|
86
30
|
const out = { decision, ...extra };
|
|
@@ -137,28 +81,14 @@ async function main() {
|
|
|
137
81
|
if (extraBlock) finish(eventRoot, 'deny', extraBlock, null, toolName);
|
|
138
82
|
}
|
|
139
83
|
|
|
140
|
-
const stop = cardStop(
|
|
141
|
-
eventRoot,
|
|
142
|
-
toolName,
|
|
143
|
-
[cmd, content, toolInput.pattern, toolInput.query, ...(ctx.paths || [])].filter(Boolean).join('\n'),
|
|
144
|
-
ctx.paths || [],
|
|
145
|
-
toolInput,
|
|
146
|
-
);
|
|
147
|
-
if (stop?.decision === 'deny') {
|
|
148
|
-
finish(eventRoot, 'deny', stop.reason, null, toolName, { gate: stop.gate });
|
|
149
|
-
}
|
|
150
|
-
if (stop?.decision === 'allow') {
|
|
151
|
-
finish(eventRoot, 'allow', null, null, toolName, {
|
|
152
|
-
gate: stop.gate,
|
|
153
|
-
hookSpecificOutput: stop.hookSpecificOutput,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
84
|
const spoken = [cmd, content, toolInput.pattern, toolInput.query, ...(ctx.paths || [])].filter(Boolean).join('\n');
|
|
158
85
|
const lensStop = lensBeforeResearch(eventRoot, toolName, spoken);
|
|
159
86
|
if (lensStop?.decision === 'deny') {
|
|
160
87
|
finish(eventRoot, 'deny', lensStop.reason, null, toolName, { gate: lensStop.gate });
|
|
161
88
|
}
|
|
89
|
+
if (lensStop?.decision === 'allow' && lensStop.reason) {
|
|
90
|
+
finish(eventRoot, 'allow', lensStop.reason, lensStop.reason, toolName, { gate: lensStop.gate });
|
|
91
|
+
}
|
|
162
92
|
|
|
163
93
|
const route = repertoireRoute([toolName, cmd, content].filter(Boolean).join(' '));
|
|
164
94
|
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
runInferenceImprovementLight,
|
|
25
25
|
} from '../../hooks/pipeline-hook-runtime.mjs';
|
|
26
26
|
import { maintainLens } from '../../hooks/goggles-pipeline.mjs';
|
|
27
|
-
import { readSavedFreshness, refreshFreshness } from '../../../nucleus/work-freshness.mjs';
|
|
27
|
+
import { freshnessNeedsRefresh, readSavedFreshness, refreshFreshness } from '../../../nucleus/work-freshness.mjs';
|
|
28
28
|
|
|
29
29
|
function extractIntent(event) {
|
|
30
30
|
return event.prompt || event.userMessage || event.user_prompt || event.compactContext || null;
|
|
@@ -137,7 +137,9 @@ async function main() {
|
|
|
137
137
|
let freshnessLine = null;
|
|
138
138
|
if (!compact) {
|
|
139
139
|
try {
|
|
140
|
-
|
|
140
|
+
const refresh = HOOK_EVENT === 'session_start'
|
|
141
|
+
|| (HOOK_EVENT === 'user_prompt_submit' && freshnessNeedsRefresh(eventRoot));
|
|
142
|
+
freshnessLine = refresh
|
|
141
143
|
? refreshFreshness(eventRoot).freshnessLine
|
|
142
144
|
: readSavedFreshness(eventRoot);
|
|
143
145
|
} catch {
|
|
@@ -914,9 +914,25 @@ async function handleDelegationGate(command, projectRoot, logDir) {
|
|
|
914
914
|
logDir,
|
|
915
915
|
`[delegation-gate] deny tool=${tool} gate=${outcome.gate} reason=${outcome.reason}`,
|
|
916
916
|
);
|
|
917
|
+
return { phase: "pre", ...outcome };
|
|
917
918
|
}
|
|
918
919
|
|
|
919
|
-
|
|
920
|
+
const spoken = `${tool} ${JSON.stringify(args)}`.slice(0, 500);
|
|
921
|
+
const { lensDecision, repertoireRoute } = await import("../hooks/pre-tool-speak.mjs");
|
|
922
|
+
const lens = lensDecision(projectRoot, tool, spoken);
|
|
923
|
+
if (lens?.decision === "deny") {
|
|
924
|
+
return {
|
|
925
|
+
allow: false,
|
|
926
|
+
phase: "pre",
|
|
927
|
+
gate: lens.gate || "lens",
|
|
928
|
+
reason: lens.reason,
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
if (lens?.decision === "allow" && lens.reason) {
|
|
932
|
+
return { phase: "pre", ...outcome, gate: "lens", reason: lens.reason };
|
|
933
|
+
}
|
|
934
|
+
const route = repertoireRoute(spoken);
|
|
935
|
+
return { phase: "pre", ...outcome, ...(route || {}) };
|
|
920
936
|
} catch (error) {
|
|
921
937
|
const message = `delegation-gate failed: ${error.message || error}`;
|
|
922
938
|
logToActivity(logDir, `[delegation-gate] error ${message}`);
|
|
@@ -68,8 +68,9 @@ function loadDelegationGate() {
|
|
|
68
68
|
].filter(Boolean);
|
|
69
69
|
const found = candidates.find((p) => existsSync(p));
|
|
70
70
|
if (!found) {
|
|
71
|
-
throw
|
|
72
|
-
'delegation-gate.js missing — run npm run build in 0xray or npm install 0xray',
|
|
71
|
+
throw Object.assign(
|
|
72
|
+
new Error('delegation-gate.js missing — run npm run build in 0xray or npm install 0xray'),
|
|
73
|
+
{ code: 'ERR_MODULE_NOT_FOUND' },
|
|
73
74
|
);
|
|
74
75
|
}
|
|
75
76
|
return createRequire(import.meta.url)(found);
|