0xray 4.0.19 → 4.0.21
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/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +1 -1
- package/.xray/features.json +1 -1
- package/AGENTS-consumer.md +4 -4
- package/AGENTS.md +4 -4
- package/README.md +14 -14
- package/SKILLS.md +3 -3
- package/dist/AGENTS.md +4 -4
- package/dist/CHANGELOG.md +27 -6
- package/dist/README.md +14 -14
- package/dist/SKILLS.md +3 -3
- package/dist/cli/commands/validate.d.ts +1 -1
- package/dist/cli/commands/validate.js +7 -1
- package/dist/integrations/cursor/README.md +6 -5
- package/dist/integrations/cursor/hooks/after-file-edit.js +6 -3
- package/dist/integrations/cursor/hooks/after-file-edit.sh +3 -0
- package/dist/integrations/cursor/hooks/before-read-file.sh +3 -0
- package/dist/integrations/cursor/hooks/before-shell-execution.sh +3 -0
- package/dist/integrations/cursor/hooks/cursor-hook-utils.js +121 -20
- package/dist/integrations/cursor/hooks/cursor-usage-receipt.js +3 -0
- package/dist/integrations/cursor/hooks/hooks.json +13 -3
- package/dist/integrations/cursor/hooks/pre-compact.js +39 -22
- package/dist/integrations/cursor/hooks/pre-compact.sh +3 -0
- package/dist/integrations/cursor/hooks/pre-tool-use.js +20 -15
- package/dist/integrations/cursor/hooks/pre-tool-use.sh +3 -0
- package/dist/integrations/cursor/hooks/xray-cloud-hook.sh +63 -0
- package/dist/integrations/grok/hooks/grok-hook-utils.js +6 -3
- package/dist/integrations/hermes-agent/bridge.mjs +14 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +416 -37
- package/dist/integrations/hooks/station-hook-runtime.mjs +15 -0
- package/dist/integrations/hooks/station-memory-ingest.mjs +152 -4
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/nucleus/delegation-gate.js +10 -11
- package/dist/nucleus/suit-temperament.d.ts +0 -5
- package/dist/nucleus/suit-temperament.js +18 -2
- package/dist/skills/orchestrator/SKILL.md +1 -1
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/scripts/foundry/README.md +2 -2
- package/scripts/foundry/assert-packed-dist-cli.mjs +4 -0
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/foundry/validate-release-docs.mjs +44 -0
- package/scripts/node/install-bridges.cjs +256 -10
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/skills/orchestrator/SKILL.md +1 -1
- package/xray/features.json +1 -1
|
@@ -23,13 +23,12 @@ import {
|
|
|
23
23
|
} from '../../hooks/delegation-gate-runtime.mjs';
|
|
24
24
|
import { appendHookActivity } from '../../grok/hooks/grok-hook-activity.js';
|
|
25
25
|
import {
|
|
26
|
-
|
|
26
|
+
heatLiveMemory,
|
|
27
27
|
maybeCaptureSessionOnHeadMove,
|
|
28
|
-
|
|
29
|
-
stationDurableHoldsNpm,
|
|
28
|
+
stationBootNeedsRefresh,
|
|
30
29
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
31
30
|
|
|
32
|
-
export { maybeCaptureSessionOnHeadMove };
|
|
31
|
+
export { heatLiveMemory, maybeCaptureSessionOnHeadMove };
|
|
33
32
|
|
|
34
33
|
export const CURSOR_HOST = 'cursor';
|
|
35
34
|
export const EVENT_CLASS_HOST = 'cursor-host-precompact';
|
|
@@ -77,6 +76,112 @@ export function cursorWorkspaceRoot(event = {}) {
|
|
|
77
76
|
);
|
|
78
77
|
}
|
|
79
78
|
|
|
79
|
+
function addHeatRoot(out, seen, value) {
|
|
80
|
+
if (!value) return;
|
|
81
|
+
const resolved = path.resolve(String(value));
|
|
82
|
+
if (seen.has(resolved)) return;
|
|
83
|
+
seen.add(resolved);
|
|
84
|
+
out.push(resolved);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isCursorWorkspaceWrapper(root) {
|
|
88
|
+
if (!root) return false;
|
|
89
|
+
const resolved = path.resolve(String(root));
|
|
90
|
+
return (
|
|
91
|
+
fs.existsSync(path.join(resolved, 'repos', 'xray')) ||
|
|
92
|
+
fs.existsSync(path.join(resolved, 'repos', 'repertoire'))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function heatRootHasCard(root) {
|
|
97
|
+
if (!root) return false;
|
|
98
|
+
const resolved = path.resolve(String(root));
|
|
99
|
+
return (
|
|
100
|
+
fs.existsSync(path.join(resolved, '.xray', 'state', 'STATION.md')) ||
|
|
101
|
+
fs.existsSync(path.join(resolved, '.xray', 'features.json'))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isHeatRootWritable(root) {
|
|
106
|
+
if (!root) return false;
|
|
107
|
+
const resolved = path.resolve(String(root));
|
|
108
|
+
try {
|
|
109
|
+
const probe = path.join(resolved, '.xray', 'state');
|
|
110
|
+
fs.mkdirSync(probe, { recursive: true });
|
|
111
|
+
fs.accessSync(probe, fs.constants.W_OK);
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function shouldHeatRoot(root) {
|
|
119
|
+
if (!root) return false;
|
|
120
|
+
const resolved = path.resolve(String(root));
|
|
121
|
+
if (isCursorWorkspaceWrapper(resolved)) return false;
|
|
122
|
+
if (heatRootHasCard(resolved)) return isHeatRootWritable(resolved);
|
|
123
|
+
return isHeatRootWritable(resolved);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function millRootFromToolPath(filePath) {
|
|
127
|
+
if (!filePath) return null;
|
|
128
|
+
const resolved = path.resolve(String(filePath));
|
|
129
|
+
const marker = `${path.sep}repos${path.sep}`;
|
|
130
|
+
const idx = resolved.indexOf(marker);
|
|
131
|
+
if (idx === -1) return null;
|
|
132
|
+
const after = resolved.slice(idx + marker.length);
|
|
133
|
+
const repo = after.split(path.sep)[0];
|
|
134
|
+
if (!repo) return null;
|
|
135
|
+
return resolved.slice(0, idx + marker.length + repo.length);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addMillCardsUnderWrapper(out, seen, wrapper) {
|
|
139
|
+
if (!wrapper || !isCursorWorkspaceWrapper(wrapper)) return;
|
|
140
|
+
const mill = path.join(path.resolve(String(wrapper)), 'repos', 'xray');
|
|
141
|
+
if (heatRootHasCard(mill) && isHeatRootWritable(mill)) addHeatRoot(out, seen, mill);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Every live context that already has a Station card — cwd plus mill when they differ. */
|
|
145
|
+
export function cursorHeatRoots(event = {}) {
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
const out = [];
|
|
148
|
+
const candidates = [event.cwd, event.workspaceRoot];
|
|
149
|
+
const listed = event.workspace_roots || event.workspaceRoots;
|
|
150
|
+
if (Array.isArray(listed)) {
|
|
151
|
+
for (const row of listed) {
|
|
152
|
+
if (typeof row === 'string') candidates.push(row);
|
|
153
|
+
else if (row && typeof row === 'object' && row.path) candidates.push(row.path);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
candidates.push(process.env.CURSOR_PROJECT_DIR);
|
|
157
|
+
for (const value of candidates) {
|
|
158
|
+
if (shouldHeatRoot(value)) addHeatRoot(out, seen, value);
|
|
159
|
+
addMillCardsUnderWrapper(out, seen, value);
|
|
160
|
+
}
|
|
161
|
+
const mill = process.env.XRAY_AI_PATH || process.env.XRAY_ROOT;
|
|
162
|
+
if (mill) {
|
|
163
|
+
const resolved = path.resolve(String(mill));
|
|
164
|
+
if (shouldHeatRoot(resolved)) addHeatRoot(out, seen, resolved);
|
|
165
|
+
}
|
|
166
|
+
const ctx = cursorToolContext(event);
|
|
167
|
+
for (const filePath of ctx.paths) {
|
|
168
|
+
const fromPath = millRootFromToolPath(filePath);
|
|
169
|
+
if (
|
|
170
|
+
fromPath &&
|
|
171
|
+
fs.existsSync(path.join(fromPath, '.xray', 'state', 'STATION.md')) &&
|
|
172
|
+
isHeatRootWritable(fromPath)
|
|
173
|
+
) {
|
|
174
|
+
addHeatRoot(out, seen, fromPath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (out.length === 0) {
|
|
178
|
+
const fallback = cursorWorkspaceRoot(event);
|
|
179
|
+
if (shouldHeatRoot(fallback)) addHeatRoot(out, seen, fallback);
|
|
180
|
+
addMillCardsUnderWrapper(out, seen, fallback);
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
80
185
|
export function cursorSessionId(event = {}) {
|
|
81
186
|
return (
|
|
82
187
|
event.conversation_id ||
|
|
@@ -88,6 +193,10 @@ export function cursorSessionId(event = {}) {
|
|
|
88
193
|
);
|
|
89
194
|
}
|
|
90
195
|
|
|
196
|
+
export function cursorGenerationId(event = {}) {
|
|
197
|
+
return event.generation_id || event.generationId || null;
|
|
198
|
+
}
|
|
199
|
+
|
|
91
200
|
export function cursorToolContext(event = {}) {
|
|
92
201
|
const toolName = event.tool_name || event.toolName || process.env.TOOL_NAME || 'unknown';
|
|
93
202
|
const toolInput = event.tool_input ?? event.toolInput ?? {};
|
|
@@ -141,27 +250,21 @@ export function classifyPreCompactEvent(event = {}, argv = process.argv) {
|
|
|
141
250
|
}
|
|
142
251
|
|
|
143
252
|
export function cursorBootNeedsRefresh(existing, root) {
|
|
144
|
-
|
|
145
|
-
if (existing.host !== CURSOR_HOST) return true;
|
|
146
|
-
if (!existing.suit_profile) return true;
|
|
147
|
-
if (existing.workspaceRoot && existing.workspaceRoot !== root) return true;
|
|
148
|
-
if (!existing.stationLine) return true;
|
|
149
|
-
const liveGit = readGitBrief(root);
|
|
150
|
-
const bootHead = existing.git && existing.git.head ? String(existing.git.head) : '';
|
|
151
|
-
if (liveGit && liveGit.head && bootHead !== liveGit.head) return true;
|
|
152
|
-
const liveResume = buildRepertoireResume(root);
|
|
153
|
-
if (liveResume && existing.repertoireResume && liveResume !== existing.repertoireResume) return true;
|
|
154
|
-
if (stationDurableHoldsNpm(root)) return true;
|
|
155
|
-
return false;
|
|
253
|
+
return stationBootNeedsRefresh(existing, root, CURSOR_HOST);
|
|
156
254
|
}
|
|
157
255
|
|
|
158
256
|
export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-use-boot', extra = {}) {
|
|
159
257
|
const bootPath = sessionBootPath(root);
|
|
258
|
+
if (!shouldHeatRoot(root)) return bootPath;
|
|
160
259
|
if (fs.existsSync(bootPath)) {
|
|
161
260
|
try {
|
|
162
261
|
const existing = JSON.parse(fs.readFileSync(bootPath, 'utf8'));
|
|
163
262
|
if (!cursorBootNeedsRefresh(existing, root)) {
|
|
164
|
-
|
|
263
|
+
try {
|
|
264
|
+
heatLiveMemory(root);
|
|
265
|
+
} catch {
|
|
266
|
+
/* dest EACCES must not deny the host tool */
|
|
267
|
+
}
|
|
165
268
|
return bootPath;
|
|
166
269
|
}
|
|
167
270
|
} catch {
|
|
@@ -172,9 +275,7 @@ export function ensureCursorSessionBoot(root, source = '0xray/cursor-pre-tool-us
|
|
|
172
275
|
host: CURSOR_HOST,
|
|
173
276
|
...extra,
|
|
174
277
|
});
|
|
175
|
-
|
|
176
|
-
maybeCaptureSessionOnHeadMove(root);
|
|
177
|
-
return written;
|
|
278
|
+
return writeSessionBoot(root, payload) || bootPath;
|
|
178
279
|
}
|
|
179
280
|
|
|
180
281
|
export function writeCursorPrecompactReceipt(root, fields) {
|
|
@@ -69,6 +69,9 @@ export function hostUsageFromPreCompactEvent(event = {}) {
|
|
|
69
69
|
if (typeof event.windowCite === 'string' && event.windowCite) usage.windowCite = event.windowCite;
|
|
70
70
|
if (typeof event.model === 'string' && event.model) usage.model = event.model;
|
|
71
71
|
if (typeof event.bcId === 'string' && event.bcId) usage.bcId = event.bcId;
|
|
72
|
+
else if (typeof event.conversation_id === 'string' && event.conversation_id) {
|
|
73
|
+
usage.bcId = event.conversation_id;
|
|
74
|
+
}
|
|
72
75
|
return usage;
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
"hooks": {
|
|
4
4
|
"preToolUse": [
|
|
5
5
|
{
|
|
6
|
-
"command": "
|
|
6
|
+
"command": ".cursor/hooks/pre-tool-use.sh"
|
|
7
7
|
}
|
|
8
8
|
],
|
|
9
9
|
"preCompact": [
|
|
10
10
|
{
|
|
11
|
-
"command": "
|
|
11
|
+
"command": ".cursor/hooks/pre-compact.sh"
|
|
12
12
|
}
|
|
13
13
|
],
|
|
14
14
|
"afterFileEdit": [
|
|
15
15
|
{
|
|
16
|
-
"command": "
|
|
16
|
+
"command": ".cursor/hooks/after-file-edit.sh"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"beforeShellExecution": [
|
|
20
|
+
{
|
|
21
|
+
"command": ".cursor/hooks/before-shell-execution.sh"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"beforeReadFile": [
|
|
25
|
+
{
|
|
26
|
+
"command": ".cursor/hooks/before-read-file.sh"
|
|
17
27
|
}
|
|
18
28
|
]
|
|
19
29
|
}
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
appendHookActivity,
|
|
10
10
|
buildSessionBootPayload,
|
|
11
11
|
classifyPreCompactEvent,
|
|
12
|
+
cursorGenerationId,
|
|
13
|
+
cursorHeatRoots,
|
|
12
14
|
cursorSessionId,
|
|
13
15
|
cursorWorkspaceRoot,
|
|
14
16
|
readStdinJson,
|
|
@@ -39,34 +41,49 @@ async function main() {
|
|
|
39
41
|
eventClass = classifyPreCompactEvent(event);
|
|
40
42
|
const eventRoot = cursorWorkspaceRoot(event);
|
|
41
43
|
const sessionId = cursorSessionId(event);
|
|
44
|
+
const generationId = cursorGenerationId(event);
|
|
42
45
|
const intent = extractIntent(event);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
46
|
+
let bootPath = null;
|
|
47
|
+
let receiptPath = null;
|
|
48
|
+
let usagePath = null;
|
|
49
|
+
let stationLine = null;
|
|
50
|
+
for (const root of cursorHeatRoots(event)) {
|
|
51
|
+
const payload = buildSessionBootPayload(root, '0xray/cursor-compact', {
|
|
52
|
+
host: 'cursor',
|
|
53
|
+
hookEvent: 'pre_compact',
|
|
54
|
+
event_class: eventClass,
|
|
55
|
+
sessionId,
|
|
56
|
+
...(intent ? { intent } : {}),
|
|
57
|
+
});
|
|
58
|
+
bootPath = writeSessionBoot(root, payload);
|
|
59
|
+
stationLine = payload.stationLine;
|
|
60
|
+
receiptPath = writeCursorPrecompactReceipt(root, {
|
|
61
|
+
event_class: eventClass,
|
|
62
|
+
hookEvent: 'pre_compact',
|
|
63
|
+
trigger: event.trigger || 'synthetic',
|
|
64
|
+
sessionId,
|
|
65
|
+
conversation_id: sessionId,
|
|
66
|
+
generation_id: generationId,
|
|
67
|
+
is_first_compaction:
|
|
68
|
+
typeof event.is_first_compaction === 'boolean' ? event.is_first_compaction : null,
|
|
69
|
+
message_count: typeof event.message_count === 'number' ? event.message_count : null,
|
|
70
|
+
messages_to_compact:
|
|
71
|
+
typeof event.messages_to_compact === 'number' ? event.messages_to_compact : null,
|
|
72
|
+
bootPath,
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
});
|
|
75
|
+
usagePath = writeCursorUsageReceipt(root, {
|
|
76
|
+
eventClass,
|
|
77
|
+
sessionId,
|
|
78
|
+
usage: hostUsageFromPreCompactEvent(event),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
64
81
|
appendHookActivity(eventRoot, 'cursor-pre-compact', 'station-written', 'success', {
|
|
65
82
|
bootPath,
|
|
66
83
|
receiptPath,
|
|
67
84
|
usagePath,
|
|
68
85
|
event_class: eventClass,
|
|
69
|
-
stationLine
|
|
86
|
+
stationLine,
|
|
70
87
|
});
|
|
71
88
|
console.log(
|
|
72
89
|
JSON.stringify({
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
appendHookActivity,
|
|
10
10
|
checkCodexPatterns,
|
|
11
11
|
checkFullTestSuite,
|
|
12
|
+
cursorHeatRoots,
|
|
12
13
|
cursorSessionId,
|
|
13
14
|
cursorToolContext,
|
|
14
15
|
cursorWorkspaceRoot,
|
|
@@ -47,57 +48,61 @@ async function main() {
|
|
|
47
48
|
try {
|
|
48
49
|
const event = await readStdinJson();
|
|
49
50
|
const eventRoot = cursorWorkspaceRoot(event);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const sessionId = cursorSessionId(event);
|
|
52
|
+
const heatRoots = cursorHeatRoots(event);
|
|
53
|
+
for (const root of heatRoots) {
|
|
54
|
+
ensureCursorSessionBoot(root, '0xray/cursor-pre-tool-use-boot', { sessionId });
|
|
55
|
+
}
|
|
56
|
+
const gateRoot = heatRoots[0] || eventRoot;
|
|
53
57
|
|
|
54
|
-
const features = loadFeatures(
|
|
55
|
-
const gateFeatures = loadDelegationGateFeatures(
|
|
58
|
+
const features = loadFeatures(gateRoot);
|
|
59
|
+
const gateFeatures = loadDelegationGateFeatures(gateRoot, 'cursor');
|
|
56
60
|
const ctx = cursorToolContext(event);
|
|
57
61
|
toolName = ctx.toolName;
|
|
58
62
|
const { content, cmd, toolInput } = ctx;
|
|
59
|
-
const sessionId = cursorSessionId(event);
|
|
60
63
|
|
|
61
64
|
const gateBlock = evaluatePreToolGate(toolName, toolInput, {
|
|
62
|
-
projectRoot:
|
|
65
|
+
projectRoot: gateRoot,
|
|
63
66
|
sessionId,
|
|
64
67
|
features: gateFeatures,
|
|
65
68
|
host: 'cursor',
|
|
66
69
|
});
|
|
67
70
|
if (!gateBlock.allow) {
|
|
68
|
-
finish(
|
|
71
|
+
finish(gateRoot, 'deny', gateBlock.reason, gateBlock.hint, toolName, { gate: gateBlock.gate });
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
if (isWriteTool(toolName) && content) {
|
|
72
75
|
const extraBlock = checkCodexPatterns(content, { terms: [11, 29] });
|
|
73
|
-
if (extraBlock) finish(
|
|
76
|
+
if (extraBlock) finish(gateRoot, 'deny', extraBlock, null, toolName);
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
if (isShellTool(toolName) && cmd) {
|
|
77
80
|
const testHint = checkFullTestSuite(cmd, features);
|
|
78
|
-
if (testHint) finish(
|
|
81
|
+
if (testHint) finish(gateRoot, 'allow', null, testHint, toolName);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
if (gateBlock.reason) {
|
|
82
|
-
finish(
|
|
85
|
+
finish(gateRoot, 'allow', gateBlock.reason, gateBlock.hint, toolName, {
|
|
83
86
|
gate: gateBlock.gate,
|
|
84
87
|
warn: true,
|
|
85
88
|
});
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
finish(
|
|
91
|
+
finish(gateRoot, 'allow', null, null, toolName);
|
|
89
92
|
} catch (err) {
|
|
90
93
|
appendHookActivity(fallbackRoot, 'cursor-pre-tool-use', 'hook-error', 'error', {
|
|
91
94
|
tool: toolName,
|
|
92
95
|
error: err.message,
|
|
96
|
+
failOpen: true,
|
|
93
97
|
});
|
|
98
|
+
// Dest EACCES on a Cloud workspace wrapper must not deny every tool.
|
|
94
99
|
finish(
|
|
95
100
|
fallbackRoot,
|
|
96
|
-
'
|
|
97
|
-
`preToolUse hook error —
|
|
101
|
+
'allow',
|
|
102
|
+
`preToolUse hook error — fail open: ${err.message}`,
|
|
98
103
|
null,
|
|
99
104
|
toolName,
|
|
100
|
-
{ gate: 'hook-error' },
|
|
105
|
+
{ gate: 'hook-error-fail-open' },
|
|
101
106
|
);
|
|
102
107
|
}
|
|
103
108
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Cloud-safe Cursor hook runner. Command in hooks.json must be a relative path.
|
|
3
|
+
# Do not put env-assignment in hooks.json — Cloud execs argv[0] without a shell.
|
|
4
|
+
set -eu
|
|
5
|
+
|
|
6
|
+
EVENT="${1:-preToolUse}"
|
|
7
|
+
JS="${2:-pre-tool-use.js}"
|
|
8
|
+
MILL=""
|
|
9
|
+
REL=""
|
|
10
|
+
|
|
11
|
+
pick_mill() {
|
|
12
|
+
mill="$1"
|
|
13
|
+
rel="$2"
|
|
14
|
+
if [ -f "${mill}/${rel}" ]; then
|
|
15
|
+
MILL="$mill"
|
|
16
|
+
REL="$rel"
|
|
17
|
+
return 0
|
|
18
|
+
fi
|
|
19
|
+
return 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if [ -n "${XRAY_AI_PATH:-}" ]; then
|
|
23
|
+
pick_mill "${XRAY_AI_PATH}" "src/integrations/cursor/hooks/${JS}" || \
|
|
24
|
+
pick_mill "${XRAY_AI_PATH}" "dist/integrations/cursor/hooks/${JS}" || true
|
|
25
|
+
fi
|
|
26
|
+
if [ -z "$MILL" ]; then
|
|
27
|
+
pick_mill "$(pwd)" "src/integrations/cursor/hooks/${JS}" || \
|
|
28
|
+
pick_mill "$(pwd)/../xray" "src/integrations/cursor/hooks/${JS}" || \
|
|
29
|
+
pick_mill "$(pwd)/repos/xray" "src/integrations/cursor/hooks/${JS}" || \
|
|
30
|
+
pick_mill "${CURSOR_PROJECT_DIR:-}/repos/xray" "src/integrations/cursor/hooks/${JS}" || \
|
|
31
|
+
pick_mill "$(pwd)/node_modules/0xray" "dist/integrations/cursor/hooks/${JS}" || \
|
|
32
|
+
pick_mill "$(pwd)/node_modules/0xray" "src/integrations/cursor/hooks/${JS}" || true
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [ -n "$MILL" ] && [ -d "$MILL" ]; then
|
|
36
|
+
MILL="$(cd "$MILL" && pwd)"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
|
40
|
+
|
|
41
|
+
log_invoke() {
|
|
42
|
+
dest="$1"
|
|
43
|
+
mkdir -p "${dest}/.xray/state" 2>/dev/null || true
|
|
44
|
+
{
|
|
45
|
+
printf 'ts=%s event=%s cwd=%s mill=%s node=%s\n' \
|
|
46
|
+
"$(date -Iseconds)" "$EVENT" "$(pwd)" "${MILL:-MISSING}" "${NODE_BIN:-MISSING}"
|
|
47
|
+
} >> "${dest}/.xray/state/cursor-hook-invoke.log" 2>/dev/null || true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
log_invoke "$(pwd)"
|
|
51
|
+
if [ -n "$MILL" ]; then
|
|
52
|
+
log_invoke "$MILL"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if [ -z "$NODE_BIN" ] || [ -z "$MILL" ] || [ ! -f "${MILL}/${REL}" ]; then
|
|
56
|
+
printf '{"permission":"allow","user_message":"xray-cloud-hook: mill missing — fail open"}\n'
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
export XRAY_HOOK_EVENT="$EVENT"
|
|
61
|
+
export XRAY_AI_PATH="$MILL"
|
|
62
|
+
export XRAY_ROOT="$MILL"
|
|
63
|
+
exec "$NODE_BIN" "${MILL}/${REL}"
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
applyStationHeat,
|
|
26
26
|
buildRepertoireResume as stationRepertoireResume,
|
|
27
27
|
readExistingBoot,
|
|
28
|
+
retainCompactFields,
|
|
28
29
|
writeStationMarkdown,
|
|
29
30
|
} from '../../hooks/station-hook-runtime.mjs';
|
|
30
31
|
|
|
@@ -285,8 +286,8 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
|
|
|
285
286
|
const frontier = gateFeatures.ceremony === 'lite';
|
|
286
287
|
const existing = readExistingBoot(root);
|
|
287
288
|
const heat = applyStationHeat(root, extra.host || 'grok', extra, existing);
|
|
289
|
+
const compactHold = retainCompactFields(existing, extra);
|
|
288
290
|
return {
|
|
289
|
-
hook: source,
|
|
290
291
|
lead_dev_mode: features.lead_dev_mode,
|
|
291
292
|
no_new_surface: features.no_new_surface,
|
|
292
293
|
host: extra.host || 'grok',
|
|
@@ -306,10 +307,12 @@ export function buildSessionBootPayload(root, source = '0xray/grok-session-start
|
|
|
306
307
|
...(conferPending ? { conferPending: true, conferTrigger: 'analyze-complexity at synthesis checkpoint' } : {}),
|
|
307
308
|
...(userAsideBoot ?? {}),
|
|
308
309
|
sessionId,
|
|
309
|
-
timestamp: new Date().toISOString(),
|
|
310
|
-
source,
|
|
311
310
|
...extra,
|
|
312
311
|
...heat,
|
|
312
|
+
...compactHold,
|
|
313
|
+
hook: source,
|
|
314
|
+
source,
|
|
315
|
+
timestamp: new Date().toISOString(),
|
|
313
316
|
};
|
|
314
317
|
}
|
|
315
318
|
|
|
@@ -468,6 +468,20 @@ async function handlePreProcess(input, projectRoot, logDir) {
|
|
|
468
468
|
const { tool, args } = input;
|
|
469
469
|
const startTime = Date.now();
|
|
470
470
|
|
|
471
|
+
try {
|
|
472
|
+
const gate = await import('../hooks/delegation-gate-runtime.mjs');
|
|
473
|
+
if (typeof gate.maybeHeatHostStation === 'function') {
|
|
474
|
+
gate.maybeHeatHostStation(projectRoot, 'hermes', {
|
|
475
|
+
source: '0xray/hermes-pre-process',
|
|
476
|
+
sessionId: input.sessionId || 'hermes',
|
|
477
|
+
hookEvent: 'pre_tool_use',
|
|
478
|
+
...(input.intent ? { intent: input.intent } : {}),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
} catch {
|
|
482
|
+
/* live memory is best-effort — quality gate still runs */
|
|
483
|
+
}
|
|
484
|
+
|
|
471
485
|
logToolEvent(logDir, "start", tool, args);
|
|
472
486
|
logToActivity(logDir, `pre-process: tool=${tool}`);
|
|
473
487
|
|