@shawnstack/quickforge 1.3.13 → 1.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/assets/{anthropic-Ckozj5YL.js → anthropic-DGgbbhP2.js} +1 -1
- package/dist/assets/{azure-openai-responses-BwnfX0zX.js → azure-openai-responses-iVkKls8h.js} +1 -1
- package/dist/assets/{google-C68FfCgv.js → google-CfWayb6J.js} +1 -1
- package/dist/assets/{google-gemini-cli-Bvmj_VP7.js → google-gemini-cli-BKFbEcDj.js} +1 -1
- package/dist/assets/{google-vertex-DfBcqFGy.js → google-vertex-Di6pCCaT.js} +1 -1
- package/dist/assets/{index-Bx9iFrzC.js → index-CgTJgJ5U.js} +515 -472
- package/dist/assets/index-t6ITXfOr.css +3 -0
- package/dist/assets/{mistral-soe30nal.js → mistral-DGp-bWeK.js} +1 -1
- package/dist/assets/{openai-codex-responses-BX3Fx3LC.js → openai-codex-responses-CFSjwMXz.js} +1 -1
- package/dist/assets/{openai-completions-CYI6ynqy.js → openai-completions-DlNb8Upk.js} +1 -1
- package/dist/assets/{openai-responses-X7pjajQb.js → openai-responses-DulHAAeh.js} +1 -1
- package/dist/assets/{openai-responses-shared-Cx1dCwfa.js → openai-responses-shared-BWikZIo_.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/agent-manager.mjs +45 -10
- package/server/auto-compaction.mjs +229 -0
- package/dist/assets/index-kYsI9py3.css +0 -3
package/package.json
CHANGED
package/server/agent-manager.mjs
CHANGED
|
@@ -15,6 +15,10 @@ import {
|
|
|
15
15
|
parseCompactArgs,
|
|
16
16
|
saveCompactBackup,
|
|
17
17
|
} from './conversation-compaction.mjs'
|
|
18
|
+
import {
|
|
19
|
+
buildAutoCompactLoopMessages,
|
|
20
|
+
maybeAutoCompactSession,
|
|
21
|
+
} from './auto-compaction.mjs'
|
|
18
22
|
import {
|
|
19
23
|
handleInternalCommand,
|
|
20
24
|
parseInternalCommandInvocation,
|
|
@@ -294,10 +298,13 @@ function addToolTimingToEvent(session, event) {
|
|
|
294
298
|
|
|
295
299
|
function updateSessionMessages(session, messages) {
|
|
296
300
|
session.agent.state.messages = messages
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function resetSessionCompaction(session) {
|
|
304
|
+
session.contextCompaction = null
|
|
305
|
+
session.lastAutoCompactAt = null
|
|
306
|
+
session.lastTransformedContextMessages = null
|
|
307
|
+
session.autoCompacting = false
|
|
301
308
|
}
|
|
302
309
|
|
|
303
310
|
function finishManualSessionRun(session, status, errorMessage) {
|
|
@@ -464,6 +471,7 @@ async function clearSession(session) {
|
|
|
464
471
|
}
|
|
465
472
|
|
|
466
473
|
updateSessionMessages(session, [])
|
|
474
|
+
resetSessionCompaction(session)
|
|
467
475
|
session.status = 'idle'
|
|
468
476
|
session.startedAt = null
|
|
469
477
|
session.finishedAt = new Date().toISOString()
|
|
@@ -576,8 +584,18 @@ function compactedContextMessages(messages) {
|
|
|
576
584
|
return index >= 0 ? messages.slice(index) : messages
|
|
577
585
|
}
|
|
578
586
|
|
|
579
|
-
function
|
|
580
|
-
|
|
587
|
+
async function transformSessionContext(session, messages, signal) {
|
|
588
|
+
await maybeAutoCompactSession({
|
|
589
|
+
session,
|
|
590
|
+
messages,
|
|
591
|
+
signal,
|
|
592
|
+
emitSessionEvent,
|
|
593
|
+
persistSession,
|
|
594
|
+
logger,
|
|
595
|
+
})
|
|
596
|
+
const transformedMessages = buildAutoCompactLoopMessages(session, messages)
|
|
597
|
+
session.lastTransformedContextMessages = transformedMessages
|
|
598
|
+
return applyActiveCommandPrompt(compactedContextMessages(transformedMessages), session?.activeCommandPrompt)
|
|
581
599
|
}
|
|
582
600
|
|
|
583
601
|
export const agentEvents = new EventEmitter()
|
|
@@ -633,6 +651,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
633
651
|
systemPrompt = null,
|
|
634
652
|
title = 'New chat',
|
|
635
653
|
createdAt = new Date().toISOString(),
|
|
654
|
+
contextCompaction = null,
|
|
636
655
|
} = config
|
|
637
656
|
|
|
638
657
|
// Resolve project context for tool calls
|
|
@@ -704,9 +723,9 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
704
723
|
sessionId,
|
|
705
724
|
convertToLlm: serverConvertToLlm,
|
|
706
725
|
onPayload: (payload) => {
|
|
707
|
-
restoreReasoningContentInPayload(payload, agent.state.messages, agent.state.model)
|
|
726
|
+
restoreReasoningContentInPayload(payload, session?.lastTransformedContextMessages || agent.state.messages, agent.state.model)
|
|
708
727
|
},
|
|
709
|
-
transformContext: (messages) =>
|
|
728
|
+
transformContext: (messages, signal) => transformSessionContext(session, messages, signal),
|
|
710
729
|
beforeToolCall: async (context) => {
|
|
711
730
|
const toolName = context.toolCall?.name
|
|
712
731
|
const toolCallId = context.toolCall?.id
|
|
@@ -756,6 +775,10 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
756
775
|
titleGenerated: false,
|
|
757
776
|
toolTimings: new Map(),
|
|
758
777
|
getApiKey,
|
|
778
|
+
contextCompaction,
|
|
779
|
+
lastTransformedContextMessages: null,
|
|
780
|
+
autoCompacting: false,
|
|
781
|
+
lastAutoCompactAt: null,
|
|
759
782
|
/** Track active SSE connections. Only one SSE stream allowed per session to prevent
|
|
760
783
|
* connection-pool exhaustion when two browser tabs load the same session. */
|
|
761
784
|
sseConnected: false,
|
|
@@ -816,8 +839,8 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
816
839
|
* Persist session data to storage.
|
|
817
840
|
*/
|
|
818
841
|
async function persistSession(session) {
|
|
819
|
-
const { sessionId, agent, scope, projectId, title, createdAt, status, startedAt, finishedAt, model, thinkingLevel, yoloMode } = session
|
|
820
|
-
const messages =
|
|
842
|
+
const { sessionId, agent, scope, projectId, title, createdAt, status, startedAt, finishedAt, model, thinkingLevel, yoloMode, contextCompaction } = session
|
|
843
|
+
const messages = agent.state.messages
|
|
821
844
|
|
|
822
845
|
if (messages.length === 0) {
|
|
823
846
|
try {
|
|
@@ -847,6 +870,7 @@ async function persistSession(session) {
|
|
|
847
870
|
taskStatus: status,
|
|
848
871
|
taskStartedAt: startedAt,
|
|
849
872
|
taskFinishedAt: finishedAt,
|
|
873
|
+
contextCompaction: contextCompaction || undefined,
|
|
850
874
|
}
|
|
851
875
|
|
|
852
876
|
// Calculate usage
|
|
@@ -894,6 +918,13 @@ async function persistSession(session) {
|
|
|
894
918
|
taskStatus: status,
|
|
895
919
|
taskStartedAt: startedAt,
|
|
896
920
|
taskFinishedAt: finishedAt,
|
|
921
|
+
contextCompaction: contextCompaction ? {
|
|
922
|
+
compactedAt: contextCompaction.compactedAt,
|
|
923
|
+
compactedUpToIndex: contextCompaction.compactedUpToIndex,
|
|
924
|
+
keepRecentTurns: contextCompaction.keepRecentTurns,
|
|
925
|
+
thresholdPercent: contextCompaction.thresholdPercent,
|
|
926
|
+
usageBefore: contextCompaction.usageBefore,
|
|
927
|
+
} : undefined,
|
|
897
928
|
}
|
|
898
929
|
|
|
899
930
|
// Write to storage atomically (read-modify-write within queue)
|
|
@@ -947,6 +978,7 @@ export async function rollbackSessionMessages(sessionId, rollbackMessageIndex) {
|
|
|
947
978
|
|
|
948
979
|
const nextMessages = messages.slice(0, rollbackIndex)
|
|
949
980
|
updateSessionMessages(session, nextMessages)
|
|
981
|
+
resetSessionCompaction(session)
|
|
950
982
|
session.status = 'idle'
|
|
951
983
|
session.finishedAt = new Date().toISOString()
|
|
952
984
|
await persistSession(session)
|
|
@@ -971,6 +1003,7 @@ export async function replaceSessionMessages(sessionId, messages) {
|
|
|
971
1003
|
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes before rolling back.'), { statusCode: 409 })
|
|
972
1004
|
}
|
|
973
1005
|
updateSessionMessages(session, Array.isArray(messages) ? messages : [])
|
|
1006
|
+
resetSessionCompaction(session)
|
|
974
1007
|
session.status = 'idle'
|
|
975
1008
|
session.finishedAt = new Date().toISOString()
|
|
976
1009
|
await persistSession(session)
|
|
@@ -1160,6 +1193,7 @@ export function getSessionState(sessionId) {
|
|
|
1160
1193
|
finishedAt: session.finishedAt,
|
|
1161
1194
|
tools: session.agent.state.tools,
|
|
1162
1195
|
messages: session.agent.state.messages,
|
|
1196
|
+
contextCompaction: session.contextCompaction,
|
|
1163
1197
|
isStreaming: session.agent.state.isStreaming,
|
|
1164
1198
|
errorMessage: session.agent.state.errorMessage,
|
|
1165
1199
|
}
|
|
@@ -1255,6 +1289,7 @@ export async function restoreAgent(sessionId) {
|
|
|
1255
1289
|
messages: sessionData.messages || [],
|
|
1256
1290
|
title: sessionData.title || 'New chat',
|
|
1257
1291
|
createdAt: sessionData.createdAt,
|
|
1292
|
+
contextCompaction: sessionData.contextCompaction || null,
|
|
1258
1293
|
})
|
|
1259
1294
|
} catch (err) {
|
|
1260
1295
|
logger.error(`Failed to restore agent ${sessionId}:`, err, { sessionId })
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { readStore } from './storage.mjs'
|
|
2
|
+
import { compactConversation, saveCompactBackup } from './conversation-compaction.mjs'
|
|
3
|
+
|
|
4
|
+
export const AUTO_COMPACT_SETTINGS_KEY = 'auto-compact-settings'
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_AUTO_COMPACT_SETTINGS = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
thresholdPercent: 80,
|
|
9
|
+
keepRecentTurns: 2,
|
|
10
|
+
minSourceChars: 1600,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const AUTO_COMPACT_MIN_INTERVAL_MS = 30_000
|
|
14
|
+
|
|
15
|
+
function clampNumber(value, fallback, min, max) {
|
|
16
|
+
const parsed = Number(value)
|
|
17
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
18
|
+
return Math.min(max, Math.max(min, Math.round(parsed)))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeAutoCompactSettings(value) {
|
|
22
|
+
if (!value || typeof value !== 'object') return { ...DEFAULT_AUTO_COMPACT_SETTINGS }
|
|
23
|
+
return {
|
|
24
|
+
enabled: value.enabled === true,
|
|
25
|
+
thresholdPercent: clampNumber(value.thresholdPercent, DEFAULT_AUTO_COMPACT_SETTINGS.thresholdPercent, 50, 95),
|
|
26
|
+
keepRecentTurns: clampNumber(value.keepRecentTurns, DEFAULT_AUTO_COMPACT_SETTINGS.keepRecentTurns, 1, 20),
|
|
27
|
+
minSourceChars: clampNumber(value.minSourceChars, DEFAULT_AUTO_COMPACT_SETTINGS.minSourceChars, 0, 200000),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function readAutoCompactSettings() {
|
|
32
|
+
const settings = await readStore('settings')
|
|
33
|
+
return normalizeAutoCompactSettings(settings?.[AUTO_COMPACT_SETTINGS_KEY])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function safeJson(value) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(value)
|
|
39
|
+
} catch {
|
|
40
|
+
return ''
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function estimateTextTokens(value) {
|
|
45
|
+
const text = String(value || '')
|
|
46
|
+
if (!text) return 0
|
|
47
|
+
const cjkChars = text.match(/[\u3400-\u9fff\uf900-\ufaff]/g)?.length ?? 0
|
|
48
|
+
const otherChars = Math.max(0, text.length - cjkChars)
|
|
49
|
+
return Math.ceil(cjkChars + otherChars / 3.5)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function contentToText(content) {
|
|
53
|
+
if (typeof content === 'string') return content
|
|
54
|
+
if (!Array.isArray(content)) return ''
|
|
55
|
+
return content.map((block) => {
|
|
56
|
+
if (!block || typeof block !== 'object') return ''
|
|
57
|
+
if (block.type === 'text') return block.text || ''
|
|
58
|
+
if (block.type === 'thinking') return block.thinking || ''
|
|
59
|
+
if (block.type === 'image') return `[image:${block.mimeType || 'unknown'}]`
|
|
60
|
+
if (block.type === 'toolCall') return `[toolCall:${block.name || 'unknown'}] ${safeJson(block.arguments)}`
|
|
61
|
+
return safeJson(block)
|
|
62
|
+
}).filter(Boolean).join('\n')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function estimateMessageTokens(message) {
|
|
66
|
+
if (!message || typeof message !== 'object') return 0
|
|
67
|
+
const parts = [message.role || '', contentToText(message.content)]
|
|
68
|
+
if (message.toolName) parts.push(message.toolName)
|
|
69
|
+
if (message.toolCallId) parts.push(message.toolCallId)
|
|
70
|
+
if (message.details !== undefined) parts.push(safeJson(message.details))
|
|
71
|
+
if (message.attachments !== undefined) parts.push(safeJson(message.attachments))
|
|
72
|
+
return estimateTextTokens(parts.join('\n'))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function estimateMessagesTokens(messages) {
|
|
76
|
+
return (Array.isArray(messages) ? messages : []).reduce((total, message) => total + estimateMessageTokens(message), 0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function estimateMessagesChars(messages) {
|
|
80
|
+
return (Array.isArray(messages) ? messages : []).reduce((total, message) => {
|
|
81
|
+
if (!message || typeof message !== 'object') return total
|
|
82
|
+
return total + [message.role || '', contentToText(message.content), safeJson(message.details), safeJson(message.attachments)].join('\n').length
|
|
83
|
+
}, 0)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function estimateContextUsage({ systemPrompt, messages, tools, model }) {
|
|
87
|
+
const contextWindow = Number(model?.contextWindow) || 0
|
|
88
|
+
const reservedOutputTokens = Math.max(0, Number(model?.maxTokens) || 4096)
|
|
89
|
+
const inputTokens =
|
|
90
|
+
estimateTextTokens(systemPrompt) +
|
|
91
|
+
estimateMessagesTokens(messages) +
|
|
92
|
+
estimateTextTokens(safeJson(tools))
|
|
93
|
+
const totalTokens = inputTokens + reservedOutputTokens
|
|
94
|
+
const percent = contextWindow > 0 ? Math.round((totalTokens / contextWindow) * 1000) / 10 : 0
|
|
95
|
+
return { inputTokens, reservedOutputTokens, totalTokens, contextWindow, percent }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isUserMessage(message) {
|
|
99
|
+
return message?.role === 'user' || message?.role === 'user-with-attachments'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function tailStartForRecentTurns(messages, keepRecentTurns) {
|
|
103
|
+
const source = Array.isArray(messages) ? messages : []
|
|
104
|
+
let seenUserTurns = 0
|
|
105
|
+
for (let index = source.length - 1; index >= 0; index--) {
|
|
106
|
+
if (!isUserMessage(source[index])) continue
|
|
107
|
+
seenUserTurns += 1
|
|
108
|
+
if (seenUserTurns >= keepRecentTurns) return index
|
|
109
|
+
}
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function compactSummaryText(message) {
|
|
114
|
+
const content = message?.content
|
|
115
|
+
const text = typeof content === 'string'
|
|
116
|
+
? content
|
|
117
|
+
: Array.isArray(content)
|
|
118
|
+
? content.filter((block) => block?.type === 'text').map((block) => block.text || '').join('\n')
|
|
119
|
+
: ''
|
|
120
|
+
const match = text.match(/<compact_summary>\s*([\s\S]*?)\s*<\/compact_summary>/)
|
|
121
|
+
return match?.[1]?.trim() || text.trim()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function userTextMessage(text) {
|
|
125
|
+
return {
|
|
126
|
+
role: 'user',
|
|
127
|
+
content: [{ type: 'text', text }],
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildCompactionSourceMessages(session, messages, tailStart) {
|
|
133
|
+
const source = []
|
|
134
|
+
const previousSummary = session.contextCompaction?.summaryMessage
|
|
135
|
+
if (previousSummary) {
|
|
136
|
+
source.push(userTextMessage([
|
|
137
|
+
'Existing rolling compact summary from earlier conversation history:',
|
|
138
|
+
'',
|
|
139
|
+
'<compact_summary>',
|
|
140
|
+
compactSummaryText(previousSummary),
|
|
141
|
+
'</compact_summary>',
|
|
142
|
+
].join('\n')))
|
|
143
|
+
}
|
|
144
|
+
source.push(...messages.slice(session.contextCompaction?.compactedUpToIndex || 0, tailStart))
|
|
145
|
+
return source
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function buildAutoCompactLoopMessages(session, messages) {
|
|
149
|
+
const summaryMessage = session?.contextCompaction?.summaryMessage
|
|
150
|
+
if (!summaryMessage) return messages
|
|
151
|
+
const keepRecentTurns = session.contextCompaction.keepRecentTurns || DEFAULT_AUTO_COMPACT_SETTINGS.keepRecentTurns
|
|
152
|
+
const tailStart = tailStartForRecentTurns(messages, keepRecentTurns)
|
|
153
|
+
return [summaryMessage, ...messages.slice(tailStart)]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function maybeAutoCompactSession({ session, messages, signal, emitSessionEvent, persistSession, logger }) {
|
|
157
|
+
if (!session || session.autoCompacting) return { compacted: false }
|
|
158
|
+
const settings = await readAutoCompactSettings()
|
|
159
|
+
if (!settings.enabled) return { compacted: false }
|
|
160
|
+
if (signal?.aborted) return { compacted: false }
|
|
161
|
+
|
|
162
|
+
const loopMessages = buildAutoCompactLoopMessages(session, messages)
|
|
163
|
+
const usage = estimateContextUsage({
|
|
164
|
+
systemPrompt: session.agent.state.systemPrompt,
|
|
165
|
+
messages: loopMessages,
|
|
166
|
+
tools: session.agent.state.tools,
|
|
167
|
+
model: session.model,
|
|
168
|
+
})
|
|
169
|
+
if (!usage.contextWindow || usage.percent < settings.thresholdPercent) return { compacted: false, usage }
|
|
170
|
+
|
|
171
|
+
const now = Date.now()
|
|
172
|
+
if (session.lastAutoCompactAt && now - session.lastAutoCompactAt < AUTO_COMPACT_MIN_INTERVAL_MS) {
|
|
173
|
+
return { compacted: false, usage, reason: 'recently_compacted' }
|
|
174
|
+
}
|
|
175
|
+
if (session.contextCompaction?.sourceMessageCount && messages.length <= session.contextCompaction.sourceMessageCount + 2) {
|
|
176
|
+
return { compacted: false, usage, reason: 'not_enough_new_messages' }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const tailStart = tailStartForRecentTurns(messages, settings.keepRecentTurns)
|
|
180
|
+
const sourceMessages = buildCompactionSourceMessages(session, messages, tailStart)
|
|
181
|
+
if (sourceMessages.length < 2 || estimateMessagesChars(sourceMessages) < settings.minSourceChars) {
|
|
182
|
+
return { compacted: false, usage, reason: 'not_enough_history' }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
session.autoCompacting = true
|
|
186
|
+
try {
|
|
187
|
+
const result = await compactConversation({
|
|
188
|
+
messages: sourceMessages,
|
|
189
|
+
model: session.model,
|
|
190
|
+
thinkingLevel: session.thinkingLevel,
|
|
191
|
+
getApiKey: session.getApiKey,
|
|
192
|
+
keepTurns: 0,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
if (result.skipped) return { compacted: false, usage, reason: result.reason || 'skipped' }
|
|
196
|
+
|
|
197
|
+
await saveCompactBackup(session.sessionId, sourceMessages)
|
|
198
|
+
const summaryMessage = userTextMessage([
|
|
199
|
+
'The previous conversation has been automatically compacted. Treat the following summary as the authoritative replacement for earlier history. If information is missing, ask for clarification instead of guessing.',
|
|
200
|
+
'',
|
|
201
|
+
'<compact_summary>',
|
|
202
|
+
result.summary,
|
|
203
|
+
'</compact_summary>',
|
|
204
|
+
].join('\n'))
|
|
205
|
+
session.contextCompaction = {
|
|
206
|
+
summaryMessage,
|
|
207
|
+
compactedUpToIndex: tailStart,
|
|
208
|
+
compactedAt: new Date().toISOString(),
|
|
209
|
+
keepRecentTurns: settings.keepRecentTurns,
|
|
210
|
+
sourceMessageCount: messages.length,
|
|
211
|
+
usageBefore: usage,
|
|
212
|
+
thresholdPercent: settings.thresholdPercent,
|
|
213
|
+
}
|
|
214
|
+
session.lastAutoCompactAt = now
|
|
215
|
+
await persistSession(session)
|
|
216
|
+
emitSessionEvent(session, {
|
|
217
|
+
type: 'messages_replaced',
|
|
218
|
+
reason: 'auto_compact',
|
|
219
|
+
messages,
|
|
220
|
+
contextCompaction: session.contextCompaction,
|
|
221
|
+
})
|
|
222
|
+
return { compacted: true, usage }
|
|
223
|
+
} catch (error) {
|
|
224
|
+
logger?.warn?.(`Auto compact failed for session ${session.sessionId}:`, error?.message || error, { sessionId: session.sessionId })
|
|
225
|
+
return { compacted: false, usage, reason: 'error', error }
|
|
226
|
+
} finally {
|
|
227
|
+
session.autoCompacting = false
|
|
228
|
+
}
|
|
229
|
+
}
|