greprag 5.75.0 → 5.77.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/codex-doctor.js +1 -0
- package/dist/commands/delivery-reminder.js +5 -5
- package/dist/commands/grok-spawn.js +208 -0
- package/dist/commands/inbox-drain.js +36 -6
- package/dist/commands/inbox-primer-reminder.js +1 -1
- package/dist/commands/inbox-watch.js +14 -2
- package/dist/commands/init.js +33 -8
- package/dist/commands/load-primer-reminder.js +8 -1
- package/dist/commands/load.js +4 -0
- package/dist/commands/opencode-interrupt.js +8 -0
- package/dist/commands/os-primer-reminder.js +2 -1
- package/dist/commands/persona-reminder.js +9 -0
- package/dist/commands/persona.js +191 -0
- package/dist/commands/reminder-registry.js +2 -0
- package/dist/commands/status.js +2 -1
- package/dist/hook.js +38 -9
- package/dist/index.js +56 -11
- package/dist/opencode-plugin.bundle.js +69 -10
- package/dist/opencode-plugin.js +45 -2
- package/dist/session-id.js +13 -0
- package/package.json +1 -1
- package/skill/greprag/SKILL.md +37 -3
- package/skill/templates/chip-spawn.md +2 -0
- package/skill/templates/grok-chip-spawn.md +101 -0
|
@@ -113,5 +113,6 @@ async function doctorCodex(args, deps) {
|
|
|
113
113
|
console.log(` PermissionRequest ctx: ${hasHook(hooks, 'PermissionRequest', 'codex-permission-context') ? 'yes' : 'no'}`);
|
|
114
114
|
console.log(` SubagentStart metadata: ${hasHook(hooks, 'SubagentStart', 'codex-subagent-start') ? 'yes' : 'no'}`);
|
|
115
115
|
console.log(` Stop store: ${hasHook(hooks, 'Stop', 'codex-store') ? 'yes' : 'no'}`);
|
|
116
|
+
console.log(` PostCompact re-announce: ${hasHook(hooks, 'PostCompact', 'recompact') ? 'yes' : 'no'}`);
|
|
116
117
|
console.log(` PostCompact session-id: ${hasHook(hooks, 'PostCompact', 'session-id') ? 'yes' : 'no'}`);
|
|
117
118
|
}
|
|
@@ -12,15 +12,15 @@ exports.buildDeliveryAnnounce = buildDeliveryAnnounce;
|
|
|
12
12
|
// adr: adr/delivery-announce-pilot.md
|
|
13
13
|
function coordinationLine(platform) {
|
|
14
14
|
if (platform === 'codex') {
|
|
15
|
-
return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo,
|
|
15
|
+
return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, and inspect their current summaries/status. Use codex_app.send_message_to_thread only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups. Delivery owner: rename this task `DEPLOY: <existing title>` for operator visibility only; transfer the prefix on handoff; remove it before reporting delivery complete or abandoned.';
|
|
16
16
|
}
|
|
17
17
|
if (platform === 'claude-code') {
|
|
18
|
-
return 'Claude Code: at the merge/deploy gate,
|
|
18
|
+
return 'Claude Code: at the merge/deploy gate, inspect peers in the same repo and send one concise delivery notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
|
|
19
19
|
}
|
|
20
20
|
if (platform === 'opencode') {
|
|
21
|
-
return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise
|
|
21
|
+
return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
|
|
22
22
|
}
|
|
23
|
-
return 'At the merge/deploy gate,
|
|
23
|
+
return 'At the merge/deploy gate, inspect peers in the same repo and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
|
|
24
24
|
}
|
|
25
25
|
function buildDeliveryAnnounce(env) {
|
|
26
26
|
if (!env.deliveryRepo)
|
|
@@ -30,7 +30,7 @@ function buildDeliveryAnnounce(env) {
|
|
|
30
30
|
`[DELIVERY — ${project}]`,
|
|
31
31
|
'Commit useful, passing work.',
|
|
32
32
|
coordinationLine(env.platform),
|
|
33
|
-
'Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately.
|
|
33
|
+
'Include visible ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Silence means nothing actionable and does not block delivery.',
|
|
34
34
|
'Use this repo\'s delivery profile and verify production.',
|
|
35
35
|
].join('\n');
|
|
36
36
|
}
|
|
@@ -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
|
|
@@ -48,7 +48,7 @@ function buildInboxPrimer(env) {
|
|
|
48
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
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
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.
|
|
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
52
|
: codex
|
|
53
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
54
|
: opencode
|
|
@@ -50,6 +50,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
50
50
|
})();
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.CONSUMER_GONE_EXIT_CODE = void 0;
|
|
53
|
+
exports.resolveWatchPlatform = resolveWatchPlatform;
|
|
53
54
|
exports.getConfig = getConfig;
|
|
54
55
|
exports.fmtDuration = fmtDuration;
|
|
55
56
|
exports.parseEventBlock = parseEventBlock;
|
|
@@ -59,6 +60,7 @@ exports.runInboxWatch = runInboxWatch;
|
|
|
59
60
|
const fs = __importStar(require("fs"));
|
|
60
61
|
const path = __importStar(require("path"));
|
|
61
62
|
const session_id_1 = require("../session-id");
|
|
63
|
+
const harness_1 = require("../harness");
|
|
62
64
|
const mechanic_friction_1 = require("../mechanic-friction");
|
|
63
65
|
const inbox_watch_supervisor_1 = require("./inbox-watch-supervisor");
|
|
64
66
|
const API_URL_DEFAULT = 'https://api.greprag.com';
|
|
@@ -84,6 +86,14 @@ exports.CONSUMER_GONE_EXIT_CODE = 65;
|
|
|
84
86
|
// than waiting for the next real message that may never come. Env-overridable
|
|
85
87
|
// for tests (the idle path otherwise takes the full 30s to observe).
|
|
86
88
|
const CONSUMER_PROBE_MS = Number(process.env.GREPRAG_WATCH_PROBE_MS) || 30_000;
|
|
89
|
+
const WATCH_PLATFORMS = new Set(['claude-code', 'codex', 'opencode', 'grok']);
|
|
90
|
+
/** Platform tag for this watch attach. Explicit opt wins; else infer. */
|
|
91
|
+
function resolveWatchPlatform(explicit) {
|
|
92
|
+
if (explicit && WATCH_PLATFORMS.has(explicit))
|
|
93
|
+
return explicit;
|
|
94
|
+
const h = (0, harness_1.inferCurrentHarness)();
|
|
95
|
+
return (h && WATCH_PLATFORMS.has(h)) ? h : undefined;
|
|
96
|
+
}
|
|
87
97
|
// -- Config (mirrors the loader in index.ts/discover.ts) -------------------
|
|
88
98
|
function loadEnvFile(filePath) {
|
|
89
99
|
try {
|
|
@@ -392,7 +402,7 @@ async function readStream(url, apiKey, signal, initialId, json, idleTimeoutMs, s
|
|
|
392
402
|
signal.removeEventListener('abort', onOuterAbort);
|
|
393
403
|
}
|
|
394
404
|
}
|
|
395
|
-
function buildUrl(apiUrl, project, session, since, receptionist, assistant, mechanic) {
|
|
405
|
+
function buildUrl(apiUrl, project, session, since, receptionist, assistant, mechanic, platform) {
|
|
396
406
|
const u = new URL(apiUrl.replace(/\/+$/, '') + '/v1/inbox/stream');
|
|
397
407
|
if (project)
|
|
398
408
|
u.searchParams.set('project', project);
|
|
@@ -406,6 +416,8 @@ function buildUrl(apiUrl, project, session, since, receptionist, assistant, mech
|
|
|
406
416
|
u.searchParams.set('role', 'mechanic');
|
|
407
417
|
else if (assistant)
|
|
408
418
|
u.searchParams.set('role', 'assistant');
|
|
419
|
+
if (platform)
|
|
420
|
+
u.searchParams.set('platform', platform);
|
|
409
421
|
return u.toString();
|
|
410
422
|
}
|
|
411
423
|
/** Public entry for `greprag inbox watch`. Dispatches by mode:
|
|
@@ -545,7 +557,7 @@ async function runWatchLoop(opts) {
|
|
|
545
557
|
watchErr(`${LOG_PREFIX} reconnecting (last_seen_id=${lastSeen})`, opts);
|
|
546
558
|
}
|
|
547
559
|
isFirstAttempt = false;
|
|
548
|
-
const url = buildUrl(cfg.apiUrl, opts.project, opts.session, cursor, !!opts.receptionist, !!opts.assistant, !!opts.mechanic);
|
|
560
|
+
const url = buildUrl(cfg.apiUrl, opts.project, opts.session, cursor, !!opts.receptionist, !!opts.assistant, !!opts.mechanic, resolveWatchPlatform(opts.platform));
|
|
549
561
|
try {
|
|
550
562
|
const lastId = await readStream(url, cfg.apiKey, controller.signal, cursor, !!opts.json, idleTimeoutMs, opts.session);
|
|
551
563
|
if (lastId)
|
package/dist/commands/init.js
CHANGED
|
@@ -668,9 +668,10 @@ Before other work this session:
|
|
|
668
668
|
\`greprag inbox watch --session $GROK_SESSION_ID --json --quiet\`
|
|
669
669
|
Not Claude Monitor. No bash while-true. \`--quiet\` required (stderr is a wake).
|
|
670
670
|
If a greprag inbox monitor is already listed in this session, do not start another. Instant exit = already armed, not a crash.
|
|
671
|
-
4.
|
|
671
|
+
4. Chip: \`greprag load grok-chip-spawn\` then \`greprag grok spawn\` (real TUI + inbox). Helper: \`spawn_subagent\` background=true. Child arms its own quiet watch with ITS \`$GROK_SESSION_ID\`. Parent sends:
|
|
672
672
|
\`greprag send "…" --to travis@greprag.com/<child-full-uuid> --from-session $GROK_SESSION_ID\`
|
|
673
673
|
Parent keeps ONE watch, for itself. Child watch events may appear on the parent TUI — resume the child to act. Do not arm a second parent watch.
|
|
674
|
+
5. Tool shell: stdin redirected + \`-NonInteractive\`. \`Read-Host\` throws. \`Start-Process\` windows die with the tool Job Object. Secret paste: \`~/.claude/scripts/prompt-secret.ps1\`. Sticky console: \`schtasks /Create … /IT\` then \`schtasks /Run\`. npm Hello: \`npm login --auth-type=web\` in that window; browser Use security key; page token ≠ Hello PIN.
|
|
674
675
|
`;
|
|
675
676
|
function getGrokHooksPath() {
|
|
676
677
|
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
@@ -691,6 +692,15 @@ function applyGrokHooks(config) {
|
|
|
691
692
|
const changes = [];
|
|
692
693
|
if (!config.hooks)
|
|
693
694
|
config.hooks = {};
|
|
695
|
+
// Empty matcher = every SessionStart source (startup, resume, /new, dashboard
|
|
696
|
+
// dispatch, fork). `startup|resume` missed /new and dashboard agents, so this
|
|
697
|
+
// session never got a recap sidecar. adr: adr/grok-platform.md
|
|
698
|
+
const sessionStartMatcher = '';
|
|
699
|
+
if (!config.hooks.SessionStart)
|
|
700
|
+
config.hooks.SessionStart = [];
|
|
701
|
+
const retargeted = ['recap', 'session-id', 'drain'].reduce((n, sub) => (n + retargetGrepragHookMatcher(config.hooks.SessionStart, sub, ['startup|resume'], sessionStartMatcher)), 0);
|
|
702
|
+
if (retargeted)
|
|
703
|
+
changes.push(`Retargeted Grok SessionStart matcher (${retargeted})`);
|
|
694
704
|
const add = (event, matcher, sub, timeout, label) => {
|
|
695
705
|
if (hasGrepragHook(config.hooks[event], sub)) {
|
|
696
706
|
changes.push(`Grok ${event} ${label} already configured (skipped)`);
|
|
@@ -701,14 +711,15 @@ function applyGrokHooks(config) {
|
|
|
701
711
|
config.hooks[event].push({ matcher, hooks: [grokCommand(sub, timeout)] });
|
|
702
712
|
changes.push(`Added Grok ${event} hook (${label})`);
|
|
703
713
|
};
|
|
704
|
-
add('SessionStart',
|
|
705
|
-
add('SessionStart',
|
|
706
|
-
add('SessionStart',
|
|
714
|
+
add('SessionStart', sessionStartMatcher, 'recap', 15, 'memory recap');
|
|
715
|
+
add('SessionStart', sessionStartMatcher, 'session-id', 5, 'session-id');
|
|
716
|
+
add('SessionStart', sessionStartMatcher, 'drain', 8, 'inbox drain');
|
|
707
717
|
add('UserPromptSubmit', '', 'notify', 8, 'prompt cache');
|
|
708
718
|
add('PreToolUse', 'Bash|run_terminal_command', 'crush-wrap', 5, 'crush-wrap');
|
|
709
719
|
add('PreToolUse', '*', 'guard', 5, 'guard');
|
|
710
720
|
add('Stop', '', 'store', 15, 'turn capture');
|
|
711
721
|
add('Stop', '', 'drain', 8, 'inbox drain');
|
|
722
|
+
add('PostCompact', '', 'recompact', 15, 'Interrupt re-announce');
|
|
712
723
|
add('PostCompact', '', 'session-id', 5, 'session-id');
|
|
713
724
|
return changes;
|
|
714
725
|
}
|
|
@@ -797,7 +808,7 @@ async function runGrokInit(opts) {
|
|
|
797
808
|
console.log(' Reload hooks in Grok (/hooks then r) or start a fresh session.');
|
|
798
809
|
console.log(' Recap lands in ~/.greprag/grok-context/<16-hex>.md — Grok ignores SessionStart stdout.');
|
|
799
810
|
console.log(' Idle inbox: Grok monitor + `greprag inbox watch --session $GROK_SESSION_ID --json --quiet`.');
|
|
800
|
-
console.log(' Second session: spawn_subagent; child arms its own watch; parent keeps one.\n');
|
|
811
|
+
console.log(' Second session: `greprag grok spawn` (TUI) or spawn_subagent; child arms its own watch; parent keeps one.\n');
|
|
801
812
|
}
|
|
802
813
|
/** greprag init --global
|
|
803
814
|
* Creates ~/.greprag/project.json with a stable UUID.
|
|
@@ -1401,14 +1412,27 @@ function applyCodexHooks(config) {
|
|
|
1401
1412
|
else {
|
|
1402
1413
|
changes.push('Codex Stop hook already configured (skipped)');
|
|
1403
1414
|
}
|
|
1404
|
-
const
|
|
1415
|
+
const postCompactAnnounceHook = {
|
|
1416
|
+
matcher: 'manual|auto',
|
|
1417
|
+
hooks: [commandHook('recompact', 10, 'Re-announcing GrepRAG primers')],
|
|
1418
|
+
};
|
|
1419
|
+
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'recompact', postCompactAnnounceHook.matcher)) {
|
|
1420
|
+
if (!config.hooks.PostCompact)
|
|
1421
|
+
config.hooks.PostCompact = [];
|
|
1422
|
+
config.hooks.PostCompact.push(postCompactAnnounceHook);
|
|
1423
|
+
changes.push('Added Codex PostCompact hook (Interrupt re-announce)');
|
|
1424
|
+
}
|
|
1425
|
+
else {
|
|
1426
|
+
changes.push('Codex PostCompact Interrupt re-announce hook already configured (skipped)');
|
|
1427
|
+
}
|
|
1428
|
+
const postCompactSessionHook = {
|
|
1405
1429
|
matcher: 'manual|auto',
|
|
1406
1430
|
hooks: [commandHook('session-id', 3, 'Restoring GrepRAG session id')],
|
|
1407
1431
|
};
|
|
1408
|
-
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id',
|
|
1432
|
+
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactSessionHook.matcher)) {
|
|
1409
1433
|
if (!config.hooks.PostCompact)
|
|
1410
1434
|
config.hooks.PostCompact = [];
|
|
1411
|
-
config.hooks.PostCompact.push(
|
|
1435
|
+
config.hooks.PostCompact.push(postCompactSessionHook);
|
|
1412
1436
|
changes.push('Added Codex PostCompact hook (session-id awareness)');
|
|
1413
1437
|
}
|
|
1414
1438
|
else {
|
|
@@ -1422,6 +1446,7 @@ function normalizeCodexHookCommands(hooks) {
|
|
|
1422
1446
|
const desired = {
|
|
1423
1447
|
recap: { timeout: 10, statusMessage: 'Loading GrepRAG memory' },
|
|
1424
1448
|
'codex-recap': { timeout: 10, statusMessage: 'Loading GrepRAG memory' },
|
|
1449
|
+
recompact: { timeout: 10, statusMessage: 'Re-announcing GrepRAG primers' },
|
|
1425
1450
|
'session-id': { timeout: 3, statusMessage: 'Loading GrepRAG session id' },
|
|
1426
1451
|
drain: { timeout: 5, statusMessage: 'Draining GrepRAG inbox' },
|
|
1427
1452
|
'codex-notify': { timeout: 300, statusMessage: 'Checking GrepRAG inbox', runner: 'greprag-codex-hook' },
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* load-primer` / `chip-spawn-pointer`). Announce-only: detect → silent,
|
|
20
20
|
* reminder → null. adr: docs/load-system.md, docs/reminder-interrupt.md */
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.codexChipSpawnPointerModule = exports.chipSpawnPointerModule = exports.loadPrimerModule = exports.CODEX_CHIP_SPAWN_POINTER = exports.OPENCODE_CHIP_SPAWN_POINTER = exports.CHIP_SPAWN_POINTER = exports.LOAD_PRIMER = void 0;
|
|
22
|
+
exports.codexChipSpawnPointerModule = exports.chipSpawnPointerModule = exports.loadPrimerModule = exports.CODEX_CHIP_SPAWN_POINTER = exports.GROK_CHIP_SPAWN_POINTER = exports.OPENCODE_CHIP_SPAWN_POINTER = exports.CHIP_SPAWN_POINTER = exports.LOAD_PRIMER = void 0;
|
|
23
23
|
/** PRIMER — teaches the loader itself. Resident (fires every SessionStart +
|
|
24
24
|
* re-teaches after compaction). Terse: it is in context every session. */
|
|
25
25
|
exports.LOAD_PRIMER = [
|
|
@@ -43,6 +43,11 @@ exports.OPENCODE_CHIP_SPAWN_POINTER = [
|
|
|
43
43
|
'[greprag chips — agent coordination is built in (messaging plumbing in the inbox primer).]',
|
|
44
44
|
'• Delegating a component of a plan to an isolated chip session, or `greprag fix spawn` just printed a FIX-chip mission? → run `greprag load chip-bootloader` FIRST for the method: OpenCode chips spawn natively via `greprag opencode chip goal create` + `greprag opencode chip spawn` (HTTP child session; manual paste only when the API is down). ≥2 chips at one objective → `greprag load chip-leader-opencode` BEFORE the first spawn.',
|
|
45
45
|
].join('\n');
|
|
46
|
+
/** POINTER — Grok Build TUI chip. Real window + inbox mesh, not spawn_subagent. */
|
|
47
|
+
exports.GROK_CHIP_SPAWN_POINTER = [
|
|
48
|
+
'[greprag chips — agent coordination is built in (messaging plumbing in the inbox primer).]',
|
|
49
|
+
'• About to spawn a chip? → run `greprag load grok-chip-spawn` FIRST. Vehicle is `greprag grok spawn` (cmd /k TUI bootloader). Child arms its own quiet inbox watch; parent keeps one and talks via `greprag send`. `spawn_subagent` is a helper, not a chip. ≥2 chips at one objective → `greprag load chip-leader` BEFORE the first spawn.',
|
|
50
|
+
].join('\n');
|
|
46
51
|
/** POINTER — Codex Desktop's native chip path. This is intentionally distinct
|
|
47
52
|
* from Claude Code spawn_task: different lifecycle, runtime, and handoff. */
|
|
48
53
|
exports.CODEX_CHIP_SPAWN_POINTER = [
|
|
@@ -70,6 +75,8 @@ exports.chipSpawnPointerModule = {
|
|
|
70
75
|
return null;
|
|
71
76
|
if (env.platform === 'opencode')
|
|
72
77
|
return exports.OPENCODE_CHIP_SPAWN_POINTER;
|
|
78
|
+
if (env.platform === 'grok')
|
|
79
|
+
return exports.GROK_CHIP_SPAWN_POINTER;
|
|
73
80
|
return exports.CHIP_SPAWN_POINTER;
|
|
74
81
|
},
|
|
75
82
|
reminder: () => null,
|
package/dist/commands/load.js
CHANGED
|
@@ -73,6 +73,10 @@ const LIBRARY = {
|
|
|
73
73
|
files: ['skill/templates/codex-chip-spawn.md'],
|
|
74
74
|
purpose: 'Spawn an independent writable native Codex Desktop task with an isolated worktree and native completion/cleanup.',
|
|
75
75
|
},
|
|
76
|
+
'grok-chip-spawn': {
|
|
77
|
+
files: ['skill/templates/grok-chip-spawn.md'],
|
|
78
|
+
purpose: 'Spawn a Grok TUI chip (cmd /k bootloader + isolated worktree). Child arms its own inbox watch; parent talks via greprag send. Not spawn_subagent.',
|
|
79
|
+
},
|
|
76
80
|
'skill-change': {
|
|
77
81
|
files: ['skill/templates/skill-change.md'],
|
|
78
82
|
purpose: 'Internal bundled schema for safely updating a skill after a run: when to edit, Convention A/B shapes, and when to propose instead.',
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.OPENCODE_QUOTA_WAITLIST_ANNOUNCE = exports.opencodeRegistry = void 0;
|
|
4
4
|
exports.buildOpenCodeEnv = buildOpenCodeEnv;
|
|
5
5
|
exports.getOpenCodeAnnounces = getOpenCodeAnnounces;
|
|
6
|
+
exports.getOpenCodePersistentAnnounces = getOpenCodePersistentAnnounces;
|
|
6
7
|
exports.getOpenCodeReminders = getOpenCodeReminders;
|
|
7
8
|
const reminder_registry_1 = require("./reminder-registry");
|
|
8
9
|
/** The opencode registry is DERIVED from the canon, never hand-whitelisted.
|
|
@@ -44,11 +45,18 @@ function buildOpenCodeEnv(params) {
|
|
|
44
45
|
updateAvailable: params.updateAvailable ?? null,
|
|
45
46
|
procedureAnnounces: params.procedureAnnounces ?? [],
|
|
46
47
|
mirroredSkills: params.mirroredSkills,
|
|
48
|
+
personaAnnounce: params.personaAnnounce ?? null,
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
function getOpenCodeAnnounces(env) {
|
|
50
52
|
return [...(0, reminder_registry_1.collectAnnounces)(env, exports.opencodeRegistry), exports.OPENCODE_QUOTA_WAITLIST_ANNOUNCE];
|
|
51
53
|
}
|
|
54
|
+
/** OpenCode rebuilds output.system for every model call. Persona is the one
|
|
55
|
+
* startup primer that must remain behaviorally active on later calls, so the
|
|
56
|
+
* plugin replays this shared-registry module from its bounded session cache. */
|
|
57
|
+
function getOpenCodePersistentAnnounces(env) {
|
|
58
|
+
return (0, reminder_registry_1.collectAnnounces)(env, exports.opencodeRegistry.filter((m) => m.id === 'persona-announce'));
|
|
59
|
+
}
|
|
52
60
|
function getOpenCodeReminders(env) {
|
|
53
61
|
return (0, reminder_registry_1.collectReminders)(env, exports.opencodeRegistry);
|
|
54
62
|
}
|
|
@@ -20,7 +20,8 @@ function buildOsPrimer(env) {
|
|
|
20
20
|
// vehicle is the chip-bootloader entry: native `greprag opencode chip` spawn).
|
|
21
21
|
const spawnEntry = env?.platform === 'codex' ? 'codex-chip-spawn'
|
|
22
22
|
: env?.platform === 'opencode' ? 'chip-bootloader'
|
|
23
|
-
: 'chip-spawn'
|
|
23
|
+
: env?.platform === 'grok' ? 'grok-chip-spawn'
|
|
24
|
+
: 'chip-spawn';
|
|
24
25
|
return [
|
|
25
26
|
'[grepragOS — the operating laws. Full doctrine: `greprag load os`.]',
|
|
26
27
|
'• Doctrine vs state: methods ship in the CLI (`greprag load`); live state lives in the repo. A skill that depends on repo state carries a "STATE — read these first" block naming exact paths.',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.personaAnnounceModule = void 0;
|
|
4
|
+
exports.personaAnnounceModule = {
|
|
5
|
+
id: 'persona-announce',
|
|
6
|
+
detect: () => ({ tier: 'silent' }),
|
|
7
|
+
announce: (env) => env.personaAnnounce || null,
|
|
8
|
+
reminder: () => null,
|
|
9
|
+
};
|