greprag 5.74.21 → 5.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/arm-reminder.js +4 -2
- package/dist/commands/codex.js +2 -7
- package/dist/commands/grok-spawn.js +208 -0
- package/dist/commands/inbox-drain.js +36 -6
- package/dist/commands/inbox-primer-reminder.js +16 -11
- package/dist/commands/inbox-watch-supervisor.js +17 -7
- package/dist/commands/inbox-watch.js +67 -31
- package/dist/commands/init.js +173 -4
- package/dist/commands/load-primer-reminder.js +8 -1
- package/dist/commands/load.js +4 -0
- package/dist/commands/os-primer-reminder.js +2 -1
- package/dist/commands/pipe-wrap.js +6 -5
- package/dist/commands/search-guard.js +2 -1
- package/dist/commands/status.js +29 -1
- package/dist/commands/watcher-registry.js +67 -22
- package/dist/grok-session.js +125 -0
- package/dist/harness.js +6 -0
- package/dist/hook-once.js +67 -0
- package/dist/hook-runtime.js +43 -0
- package/dist/hook.js +86 -19
- package/dist/index.js +62 -22
- package/dist/opencode-plugin.bundle.js +21 -8
- package/dist/session-id.js +59 -8
- package/package.json +3 -2
- package/skill/greprag/SKILL.md +41 -2
- package/skill/templates/chip-spawn.md +2 -0
- package/skill/templates/grok-chip-spawn.md +101 -0
|
@@ -79,13 +79,14 @@ exports.gcDeadSessions = gcDeadSessions;
|
|
|
79
79
|
const fs = __importStar(require("fs"));
|
|
80
80
|
const path = __importStar(require("path"));
|
|
81
81
|
const child_process_1 = require("child_process");
|
|
82
|
+
const session_id_1 = require("../session-id");
|
|
82
83
|
const WATCHER_DIRNAME = 'watchers';
|
|
83
84
|
// Matches a greprag watcher's command line in every launch shape (npm shim, bash
|
|
84
85
|
// relauncher, the supervisor's CreateProcess re-invocation). Anchored on the
|
|
85
86
|
// INVOKED BINARY (`greprag` or `index.js`) immediately followed by `inbox watch`
|
|
86
87
|
// — never a bare `inbox watch` substring. Retained for the owner-pid resolver's
|
|
87
88
|
// claude.exe match; the cleanup path no longer scans the process table at all.
|
|
88
|
-
const CLAUDE_PROC_RE = /^claude(\.exe)?$/i;
|
|
89
|
+
const CLAUDE_PROC_RE = /^(claude|grok)(\.exe)?$/i;
|
|
89
90
|
/** Default per-session watcher floor: keep this many freshest live watchers, reap
|
|
90
91
|
* only the surplus above it, never below it. K=2 = the live one + one margin. */
|
|
91
92
|
exports.DEFAULT_WATCHER_CAP = 2;
|
|
@@ -97,9 +98,26 @@ function watchersDir() {
|
|
|
97
98
|
const h = grepragHome();
|
|
98
99
|
return h ? path.join(h, WATCHER_DIRNAME) : null;
|
|
99
100
|
}
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
function sessionKey(short) {
|
|
102
|
+
return (0, session_id_1.truncateSessionId)(short) || short;
|
|
103
|
+
}
|
|
104
|
+
/** Pidfile names that belong to the same session: canonical short (16-hex
|
|
105
|
+
* for UUIDv7, 8-hex otherwise) plus the raw/--session string. A Grok watch
|
|
106
|
+
* armed with `$GROK_SESSION_ID` (dashed UUID) used to write
|
|
107
|
+
* `<full-uuid>.json` while Stop's isLocallyArmed looked at `<16-hex>.json`
|
|
108
|
+
* → perpetual UNARMED nag + singleton-guard bounce. */
|
|
109
|
+
function matchingPidfileNames(short) {
|
|
110
|
+
const want = sessionKey(short);
|
|
111
|
+
const names = new Set();
|
|
112
|
+
if (want)
|
|
113
|
+
names.add(want);
|
|
114
|
+
if (short)
|
|
115
|
+
names.add(short);
|
|
116
|
+
for (const name of allShorts()) {
|
|
117
|
+
if (name === short || sessionKey(name) === want)
|
|
118
|
+
names.add(name);
|
|
119
|
+
}
|
|
120
|
+
return [...names];
|
|
103
121
|
}
|
|
104
122
|
/** True iff `pid` is a live process. `process.kill(pid, 0)` sends no signal — it
|
|
105
123
|
* only probes existence. EPERM = exists but another user (alive); ESRCH = gone. */
|
|
@@ -114,20 +132,32 @@ function pidAlive(pid) {
|
|
|
114
132
|
return e?.code === 'EPERM';
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
|
-
/** Read a session's entry LIST.
|
|
118
|
-
*
|
|
135
|
+
/** Read a session's entry LIST. Merges alias pidfiles (dashed UUID, 16-hex,
|
|
136
|
+
* raw --session). Back-compat: a legacy single-object pidfile reads as a
|
|
137
|
+
* 1-element list. */
|
|
119
138
|
function readWatcherEntries(short) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!p)
|
|
123
|
-
return [];
|
|
124
|
-
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
125
|
-
const arr = Array.isArray(parsed) ? parsed : [parsed];
|
|
126
|
-
return arr.filter((r) => !!r && typeof r.pid === 'number');
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
139
|
+
const dir = watchersDir();
|
|
140
|
+
if (!dir)
|
|
129
141
|
return [];
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
const out = [];
|
|
144
|
+
for (const name of matchingPidfileNames(short)) {
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(dir, `${name}.json`), 'utf-8'));
|
|
147
|
+
const arr = Array.isArray(parsed) ? parsed : [parsed];
|
|
148
|
+
for (const r of arr) {
|
|
149
|
+
if (!r || typeof r.pid !== 'number')
|
|
150
|
+
continue;
|
|
151
|
+
const rec = r;
|
|
152
|
+
if (seen.has(rec.pid))
|
|
153
|
+
continue;
|
|
154
|
+
seen.add(rec.pid);
|
|
155
|
+
out.push(rec);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch { /* missing alias is fine */ }
|
|
130
159
|
}
|
|
160
|
+
return out;
|
|
131
161
|
}
|
|
132
162
|
function writeWatcherEntries(short, entries) {
|
|
133
163
|
try {
|
|
@@ -135,12 +165,21 @@ function writeWatcherEntries(short, entries) {
|
|
|
135
165
|
if (!dir)
|
|
136
166
|
return;
|
|
137
167
|
fs.mkdirSync(dir, { recursive: true });
|
|
138
|
-
const
|
|
168
|
+
const key = sessionKey(short);
|
|
169
|
+
const canonical = path.join(dir, `${key}.json`);
|
|
170
|
+
for (const name of matchingPidfileNames(short)) {
|
|
171
|
+
if (name === key)
|
|
172
|
+
continue;
|
|
173
|
+
try {
|
|
174
|
+
fs.rmSync(path.join(dir, `${name}.json`), { force: true });
|
|
175
|
+
}
|
|
176
|
+
catch { /* best-effort */ }
|
|
177
|
+
}
|
|
139
178
|
if (entries.length === 0) {
|
|
140
|
-
fs.rmSync(
|
|
179
|
+
fs.rmSync(canonical, { force: true });
|
|
141
180
|
return;
|
|
142
181
|
}
|
|
143
|
-
fs.writeFileSync(
|
|
182
|
+
fs.writeFileSync(canonical, JSON.stringify(entries));
|
|
144
183
|
}
|
|
145
184
|
catch { /* best-effort — a failed write only means re-arm, never a crash */ }
|
|
146
185
|
}
|
|
@@ -155,12 +194,18 @@ function writeWatcherPidfile(short, pid, ownerPid) {
|
|
|
155
194
|
function removeWatcherEntry(short, pid) {
|
|
156
195
|
writeWatcherEntries(short, readWatcherEntries(short).filter(r => r.pid !== pid));
|
|
157
196
|
}
|
|
158
|
-
/** Remove a session's whole pidfile (legacy / full sweep). */
|
|
197
|
+
/** Remove a session's whole pidfile (legacy / full sweep), including aliases. */
|
|
159
198
|
function removeWatcherPidfile(short) {
|
|
160
199
|
try {
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
|
|
200
|
+
const dir = watchersDir();
|
|
201
|
+
if (!dir)
|
|
202
|
+
return;
|
|
203
|
+
for (const name of matchingPidfileNames(short)) {
|
|
204
|
+
try {
|
|
205
|
+
fs.rmSync(path.join(dir, `${name}.json`), { force: true });
|
|
206
|
+
}
|
|
207
|
+
catch { /* best-effort */ }
|
|
208
|
+
}
|
|
164
209
|
}
|
|
165
210
|
catch { /* best-effort */ }
|
|
166
211
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Grok Build session files + the recap sidecar SessionStart can write
|
|
3
|
+
* (Grok ignores SessionStart stdout, so the always-on rule points here).
|
|
4
|
+
* adr: adr/grok-platform.md */
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.grokSessionDir = grokSessionDir;
|
|
40
|
+
exports.readLatestGrokUserPrompt = readLatestGrokUserPrompt;
|
|
41
|
+
exports.grokSidecarPath = grokSidecarPath;
|
|
42
|
+
exports.writeGrokSidecar = writeGrokSidecar;
|
|
43
|
+
exports.appendGrokSidecar = appendGrokSidecar;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
function grokHome() {
|
|
48
|
+
if (process.env.GROK_HOME)
|
|
49
|
+
return process.env.GROK_HOME;
|
|
50
|
+
return path.join(os.homedir(), '.grok');
|
|
51
|
+
}
|
|
52
|
+
function grokSessionDir(cwd, sessionId) {
|
|
53
|
+
const encoded = encodeURIComponent(path.resolve(cwd));
|
|
54
|
+
return path.join(grokHome(), 'sessions', encoded, sessionId);
|
|
55
|
+
}
|
|
56
|
+
function extractText(content) {
|
|
57
|
+
if (typeof content === 'string')
|
|
58
|
+
return content.trim();
|
|
59
|
+
if (!Array.isArray(content))
|
|
60
|
+
return '';
|
|
61
|
+
return content
|
|
62
|
+
.filter((b) => b?.type === 'text' && typeof b.text === 'string')
|
|
63
|
+
.map((b) => String(b.text))
|
|
64
|
+
.join('\n')
|
|
65
|
+
.trim();
|
|
66
|
+
}
|
|
67
|
+
function unwrapUserQuery(text) {
|
|
68
|
+
const m = text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/i);
|
|
69
|
+
return (m?.[1] || text).trim();
|
|
70
|
+
}
|
|
71
|
+
/** Latest real user prompt from Grok chat_history.jsonl. */
|
|
72
|
+
function readLatestGrokUserPrompt(cwd, sessionId) {
|
|
73
|
+
if (!sessionId)
|
|
74
|
+
return '';
|
|
75
|
+
const file = path.join(grokSessionDir(cwd, sessionId), 'chat_history.jsonl');
|
|
76
|
+
let raw;
|
|
77
|
+
try {
|
|
78
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
const lines = raw.split(/\n/);
|
|
84
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
85
|
+
const line = lines[i].trim();
|
|
86
|
+
if (!line)
|
|
87
|
+
continue;
|
|
88
|
+
let entry;
|
|
89
|
+
try {
|
|
90
|
+
entry = JSON.parse(line);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (entry.type !== 'user')
|
|
96
|
+
continue;
|
|
97
|
+
const text = unwrapUserQuery(extractText(entry.content));
|
|
98
|
+
if (text)
|
|
99
|
+
return text;
|
|
100
|
+
}
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
function grokSidecarPath(short) {
|
|
104
|
+
return path.join(os.homedir(), '.greprag', 'grok-context', `${short}.md`);
|
|
105
|
+
}
|
|
106
|
+
function writeGrokSidecar(short, text) {
|
|
107
|
+
if (!short || !text)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
const file = grokSidecarPath(short);
|
|
111
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
112
|
+
fs.writeFileSync(file, text);
|
|
113
|
+
}
|
|
114
|
+
catch { /* sidecar is best-effort */ }
|
|
115
|
+
}
|
|
116
|
+
function appendGrokSidecar(short, text) {
|
|
117
|
+
if (!short || !text)
|
|
118
|
+
return;
|
|
119
|
+
try {
|
|
120
|
+
const file = grokSidecarPath(short);
|
|
121
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
122
|
+
fs.appendFileSync(file, (fs.existsSync(file) ? '\n\n' : '') + text);
|
|
123
|
+
}
|
|
124
|
+
catch { /* sidecar is best-effort */ }
|
|
125
|
+
}
|
package/dist/harness.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.REPAIR_TARGET_HARNESSES = [
|
|
|
8
8
|
'claude-code',
|
|
9
9
|
'codex',
|
|
10
10
|
'opencode',
|
|
11
|
+
'grok',
|
|
11
12
|
'all',
|
|
12
13
|
];
|
|
13
14
|
function normalizeHarness(raw) {
|
|
@@ -20,6 +21,8 @@ function normalizeHarness(raw) {
|
|
|
20
21
|
return 'codex';
|
|
21
22
|
if (v === 'opencode' || v === 'open-code' || v === 'open_code')
|
|
22
23
|
return 'opencode';
|
|
24
|
+
if (v === 'grok' || v === 'grok-build' || v === 'grok_build')
|
|
25
|
+
return 'grok';
|
|
23
26
|
if (v === 'all' || v === 'any' || v === 'universal')
|
|
24
27
|
return 'all';
|
|
25
28
|
return null;
|
|
@@ -30,6 +33,9 @@ function inferCurrentHarness() {
|
|
|
30
33
|
if (h && h !== 'all')
|
|
31
34
|
return h;
|
|
32
35
|
}
|
|
36
|
+
// Grok sets CLAUDE_PROJECT_DIR as a compat alias. Detect Grok BEFORE Claude.
|
|
37
|
+
if (process.env.GROK_SESSION_ID || process.env.GROK_HOOK_EVENT)
|
|
38
|
+
return 'grok';
|
|
33
39
|
if (process.env.CODEX_THREAD_ID)
|
|
34
40
|
return 'codex';
|
|
35
41
|
if (process.env.GREPRAG_OPENCODE_SESSION_ID || process.env.OPENCODE_SESSION_ID)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Once-per-event stamp so leaked Claude greprag hooks + dedicated
|
|
3
|
+
* ~/.grok/hooks/greprag.json cannot double-store / double-recap.
|
|
4
|
+
* adr: adr/grok-platform.md */
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.claimHookOnce = claimHookOnce;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const TTL_MS = 60_000;
|
|
44
|
+
function stampPath(sessionId, event, sub, turnId) {
|
|
45
|
+
const safe = [sessionId, event, sub, turnId].join('-').replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 180);
|
|
46
|
+
return path.join(os.homedir(), '.greprag', 'hook-once', `${safe}.json`);
|
|
47
|
+
}
|
|
48
|
+
/** True = this process should run the subcommand. False = duplicate, skip. */
|
|
49
|
+
function claimHookOnce(sessionId, event, subcommand, turnId) {
|
|
50
|
+
if (!sessionId)
|
|
51
|
+
return true;
|
|
52
|
+
const key = turnId || 'noturn';
|
|
53
|
+
const file = stampPath(sessionId, event || 'unknown', subcommand, key);
|
|
54
|
+
try {
|
|
55
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
56
|
+
if (fs.existsSync(file)) {
|
|
57
|
+
const age = Date.now() - fs.statSync(file).mtimeMs;
|
|
58
|
+
if (age < TTL_MS)
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
fs.writeFileSync(file, JSON.stringify({ ts: Date.now(), subcommand }));
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/hook-runtime.js
CHANGED
|
@@ -33,12 +33,55 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeHookInput = normalizeHookInput;
|
|
37
|
+
exports.isGrokProcess = isGrokProcess;
|
|
38
|
+
exports.isShellTool = isShellTool;
|
|
36
39
|
exports.ensureEnv = ensureEnv;
|
|
37
40
|
exports.getHookConfig = getHookConfig;
|
|
38
41
|
exports.apiCall = apiCall;
|
|
39
42
|
exports.writeAdditionalContext = writeAdditionalContext;
|
|
40
43
|
const fs = __importStar(require("fs"));
|
|
41
44
|
const path = __importStar(require("path"));
|
|
45
|
+
function asString(v) {
|
|
46
|
+
return typeof v === 'string' && v ? v : undefined;
|
|
47
|
+
}
|
|
48
|
+
function asObj(v) {
|
|
49
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? v : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Grok sends camelCase; Claude/Codex send snake_case. One bag for every hook.
|
|
52
|
+
* adr: adr/grok-platform.md */
|
|
53
|
+
function normalizeHookInput(raw) {
|
|
54
|
+
const r = (raw && typeof raw === 'object') ? raw : {};
|
|
55
|
+
const event = asString(r.hook_event_name) || asString(r.hookEventName)
|
|
56
|
+
|| asString(process.env.GROK_HOOK_EVENT);
|
|
57
|
+
const session = asString(r.session_id) || asString(r.sessionId)
|
|
58
|
+
|| asString(process.env.GROK_SESSION_ID)
|
|
59
|
+
|| asString(process.env.CLAUDE_CODE_SESSION_ID)
|
|
60
|
+
|| asString(process.env.CODEX_THREAD_ID);
|
|
61
|
+
const cwd = asString(r.cwd) || asString(r.workspaceRoot)
|
|
62
|
+
|| asString(process.env.GROK_WORKSPACE_ROOT);
|
|
63
|
+
const toolInput = asObj(r.tool_input) || asObj(r.toolInput);
|
|
64
|
+
return {
|
|
65
|
+
session_id: session,
|
|
66
|
+
turn_id: asString(r.turn_id) || asString(r.promptId) || asString(r.prompt_id),
|
|
67
|
+
transcript_path: asString(r.transcript_path) || asString(r.transcriptPath),
|
|
68
|
+
cwd,
|
|
69
|
+
hook_event_name: event,
|
|
70
|
+
prompt: asString(r.prompt),
|
|
71
|
+
model: asString(r.model),
|
|
72
|
+
last_assistant_message: asString(r.last_assistant_message) || asString(r.lastAssistantMessage),
|
|
73
|
+
tool_name: asString(r.tool_name) || asString(r.toolName),
|
|
74
|
+
tool_input: toolInput,
|
|
75
|
+
stop_hook_active: r.stop_hook_active === true || r.stopHookActive === true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function isGrokProcess() {
|
|
79
|
+
return !!(process.env.GROK_SESSION_ID || process.env.GROK_HOOK_EVENT);
|
|
80
|
+
}
|
|
81
|
+
function isShellTool(name) {
|
|
82
|
+
const n = (name || '').toLowerCase();
|
|
83
|
+
return n === 'bash' || n === 'run_terminal_command' || n === 'shell';
|
|
84
|
+
}
|
|
42
85
|
const API_URL_DEFAULT = 'https://api.greprag.com';
|
|
43
86
|
function loadEnvFile(filePath) {
|
|
44
87
|
try {
|
package/dist/hook.js
CHANGED
|
@@ -50,6 +50,9 @@ const mechanic_role_1 = require("./mechanic-role");
|
|
|
50
50
|
const assistant_doctrine_1 = require("./assistant-doctrine");
|
|
51
51
|
const session_id_1 = require("./session-id");
|
|
52
52
|
const hook_runtime_1 = require("./hook-runtime");
|
|
53
|
+
const harness_1 = require("./harness");
|
|
54
|
+
const hook_once_1 = require("./hook-once");
|
|
55
|
+
const grok_session_1 = require("./grok-session");
|
|
53
56
|
const codex_prompt_cache_1 = require("./codex-prompt-cache");
|
|
54
57
|
// adr: adr/memory-provenance-capture.md — classify harness-injected user text
|
|
55
58
|
// (skill bodies, continuation summaries, chip prompts) pre-LLM at capture and
|
|
@@ -1060,12 +1063,19 @@ async function store(input, source = 'claude-code') {
|
|
|
1060
1063
|
if (!anchor.memoryCapture)
|
|
1061
1064
|
return;
|
|
1062
1065
|
const turn = parseLatestTurn(input.transcript_path);
|
|
1063
|
-
if (source === 'codex' && (!turn.userPrompt || !turn.agentResponse)) {
|
|
1066
|
+
if ((source === 'codex' || source === 'grok') && (!turn.userPrompt || !turn.agentResponse)) {
|
|
1064
1067
|
const cached = (0, codex_prompt_cache_1.readCodexPromptCache)(input);
|
|
1065
1068
|
if (!turn.userPrompt && cached) {
|
|
1066
1069
|
turn.userPrompt = cached.prompt;
|
|
1067
1070
|
turn.provenance = (0, turn_provenance_1.classifyUserText)(cached.prompt);
|
|
1068
1071
|
}
|
|
1072
|
+
if (!turn.userPrompt && source === 'grok') {
|
|
1073
|
+
const fromDisk = (0, grok_session_1.readLatestGrokUserPrompt)(cwd, input.session_id);
|
|
1074
|
+
if (fromDisk) {
|
|
1075
|
+
turn.userPrompt = fromDisk;
|
|
1076
|
+
turn.provenance = (0, turn_provenance_1.classifyUserText)(fromDisk);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1069
1079
|
if (!turn.agentResponse && typeof input.last_assistant_message === 'string') {
|
|
1070
1080
|
turn.agentResponse = input.last_assistant_message.trim();
|
|
1071
1081
|
}
|
|
@@ -1082,7 +1092,7 @@ async function store(input, source = 'claude-code') {
|
|
|
1082
1092
|
turn.filesTouched = Array.from(filesSet).sort();
|
|
1083
1093
|
}
|
|
1084
1094
|
}
|
|
1085
|
-
if (source === 'codex') {
|
|
1095
|
+
if (source === 'codex' || source === 'grok') {
|
|
1086
1096
|
turn.toolCalls.push(...(0, codex_hook_events_1.readCodexSubagentToolCalls)(input));
|
|
1087
1097
|
}
|
|
1088
1098
|
// (Memory-reflex efficacy capture removed 2026-06-22 with the auto-inject it scored.)
|
|
@@ -1182,7 +1192,7 @@ async function store(input, source = 'claude-code') {
|
|
|
1182
1192
|
// is network-backed. Codex Stop spools it to a detached worker so the app's
|
|
1183
1193
|
// UI-critical Stop path does not wait on judge/distill calls; Claude Code keeps
|
|
1184
1194
|
// the prior synchronous lifecycle.
|
|
1185
|
-
if (source === 'codex') {
|
|
1195
|
+
if (source === 'codex' || source === 'grok') {
|
|
1186
1196
|
(0, inline_atom_background_1.spawnInlineAtomObserver)({
|
|
1187
1197
|
cwd,
|
|
1188
1198
|
sessionId: input.session_id,
|
|
@@ -1319,7 +1329,17 @@ function shouldEmitDriftWarning(storedId, derivedId) {
|
|
|
1319
1329
|
return true;
|
|
1320
1330
|
return (Date.now() - lastMs) >= DRIFT_WARNING_TTL_MS;
|
|
1321
1331
|
}
|
|
1322
|
-
function
|
|
1332
|
+
function grokSidecarHead(short, full) {
|
|
1333
|
+
const arm = (0, session_id_1.armMonitorCommand)(short, null, false, false, 'grok');
|
|
1334
|
+
return (0, session_id_1.buildSessionIdContext)(short, (0, session_id_1.readIdentityAlias)())
|
|
1335
|
+
+ `\n\nARM (Grok monitor persistent:true): \`${arm}\`\n`
|
|
1336
|
+
+ `Send with --from-session ${full || short} (full UUID or 16-hex, never 8-hex).\n`
|
|
1337
|
+
+ 'Chip: `greprag load grok-chip-spawn` then `greprag grok spawn`. Helper: spawn_subagent. Parent keeps ONE watch. Peers: `greprag inbox watchers` then `greprag send`.\n'
|
|
1338
|
+
+ 'If a greprag inbox monitor is already listed, do not start another. Instant exit = already armed.\n\n';
|
|
1339
|
+
}
|
|
1340
|
+
function writeRecapOutput(text, mode, grokShort, grokFull) {
|
|
1341
|
+
if (grokShort)
|
|
1342
|
+
(0, grok_session_1.writeGrokSidecar)(grokShort, grokSidecarHead(grokShort, grokFull) + (text || ''));
|
|
1323
1343
|
if (!text)
|
|
1324
1344
|
return;
|
|
1325
1345
|
if (mode === 'additionalContext') {
|
|
@@ -1427,8 +1447,9 @@ function activeCommandDrift() {
|
|
|
1427
1447
|
* Storage and display are both UTC. The agent can compute local time itself
|
|
1428
1448
|
* if needed — a server-side UTC display avoids straddle-day confusion. */
|
|
1429
1449
|
async function recap(input, mode = 'plain', opts = {}) {
|
|
1430
|
-
const platform = opts.platform || 'claude-code';
|
|
1431
|
-
const hasLocalMonitorLifecycle = platform === 'claude-code';
|
|
1450
|
+
const platform = opts.platform || (0, harness_1.inferCurrentHarness)() || 'claude-code';
|
|
1451
|
+
const hasLocalMonitorLifecycle = platform === 'claude-code' || platform === 'grok';
|
|
1452
|
+
const grokShort = platform === 'grok' ? ((0, session_id_1.truncateSessionId)(input.session_id) || null) : null;
|
|
1432
1453
|
let sessionOwnerPid = null;
|
|
1433
1454
|
// SessionStart watcher cleanup (best-effort, side-effect only — never writes to
|
|
1434
1455
|
// stdout, which carries this hook's additionalContext JSON). Three snapshot-free
|
|
@@ -1476,7 +1497,10 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1476
1497
|
// session started outside a project gets the session-id line (its own anchor-free
|
|
1477
1498
|
// hook) but never the inbox rules — the exact gap a fresh unanchored session hit.
|
|
1478
1499
|
// adr: adr/address-grammar.md
|
|
1479
|
-
writeRecapOutput((0, inbox_primer_reminder_1.buildInboxPrimer)({
|
|
1500
|
+
writeRecapOutput((0, inbox_primer_reminder_1.buildInboxPrimer)({
|
|
1501
|
+
short: (0, session_id_1.truncateSessionId)(input.session_id) || '',
|
|
1502
|
+
platform,
|
|
1503
|
+
}) + '\n', mode, grokShort, input.session_id);
|
|
1480
1504
|
return;
|
|
1481
1505
|
}
|
|
1482
1506
|
// Mechanic matchset boot pull (D5) — rides the existing SessionStart call
|
|
@@ -1696,7 +1720,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1696
1720
|
announceReg = (0, reminder_registry_1.compactReannounceModules)(announceReg);
|
|
1697
1721
|
const announceBlock = (0, reminder_registry_1.collectAnnounces)(announceEnv, announceReg).join('\n\n') || null;
|
|
1698
1722
|
if (opts.compact) {
|
|
1699
|
-
writeRecapOutput(announceBlock ? announceBlock + '\n' : '', mode);
|
|
1723
|
+
writeRecapOutput(announceBlock ? announceBlock + '\n' : '', mode, grokShort, input.session_id);
|
|
1700
1724
|
return;
|
|
1701
1725
|
}
|
|
1702
1726
|
const preamble = () => (announceBlock ? announceBlock + '\n' : '');
|
|
@@ -1705,7 +1729,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1705
1729
|
// hook globally. Setup warnings still fire above.
|
|
1706
1730
|
if (!anchor.sessionStartRecap) {
|
|
1707
1731
|
const out = preamble();
|
|
1708
|
-
writeRecapOutput(out, mode);
|
|
1732
|
+
writeRecapOutput(out, mode, grokShort, input.session_id);
|
|
1709
1733
|
return;
|
|
1710
1734
|
}
|
|
1711
1735
|
// Recap body is hourlies-only — daily summaries used to render here (one
|
|
@@ -1722,7 +1746,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1722
1746
|
// if they're pending.
|
|
1723
1747
|
if (!body) {
|
|
1724
1748
|
const out = preamble();
|
|
1725
|
-
writeRecapOutput(out, mode);
|
|
1749
|
+
writeRecapOutput(out, mode, grokShort, input.session_id);
|
|
1726
1750
|
return;
|
|
1727
1751
|
}
|
|
1728
1752
|
const parts = [];
|
|
@@ -1731,7 +1755,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1731
1755
|
parts.push('');
|
|
1732
1756
|
}
|
|
1733
1757
|
parts.push(body);
|
|
1734
|
-
writeRecapOutput(parts.join('\n') + '\n', mode);
|
|
1758
|
+
writeRecapOutput(parts.join('\n') + '\n', mode, grokShort, input.session_id);
|
|
1735
1759
|
}
|
|
1736
1760
|
/** Arm-state detection moved LOCAL (2026-06-04). The former `isSessionArmed`
|
|
1737
1761
|
* asked the server's watcher registry "does this session have a live socket?"
|
|
@@ -1814,8 +1838,14 @@ async function procedureCheck(input) {
|
|
|
1814
1838
|
}
|
|
1815
1839
|
}
|
|
1816
1840
|
async function notify(input, source = 'claude-code', chipContext) {
|
|
1817
|
-
|
|
1841
|
+
const harness = (0, harness_1.inferCurrentHarness)() || source;
|
|
1842
|
+
if (source === 'codex' || harness === 'grok')
|
|
1818
1843
|
(0, codex_prompt_cache_1.cacheCodexPrompt)(input);
|
|
1844
|
+
if (harness === 'grok') {
|
|
1845
|
+
// UserPromptSubmit is observe-only on Grok — stdout cannot inject.
|
|
1846
|
+
// Cache the prompt for Stop capture, then return.
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1819
1849
|
// Codex Desktop reliably executes only the first UserPromptSubmit command.
|
|
1820
1850
|
// Procedure therefore shares the proven codex-notify bridge instead of
|
|
1821
1851
|
// depending on a sibling registration that the harness may skip.
|
|
@@ -1993,7 +2023,9 @@ async function collisionCheck(input) {
|
|
|
1993
2023
|
* poll live-tails with no double-delivery. Closes the dead-window gap where a
|
|
1994
2024
|
* session-directed message that lands between watcher death and re-arm sits
|
|
1995
2025
|
* unread until a manual `greprag inbox`. Inbound-only — the front desk (cold
|
|
1996
|
-
* opens + email) stays with the human-scoped `mail` hook.
|
|
2026
|
+
* opens + email) stays with the human-scoped `mail` hook. Injects only when
|
|
2027
|
+
* unarmed (`isLocallyArmed`); a live watcher already delivered the body.
|
|
2028
|
+
* Grok also wires this on Stop as the unarmed floor. All real logic lives
|
|
1997
2029
|
* in ./commands/inbox-drain (pure + tested). Best-effort: any miss → silent,
|
|
1998
2030
|
* never blocks SessionStart. adr: adr/monitor-resilience.md */
|
|
1999
2031
|
async function drain(input) {
|
|
@@ -2004,9 +2036,14 @@ async function drain(input) {
|
|
|
2004
2036
|
const short = (0, session_id_1.truncateSessionId)(input.session_id);
|
|
2005
2037
|
if (!short)
|
|
2006
2038
|
return; // no session id → silent
|
|
2007
|
-
const result = await (0, inbox_drain_1.runInboxDrain)({
|
|
2039
|
+
const result = await (0, inbox_drain_1.runInboxDrain)({
|
|
2040
|
+
session: short, apiUrl: cfg.apiUrl, apiKey: cfg.apiKey,
|
|
2041
|
+
armed: (0, watcher_registry_1.isLocallyArmed)(short),
|
|
2042
|
+
});
|
|
2008
2043
|
if (result.context) {
|
|
2009
2044
|
(0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'SessionStart', result.context);
|
|
2045
|
+
if ((0, harness_1.inferCurrentHarness)() === 'grok')
|
|
2046
|
+
(0, grok_session_1.appendGrokSidecar)(short, result.context);
|
|
2010
2047
|
}
|
|
2011
2048
|
}
|
|
2012
2049
|
function validateChip(title, prompt) {
|
|
@@ -2208,14 +2245,19 @@ async function main() {
|
|
|
2208
2245
|
chunks.push(chunk);
|
|
2209
2246
|
}
|
|
2210
2247
|
const raw = Buffer.concat(chunks).toString('utf-8').trim();
|
|
2211
|
-
|
|
2212
|
-
input = JSON.parse(raw);
|
|
2248
|
+
input = (0, hook_runtime_1.normalizeHookInput)(raw ? JSON.parse(raw) : {});
|
|
2213
2249
|
}
|
|
2214
2250
|
catch {
|
|
2215
2251
|
process.exit(0);
|
|
2216
2252
|
}
|
|
2253
|
+
if (!(0, hook_once_1.claimHookOnce)(input.session_id, input.hook_event_name, subcommand, input.turn_id)) {
|
|
2254
|
+
process.exit(0);
|
|
2255
|
+
}
|
|
2256
|
+
const harness = (0, harness_1.inferCurrentHarness)();
|
|
2217
2257
|
if (subcommand === 'recap') {
|
|
2218
|
-
await recap(input
|
|
2258
|
+
await recap(input, harness === 'grok' ? 'additionalContext' : 'plain', {
|
|
2259
|
+
platform: harness === 'grok' ? 'grok' : undefined,
|
|
2260
|
+
});
|
|
2219
2261
|
}
|
|
2220
2262
|
else if (subcommand === 'recompact') {
|
|
2221
2263
|
// PostCompact — re-present the Interrupt System's announce modules (default-on;
|
|
@@ -2228,7 +2270,7 @@ async function main() {
|
|
|
2228
2270
|
await recap(input, 'additionalContext', { platform: 'codex' });
|
|
2229
2271
|
}
|
|
2230
2272
|
else if (subcommand === 'notify') {
|
|
2231
|
-
await notify(input);
|
|
2273
|
+
await notify(input, harness === 'grok' ? 'grok' : 'claude-code');
|
|
2232
2274
|
}
|
|
2233
2275
|
else if (subcommand === 'mail') {
|
|
2234
2276
|
await mail(input);
|
|
@@ -2273,6 +2315,23 @@ async function main() {
|
|
|
2273
2315
|
// hook's job now (it injects the arm directive on any turn the session is
|
|
2274
2316
|
// found to have no live watcher), so SessionStart no longer arms.
|
|
2275
2317
|
(0, session_id_1.handleSessionIdHook)(input);
|
|
2318
|
+
if (harness === 'grok') {
|
|
2319
|
+
const short = (0, session_id_1.truncateSessionId)(input.session_id);
|
|
2320
|
+
if (short) {
|
|
2321
|
+
let existing = '';
|
|
2322
|
+
try {
|
|
2323
|
+
existing = fs.readFileSync((0, grok_session_1.grokSidecarPath)(short), 'utf-8');
|
|
2324
|
+
}
|
|
2325
|
+
catch { /* none yet */ }
|
|
2326
|
+
if (!existing.includes('ARM (Grok monitor')) {
|
|
2327
|
+
const block = grokSidecarHead(short, input.session_id);
|
|
2328
|
+
if (existing)
|
|
2329
|
+
(0, grok_session_1.appendGrokSidecar)(short, block);
|
|
2330
|
+
else
|
|
2331
|
+
(0, grok_session_1.writeGrokSidecar)(short, block);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2276
2335
|
}
|
|
2277
2336
|
else if (subcommand === 'pre-spawn-check') {
|
|
2278
2337
|
handlePreSpawnCheck(input);
|
|
@@ -2361,7 +2420,15 @@ async function main() {
|
|
|
2361
2420
|
// the next turn's PreToolUse `source:"state"` rules read a fresh stress /
|
|
2362
2421
|
// turnCount reading. Order: store first (the primary job), then the
|
|
2363
2422
|
// best-effort state stash. docs/ingress-trigger-bridge.md
|
|
2364
|
-
await store(input);
|
|
2423
|
+
await store(input, harness === 'grok' ? 'grok' : 'claude-code');
|
|
2424
|
+
if (harness === 'grok') {
|
|
2425
|
+
// Floor: inject unread mail only when unarmed. Do NOT nag UNARMED here —
|
|
2426
|
+
// Grok Stop fires every turn, and a pidfile-alias miss made that a
|
|
2427
|
+
// re-arm loop (watch already live → singleton-guard exits instantly →
|
|
2428
|
+
// model arms again). Arm teaching is sidecar + rules.
|
|
2429
|
+
// adr: adr/grok-platform.md
|
|
2430
|
+
await drain(input);
|
|
2431
|
+
}
|
|
2365
2432
|
stateUpdate(input);
|
|
2366
2433
|
}
|
|
2367
2434
|
}
|