Orginize
This commit is contained in:
@@ -0,0 +1,774 @@
|
||||
/**
|
||||
* 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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (tc).function
|
||||
if (fn && typeof fn === 'object') {
|
||||
const n = /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (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 <summary>/<analysis> 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(/<analysis>[\s\S]*?<\/analysis>/gi, '')
|
||||
result = result.replace(/<\/?summary>/gi, '')
|
||||
result = result
|
||||
.replace(/<\/summary>/g, '<\u200b/summary>')
|
||||
.replace(/<summary>/g, '<\u200bsummary>')
|
||||
.replace(/<\/analysis>/g, '<\u200b/analysis>')
|
||||
.replace(/<analysis>/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 (/^<user_query>/.test(t)) return t
|
||||
return '<user_query>\n' + t + '\n</user_query>'
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<string, unknown>} */ (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<string, unknown>} */ (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<string, unknown>} */ (tc).function
|
||||
if (fn && typeof fn === 'object') {
|
||||
const n = /** @type {Record<string, unknown>} */ (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 <system-reminder> (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 '<system-reminder>\n' + parts.join('\n') + '\n</system-reminder>'
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string, unknown>} */ (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<string, unknown>} cfg
|
||||
* @param {Record<string, string>} [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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user