/** * Voice/coding turn budgets. No Bare imports. * * Coding agents may chain many shells. A spoken GNOME assistant should run one * command, then answer — otherwise a 4B model loops hostnamectl/uname/free. */ function num(value, fallback) { const n = Number(value); return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback; } function fromPayload(payload, origin) { payload = payload || {}; const voice = payload.voice === true || origin === 'jarvis-qvac'; const unlimitedShell = payload.maxShellCalls === 0 || payload.maxShellCalls === false; return { voice, maxTurns: num(payload.maxTurns, voice ? 6 : 24), maxShellCalls: unlimitedShell ? 0 : num(payload.maxShellCalls, voice ? 1 : 0), maxToolRounds: num(payload.maxToolRounds, voice ? 4 : 0), shellCalls: 0, toolRounds: 0, answerOnly: false, }; } function capped(limit) { return limit > 0; } function shouldSkipShell(budget) { return !!(budget && capped(budget.maxShellCalls) && budget.shellCalls >= budget.maxShellCalls); } function markShell(budget) { if (!budget) return; budget.shellCalls += 1; if (shouldSkipShell(budget)) budget.answerOnly = true; } function markToolRound(budget) { if (!budget) return; budget.toolRounds += 1; if (capped(budget.maxToolRounds) && budget.toolRounds >= budget.maxToolRounds) budget.answerOnly = true; } function forceAnswer(budget) { if (budget) budget.answerOnly = true; } function skipShellMessage() { return 'A terminal command already ran this turn. Answer the user from that output. Do not run another command.'; } function answerNowMessage() { return 'You have tool results. Reply to the user in one to three sentences. Do not call more tools.'; } function lastToolText(history, maxChars) { const list = Array.isArray(history) ? history : []; for (let i = list.length - 1; i >= 0; i--) { if (list[i] && list[i].role === 'tool' && list[i].content) { const text = String(list[i].content).replace(/\s+/g, ' ').trim(); if (!text) continue; const max = maxChars > 0 ? maxChars : 400; return text.length > max ? text.slice(0, max) + '…' : text; } } return ''; } module.exports = { fromPayload, shouldSkipShell, markShell, markToolRound, forceAnswer, skipShellMessage, answerNowMessage, lastToolText, };