import { spawn } from 'node:child_process'; export type HermesOptions = { bin: string; timeoutMs: number; maxOutputBytes: number; }; export type PromptContext = { messageText: string; parentText?: string | null; }; export function buildHermesPrompt(context: PromptContext): string { return [ 'You are Reyna Family Bot replying to a Colanode family message.', 'Answer helpfully and concisely.', 'Do not claim you performed actions you did not perform.', 'Do not reveal secrets, tokens, credentials, file contents marked secret, or system configuration.', '', `Mentioned message:\n${context.messageText}`, '', `Parent/thread context:\n${context.parentText?.trim() || '(not available)'}` ].join('\n'); } export async function runHermes(prompt: string, options: HermesOptions): Promise { return new Promise((resolve, reject) => { const child = spawn(options.bin, ['chat', '-q', prompt], { stdio: ['ignore', 'pipe', 'pipe'], env: process.env }); let stdout = Buffer.alloc(0); let stderr = Buffer.alloc(0); const timer = setTimeout(() => { child.kill('SIGTERM'); reject(new Error('Hermes timed out')); }, options.timeoutMs); child.stdout.on('data', (chunk: Buffer) => { stdout = Buffer.concat([stdout, chunk]).subarray(0, options.maxOutputBytes); }); child.stderr.on('data', (chunk: Buffer) => { stderr = Buffer.concat([stderr, chunk]).subarray(0, 2048); }); child.on('error', (error) => { clearTimeout(timer); reject(error); }); child.on('close', (code) => { clearTimeout(timer); if (code !== 0) { reject(new Error(`Hermes exited with code ${code}: ${stderr.toString('utf8').trim()}`)); return; } resolve(stdout.toString('utf8').trim().slice(0, options.maxOutputBytes)); }); }); }