/** * Advanced context-window compaction for /bin/agent. * Tiered rollover: shrink fat tool payloads → digest older turns → * rolling summary message → hard trim fallback. */ /** * @param {unknown} value */ function bareAgentCompactEstimateTokens(value) { if (typeof bareAgentEstimateTokens === 'function') { return bareAgentEstimateTokens(value) } try { return Math.max(0, Math.ceil(JSON.stringify(value == null ? '' : value).length / 4)) } catch { return 0 } } /** * @param {unknown} msg * @returns {string} */ function bareAgentMessagePlainText(msg) { if (!msg || typeof msg !== 'object') return '' const m = /** @type {Record} */ (msg) const c = m.content if (typeof c === 'string') return c if (Array.isArray(c)) { /** @type {string[]} */ const parts = [] for (const part of c) { if (typeof part === 'string') parts.push(part) else if (part && typeof part === 'object') { const p = /** @type {Record} */ (part) if (typeof p.text === 'string') parts.push(p.text) else if (typeof p.content === 'string') parts.push(p.content) } } return parts.join('\n') } if (m.tool_calls && Array.isArray(m.tool_calls)) { /** @type {string[]} */ const names = [] for (const tc of m.tool_calls) { if (!tc || typeof tc !== 'object') continue const fn = /** @type {Record} */ (tc).function if (fn && typeof fn === 'object') { const n = /** @type {Record} */ (fn).name if (typeof n === 'string' && n) names.push(n) } } if (names.length) return 'tool_calls: ' + names.join(', ') } return '' } /** * @param {string} s * @param {number} max */ function bareAgentClipText(s, max) { const t = String(s || '').replace(/\s+/g, ' ').trim() if (t.length <= max) return t if (max < 24) return t.slice(0, max) const head = Math.floor(max * 0.62) const tail = Math.max(8, max - head - 5) return t.slice(0, head) + ' … ' + t.slice(-tail) } /** * Shrink oversized tool / assistant payloads in-place (clone). * @param {unknown[]} msgs * @param {number} toolMaxChars * @returns {unknown[]} */ function bareAgentShrinkFatMessages(msgs, toolMaxChars) { const max = Math.max(240, Math.floor(toolMaxChars || 1600)) /** @type {unknown[]} */ const out = [] for (const msg of msgs) { if (!msg || typeof msg !== 'object') { out.push(msg) continue } const m = /** @type {Record} */ (msg) const role = String(m.role || '') if (role === 'tool' && typeof m.content === 'string' && m.content.length > max) { out.push({ ...m, content: bareAgentClipText(m.content, max) + '\n[tool output compacted ' + String(m.content.length) + '→' + String(max) + ' chars]' }) continue } if ( (role === 'assistant' || role === 'user') && typeof m.content === 'string' && m.content.length > max * 4 ) { out.push({ ...m, content: bareAgentClipText(m.content, max * 4) + '\n[message compacted]' }) continue } out.push(msg) } return out } /** * Group chat into [system?] + turn groups (user | assistant+tools*). * @param {unknown[]} msgs * @returns {{ system: unknown | null, groups: unknown[][] }} */ function bareAgentGroupMessageTurns(msgs) { /** @type {unknown | null} */ let system = null /** @type {unknown[][]} */ const groups = [] /** @type {unknown[]} */ let cur = [] function flush() { if (cur.length) { groups.push(cur) cur = [] } } for (const msg of Array.isArray(msgs) ? msgs : []) { if (!msg || typeof msg !== 'object') continue const role = String(/** @type {Record} */ (msg).role || '') if (role === 'system' && system == null && groups.length === 0 && !cur.length) { system = msg continue } if (role === 'user') { flush() cur = [msg] continue } if (role === 'assistant' || role === 'tool') { if (!cur.length) cur = [msg] else cur.push(msg) continue } // unknown roles — attach to current or solo group if (!cur.length) cur = [msg] else cur.push(msg) } flush() return { system, groups } } /** * True if message is a prior compaction summary we injected. * @param {unknown} msg */ function bareAgentIsCompactionMessage(msg) { if (!msg || typeof msg !== 'object') return false const m = /** @type {Record} */ (msg) if (m.bare_os_compaction === true) return true const c = typeof m.content === 'string' ? m.content : '' const t = c.trim() return ( String(m.role || '') === 'user' && (/^\[context compaction\]/i.test(t) || /^This session is being continued from a previous conversation/i.test(t) || m.bare_os_reminder === true) ) } /** Grok-style continuation carrier minimum (extractive; slightly below LLM 500). */ var BARE_AGENT_MIN_SUMMARY_SEED_CHARS = 160 /** * Strip drafting tags and neutralize leftover / tokens * so they cannot prime the next turn (Grok format_compact_summary). * @param {string} raw */ function bareAgentFormatCompactSummary(raw) { let result = String(raw || '') result = result.replace(/[\s\S]*?<\/analysis>/gi, '') result = result.replace(/<\/?summary>/gi, '') result = result .replace(/<\/summary>/g, '<\u200b/summary>') .replace(//g, '<\u200bsummary>') .replace(/<\/analysis>/g, '<\u200b/analysis>') .replace(//g, '<\u200banalysis>') while (result.indexOf('\n\n\n') >= 0) result = result.replace(/\n\n\n/g, '\n\n') return result.trim() } /** * @param {string} raw */ function bareAgentFormatCompactSummaryContent(raw) { const cleaned = bareAgentFormatCompactSummary(raw) return ( 'This session is being continued from a previous conversation that ran out of context. ' + 'The summary below covers the earlier portion of the conversation.\n\n' + cleaned ) } /** * @param {string} text */ function bareAgentWrapUserQuery(text) { const t = String(text || '').trim() if (!t) return '' if (/^/.test(t)) return t return '\n' + t + '\n' } /** * @param {string} raw */ function bareAgentIsDegenerateSummary(raw) { return bareAgentFormatCompactSummary(raw).length < BARE_AGENT_MIN_SUMMARY_SEED_CHARS } /** * Collect unique path-like and command tokens for the artifacts section. * @param {string} text * @param {number} max */ function bareAgentExtractPathTokens(text, max) { const s = String(text || '') const found = [] const re = /(?:~|\/)[A-Za-z0-9._+\-@/]+/g let m while ((m = re.exec(s)) && found.length < max) { const p = m[0] if (p.length < 3 || found.indexOf(p) >= 0) continue found.push(p) } return found } /** * One-line digest for a message (extractive). * @param {unknown} msg * @param {number} max */ function bareAgentDigestMessage(msg, max) { if (!msg || typeof msg !== 'object') return '' const m = /** @type {Record} */ (msg) const role = String(m.role || '?') const text = bareAgentMessagePlainText(m) if (role === 'tool') { const name = typeof m.name === 'string' ? m.name : typeof m.tool_call_id === 'string' ? m.tool_call_id.slice(0, 12) : 'tool' return '- tool:' + name + ' ' + bareAgentClipText(text, Math.max(40, max - 24)) } if (role === 'assistant') { return '- assistant ' + bareAgentClipText(text || '(tools only)', max) } if (role === 'user') { if (bareAgentIsCompactionMessage(m)) { return '- prior-compaction ' + bareAgentClipText(text.replace(/^\[context compaction\][^\n]*\n?/i, ''), max) } return '- user ' + bareAgentClipText(text, max) } return '- ' + role + ' ' + bareAgentClipText(text, max) } /** * Grok-style 7-section extractive summary of older turn groups. * @param {unknown[][]} groups * @param {{ maxChars?: number, perMsg?: number }} [opts] */ function bareAgentBuildStructuredSummary(groups, opts) { const maxChars = Math.max(400, Math.floor((opts && opts.maxChars) || 3500)) const perMsg = Math.max(60, Math.floor((opts && opts.perMsg) || 180)) /** @type {string[]} */ const userMsgs = [] /** @type {string[]} */ const tools = [] /** @type {string[]} */ const errors = [] /** @type {string[]} */ const solves = [] /** @type {string[]} */ const concepts = [] /** @type {string[]} */ const files = [] function pushUnique(arr, s, cap) { const t = String(s || '').trim() if (!t || arr.indexOf(t) >= 0) return if (arr.length >= cap) return arr.push(t) } for (const g of groups) { for (const msg of g) { if (!msg || typeof msg !== 'object') continue const m = /** @type {Record} */ (msg) const role = String(m.role || '') const text = bareAgentMessagePlainText(m) for (const p of bareAgentExtractPathTokens(text, 8)) pushUnique(files, p, 24) if (role === 'user' && !bareAgentIsCompactionMessage(m)) { pushUnique(userMsgs, bareAgentClipText(text, perMsg + 80), 16) } else if (role === 'tool') { const name = typeof m.name === 'string' ? m.name : 'tool' pushUnique(tools, name + ': ' + bareAgentClipText(text, perMsg), 20) if (/error|fail|denied|not found|ENOENT|EXIT:[1-9]/i.test(text)) { pushUnique(errors, name + ' ' + bareAgentClipText(text, 140), 12) } } else if (role === 'assistant') { if (m.tool_calls && Array.isArray(m.tool_calls)) { for (const tc of m.tool_calls) { if (!tc || typeof tc !== 'object') continue const fn = /** @type {Record} */ (tc).function if (fn && typeof fn === 'object') { const n = /** @type {Record} */ (fn).name if (typeof n === 'string' && n) pushUnique(concepts, n, 16) } } } if (text) pushUnique(solves, bareAgentClipText(text, perMsg), 12) } } } const intent = userMsgs.length ? userMsgs[0] : '(no explicit user request in compacted prefix)' /** @type {string[]} */ const lines = [ '1. Primary Request and Intent:', ' ' + intent, '', '2. Key Technical Concepts:', concepts.length ? concepts.map((c) => ' - ' + c).join('\n') : ' - (none extracted)', '', '3. Tool Usage & Verification:', tools.length ? tools.map((c) => ' - ' + c).join('\n') : ' - (none)', '', '4. Files & Code Artifacts:', files.length ? files.map((c) => ' - ' + c).join('\n') : ' - (none)', '', '5. Errors and Fixes:', errors.length ? errors.map((c) => ' - ' + c).join('\n') : ' - (none recorded)', '', '6. Problem Solving:', solves.length ? solves.map((c) => ' - ' + c).join('\n') : ' - (in progress)', '', '7. User Messages:', userMsgs.length ? userMsgs.map((c) => ' - ' + c).join('\n') : ' - (none)' ] let body = lines.join('\n') if (body.length > maxChars) body = body.slice(0, maxChars - 20) + '\n… truncated' return body } /** * Build a rolling summary from older turn groups. * @param {unknown[][]} groups * @param {{ maxChars?: number, perMsg?: number }} [opts] */ function bareAgentBuildCompactionSummary(groups, opts) { const structured = bareAgentBuildStructuredSummary(groups, opts) return bareAgentFormatCompactSummaryContent(structured) } /** * Post-compaction (Grok reminder.rs analogue). * @param {{ * autonomous?: { active?: boolean, goal?: string, status?: string, remainingMs?: number }, * droppedGroups?: number, * afterTokens?: number, * budget?: number * }} [opts] */ function bareAgentBuildSystemReminder(opts) { const o = opts && typeof opts === 'object' ? opts : {} /** @type {string[]} */ const parts = [] const auto = o.autonomous if (auto && auto.active) { parts.push( 'Autonomous run: ' + String(auto.goal || '(goal)').slice(0, 240) + ' (status=' + String(auto.status || 'running') + (typeof auto.remainingMs === 'number' ? ', remaining=' + String(Math.max(0, Math.round(auto.remainingMs / 1000))) + 's' : '') + '). Keep using tools until task_complete.' ) } if (typeof o.droppedGroups === 'number' && o.droppedGroups > 0) { parts.push( 'Context compacted: ' + String(o.droppedGroups) + ' older turns summarized. Recent messages after the last user query are verbatim.' ) } if (typeof o.afterTokens === 'number' && typeof o.budget === 'number' && o.budget > 0) { parts.push( 'Context usage after compact: ' + String(o.afterTokens) + '/' + String(o.budget) + ' tokens (est.).' ) } if (!parts.length) return '' return '\n' + parts.join('\n') + '\n' } /** * Grok assemble: [system, last user query, recent after that turn, summary, reminder]. * @param {{ * system?: unknown | null, * lastUserQuery?: string, * recent?: unknown[], * summaryText?: string, * reminder?: string * }} parts * @returns {unknown[]} */ function bareAgentAssembleCompactedHistory(parts) { const p = parts && typeof parts === 'object' ? parts : {} /** @type {unknown[]} */ const out = [] if (p.system) out.push(p.system) const q = String(p.lastUserQuery || '').trim() if (q) { out.push({ role: 'user', content: bareAgentWrapUserQuery(q), bare_os_user_query: true }) } if (Array.isArray(p.recent)) { for (const m of p.recent) out.push(m) } const summary = String(p.summaryText || '').trim() if (summary) { out.push({ role: 'user', content: summary, bare_os_compaction: true }) } const rem = String(p.reminder || '').trim() if (rem) { out.push({ role: 'user', content: rem, bare_os_compaction: true, bare_os_reminder: true }) } return out } /** * Last non-compaction user text + messages after that turn. * @param {unknown[][]} groups */ function bareAgentSplitLastUserAndRecent(groups) { let lastUserIdx = -1 let lastUserText = '' for (let i = groups.length - 1; i >= 0; i--) { const g = groups[i] if (!g || !g.length) continue const first = g[0] if (!first || typeof first !== 'object') continue const m = /** @type {Record} */ (first) if (String(m.role || '') !== 'user') continue if (bareAgentIsCompactionMessage(m)) continue lastUserIdx = i lastUserText = bareAgentMessagePlainText(m) break } if (lastUserIdx < 0) { return { lastUserText: '', older: groups, recent: /** @type {unknown[]} */ ([]) } } const older = groups.slice(0, lastUserIdx) /** @type {unknown[]} */ const recent = [] const lastGroup = groups[lastUserIdx] || [] for (let i = 1; i < lastGroup.length; i++) recent.push(lastGroup[i]) for (let i = lastUserIdx + 1; i < groups.length; i++) { for (const m of groups[i]) recent.push(m) } return { lastUserText, older, recent } } /** * @param {unknown[]} msgs * @param {number} ctxSize * @param {{ * tools?: unknown[], * reserveCompletion?: number, * keepRecent?: number, * toolMaxChars?: number, * summaryMaxChars?: number, * softRatio?: number, * mode?: 'auto' | 'off' | 'aggressive', * autonomous?: { active?: boolean, goal?: string, status?: string, remainingMs?: number } * }} [opts] * @returns {{ * messages: unknown[], * meta: { * compacted: boolean, * mode: string, * beforeTokens: number, * afterTokens: number, * budget: number, * droppedGroups: number, * tiers: string[] * } * }} */ function bareAgentCompactMessagesForCtx(msgs, ctxSize, opts) { const modeRaw = String((opts && opts.mode) || 'auto').trim().toLowerCase() const mode = modeRaw === 'off' || modeRaw === 'aggressive' ? modeRaw : 'auto' /** @type {string[]} */ const tiers = [] const ctx = Math.max(2048, Math.floor(Number(ctxSize) || 4096)) const reserve = opts && Number.isFinite(Number(opts.reserveCompletion)) ? Math.max(256, Math.floor(Number(opts.reserveCompletion))) : Math.min(2048, Math.max(256, Math.floor(ctx * 0.12))) const toolsTok = bareAgentCompactEstimateTokens( opts && Array.isArray(opts.tools) && opts.tools.length ? opts.tools : [] ) const budget = Math.max(512, ctx - reserve - toolsTok) const softRatio = mode === 'aggressive' ? 0.7 : Math.min(0.95, Math.max(0.55, Number(opts && opts.softRatio) || 0.85)) const softBudget = Math.floor(budget * softRatio) const keepRecentDefault = mode === 'aggressive' ? 4 : 8 let keepRecent = Math.max( 2, Math.floor( Number(opts && opts.keepRecent) > 0 ? Number(opts.keepRecent) : keepRecentDefault ) ) const toolMax = mode === 'aggressive' ? Math.min(900, Number(opts && opts.toolMaxChars) || 900) : Math.max(400, Number(opts && opts.toolMaxChars) || 1600) const summaryMax = mode === 'aggressive' ? Math.min(2200, Number(opts && opts.summaryMaxChars) || 2200) : Math.max(800, Number(opts && opts.summaryMaxChars) || 3500) const input = Array.isArray(msgs) ? msgs.slice() : [] const beforeTokens = bareAgentCompactEstimateTokens(input) if (mode === 'off') { const trimmed = typeof bareAgentTrimMessagesForCtx === 'function' ? bareAgentTrimMessagesForCtx(input, ctxSize, opts) : input return { messages: trimmed, meta: { compacted: false, mode, beforeTokens, afterTokens: bareAgentCompactEstimateTokens(trimmed), budget, droppedGroups: 0, tiers: ['off'] } } } /** @type {unknown[]} */ let out = bareAgentShrinkFatMessages(input, toolMax) if (out !== input && bareAgentCompactEstimateTokens(out) < beforeTokens) { tiers.push('shrink-tools') } // Already fits soft budget — keep light shrink only. if (bareAgentCompactEstimateTokens(out) <= softBudget) { return { messages: out, meta: { compacted: tiers.length > 0, mode, beforeTokens, afterTokens: bareAgentCompactEstimateTokens(out), budget, droppedGroups: 0, tiers: tiers.length ? tiers : ['noop'] } } } const { system, groups } = bareAgentGroupMessageTurns(out) let droppedGroups = 0 function applyFullReplace(allGroups, keep) { const split = bareAgentSplitLastUserAndRecent(allGroups) /** @type {unknown[][]} */ let older = split.older /** @type {unknown[]} */ let recent = split.recent // If "recent after last user" is huge, keep only the tail of those messages // by regrouping them and folding extras into older. if (keep > 0 && recent.length > keep * 4) { const tail = recent.slice(-(keep * 3)) const head = recent.slice(0, Math.max(0, recent.length - tail.length)) if (head.length) older = older.concat([head]) recent = tail } const summaryText = bareAgentBuildCompactionSummary(older, { maxChars: summaryMax, perMsg: mode === 'aggressive' ? 120 : 180 }) if (older.length && bareAgentIsDegenerateSummary(summaryText)) { tiers.push('degenerate-keep') return null } const reminder = bareAgentBuildSystemReminder({ autonomous: opts && opts.autonomous, droppedGroups: older.length, afterTokens: 0, budget }) droppedGroups += older.length return bareAgentAssembleCompactedHistory({ system: system, lastUserQuery: split.lastUserText, recent: recent, summaryText: summaryText, reminder: reminder }) } if (groups.length > keepRecent) { const next = applyFullReplace(groups, keepRecent) if (next) { out = next tiers.push('full-replace') } } let guard = 0 while ( bareAgentCompactEstimateTokens(out) > budget && keepRecent > 2 && guard < 8 ) { keepRecent-- guard++ const again = bareAgentGroupMessageTurns(out) if (again.groups.length <= keepRecent) break const next = applyFullReplace(again.groups, keepRecent) if (!next) break out = next tiers.push('tighten-keep=' + String(keepRecent)) } // Hard fallback if (bareAgentCompactEstimateTokens(out) > budget) { tiers.push('hard-trim') out = typeof bareAgentTrimMessagesForCtx === 'function' ? bareAgentTrimMessagesForCtx(out, ctxSize, opts) : out } const afterTokens = bareAgentCompactEstimateTokens(out) return { messages: out, meta: { compacted: tiers.some((t) => t !== 'noop'), mode, beforeTokens, afterTokens, budget, droppedGroups, tiers } } } /** * Disk history compaction: shrink tools + optional rolling summary when huge. * @param {unknown[]} msgs * @param {number} maxBytes * @param {{ keepRecent?: number }} [opts] */ function bareAgentCompactMessagesForDisk(msgs, maxBytes, opts) { const cap = Math.max(20_000, Math.floor(maxBytes || 500_000)) let out = bareAgentShrinkFatMessages( Array.isArray(msgs) ? msgs : [], 2400 ) if (JSON.stringify(out).length <= cap) return out // Approximate tokens from byte budget (chars/4). const approxCtx = Math.max(4096, Math.floor(cap / 4)) const packed = bareAgentCompactMessagesForCtx(out, approxCtx, { mode: 'aggressive', keepRecent: (opts && opts.keepRecent) || 12, reserveCompletion: 256, toolMaxChars: 1200, summaryMaxChars: 6000 }) out = packed.messages if ( typeof bareAgentTrimMessages === 'function' && JSON.stringify(out).length > cap ) { out = bareAgentTrimMessages(out, cap) } return out } /** * Resolve compaction settings from agent config + env. * @param {Record} cfg * @param {Record} [env] */ function bareAgentCompactionSettings(cfg, env) { const e = env && typeof env === 'object' ? env : {} const envMode = String(e.BARE_OS_AGENT_COMPACTION || '').trim().toLowerCase() const cfgMode = String( (cfg && cfg.context_compaction) || '' ) .trim() .toLowerCase() let mode = envMode || cfgMode || 'auto' if (mode !== 'off' && mode !== 'aggressive' && mode !== 'auto') mode = 'auto' const keepRecent = Number( e.BARE_OS_AGENT_COMPACTION_KEEP || (cfg && cfg.compaction_keep_recent) || 0 ) const toolMaxChars = Number( e.BARE_OS_AGENT_COMPACTION_TOOL_CHARS || (cfg && cfg.compaction_tool_chars) || 0 ) return { mode: /** @type {'auto'|'off'|'aggressive'} */ (mode), keepRecent: Number.isFinite(keepRecent) && keepRecent > 0 ? Math.min(32, Math.floor(keepRecent)) : undefined, toolMaxChars: Number.isFinite(toolMaxChars) && toolMaxChars > 0 ? Math.min(8000, Math.floor(toolMaxChars)) : undefined } }