From de88930ffd34c641c6858df0f7020ad9b8ccace0 Mon Sep 17 00:00:00 2001 From: Operator & Codex Date: Wed, 29 Apr 2026 08:51:35 +0200 Subject: [PATCH] Improve runtime status clarity and direct answers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Build: pass | Tests: FAIL — Tests 1 failed | 2043 passed (2044) --- src/agent-runner.ts | 1 + src/pi-profile.test.ts | 7 +++ src/pi-profile.ts | 1 + src/telegram-commands.test.ts | 38 +++++++++++++++ src/telegram-commands.ts | 87 ++++++++++++++++++++++++++++------- 5 files changed, 118 insertions(+), 16 deletions(-) diff --git a/src/agent-runner.ts b/src/agent-runner.ts index 4d60b7c..c7f1ec7 100644 --- a/src/agent-runner.ts +++ b/src/agent-runner.ts @@ -415,6 +415,7 @@ export async function runJailAgent( const runtimeInfo = `Rule: Your current LLM runtime is provider=${effectiveProvider || 'unknown'}, model=${effectiveModel || 'unknown'}. ` + `State this accurately if asked — do not guess or substitute another model name.\n` + + `Rule: Do not end your reply with a promise of future inspection or follow-up unless you are actually providing that follow-up in the same message. If there is no real async job, answer directly now.\n` + `Rule: The Mevy service runs as a FreeBSD rc.d service and executes \`node dist/index.js\` (not \`node src/index.ts\`). ` + `Do not tell the operator to run \`just dev\` or start the service manually unless explicitly asked; instead, instruct to check \`sudo service ${PLATFORM_SERVICE_NAME} status\` and use \`sudo service ${PLATFORM_SERVICE_NAME} restart\` when needed.\n` + `Rule: Text-to-speech uses the \`edge-tts\` CLI provided by the repo wrapper at \`/home/mevy/mevy-ai/bin/edge-tts\`, which runs from a persistent uv venv at \`/home/mevy/mevy-ai/.venv-edge-tts\`. ` + diff --git a/src/pi-profile.test.ts b/src/pi-profile.test.ts index 6f5cc51..48bf6cc 100644 --- a/src/pi-profile.test.ts +++ b/src/pi-profile.test.ts @@ -38,4 +38,11 @@ describe('pi-profile', () => { expect(prompt).toContain('customer-safe summary'); expect(prompt).toContain('Session policy: persistent'); }); + + it('teaches operator mode to answer directly instead of promising follow-up', () => { + const profile = resolvePiTuiProfile({ profile: 'operator' }); + const prompt = buildPiTuiProfilePrompt(profile.name, profile); + expect(prompt).toContain('Do not reply with placeholder lines like "let me inspect"'); + expect(prompt).toContain('If you can answer now, answer now.'); + }); }); diff --git a/src/pi-profile.ts b/src/pi-profile.ts index 3ae47c0..af5f13b 100644 --- a/src/pi-profile.ts +++ b/src/pi-profile.ts @@ -80,6 +80,7 @@ export const PI_TUI_PROFILES: Record = 'Stay concise and operational.', 'Prefer immediate fixes over long explanations.', 'Always end your turn with a visible text reply. Never finish with only a thinking block — your final output must be readable text.', + 'Do not reply with placeholder lines like "let me inspect" or "I will check" unless the findings are included in the same reply. If you can answer now, answer now.', ], }, status: { diff --git a/src/telegram-commands.test.ts b/src/telegram-commands.test.ts index ccd99c3..b8fccb9 100644 --- a/src/telegram-commands.test.ts +++ b/src/telegram-commands.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { augmentPickerModels, + buildStatusRuntimeLines, getTtsModeForChat, setTtsModeForChat, clearTtsOverride, @@ -200,3 +201,40 @@ describe('augmentPickerModels', () => { expect(models.map((m) => m.id)).toEqual(['openai/o3']); }); }); + +describe('buildStatusRuntimeLines', () => { + it('shows default, chat override, heartbeat override, and drift clearly', () => { + const lines = buildStatusRuntimeLines({ + defaultProvider: 'deepseek', + defaultModel: 'deepseek-chat', + heartbeatProvider: 'deepseek', + heartbeatModel: 'deepseek-v4-flash', + overrideProvider: 'openrouter', + overrideModel: 'openai/o3', + actualProvider: 'deepseek', + actualModel: 'deepseek-chat', + }); + + expect(lines).toEqual([ + 'Default runtime: deepseek/deepseek-chat', + 'Chat runtime: openrouter/openai/o3 (override)', + 'Heartbeat runtime: deepseek/deepseek-v4-flash', + 'Last actual: deepseek/deepseek-chat ⚠ drift', + ]); + }); + + it('shows a default-backed chat runtime when there is no override', () => { + const lines = buildStatusRuntimeLines({ + defaultProvider: 'deepseek', + defaultModel: 'deepseek-chat', + actualProvider: 'deepseek', + actualModel: 'deepseek-chat', + }); + + expect(lines).toEqual([ + 'Default runtime: deepseek/deepseek-chat', + 'Chat runtime: deepseek/deepseek-chat (default)', + 'Last actual: deepseek/deepseek-chat', + ]); + }); +}); diff --git a/src/telegram-commands.ts b/src/telegram-commands.ts index 68c030a..8b4e3a2 100644 --- a/src/telegram-commands.ts +++ b/src/telegram-commands.ts @@ -14,6 +14,8 @@ import { AGENT_BUDGET_PAUSE_PCT, ASSISTANT_NAME, CMS_WEBROOT, + HEARTBEAT_MODEL, + HEARTBEAT_PROVIDER, PLATFORM_ID, PLATFORM_SERVICE_NAME, PI_TUI_MODEL, @@ -231,6 +233,56 @@ function getChatLastActualRuntimeLine(chatJid: string): string | null { return `Last actual runtime: ${provider} / ${model}`; } +export function buildStatusRuntimeLines(opts: { + defaultProvider: string; + defaultModel: string; + heartbeatProvider?: string; + heartbeatModel?: string; + overrideProvider?: string; + overrideModel?: string; + actualProvider?: string; + actualModel?: string; +}): string[] { + const defaultProvider = (opts.defaultProvider || '').trim() || 'default'; + const defaultModel = (opts.defaultModel || '').trim() || 'default'; + const overrideProvider = (opts.overrideProvider || '').trim(); + const overrideModel = (opts.overrideModel || '').trim(); + const actualProvider = (opts.actualProvider || '').trim(); + const actualModel = (opts.actualModel || '').trim(); + const heartbeatProvider = (opts.heartbeatProvider || '').trim(); + const heartbeatModel = (opts.heartbeatModel || '').trim(); + + const effectiveProvider = overrideProvider || defaultProvider; + const effectiveModel = overrideModel || defaultModel; + const hasOverride = Boolean(overrideProvider || overrideModel); + + const lines = [ + `Default runtime: ${defaultProvider}/${defaultModel}`, + `Chat runtime: ${effectiveProvider}/${effectiveModel}${hasOverride ? ' (override)' : ' (default)'}`, + ]; + + const effectiveHeartbeatProvider = heartbeatProvider || defaultProvider; + const effectiveHeartbeatModel = heartbeatModel || defaultModel; + const heartbeatDiffers = + effectiveHeartbeatProvider !== defaultProvider || + effectiveHeartbeatModel !== defaultModel; + if (heartbeatDiffers) { + lines.push( + `Heartbeat runtime: ${effectiveHeartbeatProvider}/${effectiveHeartbeatModel}`, + ); + } + + if (actualProvider && actualModel) { + const drifted = + actualProvider !== effectiveProvider || actualModel !== effectiveModel; + lines.push( + `Last actual: ${actualProvider}/${actualModel}${drifted ? ' ⚠ drift' : ''}`, + ); + } + + return lines; +} + async function replyAuthFailure(ctxArg: any, message: string): Promise { try { if (ctxArg.callbackQuery) { @@ -2004,23 +2056,19 @@ export async function handleStatusCommand( const overrideModel = (group?.jailConfig?.model || '').trim(); const actualProvider = (group?.jailConfig?.lastRuntimeProvider || '').trim(); const actualModel = (group?.jailConfig?.lastRuntimeModel || '').trim(); - const hasOverride = Boolean(overrideProvider || overrideModel); - const effectiveProvider = - overrideProvider || (defaultProvider || '').trim(); - const effectiveModel = overrideModel || defaultModel; - - if (effectiveProvider) { + if (defaultProvider) { lines.push( - `Model: ${effectiveProvider}/${effectiveModel}${hasOverride ? ' (override)' : ''}`, + ...buildStatusRuntimeLines({ + defaultProvider, + defaultModel, + heartbeatProvider: HEARTBEAT_PROVIDER, + heartbeatModel: HEARTBEAT_MODEL, + overrideProvider, + overrideModel, + actualProvider, + actualModel, + }), ); - if (hasOverride && defaultProvider) { - lines.push( - `Default model: ${defaultProvider}/${defaultModel}`, - ); - } - if (actualProvider && actualModel) { - lines.push(`Last actual: ${actualProvider}/${actualModel}`); - } } const active = c.queue.getActiveCount(); @@ -2031,7 +2079,14 @@ export async function handleStatusCommand( lines.push(`Groups: ${Object.keys(groups).length} registered`); } else { if (defaultProvider) { - lines.push(`Model: ${defaultProvider}/${defaultModel}`); + lines.push( + ...buildStatusRuntimeLines({ + defaultProvider, + defaultModel, + heartbeatProvider: HEARTBEAT_PROVIDER, + heartbeatModel: HEARTBEAT_MODEL, + }), + ); } }