greprag 5.76.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/init.js +18 -3
- package/dist/commands/opencode-interrupt.js +8 -0
- 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 +1 -0
- package/dist/hook.js +25 -2
- package/dist/index.js +11 -0
- package/dist/opencode-plugin.bundle.js +61 -8
- package/dist/opencode-plugin.js +45 -2
- package/package.json +1 -1
|
@@ -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
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -719,6 +719,7 @@ function applyGrokHooks(config) {
|
|
|
719
719
|
add('PreToolUse', '*', 'guard', 5, 'guard');
|
|
720
720
|
add('Stop', '', 'store', 15, 'turn capture');
|
|
721
721
|
add('Stop', '', 'drain', 8, 'inbox drain');
|
|
722
|
+
add('PostCompact', '', 'recompact', 15, 'Interrupt re-announce');
|
|
722
723
|
add('PostCompact', '', 'session-id', 5, 'session-id');
|
|
723
724
|
return changes;
|
|
724
725
|
}
|
|
@@ -1411,14 +1412,27 @@ function applyCodexHooks(config) {
|
|
|
1411
1412
|
else {
|
|
1412
1413
|
changes.push('Codex Stop hook already configured (skipped)');
|
|
1413
1414
|
}
|
|
1414
|
-
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 = {
|
|
1415
1429
|
matcher: 'manual|auto',
|
|
1416
1430
|
hooks: [commandHook('session-id', 3, 'Restoring GrepRAG session id')],
|
|
1417
1431
|
};
|
|
1418
|
-
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id',
|
|
1432
|
+
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactSessionHook.matcher)) {
|
|
1419
1433
|
if (!config.hooks.PostCompact)
|
|
1420
1434
|
config.hooks.PostCompact = [];
|
|
1421
|
-
config.hooks.PostCompact.push(
|
|
1435
|
+
config.hooks.PostCompact.push(postCompactSessionHook);
|
|
1422
1436
|
changes.push('Added Codex PostCompact hook (session-id awareness)');
|
|
1423
1437
|
}
|
|
1424
1438
|
else {
|
|
@@ -1432,6 +1446,7 @@ function normalizeCodexHookCommands(hooks) {
|
|
|
1432
1446
|
const desired = {
|
|
1433
1447
|
recap: { timeout: 10, statusMessage: 'Loading GrepRAG memory' },
|
|
1434
1448
|
'codex-recap': { timeout: 10, statusMessage: 'Loading GrepRAG memory' },
|
|
1449
|
+
recompact: { timeout: 10, statusMessage: 'Re-announcing GrepRAG primers' },
|
|
1435
1450
|
'session-id': { timeout: 3, statusMessage: 'Loading GrepRAG session id' },
|
|
1436
1451
|
drain: { timeout: 5, statusMessage: 'Draining GrepRAG inbox' },
|
|
1437
1452
|
'codex-notify': { timeout: 300, statusMessage: 'Checking GrepRAG inbox', runner: 'greprag-codex-hook' },
|
|
@@ -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
|
}
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** greprag persona — tenant-scoped speaking instructions. adr: adr/persona.md
|
|
3
|
+
*
|
|
4
|
+
* Thin HTTP wrapper over /v1/persona. The server owns built-in template content;
|
|
5
|
+
* custom text is sent verbatim and injected without rewriting.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.runPersona = runPersona;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
function getConfig() {
|
|
44
|
+
return {
|
|
45
|
+
apiUrl: process.env.GREPRAG_API_URL || 'https://api.greprag.com',
|
|
46
|
+
apiKey: process.env.GREPRAG_API_KEY || '',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function getFlag(args, flag) {
|
|
50
|
+
const idx = args.indexOf(flag);
|
|
51
|
+
return idx === -1 || idx + 1 >= args.length ? undefined : args[idx + 1];
|
|
52
|
+
}
|
|
53
|
+
const HELP = `greprag persona — tenant-scoped instructions for how agents speak to you
|
|
54
|
+
|
|
55
|
+
USAGE
|
|
56
|
+
greprag persona list
|
|
57
|
+
List built-in Persona templates.
|
|
58
|
+
|
|
59
|
+
greprag persona use <template>
|
|
60
|
+
Activate a built-in template, e.g. vibe-shaper.
|
|
61
|
+
|
|
62
|
+
greprag persona set <text>
|
|
63
|
+
greprag persona set --file <path>
|
|
64
|
+
greprag persona set --stdin
|
|
65
|
+
Save and activate a custom Persona verbatim.
|
|
66
|
+
|
|
67
|
+
greprag persona show
|
|
68
|
+
Show the active Persona.
|
|
69
|
+
|
|
70
|
+
greprag persona clear
|
|
71
|
+
Remove the active Persona.`;
|
|
72
|
+
async function api(method, pathname, body) {
|
|
73
|
+
const cfg = getConfig();
|
|
74
|
+
if (!cfg.apiKey) {
|
|
75
|
+
console.error('GREPRAG_API_KEY not set — run `greprag init` first.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const res = await fetch(`${cfg.apiUrl}${pathname}`, {
|
|
79
|
+
method,
|
|
80
|
+
headers: {
|
|
81
|
+
'Authorization': `Bearer ${cfg.apiKey}`,
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
},
|
|
84
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
85
|
+
});
|
|
86
|
+
let data = {};
|
|
87
|
+
try {
|
|
88
|
+
data = await res.json();
|
|
89
|
+
}
|
|
90
|
+
catch { /* non-JSON */ }
|
|
91
|
+
return { ok: res.ok, status: res.status, data };
|
|
92
|
+
}
|
|
93
|
+
function fail(data, status) {
|
|
94
|
+
console.error(`Error${status ? ` (${status})` : ''}: ${data.error || 'request failed'}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
function readPersonaInput(args) {
|
|
98
|
+
const file = getFlag(args, '--file');
|
|
99
|
+
if (file) {
|
|
100
|
+
try {
|
|
101
|
+
return file === '-' ? fs.readFileSync(0, 'utf-8') : fs.readFileSync(file, 'utf-8');
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error(`persona set: could not read --file ${file}: ${err.message}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (args.includes('--stdin')) {
|
|
109
|
+
try {
|
|
110
|
+
return fs.readFileSync(0, 'utf-8');
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.error(`persona set: could not read stdin: ${err.message}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return args.filter((a) => a !== '--stdin').join(' ');
|
|
118
|
+
}
|
|
119
|
+
async function list() {
|
|
120
|
+
const { ok, status, data } = await api('GET', '/v1/persona/templates');
|
|
121
|
+
if (!ok)
|
|
122
|
+
fail(data, status);
|
|
123
|
+
const templates = data.templates || [];
|
|
124
|
+
if (!templates.length) {
|
|
125
|
+
console.log('No built-in Persona templates.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log('Built-in Persona templates:\n');
|
|
129
|
+
for (const t of templates) {
|
|
130
|
+
console.log(` ${t.id} — ${t.name}`);
|
|
131
|
+
console.log(` ${t.description}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function useTemplate(id) {
|
|
135
|
+
if (!id) {
|
|
136
|
+
console.error('Usage: greprag persona use <template>');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const { ok, status, data } = await api('PUT', '/v1/persona/template', { templateId: id });
|
|
140
|
+
if (!ok)
|
|
141
|
+
fail(data, status);
|
|
142
|
+
const p = data.persona;
|
|
143
|
+
console.log(`Persona active: ${p.templateId} (built-in template)`);
|
|
144
|
+
}
|
|
145
|
+
async function set(args) {
|
|
146
|
+
const content = readPersonaInput(args);
|
|
147
|
+
const { ok, status, data } = await api('PUT', '/v1/persona/custom', { content });
|
|
148
|
+
if (!ok)
|
|
149
|
+
fail(data, status);
|
|
150
|
+
const p = data.persona;
|
|
151
|
+
console.log(`Persona active: custom (${p.content?.length ?? content.length} chars)`);
|
|
152
|
+
}
|
|
153
|
+
async function show() {
|
|
154
|
+
const { ok, status, data } = await api('GET', '/v1/persona');
|
|
155
|
+
if (!ok)
|
|
156
|
+
fail(data, status);
|
|
157
|
+
const p = data.persona;
|
|
158
|
+
if (!p) {
|
|
159
|
+
console.log('No Persona set.');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const source = p.mode === 'template' ? `template ${p.templateId}` : 'custom';
|
|
163
|
+
console.log(`Persona (${source})`);
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log(p.content || '');
|
|
166
|
+
}
|
|
167
|
+
async function clear() {
|
|
168
|
+
const { ok, status, data } = await api('DELETE', '/v1/persona');
|
|
169
|
+
if (!ok)
|
|
170
|
+
fail(data, status);
|
|
171
|
+
console.log(data.removed ? 'Persona cleared.' : 'No Persona was set.');
|
|
172
|
+
}
|
|
173
|
+
async function runPersona(args) {
|
|
174
|
+
const sub = args[0];
|
|
175
|
+
const rest = args.slice(1);
|
|
176
|
+
if (!sub || sub === '--help' || sub === '-h' || sub === 'help') {
|
|
177
|
+
console.log(HELP);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
switch (sub) {
|
|
181
|
+
case 'list': return list();
|
|
182
|
+
case 'use': return useTemplate(rest[0]);
|
|
183
|
+
case 'set': return set(rest);
|
|
184
|
+
case 'show': return show();
|
|
185
|
+
case 'clear': return clear();
|
|
186
|
+
default:
|
|
187
|
+
console.error(`Unknown persona command: ${sub}\n`);
|
|
188
|
+
console.log(HELP);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -28,6 +28,7 @@ const skill_mirror_reminder_1 = require("./skill-mirror-reminder");
|
|
|
28
28
|
const procedure_reminder_1 = require("./procedure-reminder");
|
|
29
29
|
const loadout_reminder_1 = require("./loadout-reminder");
|
|
30
30
|
const delivery_reminder_1 = require("./delivery-reminder");
|
|
31
|
+
const persona_reminder_1 = require("./persona-reminder");
|
|
31
32
|
/** Registry order = display order. THE single agent-facing announce/reminder assembly:
|
|
32
33
|
* the hook does I/O → fills ReminderEnv → collectAnnounces (SessionStart) / collectReminders
|
|
33
34
|
* (per turn) render every module here in this order. A module may own either surface or both;
|
|
@@ -46,6 +47,7 @@ exports.REGISTRY = [
|
|
|
46
47
|
skill_mirror_reminder_1.skillMirrorAnnounceModule, // native-equivalent activation index for load-only mirrored skills
|
|
47
48
|
procedure_reminder_1.procedureAnnounceModule, // active project procedures, rendered generically from operator-owned installs
|
|
48
49
|
delivery_reminder_1.deliveryControlModule, // repo-scoped announce-first Delivery System pilot (all harnesses)
|
|
50
|
+
persona_reminder_1.personaAnnounceModule, // tenant-scoped Persona, shared across projects/harnesses
|
|
49
51
|
loadout_reminder_1.loadoutRegistrarModule, // equipped loadouts: one advisor announce each + per-turn keyword Match (docs/loadout.md)
|
|
50
52
|
setup_reminder_1.setupWarningModule,
|
|
51
53
|
version_reminder_1.versionUpgradeModule, // Deficiency-gated announce — silent unless a newer release exists
|
package/dist/commands/status.js
CHANGED
|
@@ -191,6 +191,7 @@ function buildStatus(cwd, platform = 'all') {
|
|
|
191
191
|
pre_tool_use_chip_guard: hasCodexHook(codexHooks.hooks?.PreToolUse, 'codex-pretooluse')
|
|
192
192
|
|| hasCodexHook(codexHooks.hooks?.PreToolUse, 'codex-chip-hook'),
|
|
193
193
|
stop_store: hasCodexHook(codexHooks.hooks?.Stop, 'codex-store'),
|
|
194
|
+
post_compact_reannounce: hasCodexHook(codexHooks.hooks?.PostCompact, 'recompact'),
|
|
194
195
|
post_compact_session_id: hasCodexHook(codexHooks.hooks?.PostCompact, 'session-id'),
|
|
195
196
|
};
|
|
196
197
|
const opencodePluginInstalled = fs.existsSync(opencodePluginPath);
|
package/dist/hook.js
CHANGED
|
@@ -1613,11 +1613,29 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1613
1613
|
return empty;
|
|
1614
1614
|
}
|
|
1615
1615
|
})();
|
|
1616
|
+
// Tenant Persona primer. The server owns both template resolution and
|
|
1617
|
+
// the provenance envelope; harnesses transport it unchanged. adr: adr/persona.md
|
|
1618
|
+
const personaPromise = (async () => {
|
|
1619
|
+
try {
|
|
1620
|
+
const res = await fetch(`${cfg.apiUrl}/v1/persona`, {
|
|
1621
|
+
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
|
|
1622
|
+
});
|
|
1623
|
+
if (!res.ok)
|
|
1624
|
+
return null;
|
|
1625
|
+
const data = await res.json();
|
|
1626
|
+
return typeof data.announce === 'string' && data.announce.length > 0
|
|
1627
|
+
? data.announce
|
|
1628
|
+
: null;
|
|
1629
|
+
}
|
|
1630
|
+
catch {
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
})();
|
|
1616
1634
|
const updateAvailablePromise = checkForUpdate();
|
|
1617
1635
|
const bodyPromise = (!opts.compact && anchor.sessionStartRecap)
|
|
1618
1636
|
? (0, opencode_plugin_helpers_1.buildRecapBody)(cfg.apiUrl, cfg.apiKey, anchor)
|
|
1619
1637
|
: Promise.resolve('');
|
|
1620
|
-
const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, updateAvailable, body,] = await Promise.all([
|
|
1638
|
+
const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, personaAnnounce, updateAvailable, body,] = await Promise.all([
|
|
1621
1639
|
matchsetPromise,
|
|
1622
1640
|
corpusApiDocsPromise,
|
|
1623
1641
|
docPointersPromise,
|
|
@@ -1625,6 +1643,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1625
1643
|
skillGainsPromise,
|
|
1626
1644
|
mirroredSkillsPromise,
|
|
1627
1645
|
loadoutStatePromise,
|
|
1646
|
+
personaPromise,
|
|
1628
1647
|
updateAvailablePromise,
|
|
1629
1648
|
bodyPromise,
|
|
1630
1649
|
]);
|
|
@@ -1704,6 +1723,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1704
1723
|
mirroredSkills,
|
|
1705
1724
|
procedureAnnounces,
|
|
1706
1725
|
equippedLoadouts,
|
|
1726
|
+
personaAnnounce,
|
|
1707
1727
|
updateAvailable,
|
|
1708
1728
|
activeCommandDrift: activeCommandDrift(),
|
|
1709
1729
|
loadoutUpdateRequired,
|
|
@@ -2264,7 +2284,10 @@ async function main() {
|
|
|
2264
2284
|
// opt out per-module with reannounceOnCompact:false) so the doctrine the compaction
|
|
2265
2285
|
// wiped (incl. the watcher-arm announce) returns. additionalContext = the JSON form
|
|
2266
2286
|
// PostCompact injects. adr: adr/monitor-resilience.md, docs/reminder-interrupt.md
|
|
2267
|
-
await recap(input, 'additionalContext', {
|
|
2287
|
+
await recap(input, 'additionalContext', {
|
|
2288
|
+
compact: true,
|
|
2289
|
+
platform: harness === 'grok' ? 'grok' : undefined,
|
|
2290
|
+
});
|
|
2268
2291
|
}
|
|
2269
2292
|
else if (subcommand === 'codex-recap') {
|
|
2270
2293
|
await recap(input, 'additionalContext', { platform: 'codex' });
|
package/dist/index.js
CHANGED
|
@@ -66,6 +66,7 @@ const doc_1 = require("./commands/doc");
|
|
|
66
66
|
const parity_1 = require("./commands/parity");
|
|
67
67
|
const agents_1 = require("./commands/agents");
|
|
68
68
|
const loadout_1 = require("./commands/loadout");
|
|
69
|
+
const persona_1 = require("./commands/persona");
|
|
69
70
|
const assistant_1 = require("./commands/assistant");
|
|
70
71
|
const email_1 = require("./commands/email");
|
|
71
72
|
const sms_1 = require("./commands/sms");
|
|
@@ -1568,6 +1569,14 @@ Loadout (gift a skill bundle to another tenant — offer, never auto-install):
|
|
|
1568
1569
|
loadout unequip <name> [--delete-skills]
|
|
1569
1570
|
Stop a loadout's auto-inflate.
|
|
1570
1571
|
|
|
1572
|
+
Persona (tenant-scoped speaking instructions):
|
|
1573
|
+
persona list List built-in Persona templates.
|
|
1574
|
+
persona use <template> Activate a built-in Persona template.
|
|
1575
|
+
persona set <text>|--file <f>|--stdin
|
|
1576
|
+
Save and activate a custom Persona verbatim.
|
|
1577
|
+
persona show Show the active Persona.
|
|
1578
|
+
persona clear Remove the active Persona.
|
|
1579
|
+
|
|
1571
1580
|
GrepRAG Instructions (canonical global/repo CLAUDE.md + AGENTS.md):
|
|
1572
1581
|
instructions pull <file> [--scope global|repo] [--push]
|
|
1573
1582
|
Pull disk instructions into canon.
|
|
@@ -1639,6 +1648,7 @@ const HELP_ALL_GROUPS = [
|
|
|
1639
1648
|
['delivery', delivery_1.runDelivery],
|
|
1640
1649
|
['assistant', assistant_1.runAssistant],
|
|
1641
1650
|
['instructions', agents_1.runInstructions],
|
|
1651
|
+
['persona', persona_1.runPersona],
|
|
1642
1652
|
['doc', doc_1.runDoc],
|
|
1643
1653
|
['skill', skill_1.runSkill],
|
|
1644
1654
|
['opencode', opencode],
|
|
@@ -1926,6 +1936,7 @@ async function main() {
|
|
|
1926
1936
|
case 'instructions': return (0, agents_1.runInstructions)(subArgs); // Canonical GrepRAG Instructions + rendered followers
|
|
1927
1937
|
case 'agents': return (0, agents_1.runAgents)(subArgs); // Deprecated alias for GrepRAG Instructions
|
|
1928
1938
|
case 'loadout': return (0, loadout_1.runLoadout)(subArgs); // Loadout: cross-tenant skill-bundle gifting (docs/loadout.md)
|
|
1939
|
+
case 'persona': return (0, persona_1.runPersona)(subArgs); // tenant-scoped speaking instructions
|
|
1929
1940
|
case 'assistant': return (0, assistant_1.runAssistant)(subArgs); // designate this project as the tenant's Assistant (role flag)
|
|
1930
1941
|
case 'opencode': return opencode(subArgs);
|
|
1931
1942
|
case 'grok': return (0, grok_spawn_1.runGrok)(subArgs);
|
|
@@ -2438,15 +2438,15 @@ var loadoutRegistrarModule = {
|
|
|
2438
2438
|
// src/commands/delivery-reminder.ts
|
|
2439
2439
|
function coordinationLine(platform) {
|
|
2440
2440
|
if (platform === "codex") {
|
|
2441
|
-
return "Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo,
|
|
2441
|
+
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.";
|
|
2442
2442
|
}
|
|
2443
2443
|
if (platform === "claude-code") {
|
|
2444
|
-
return "Claude Code: at the merge/deploy gate,
|
|
2444
|
+
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.";
|
|
2445
2445
|
}
|
|
2446
2446
|
if (platform === "opencode") {
|
|
2447
|
-
return "OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise
|
|
2447
|
+
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.";
|
|
2448
2448
|
}
|
|
2449
|
-
return "At the merge/deploy gate,
|
|
2449
|
+
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.";
|
|
2450
2450
|
}
|
|
2451
2451
|
function buildDeliveryAnnounce(env) {
|
|
2452
2452
|
if (!env.deliveryRepo)
|
|
@@ -2456,7 +2456,7 @@ function buildDeliveryAnnounce(env) {
|
|
|
2456
2456
|
`[DELIVERY \u2014 ${project}]`,
|
|
2457
2457
|
"Commit useful, passing work.",
|
|
2458
2458
|
coordinationLine(env.platform),
|
|
2459
|
-
"Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately.
|
|
2459
|
+
"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.",
|
|
2460
2460
|
"Use this repo's delivery profile and verify production."
|
|
2461
2461
|
].join("\n");
|
|
2462
2462
|
}
|
|
@@ -2467,6 +2467,14 @@ var deliveryControlModule = {
|
|
|
2467
2467
|
reminder: () => null
|
|
2468
2468
|
};
|
|
2469
2469
|
|
|
2470
|
+
// src/commands/persona-reminder.ts
|
|
2471
|
+
var personaAnnounceModule = {
|
|
2472
|
+
id: "persona-announce",
|
|
2473
|
+
detect: () => ({ tier: "silent" }),
|
|
2474
|
+
announce: (env) => env.personaAnnounce || null,
|
|
2475
|
+
reminder: () => null
|
|
2476
|
+
};
|
|
2477
|
+
|
|
2470
2478
|
// src/commands/reminder-registry.ts
|
|
2471
2479
|
var REGISTRY = [
|
|
2472
2480
|
osPrimerModule,
|
|
@@ -2488,6 +2496,8 @@ var REGISTRY = [
|
|
|
2488
2496
|
// active project procedures, rendered generically from operator-owned installs
|
|
2489
2497
|
deliveryControlModule,
|
|
2490
2498
|
// repo-scoped announce-first Delivery System pilot (all harnesses)
|
|
2499
|
+
personaAnnounceModule,
|
|
2500
|
+
// tenant-scoped Persona, shared across projects/harnesses
|
|
2491
2501
|
loadoutRegistrarModule,
|
|
2492
2502
|
// equipped loadouts: one advisor announce each + per-turn keyword Match (docs/loadout.md)
|
|
2493
2503
|
setupWarningModule,
|
|
@@ -2591,12 +2601,16 @@ function buildOpenCodeEnv(params) {
|
|
|
2591
2601
|
corpusApiDocs: params.corpusApiDocs ?? [],
|
|
2592
2602
|
updateAvailable: params.updateAvailable ?? null,
|
|
2593
2603
|
procedureAnnounces: params.procedureAnnounces ?? [],
|
|
2594
|
-
mirroredSkills: params.mirroredSkills
|
|
2604
|
+
mirroredSkills: params.mirroredSkills,
|
|
2605
|
+
personaAnnounce: params.personaAnnounce ?? null
|
|
2595
2606
|
};
|
|
2596
2607
|
}
|
|
2597
2608
|
function getOpenCodeAnnounces(env) {
|
|
2598
2609
|
return [...collectAnnounces(env, opencodeRegistry), OPENCODE_QUOTA_WAITLIST_ANNOUNCE];
|
|
2599
2610
|
}
|
|
2611
|
+
function getOpenCodePersistentAnnounces(env) {
|
|
2612
|
+
return collectAnnounces(env, opencodeRegistry.filter((m) => m.id === "persona-announce"));
|
|
2613
|
+
}
|
|
2600
2614
|
function getOpenCodeReminders(env) {
|
|
2601
2615
|
return collectReminders(env, opencodeRegistry);
|
|
2602
2616
|
}
|
|
@@ -4296,6 +4310,8 @@ async function fetchRecapInbox(anchor) {
|
|
|
4296
4310
|
}
|
|
4297
4311
|
var relayArmedBySession = /* @__PURE__ */ new Set();
|
|
4298
4312
|
var announcedBySession = /* @__PURE__ */ new Set();
|
|
4313
|
+
var personaAnnounceBySession = /* @__PURE__ */ new Map();
|
|
4314
|
+
var PERSONA_SESSION_CACHE_MAX = 100;
|
|
4299
4315
|
var turnCountBySession = /* @__PURE__ */ new Map();
|
|
4300
4316
|
var procedurePromptKeyBySession = /* @__PURE__ */ new Map();
|
|
4301
4317
|
var cachedRecap = null;
|
|
@@ -4333,6 +4349,32 @@ async function fetchSkillActivation(apiKey, cwd) {
|
|
|
4333
4349
|
return void 0;
|
|
4334
4350
|
}
|
|
4335
4351
|
}
|
|
4352
|
+
async function fetchPersonaAnnounce(apiKey) {
|
|
4353
|
+
try {
|
|
4354
|
+
const response = await fetch(`${API_URL}/v1/persona`, {
|
|
4355
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
4356
|
+
});
|
|
4357
|
+
if (!response.ok)
|
|
4358
|
+
return null;
|
|
4359
|
+
const data = await response.json();
|
|
4360
|
+
return typeof data.announce === "string" && data.announce.length > 0 ? data.announce : null;
|
|
4361
|
+
} catch {
|
|
4362
|
+
return null;
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
async function personaAnnounceForSession(sessionId, apiKey) {
|
|
4366
|
+
if (personaAnnounceBySession.has(sessionId)) {
|
|
4367
|
+
return personaAnnounceBySession.get(sessionId) ?? null;
|
|
4368
|
+
}
|
|
4369
|
+
const announce = await fetchPersonaAnnounce(apiKey);
|
|
4370
|
+
if (personaAnnounceBySession.size >= PERSONA_SESSION_CACHE_MAX) {
|
|
4371
|
+
const oldest = personaAnnounceBySession.keys().next().value;
|
|
4372
|
+
if (oldest)
|
|
4373
|
+
personaAnnounceBySession.delete(oldest);
|
|
4374
|
+
}
|
|
4375
|
+
personaAnnounceBySession.set(sessionId, announce);
|
|
4376
|
+
return announce;
|
|
4377
|
+
}
|
|
4336
4378
|
function startSessionRelay(sessionId, serverUrl) {
|
|
4337
4379
|
if (process.env.GREPRAG_OPENCODE_RELAY === "0")
|
|
4338
4380
|
return;
|
|
@@ -4667,6 +4709,7 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
4667
4709
|
if (sid8) {
|
|
4668
4710
|
const turnCount = (turnCountBySession.get(sid8) ?? 0) + 1;
|
|
4669
4711
|
turnCountBySession.set(sid8, turnCount);
|
|
4712
|
+
const firstAnnounce = !announcedBySession.has(sid8);
|
|
4670
4713
|
const env = buildOpenCodeEnv({
|
|
4671
4714
|
short: sid8,
|
|
4672
4715
|
turnCount,
|
|
@@ -4682,9 +4725,12 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
4682
4725
|
return [];
|
|
4683
4726
|
}
|
|
4684
4727
|
})(),
|
|
4685
|
-
mirroredSkills:
|
|
4728
|
+
mirroredSkills: firstAnnounce ? await fetchSkillActivation(apiKey, workingDir) : void 0,
|
|
4729
|
+
// output.system is fresh on EVERY transform call. Resolve Persona
|
|
4730
|
+
// once/session, then replay the cached shared-registry announce.
|
|
4731
|
+
personaAnnounce: await personaAnnounceForSession(sid8, apiKey)
|
|
4686
4732
|
});
|
|
4687
|
-
if (
|
|
4733
|
+
if (firstAnnounce) {
|
|
4688
4734
|
announcedBySession.add(sid8);
|
|
4689
4735
|
const announces = getOpenCodeAnnounces(env);
|
|
4690
4736
|
if (announces.length > 0) {
|
|
@@ -4693,6 +4739,13 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
|
|
|
4693
4739
|
}
|
|
4694
4740
|
dlog(`interrupt: pushed ${announces.length} announce(s) for sid=${sid8}`);
|
|
4695
4741
|
}
|
|
4742
|
+
} else {
|
|
4743
|
+
const persistent = getOpenCodePersistentAnnounces(env);
|
|
4744
|
+
for (const line of persistent)
|
|
4745
|
+
pushSystemPrompt(output, line);
|
|
4746
|
+
if (persistent.length > 0) {
|
|
4747
|
+
dlog(`interrupt: replayed ${persistent.length} persistent announce(s) for sid=${sid8}`);
|
|
4748
|
+
}
|
|
4696
4749
|
}
|
|
4697
4750
|
const reminders = getOpenCodeReminders(env);
|
|
4698
4751
|
if (reminders.length > 0) {
|
package/dist/opencode-plugin.js
CHANGED
|
@@ -548,6 +548,8 @@ async function fetchRecapInbox(anchor) {
|
|
|
548
548
|
// ============================================================================
|
|
549
549
|
const relayArmedBySession = new Set();
|
|
550
550
|
const announcedBySession = new Set();
|
|
551
|
+
const personaAnnounceBySession = new Map();
|
|
552
|
+
const PERSONA_SESSION_CACHE_MAX = 100;
|
|
551
553
|
const turnCountBySession = new Map();
|
|
552
554
|
const procedurePromptKeyBySession = new Map();
|
|
553
555
|
/** Cached recap body per projectId. opencode fires
|
|
@@ -596,6 +598,35 @@ async function fetchSkillActivation(apiKey, cwd) {
|
|
|
596
598
|
return undefined;
|
|
597
599
|
}
|
|
598
600
|
}
|
|
601
|
+
async function fetchPersonaAnnounce(apiKey) {
|
|
602
|
+
try {
|
|
603
|
+
const response = await fetch(`${API_URL}/v1/persona`, {
|
|
604
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
605
|
+
});
|
|
606
|
+
if (!response.ok)
|
|
607
|
+
return null;
|
|
608
|
+
const data = await response.json();
|
|
609
|
+
return typeof data.announce === 'string' && data.announce.length > 0
|
|
610
|
+
? data.announce
|
|
611
|
+
: null;
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function personaAnnounceForSession(sessionId, apiKey) {
|
|
618
|
+
if (personaAnnounceBySession.has(sessionId)) {
|
|
619
|
+
return personaAnnounceBySession.get(sessionId) ?? null;
|
|
620
|
+
}
|
|
621
|
+
const announce = await fetchPersonaAnnounce(apiKey);
|
|
622
|
+
if (personaAnnounceBySession.size >= PERSONA_SESSION_CACHE_MAX) {
|
|
623
|
+
const oldest = personaAnnounceBySession.keys().next().value;
|
|
624
|
+
if (oldest)
|
|
625
|
+
personaAnnounceBySession.delete(oldest);
|
|
626
|
+
}
|
|
627
|
+
personaAnnounceBySession.set(sessionId, announce);
|
|
628
|
+
return announce;
|
|
629
|
+
}
|
|
599
630
|
// ============================================================================
|
|
600
631
|
// Per-session relay spawner — "anytime opencode is opened and being used"
|
|
601
632
|
// ============================================================================
|
|
@@ -1002,6 +1033,7 @@ const GrepRAGMemoryPlugin = async (ctx) => {
|
|
|
1002
1033
|
if (sid8) {
|
|
1003
1034
|
const turnCount = (turnCountBySession.get(sid8) ?? 0) + 1;
|
|
1004
1035
|
turnCountBySession.set(sid8, turnCount);
|
|
1036
|
+
const firstAnnounce = !announcedBySession.has(sid8);
|
|
1005
1037
|
// Build env — only populate what's available in the opencode context
|
|
1006
1038
|
const env = (0, opencode_interrupt_1.buildOpenCodeEnv)({
|
|
1007
1039
|
short: sid8,
|
|
@@ -1019,12 +1051,15 @@ const GrepRAGMemoryPlugin = async (ctx) => {
|
|
|
1019
1051
|
return [];
|
|
1020
1052
|
}
|
|
1021
1053
|
})(),
|
|
1022
|
-
mirroredSkills:
|
|
1054
|
+
mirroredSkills: firstAnnounce
|
|
1023
1055
|
? await fetchSkillActivation(apiKey, workingDir)
|
|
1024
1056
|
: undefined,
|
|
1057
|
+
// output.system is fresh on EVERY transform call. Resolve Persona
|
|
1058
|
+
// once/session, then replay the cached shared-registry announce.
|
|
1059
|
+
personaAnnounce: await personaAnnounceForSession(sid8, apiKey),
|
|
1025
1060
|
});
|
|
1026
1061
|
// Announces: inject on first fire per session (static primers)
|
|
1027
|
-
if (
|
|
1062
|
+
if (firstAnnounce) {
|
|
1028
1063
|
announcedBySession.add(sid8);
|
|
1029
1064
|
const announces = (0, opencode_interrupt_1.getOpenCodeAnnounces)(env);
|
|
1030
1065
|
if (announces.length > 0) {
|
|
@@ -1034,6 +1069,14 @@ const GrepRAGMemoryPlugin = async (ctx) => {
|
|
|
1034
1069
|
dlog(`interrupt: pushed ${announces.length} announce(s) for sid=${sid8}`);
|
|
1035
1070
|
}
|
|
1036
1071
|
}
|
|
1072
|
+
else {
|
|
1073
|
+
const persistent = (0, opencode_interrupt_1.getOpenCodePersistentAnnounces)(env);
|
|
1074
|
+
for (const line of persistent)
|
|
1075
|
+
pushSystemPrompt(output, line);
|
|
1076
|
+
if (persistent.length > 0) {
|
|
1077
|
+
dlog(`interrupt: replayed ${persistent.length} persistent announce(s) for sid=${sid8}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1037
1080
|
// Reminders: inject every turn (dynamic nudges)
|
|
1038
1081
|
const reminders = (0, opencode_interrupt_1.getOpenCodeReminders)(env);
|
|
1039
1082
|
if (reminders.length > 0) {
|