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
|
@@ -40,7 +40,9 @@ function buildArmReminder(d, env) {
|
|
|
40
40
|
return null;
|
|
41
41
|
const unread = Number((d.detail && d.detail.unread) || 0);
|
|
42
42
|
const arm = env
|
|
43
|
-
?
|
|
43
|
+
? (env.platform === 'grok'
|
|
44
|
+
? `Grok \`monitor\` tool persistent:true command: \`${(0, session_id_1.armMonitorCommand)(env.short, env.ownerPid, env.assistant, env.mechanic, 'grok')}\``
|
|
45
|
+
: `ToolSearch select:Monitor → persistent Monitor (timeout_ms:3600000) command: \`${(0, session_id_1.armMonitorCommand)(env.short, env.ownerPid, env.assistant, env.mechanic)}\``)
|
|
44
46
|
: `arm your Monitor inbox watcher`;
|
|
45
47
|
const grounded = `greprag reads your watcher as DOWN (isLocallyArmed=false — a real pidfile check on your session, NOT a timer; the hook CAN see your watcher)`;
|
|
46
48
|
if (unread > 0) {
|
|
@@ -67,7 +69,7 @@ exports.watcherArmModule = {
|
|
|
67
69
|
// keeps its historical full-registry behavior. opencode is EXCLUDED: its inbox
|
|
68
70
|
// delivery is the plugin-armed relay (startSessionRelay), so "arm your Monitor"
|
|
69
71
|
// is unactionable noise there.
|
|
70
|
-
harnesses: ['claude-code', 'codex'],
|
|
72
|
+
harnesses: ['claude-code', 'codex', 'grok'],
|
|
71
73
|
detect: armDetect,
|
|
72
74
|
announce: () => null,
|
|
73
75
|
reminder: (d, env) => buildArmReminder(d, env),
|
package/dist/commands/codex.js
CHANGED
|
@@ -62,11 +62,6 @@ Object.defineProperty(exports, "removeCodexStartup", { enumerable: true, get: fu
|
|
|
62
62
|
Object.defineProperty(exports, "statusCodexStartup", { enumerable: true, get: function () { return codex_startup_2.statusCodexStartup; } });
|
|
63
63
|
const API_URL_DEFAULT = 'https://api.greprag.com';
|
|
64
64
|
const HEARTBEAT_IDLE_MS = 70_000;
|
|
65
|
-
const FULL_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
66
|
-
function sameSessionId(left, right) {
|
|
67
|
-
return !!left && !!right && (FULL_UUID_RE.test(left) && FULL_UUID_RE.test(right)
|
|
68
|
-
? left.toLowerCase() === right.toLowerCase() : (0, session_id_1.truncateSessionId)(left) === (0, session_id_1.truncateSessionId)(right));
|
|
69
|
-
}
|
|
70
65
|
function home() {
|
|
71
66
|
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
72
67
|
}
|
|
@@ -233,12 +228,12 @@ async function handleMessage(msg, opts, session8, codexSession) {
|
|
|
233
228
|
return result;
|
|
234
229
|
}
|
|
235
230
|
function shouldDeliverToThisSession(msg, sessionId) {
|
|
236
|
-
if (msg.to_session_id && !sameSessionId(msg.to_session_id, sessionId))
|
|
231
|
+
if (msg.to_session_id && !(0, session_id_1.sameSessionId)(msg.to_session_id, sessionId))
|
|
237
232
|
return false;
|
|
238
233
|
const fromSession = msg.from?.session_id ?? null;
|
|
239
234
|
if (msg.message_type === mechanic_friction_1.MECHANIC_FRICTION_MESSAGE_TYPE)
|
|
240
235
|
return true;
|
|
241
|
-
if (fromSession && sameSessionId(fromSession, sessionId))
|
|
236
|
+
if (fromSession && (0, session_id_1.sameSessionId)(fromSession, sessionId))
|
|
242
237
|
return false;
|
|
243
238
|
return true;
|
|
244
239
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Open a fresh Grok Build TUI from another agent session.
|
|
3
|
+
*
|
|
4
|
+
* Grok's tool-runner Job Object kills `Start-Process` / `cmd /c start`
|
|
5
|
+
* children when the tool call returns. The surviving launch is a scheduled
|
|
6
|
+
* task (`schtasks /Create /IT` then `/Run`) that `start`s a new `cmd /k`
|
|
7
|
+
* window running grok.exe — a visible command-prompt bootloader outside the
|
|
8
|
+
* job. adr: adr/grok-platform.md */
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.resolveGrokExe = resolveGrokExe;
|
|
44
|
+
exports.buildGrokSpawnScript = buildGrokSpawnScript;
|
|
45
|
+
exports.grokSpawnHelp = grokSpawnHelp;
|
|
46
|
+
exports.runGrokSpawn = runGrokSpawn;
|
|
47
|
+
exports.runGrok = runGrok;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const child_process_1 = require("child_process");
|
|
52
|
+
function winQuote(value) {
|
|
53
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
54
|
+
}
|
|
55
|
+
function resolveGrokExe() {
|
|
56
|
+
const home = os.homedir();
|
|
57
|
+
const named = process.platform === 'win32' ? 'grok.exe' : 'grok';
|
|
58
|
+
const pinned = path.join(home, '.grok', 'bin', named);
|
|
59
|
+
if (fs.existsSync(pinned))
|
|
60
|
+
return pinned;
|
|
61
|
+
try {
|
|
62
|
+
const out = (0, child_process_1.execFileSync)(process.platform === 'win32' ? 'where.exe' : 'which', ['grok'], {
|
|
63
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
64
|
+
});
|
|
65
|
+
const first = out.split(/\r?\n/).map(s => s.trim()).find(s => s);
|
|
66
|
+
if (first && fs.existsSync(first))
|
|
67
|
+
return first;
|
|
68
|
+
}
|
|
69
|
+
catch { /* fall through */ }
|
|
70
|
+
return named;
|
|
71
|
+
}
|
|
72
|
+
function buildGrokSpawnScript(opts) {
|
|
73
|
+
const cwd = path.resolve(opts.cwd);
|
|
74
|
+
const title = opts.title || `grok ${path.basename(cwd)}`;
|
|
75
|
+
const grok = opts.grokExe || resolveGrokExe();
|
|
76
|
+
const promptArg = opts.prompt ? ` ${winQuote(opts.prompt)}` : '';
|
|
77
|
+
// Self-relaunch: schtasks runs this hidden; `start cmd /k %~f0 --boot`
|
|
78
|
+
// opens a visible command-prompt bootloader that then execs grok.exe.
|
|
79
|
+
// Immediate task-delete used to cancel the launch — caller waits.
|
|
80
|
+
return [
|
|
81
|
+
'@echo off',
|
|
82
|
+
'if /I "%~1"=="--boot" goto boot',
|
|
83
|
+
`start ${winQuote(title)} cmd /k "%~f0" --boot`,
|
|
84
|
+
'goto :eof',
|
|
85
|
+
':boot',
|
|
86
|
+
`cd /d ${winQuote(cwd)}`,
|
|
87
|
+
`echo [greprag] booting Grok TUI in ${cwd}`,
|
|
88
|
+
`${winQuote(grok)}${promptArg}`,
|
|
89
|
+
'echo.',
|
|
90
|
+
'echo [greprag] grok exited. This window is yours.',
|
|
91
|
+
].join('\r\n') + '\r\n';
|
|
92
|
+
}
|
|
93
|
+
function grokSpawnHelp() {
|
|
94
|
+
return `greprag grok spawn — open a fresh Grok Build TUI in a new command prompt.
|
|
95
|
+
|
|
96
|
+
greprag grok spawn [--cwd <path>] [--prompt "<text>"|--prompt-file <path>] [--title <name>]
|
|
97
|
+
greprag grok spawn --dry-run
|
|
98
|
+
|
|
99
|
+
Windows: schtasks /IT starts a new \`cmd /k\` window (bootloader) that runs
|
|
100
|
+
grok.exe. That escapes the agent tool Job Object. The child is a real TUI
|
|
101
|
+
with its own $GROK_SESSION_ID; it arms its own inbox watch. This session
|
|
102
|
+
keeps one watch. Message it with:
|
|
103
|
+
greprag send "…" --to <handle>@greprag.com/<child-uuid> --from-session $GROK_SESSION_ID
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
function sleepMs(ms) {
|
|
107
|
+
const end = Date.now() + ms;
|
|
108
|
+
while (Date.now() < end) { /* wait for Task Scheduler to start the .cmd */ }
|
|
109
|
+
}
|
|
110
|
+
function runGrokSpawn(args) {
|
|
111
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
112
|
+
console.log(grokSpawnHelp());
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const cwd = path.resolve(getArg(args, '--cwd') || process.cwd());
|
|
116
|
+
const promptFile = getArg(args, '--prompt-file');
|
|
117
|
+
let prompt = getArg(args, '--prompt') || positionalPrompt(args);
|
|
118
|
+
if (promptFile) {
|
|
119
|
+
if (!fs.existsSync(promptFile)) {
|
|
120
|
+
console.error(`greprag grok spawn: prompt file not found: ${promptFile}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
prompt = fs.readFileSync(promptFile, 'utf8').replace(/^\uFEFF/, '').trimEnd();
|
|
124
|
+
}
|
|
125
|
+
const title = getArg(args, '--title');
|
|
126
|
+
const dryRun = args.includes('--dry-run');
|
|
127
|
+
const grokExe = resolveGrokExe();
|
|
128
|
+
const opts = { cwd, prompt, title, grokExe, dryRun };
|
|
129
|
+
if (process.platform !== 'win32') {
|
|
130
|
+
const cmd = [grokExe, '--cwd', cwd, prompt].filter((p) => !!p);
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
console.log(cmd.join(' '));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.error('greprag grok spawn: Windows-only bootloader (Job Object). Run this yourself:');
|
|
136
|
+
console.error(` ${cmd.map(a => /\s/.test(a) ? JSON.stringify(a) : a).join(' ')}`);
|
|
137
|
+
process.exitCode = 1;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const script = buildGrokSpawnScript(opts);
|
|
141
|
+
if (dryRun) {
|
|
142
|
+
process.stdout.write(script);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) {
|
|
146
|
+
console.error(`greprag grok spawn: cwd is not a directory: ${cwd}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
const tn = `greprag-grok-${Date.now().toString(36)}`;
|
|
150
|
+
const file = path.join(os.tmpdir(), `${tn}.cmd`);
|
|
151
|
+
fs.writeFileSync(file, script, 'utf8');
|
|
152
|
+
try {
|
|
153
|
+
(0, child_process_1.execFileSync)('schtasks', [
|
|
154
|
+
'/Create', '/TN', tn, '/TR', file, '/SC', 'ONCE', '/ST', '23:59',
|
|
155
|
+
'/F', '/IT',
|
|
156
|
+
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
157
|
+
(0, child_process_1.execFileSync)('schtasks', ['/Run', '/TN', tn], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
158
|
+
// /Run is async. Deleting immediately cancelled the launch (field 2026-08-21).
|
|
159
|
+
sleepMs(2500);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
163
|
+
console.error(`greprag grok spawn: schtasks failed: ${msg}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
try {
|
|
168
|
+
(0, child_process_1.execFileSync)('schtasks', ['/Delete', '/TN', tn, '/F'], { stdio: 'ignore' });
|
|
169
|
+
}
|
|
170
|
+
catch { /* leftover task is harmless */ }
|
|
171
|
+
}
|
|
172
|
+
console.log(`Spawned Grok TUI (cmd /k bootloader) for ${cwd}${prompt ? ` — prompt: ${prompt}` : ''}`);
|
|
173
|
+
console.log('The new session arms its own inbox watch. This session keeps one.');
|
|
174
|
+
}
|
|
175
|
+
function runGrok(args) {
|
|
176
|
+
const sub = args[0];
|
|
177
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
178
|
+
console.log(grokSpawnHelp());
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (sub === 'spawn')
|
|
182
|
+
return runGrokSpawn(args.slice(1));
|
|
183
|
+
console.error(`Unknown "grok ${sub}". Run \`greprag grok spawn --help\`.`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
function getArg(args, flag) {
|
|
187
|
+
const idx = args.indexOf(flag);
|
|
188
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
189
|
+
return undefined;
|
|
190
|
+
const value = args[idx + 1];
|
|
191
|
+
return value.startsWith('--') ? undefined : value;
|
|
192
|
+
}
|
|
193
|
+
function positionalPrompt(args) {
|
|
194
|
+
const skip = new Set(['--cwd', '--prompt', '--prompt-file', '--title']);
|
|
195
|
+
const out = [];
|
|
196
|
+
for (let i = 0; i < args.length; i++) {
|
|
197
|
+
if (skip.has(args[i])) {
|
|
198
|
+
i += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (args[i] === '--dry-run' || args[i] === '--help' || args[i] === '-h')
|
|
202
|
+
continue;
|
|
203
|
+
if (args[i].startsWith('--'))
|
|
204
|
+
continue;
|
|
205
|
+
out.push(args[i]);
|
|
206
|
+
}
|
|
207
|
+
return out.length ? out.join(' ') : undefined;
|
|
208
|
+
}
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
* inbox`. This closes it: on every arm (SessionStart startup|resume|compact)
|
|
10
10
|
* drain THIS session's unread, session-DIRECTED messages into the agent's
|
|
11
11
|
* context, tagged peer/human, mark them read, and advance the stored cursor so
|
|
12
|
-
* there is no double-delivery.
|
|
12
|
+
* there is no double-delivery.
|
|
13
|
+
*
|
|
14
|
+
* INJECT ONLY WHEN UNARMED. A live watcher already printed the body (Claude
|
|
15
|
+
* Monitor / Grok monitor stdout). Grok also wires drain on Stop because
|
|
16
|
+
* SessionStart stdout cannot inject — that Stop path is the unarmed floor.
|
|
17
|
+
* Passing `armed: true` still marks inbound unread + advances the cursor, but
|
|
18
|
+
* does not inject (and never claims "no live watcher"). adr: adr/monitor-resilience.md
|
|
13
19
|
*
|
|
14
20
|
* SCOPE = session-directed INBOUND only (to_session_id == me). NOT the front
|
|
15
21
|
* desk (cold opens + email — that's the human-scoped `mail` hook's job) and NOT
|
|
@@ -126,7 +132,31 @@ async function runInboxDrain(opts) {
|
|
|
126
132
|
return none;
|
|
127
133
|
// Chronological (oldest → newest) so a coordination thread reads in order.
|
|
128
134
|
inbound.sort((a, b) => tsOf(a) - tsOf(b));
|
|
129
|
-
// 3.
|
|
135
|
+
// 3. Armed watcher already delivered the body (live stdout). Mark every
|
|
136
|
+
// inbound-unread read and advance the cursor, but do not inject — the
|
|
137
|
+
// unarmed copy would lie, and Grok Stop would duplicate a live interrupt.
|
|
138
|
+
const cursor = newestId(messages);
|
|
139
|
+
if (opts.armed) {
|
|
140
|
+
if (cursor) {
|
|
141
|
+
try {
|
|
142
|
+
writeCursor(opts.session, cursor);
|
|
143
|
+
}
|
|
144
|
+
catch { /* best-effort */ }
|
|
145
|
+
}
|
|
146
|
+
const ids = inbound.map(m => m.id).filter(Boolean);
|
|
147
|
+
if (ids.length > 0) {
|
|
148
|
+
try {
|
|
149
|
+
await doFetch(`${base}/v1/inbox/read`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, 'Content-Type': 'application/json' },
|
|
152
|
+
body: JSON.stringify({ ids }),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch { /* mark-read is best-effort */ }
|
|
156
|
+
}
|
|
157
|
+
return { context: null, drained: inbound.length, truncated: 0, cursorAdvancedTo: cursor };
|
|
158
|
+
}
|
|
159
|
+
// 4. Cap the injected volume to the NEWEST `cap`. Older inbound-unread stay
|
|
130
160
|
// unread (NOT marked, NOT silently dropped): they remain in `greprag inbox`
|
|
131
161
|
// and the next arm re-drains them. The overflow count is stated below.
|
|
132
162
|
let drain = inbound;
|
|
@@ -135,20 +165,20 @@ async function runInboxDrain(opts) {
|
|
|
135
165
|
truncated = inbound.length - cap;
|
|
136
166
|
drain = inbound.slice(inbound.length - cap); // newest `cap`, still chronological
|
|
137
167
|
}
|
|
138
|
-
//
|
|
168
|
+
// 5. Build the injected context (peer/human tagged). Unarmed-only — this copy
|
|
169
|
+
// is true because we returned above when a live watcher was present.
|
|
139
170
|
const context = buildDrainContext(drain, truncated, opts.session);
|
|
140
|
-
//
|
|
171
|
+
// 6. Advance the poll cursor to the newest endpoint message id so the just-
|
|
141
172
|
// armed poll resumes strictly AFTER everything already in the inbox — no
|
|
142
173
|
// double-delivery. Only when there is something to drain (never disturb a
|
|
143
174
|
// healthy poll's cursor on an empty arm).
|
|
144
|
-
const cursor = newestId(messages);
|
|
145
175
|
if (cursor) {
|
|
146
176
|
try {
|
|
147
177
|
writeCursor(opts.session, cursor);
|
|
148
178
|
}
|
|
149
179
|
catch { /* best-effort */ }
|
|
150
180
|
}
|
|
151
|
-
//
|
|
181
|
+
// 7. Mark the drained (injected) ids read LAST — after inject + cursor — so a
|
|
152
182
|
// crash before this point re-drains next arm rather than losing a message.
|
|
153
183
|
// Inbound ids only, so a peer's view of my outbound is never touched.
|
|
154
184
|
// Fail-quiet: an unreachable / not-yet-deployed /read route leaves the
|
|
@@ -30,7 +30,8 @@ const session_id_1 = require("../session-id");
|
|
|
30
30
|
function buildInboxPrimer(env) {
|
|
31
31
|
const codex = env.platform === 'codex';
|
|
32
32
|
const opencode = env.platform === 'opencode';
|
|
33
|
-
const
|
|
33
|
+
const grok = env.platform === 'grok';
|
|
34
|
+
const arm = (0, session_id_1.armMonitorCommand)(env.short, env.ownerPid, env.assistant, env.mechanic, env.platform);
|
|
34
35
|
return [
|
|
35
36
|
// LEAD — the forcing function (restored 2026-06-22). ARM is an ALARM, not reference:
|
|
36
37
|
// you are unreachable until you do it, and it must be re-done every time the watcher
|
|
@@ -39,16 +40,20 @@ function buildInboxPrimer(env) {
|
|
|
39
40
|
// opencode gets NO alarm: its relay self-arms in the plugin (startSessionRelay) —
|
|
40
41
|
// "ToolSearch select:Monitor" is unactionable there (neither tool exists) and an
|
|
41
42
|
// un-followable alarm decays into banner-blindness for the announces around it.
|
|
42
|
-
|
|
43
|
-
? '[
|
|
44
|
-
:
|
|
45
|
-
? '[
|
|
46
|
-
:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
grok
|
|
44
|
+
? '[GROK INBOX — greprag is your agent-to-agent mesh. Idle wake is Grok `monitor` (NOT Claude Monitor, NOT a bash while-true). Turn-boundary drain also rides Stop additionalContext. Unarmed, idle mail waits until the next Stop or user prompt.]'
|
|
45
|
+
: codex
|
|
46
|
+
? '[CODEX INBOX — greprag is your agent-to-agent mesh. Codex-to-Codex: discover tasks and their repo/workspace with `codex_app.list_threads`, then coordinate with `codex_app.send_message_to_thread`. Reaching ANY non-Codex peer (Claude Code / opencode / Grok / another tenant\'s agent): `greprag send` IS the live primary path — native Codex messaging cannot reach them.]'
|
|
47
|
+
: opencode
|
|
48
|
+
? '[OPENCODE INBOX — greprag is your agent-to-agent mesh. Inbound delivery is AUTOMATIC here: the greprag plugin arms a relay for this session (nothing to arm or re-arm) and inbound peer messages arrive as injected turns.]'
|
|
49
|
+
: '[⚠ ARM YOUR INBOX WATCHER NOW — you are UNREACHABLE until you do. Peers are live in your mesh trying to coordinate with this session, and the operator can DM you — but they reach you ONLY while armed. Unarmed, you are invisible and their messages silently pile up. The watcher can DROP ANYTIME (crash / reload / OOM) — EVERY turn you are flagged unarmed you MUST re-arm, or coordination breaks.]',
|
|
50
|
+
grok
|
|
51
|
+
? `ARM (idle wake): Grok \`monitor\` tool, persistent:true, description:"greprag inbox ${env.short}", command: \`${arm}\`. \`--quiet\` is REQUIRED — Grok treats stderr as wake events; the Claude bash wrapper is PowerShell-invalid. Use full UUID / 16-hex, never 8-hex. A chip → \`greprag load grok-chip-spawn\` then \`greprag grok spawn\` (child arms its own watch; parent keeps ONE). A helper → spawn_subagent. Floor: Stop-hook drain injects unread mail even if you never arm. Then \`greprag inbox\`.`
|
|
52
|
+
: codex
|
|
53
|
+
? 'CODEX: Native Codex tools are the source of truth: `codex_app.list_threads` discovers tasks plus their repo/workspace, and `codex_app.send_message_to_thread` handles Codex-to-Codex coordination. GrepRAG inbox rows for Codex surface through turn hooks only: SessionStart `drain` and UserPromptSubmit `codex-notify`. There is no Codex startup watcher to install. If hooks do not fire, open Codex Desktop Settings -> Settings -> Hooks, trust the GrepRAG commands, start a fresh Codex session, then drain what is waiting: `greprag inbox`.'
|
|
54
|
+
: opencode
|
|
55
|
+
? 'DELIVERY: the relay arms itself on this session\'s first turn and injects inbound messages as they land — treat an injected `Message from <handle> (session <8hex>):` turn as async peer mail, not the operator typing. Drain anything already waiting: `greprag inbox`.'
|
|
56
|
+
: `ARM: Monitor is a deferred tool — \`ToolSearch select:Monitor\`, then call Monitor with persistent:true, timeout_ms:3600000, description:"greprag inbox ${env.short}", command: \`${arm}\`. The \`--session ${env.short}\` is REQUIRED — a bare \`greprag inbox watch\` writes no pidfile, so the arm-check reads you unarmed forever. NEVER run it bare also because it is a foreground stream and hangs the turn. Then drain what is waiting: \`greprag inbox\`.`,
|
|
52
57
|
'',
|
|
53
58
|
// The facts + directives — a flat list (no MODEL/RULES scaffolding; the labels were
|
|
54
59
|
// human doc-structure, dead weight to an agent). Everything that helps the agent DECIDE
|
|
@@ -36,6 +36,16 @@ const child_process_1 = require("child_process");
|
|
|
36
36
|
const inbox_watch_1 = require("./inbox-watch");
|
|
37
37
|
const watcher_registry_1 = require("./watcher-registry");
|
|
38
38
|
const LOG_PREFIX = '[greprag inbox watch]';
|
|
39
|
+
function watchQuiet() {
|
|
40
|
+
return process.env.GREPRAG_WATCH_QUIET === '1'
|
|
41
|
+
|| !!process.env.GROK_SESSION_ID
|
|
42
|
+
|| process.argv.includes('--quiet');
|
|
43
|
+
}
|
|
44
|
+
function slog(msg) {
|
|
45
|
+
if (watchQuiet())
|
|
46
|
+
return;
|
|
47
|
+
console.error(msg);
|
|
48
|
+
}
|
|
39
49
|
// ---- Supervisor-driven watcher liveness (registry freshness) -------------
|
|
40
50
|
// adr: docs/registry-freshness-design.md
|
|
41
51
|
//
|
|
@@ -107,12 +117,12 @@ exports.FATAL_EXIT_CODE = 64;
|
|
|
107
117
|
function installLastResortHandlers() {
|
|
108
118
|
process.on('uncaughtException', (err) => {
|
|
109
119
|
const msg = (err && err.message) || String(err);
|
|
110
|
-
|
|
120
|
+
slog(`${LOG_PREFIX} uncaughtException: ${msg}`);
|
|
111
121
|
process.exit(2);
|
|
112
122
|
});
|
|
113
123
|
process.on('unhandledRejection', (reason) => {
|
|
114
124
|
const msg = (reason && reason.message) || String(reason);
|
|
115
|
-
|
|
125
|
+
slog(`${LOG_PREFIX} unhandledRejection: ${msg}`);
|
|
116
126
|
process.exit(2);
|
|
117
127
|
});
|
|
118
128
|
}
|
|
@@ -263,7 +273,7 @@ async function runSupervisor() {
|
|
|
263
273
|
// (`armMonitorCommand`) relaunches a fresh supervisor — turn-free recovery, the
|
|
264
274
|
// missing rung for supervisor death. adr: adr/monitor-resilience.md
|
|
265
275
|
const onFatalError = (label) => (err) => {
|
|
266
|
-
|
|
276
|
+
slog(`${LOG_PREFIX} supervisor ${label}: ${(err && err.message) || String(err)}`);
|
|
267
277
|
process.exit(1); // non-zero (not 0/64) → the relauncher relaunches us
|
|
268
278
|
};
|
|
269
279
|
const onUncaught = onFatalError('uncaughtException');
|
|
@@ -289,7 +299,7 @@ async function runSupervisor() {
|
|
|
289
299
|
// genuinely live sibling. adr: adr/monitor-resilience.md
|
|
290
300
|
if (session && (0, watcher_registry_1.isLocallyArmed)(session)) {
|
|
291
301
|
(0, watcher_registry_1.watcherAuditLog)(`arm-skip session=${session} singleton-guard (live sibling pidfile)`);
|
|
292
|
-
|
|
302
|
+
slog(`${LOG_PREFIX} session ${session} already has a live watcher — not starting a second.`);
|
|
293
303
|
return;
|
|
294
304
|
}
|
|
295
305
|
// Local pidfile (Part 1 of the local-liveness fix): record THIS supervisor's
|
|
@@ -333,7 +343,7 @@ async function runSupervisor() {
|
|
|
333
343
|
}
|
|
334
344
|
if (spawnErr || !currentChild) {
|
|
335
345
|
const reason = spawnErr ? spawnErr.message : 'spawn returned null child';
|
|
336
|
-
|
|
346
|
+
slog(`${LOG_PREFIX} supervisor: spawn failed (${reason}) — retry in ${(0, inbox_watch_1.fmtDuration)(backoff)}`);
|
|
337
347
|
await sleepUntilOrShutdown(backoff, () => shutdownRequested);
|
|
338
348
|
backoff = Math.min(backoff * SUPERVISOR_BACKOFF_FACTOR, SUPERVISOR_BACKOFF_MAX_MS);
|
|
339
349
|
continue;
|
|
@@ -402,7 +412,7 @@ async function runSupervisor() {
|
|
|
402
412
|
const hint = native
|
|
403
413
|
? `the child is dying at the OS level (${reason}) before it can run — characteristic of system commit-charge / page-file exhaustion (NOT physical RAM; see docs/session-heap-ceiling.md). Standing down so we stop feeding the exhaustion; the next-turn arm reminder retries once the machine recovers.`
|
|
404
414
|
: `the child cannot stay up (${reason}). Run \`greprag inbox watch --no-supervise\` to see the underlying error. Standing down to avoid a respawn storm.`;
|
|
405
|
-
|
|
415
|
+
slog(`${LOG_PREFIX} supervisor: child crash-looped ${consecutiveFastDeaths}× (each < ${(0, inbox_watch_1.fmtDuration)(CRASH_LOOP_LIFETIME_MS)}) — ${hint}`);
|
|
406
416
|
(0, watcher_registry_1.watcherAuditLog)(`crash-loop session=${session ?? 'tenant'} deaths=${consecutiveFastDeaths} last=${reason}`);
|
|
407
417
|
terminalReason = `crash-loop-standdown (${reason})`;
|
|
408
418
|
// Exit FATAL (64) so the bash relauncher BREAKs rather than relaunching us
|
|
@@ -411,7 +421,7 @@ async function runSupervisor() {
|
|
|
411
421
|
process.exitCode = exports.FATAL_EXIT_CODE;
|
|
412
422
|
break;
|
|
413
423
|
}
|
|
414
|
-
|
|
424
|
+
slog(`${LOG_PREFIX} supervisor: child died (${reason}) after ${(0, inbox_watch_1.fmtDuration)(liveMs)} — respawning in ${(0, inbox_watch_1.fmtDuration)(backoff)}`);
|
|
415
425
|
await sleepUntilOrShutdown(backoff, () => shutdownRequested);
|
|
416
426
|
backoff = Math.min(backoff * SUPERVISOR_BACKOFF_FACTOR, SUPERVISOR_BACKOFF_MAX_MS);
|
|
417
427
|
}
|