praxis-agent 0.66.0 → 0.67.1
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/dist/application/compaction-accounting.d.ts +49 -0
- package/dist/application/compaction-accounting.js +349 -0
- package/dist/application/compaction-errors.d.ts +21 -0
- package/dist/application/compaction-errors.js +24 -0
- package/dist/application/context-engine.js +10 -1
- package/dist/application/native-session-transcript.d.ts +3 -0
- package/dist/application/native-session-transcript.js +36 -4
- package/dist/application/session-service.d.ts +2 -1
- package/dist/application/session-service.js +532 -207
- package/dist/application/turn-accounting.js +0 -4
- package/dist/application/turn-persistence.d.ts +8 -3
- package/dist/application/turn-persistence.js +21 -33
- package/dist/build-identity.json +1 -1
- package/dist/native/ownership.d.ts +6 -1
- package/dist/native/ownership.js +6 -0
- package/dist/persistence/native-compaction-receipt-store.d.ts +40 -0
- package/dist/persistence/native-compaction-receipt-store.js +380 -0
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ import { usageCostUsd } from '../core/usage.js';
|
|
|
22
22
|
import { isSessionId } from '../core/session.js';
|
|
23
23
|
import { ContextBudget, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
24
24
|
import { ContextEngine } from './context-engine.js';
|
|
25
|
-
import { ContextPreparation } from './context-preparation.js';
|
|
25
|
+
import { ContextPreparation, StaleContextGenerationError, } from './context-preparation.js';
|
|
26
26
|
import { TurnMemoryCoordinator } from './turn-memory-coordinator.js';
|
|
27
27
|
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
28
28
|
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
@@ -33,8 +33,11 @@ import { lastUserPrompt, projectTranscriptDisplay, unresolvedActiveToolCallIds,
|
|
|
33
33
|
import { InMemoryTranscriptStore } from '../persistence/in-memory-transcript-store.js';
|
|
34
34
|
import { NativeTranscriptStore, } from '../persistence/native-transcript-store.js';
|
|
35
35
|
import { NativeSessionTranscript, } from './native-session-transcript.js';
|
|
36
|
-
import { TurnPersistence } from './turn-persistence.js';
|
|
36
|
+
import { deriveTurnProjectionCursor, TurnPersistence, } from './turn-persistence.js';
|
|
37
37
|
import { TurnAccounting } from './turn-accounting.js';
|
|
38
|
+
import { CompactionAccounting } from './compaction-accounting.js';
|
|
39
|
+
import { classifyCompactionError } from './compaction-errors.js';
|
|
40
|
+
import { NativeCompactionReceiptFileStore } from '../persistence/native-compaction-receipt-store.js';
|
|
38
41
|
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
39
42
|
import { ModelCompactor } from './model-compactor.js';
|
|
40
43
|
import { agentMemoryPrompt, ClaudeSubagentExecutor, StructuredOutputRegistry, } from './subagent-service.js';
|
|
@@ -670,6 +673,7 @@ export class ClaudeSessionService {
|
|
|
670
673
|
activeProvider;
|
|
671
674
|
mcpClosePromise;
|
|
672
675
|
sessionCostTrackers = new Map();
|
|
676
|
+
compactionAccountings = new Map();
|
|
673
677
|
activeCostSessionId;
|
|
674
678
|
closeCostSavePromise;
|
|
675
679
|
closeMetadataSavePromise;
|
|
@@ -918,6 +922,7 @@ export class ClaudeSessionService {
|
|
|
918
922
|
await this.closeMetadataSavePromise;
|
|
919
923
|
this.closeCostSavePromise ??= this.persistActiveSessionCost();
|
|
920
924
|
await this.closeCostSavePromise;
|
|
925
|
+
this.compactionAccountings.clear();
|
|
921
926
|
this.mcpClosePromise ??= this.options.mcp?.close?.() ?? Promise.resolve();
|
|
922
927
|
await this.mcpClosePromise;
|
|
923
928
|
await this.leadOperations?.close();
|
|
@@ -1996,12 +2001,21 @@ export class ClaudeSessionService {
|
|
|
1996
2001
|
this.assertSessionPersistence();
|
|
1997
2002
|
const provider = this.provider();
|
|
1998
2003
|
const sessionPaths = this.paths(sessionId);
|
|
2004
|
+
const nativeStore = new NativeTranscriptStore({
|
|
2005
|
+
transcriptFile: sessionPaths.sessionFile,
|
|
2006
|
+
lockFile: join(sessionPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
2007
|
+
});
|
|
2008
|
+
const existingTranscript = await nativeStore.load();
|
|
2009
|
+
if (existingTranscript.records.length === 0)
|
|
2010
|
+
throw new Error('native transcript session is missing or empty');
|
|
2011
|
+
if (existingTranscript.records.some((record) => record.event.sessionId !== sessionId))
|
|
2012
|
+
throw new Error('native transcript sessionId does not match');
|
|
2013
|
+
await this.activateSessionCostTracker(sessionId);
|
|
2014
|
+
const compactionAccounting = this.compactionAccounting(sessionId, nativeStore);
|
|
2015
|
+
await compactionAccounting.recover();
|
|
1999
2016
|
const nativeTranscript = new NativeSessionTranscript({
|
|
2000
2017
|
sessionId,
|
|
2001
|
-
store:
|
|
2002
|
-
transcriptFile: sessionPaths.sessionFile,
|
|
2003
|
-
lockFile: join(sessionPaths.praxisRoot, 'locks', `${sessionId}.lock`),
|
|
2004
|
-
}),
|
|
2018
|
+
store: nativeStore,
|
|
2005
2019
|
});
|
|
2006
2020
|
return nativeTranscript.withLease({
|
|
2007
2021
|
kind: 'resume',
|
|
@@ -2014,7 +2028,12 @@ export class ClaudeSessionService {
|
|
|
2014
2028
|
if (selection) {
|
|
2015
2029
|
const targetIndex = branchEvents.findIndex((event) => event.id === selection.messageId);
|
|
2016
2030
|
if (targetIndex < 0)
|
|
2017
|
-
throw new Error(`No native rewind point found with event.id: ${selection.messageId}`)
|
|
2031
|
+
throw classifyCompactionError(new Error(`No native rewind point found with event.id: ${selection.messageId}`), {
|
|
2032
|
+
trigger: 'manual',
|
|
2033
|
+
phase: 'validation',
|
|
2034
|
+
durableState: 'not_committed',
|
|
2035
|
+
recoveryDisposition: 'none',
|
|
2036
|
+
});
|
|
2018
2037
|
const selectedEvents = selection.direction === 'from'
|
|
2019
2038
|
? branchEvents.slice(targetIndex)
|
|
2020
2039
|
: branchEvents.slice(0, targetIndex);
|
|
@@ -2029,7 +2048,17 @@ export class ClaudeSessionService {
|
|
|
2029
2048
|
: branchEvents[targetIndex - 1])?.id;
|
|
2030
2049
|
}
|
|
2031
2050
|
else {
|
|
2032
|
-
|
|
2051
|
+
try {
|
|
2052
|
+
memorySelection = await this.selectMemoryPreservedCompact(sessionId, projectNativeSessionEntries(branchEvents));
|
|
2053
|
+
}
|
|
2054
|
+
catch (error) {
|
|
2055
|
+
throw classifyCompactionError(error, {
|
|
2056
|
+
trigger: 'manual',
|
|
2057
|
+
phase: 'validation',
|
|
2058
|
+
durableState: 'not_committed',
|
|
2059
|
+
recoveryDisposition: 'none',
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2033
2062
|
if (memorySelection) {
|
|
2034
2063
|
messages = [
|
|
2035
2064
|
memorySelection.memoryMessage,
|
|
@@ -2040,118 +2069,188 @@ export class ClaudeSessionService {
|
|
|
2040
2069
|
}
|
|
2041
2070
|
}
|
|
2042
2071
|
if (messages.length === 0)
|
|
2043
|
-
throw new Error('Cannot compact an empty native transcript')
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2072
|
+
throw classifyCompactionError(new Error('Cannot compact an empty native transcript'), {
|
|
2073
|
+
trigger: 'manual',
|
|
2074
|
+
phase: 'validation',
|
|
2075
|
+
durableState: 'not_committed',
|
|
2076
|
+
recoveryDisposition: 'none',
|
|
2077
|
+
});
|
|
2047
2078
|
const preTokens = estimateModelRequestTokens(messages);
|
|
2048
2079
|
this.options.eventSink?.({ type: 'state', state: 'compacting' });
|
|
2049
2080
|
const contextWindowTokens = this.contextBudget(provider)?.contextWindowTokens ??
|
|
2050
2081
|
provider.capabilities.contextWindowTokens ??
|
|
2051
2082
|
200_000;
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2083
|
+
let preCompact;
|
|
2084
|
+
try {
|
|
2085
|
+
preCompact = await this.runAdvisoryHook(sessionId, 'PreCompact', {
|
|
2086
|
+
trigger: 'manual',
|
|
2087
|
+
custom_instructions: selection?.context ?? null,
|
|
2088
|
+
}, 'manual', signal);
|
|
2089
|
+
}
|
|
2090
|
+
catch (error) {
|
|
2091
|
+
throw classifyCompactionError(error, {
|
|
2092
|
+
trigger: 'manual',
|
|
2093
|
+
phase: 'generation',
|
|
2094
|
+
durableState: 'not_committed',
|
|
2095
|
+
recoveryDisposition: 'none',
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
let compacted;
|
|
2099
|
+
try {
|
|
2100
|
+
compacted = await (this.options.compactor ?? new ModelCompactor(provider)).compact({
|
|
2101
|
+
messages: [
|
|
2102
|
+
...messages,
|
|
2103
|
+
...(selection?.context
|
|
2104
|
+
? [
|
|
2105
|
+
{
|
|
2106
|
+
role: 'user',
|
|
2107
|
+
content: `Additional summarization context: ${selection.context}`,
|
|
2108
|
+
},
|
|
2109
|
+
]
|
|
2110
|
+
: []),
|
|
2111
|
+
...successfulHookOutput(preCompact).map((content) => ({
|
|
2112
|
+
role: 'user',
|
|
2113
|
+
content: `Additional summarization context: ${content}`,
|
|
2114
|
+
})),
|
|
2115
|
+
],
|
|
2116
|
+
targetTokens: Math.min(8192, Math.max(1, Math.floor(contextWindowTokens / 4))),
|
|
2117
|
+
contextWindowTokens,
|
|
2118
|
+
...(signal ? { signal } : {}),
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
catch (error) {
|
|
2122
|
+
throw classifyCompactionError(error, {
|
|
2123
|
+
trigger: 'manual',
|
|
2124
|
+
phase: 'generation',
|
|
2125
|
+
durableState: 'not_committed',
|
|
2126
|
+
recoveryDisposition: 'none',
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2076
2129
|
if (signal?.aborted)
|
|
2077
2130
|
throw new AgentRunCancelledError();
|
|
2078
|
-
|
|
2079
|
-
|
|
2131
|
+
let durationMs;
|
|
2132
|
+
let durationWithoutRetriesMs;
|
|
2133
|
+
try {
|
|
2134
|
+
const durations = requireCompactionDurations(compacted);
|
|
2135
|
+
durationMs = durations.durationMs;
|
|
2136
|
+
durationWithoutRetriesMs = durations.durationWithoutRetriesMs;
|
|
2137
|
+
requireManualCompactUsage(compacted.usage);
|
|
2138
|
+
}
|
|
2139
|
+
catch (error) {
|
|
2140
|
+
throw classifyCompactionError(error, {
|
|
2141
|
+
trigger: 'manual',
|
|
2142
|
+
phase: 'validation',
|
|
2143
|
+
durableState: 'not_committed',
|
|
2144
|
+
recoveryDisposition: 'none',
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2080
2147
|
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
2081
2148
|
? compacted.model
|
|
2082
2149
|
: provider.model;
|
|
2083
2150
|
const meaningfulMetering = compacted.summary.trim().length > 0 &&
|
|
2084
|
-
|
|
2151
|
+
hasNonZeroUsage(compacted.usage);
|
|
2152
|
+
if (compacted.summary.trim().length === 0)
|
|
2153
|
+
throw classifyCompactionError(new Error('Native compact summary must not be blank'), {
|
|
2154
|
+
trigger: 'manual',
|
|
2155
|
+
phase: 'validation',
|
|
2156
|
+
durableState: 'not_committed',
|
|
2157
|
+
recoveryDisposition: 'none',
|
|
2158
|
+
});
|
|
2085
2159
|
if (meaningfulMetering &&
|
|
2086
2160
|
(compactModel === undefined || compactModel.trim() === ''))
|
|
2087
|
-
throw new Error('Manual compact usage requires a nonblank model identity')
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
if (meaningfulMetering &&
|
|
2093
|
-
compactModel !== undefined &&
|
|
2094
|
-
compactModel.trim() !== '') {
|
|
2095
|
-
const pricing = this.options.pricing?.resolve(compactModel);
|
|
2096
|
-
const costUsd = pricing
|
|
2097
|
-
? usageCostUsd(compacted.usage, pricing)
|
|
2098
|
-
: undefined;
|
|
2099
|
-
meteringTurnInput = {
|
|
2100
|
-
model: compactModel,
|
|
2101
|
-
usage: compacted.usage,
|
|
2102
|
-
...(costUsd === undefined ? {} : { costUsd }),
|
|
2103
|
-
...(compacted.usage.webSearchRequests === undefined
|
|
2104
|
-
? {}
|
|
2105
|
-
: { webSearchRequests: compacted.usage.webSearchRequests }),
|
|
2106
|
-
apiDurationMs: durationMs,
|
|
2107
|
-
apiDurationWithoutRetriesMs: durationWithoutRetriesMs,
|
|
2108
|
-
};
|
|
2109
|
-
const preflight = new ClaudeSessionCostTracker({
|
|
2110
|
-
sessionId,
|
|
2111
|
-
restored: tracker.snapshot(),
|
|
2161
|
+
throw classifyCompactionError(new Error('Manual compact usage requires a nonblank model identity'), {
|
|
2162
|
+
trigger: 'manual',
|
|
2163
|
+
phase: 'validation',
|
|
2164
|
+
durableState: 'not_committed',
|
|
2165
|
+
recoveryDisposition: 'none',
|
|
2112
2166
|
});
|
|
2113
|
-
|
|
2114
|
-
|
|
2167
|
+
const preparedTransaction = await compactionAccounting.prepare({
|
|
2168
|
+
trigger: 'manual',
|
|
2169
|
+
metric: {
|
|
2170
|
+
usage: compacted.usage,
|
|
2171
|
+
...(hasNonZeroUsage(compacted.usage) && compactModel !== undefined
|
|
2172
|
+
? { model: compactModel }
|
|
2173
|
+
: {}),
|
|
2174
|
+
durationApiMs: durationMs,
|
|
2175
|
+
durationApiWithoutRetriesMs: durationWithoutRetriesMs,
|
|
2176
|
+
},
|
|
2177
|
+
});
|
|
2115
2178
|
const postTokens = estimateModelRequestTokens([
|
|
2116
2179
|
{ role: 'user', content: compacted.summary },
|
|
2117
2180
|
]);
|
|
2118
2181
|
if (signal?.aborted)
|
|
2119
2182
|
throw new AgentRunCancelledError();
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2183
|
+
let ids;
|
|
2184
|
+
try {
|
|
2185
|
+
ids = await lease.appendCompaction({
|
|
2186
|
+
summary: compacted.summary,
|
|
2187
|
+
trigger: 'manual',
|
|
2188
|
+
preTokens,
|
|
2189
|
+
postTokens,
|
|
2190
|
+
durationMs,
|
|
2191
|
+
boundaryId: preparedTransaction.boundaryId,
|
|
2192
|
+
summaryId: preparedTransaction.summaryId,
|
|
2193
|
+
...(preservedMessages.length > 0 ? { preservedMessages } : {}),
|
|
2194
|
+
...(logicalParentId === undefined ? {} : { logicalParentId }),
|
|
2195
|
+
...(selection
|
|
2196
|
+
? {
|
|
2197
|
+
direction: selection.direction === 'to'
|
|
2198
|
+
? 'up_to'
|
|
2199
|
+
: 'from',
|
|
2200
|
+
messagesSummarized: messages.length,
|
|
2201
|
+
preservePrefix: selection.direction === 'from' &&
|
|
2202
|
+
branchEvents.findIndex((event) => event.id === selection.messageId) > 0,
|
|
2203
|
+
}
|
|
2204
|
+
: {}),
|
|
2205
|
+
...(memorySelection
|
|
2206
|
+
? {
|
|
2207
|
+
direction: 'from',
|
|
2208
|
+
messagesSummarized: messages.length,
|
|
2209
|
+
preservePrefix: false,
|
|
2210
|
+
}
|
|
2211
|
+
: {}),
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
catch (error) {
|
|
2215
|
+
throw classifyCompactionError(error, {
|
|
2216
|
+
trigger: 'manual',
|
|
2217
|
+
phase: 'transcript_commit',
|
|
2218
|
+
durableState: 'indeterminate',
|
|
2219
|
+
recoveryDisposition: 'reconcile',
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
await preparedTransaction.commit({
|
|
2223
|
+
kind: 'compaction',
|
|
2224
|
+
boundaryId: ids.boundaryId,
|
|
2225
|
+
summaryId: ids.summaryId,
|
|
2154
2226
|
});
|
|
2227
|
+
try {
|
|
2228
|
+
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'manual', compact_summary: compacted.summary }, 'manual', signal);
|
|
2229
|
+
}
|
|
2230
|
+
catch (error) {
|
|
2231
|
+
throw classifyCompactionError(error, {
|
|
2232
|
+
trigger: 'manual',
|
|
2233
|
+
phase: 'post_commit',
|
|
2234
|
+
durableState: 'committed',
|
|
2235
|
+
recoveryDisposition: 'reconcile',
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
this.options.eventSink?.({
|
|
2240
|
+
type: 'compact-boundary',
|
|
2241
|
+
trigger: 'manual',
|
|
2242
|
+
preTokens,
|
|
2243
|
+
uuid: ids.boundaryId,
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
catch (error) {
|
|
2247
|
+
throw classifyCompactionError(error, {
|
|
2248
|
+
trigger: 'manual',
|
|
2249
|
+
phase: 'post_commit',
|
|
2250
|
+
durableState: 'committed',
|
|
2251
|
+
recoveryDisposition: 'reconcile',
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2155
2254
|
return {
|
|
2156
2255
|
summary: compacted.summary,
|
|
2157
2256
|
usage: compacted.usage,
|
|
@@ -2340,7 +2439,7 @@ export class ClaudeSessionService {
|
|
|
2340
2439
|
await this.appendNativeCommand(sessionId, `/permission-mode ${permissionMode}`);
|
|
2341
2440
|
await this.options.interactiveTools?.setMode(sessionId, permissionMode);
|
|
2342
2441
|
}
|
|
2343
|
-
async activateSessionCostTracker(sessionId) {
|
|
2442
|
+
async activateSessionCostTracker(sessionId, persistPrevious = true) {
|
|
2344
2443
|
if (this.activeCostSessionId === sessionId)
|
|
2345
2444
|
return;
|
|
2346
2445
|
const store = this.options.costStateStore;
|
|
@@ -2349,7 +2448,7 @@ export class ClaudeSessionService {
|
|
|
2349
2448
|
// Native state slot is not overwritten before it is read.
|
|
2350
2449
|
const loaded = await store.load(sessionId);
|
|
2351
2450
|
const currentId = this.activeCostSessionId;
|
|
2352
|
-
if (currentId !== undefined) {
|
|
2451
|
+
if (persistPrevious && currentId !== undefined) {
|
|
2353
2452
|
const current = this.sessionCostTrackers.get(currentId);
|
|
2354
2453
|
if (current)
|
|
2355
2454
|
await store.save(current.snapshot());
|
|
@@ -2405,17 +2504,17 @@ export class ClaudeSessionService {
|
|
|
2405
2504
|
});
|
|
2406
2505
|
}
|
|
2407
2506
|
async costSnapshot(sessionId) {
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
const tracker =
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2507
|
+
const priorActiveCostSessionId = this.activeCostSessionId;
|
|
2508
|
+
await this.activateSessionCostTracker(sessionId, false);
|
|
2509
|
+
// Snapshots are observational and must not change which session close()
|
|
2510
|
+
// persists. A subsequent turn/compact activation will perform the normal
|
|
2511
|
+
// target-load-before-current-save transition.
|
|
2512
|
+
this.activeCostSessionId = priorActiveCostSessionId;
|
|
2513
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
2514
|
+
if (!tracker)
|
|
2515
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
2516
|
+
const accounting = this.compactionAccounting(sessionId, this.nativeStore(sessionId));
|
|
2517
|
+
await accounting.recover();
|
|
2419
2518
|
return this.withDetachedSubagentUsage(sessionId, tracker.snapshot());
|
|
2420
2519
|
}
|
|
2421
2520
|
async withDetachedSubagentUsage(sessionId, base) {
|
|
@@ -2524,7 +2623,7 @@ export class ClaudeSessionService {
|
|
|
2524
2623
|
}
|
|
2525
2624
|
const sessionPaths = this.paths(sessionId);
|
|
2526
2625
|
const toolResultDirectory = join(sessionPaths.projectRoot, sessionId, 'tool-results');
|
|
2527
|
-
const runUnderLease = async (persistence) => {
|
|
2626
|
+
const runUnderLease = async (persistence, compactionAccounting) => {
|
|
2528
2627
|
const activeTracker = this.sessionCostTrackers.get(sessionId);
|
|
2529
2628
|
if (!activeTracker) {
|
|
2530
2629
|
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
@@ -2537,7 +2636,7 @@ export class ClaudeSessionService {
|
|
|
2537
2636
|
const view = persistence.view();
|
|
2538
2637
|
return {
|
|
2539
2638
|
entries: [...view.projectionEntries],
|
|
2540
|
-
|
|
2639
|
+
cursor: view.projectionCursor,
|
|
2541
2640
|
};
|
|
2542
2641
|
};
|
|
2543
2642
|
const commitProjection = async (entries) => {
|
|
@@ -2698,7 +2797,7 @@ export class ClaudeSessionService {
|
|
|
2698
2797
|
const flushRecoveryHookOutcomes = async () => {
|
|
2699
2798
|
const entries = [];
|
|
2700
2799
|
let history = projectionSnapshot().entries;
|
|
2701
|
-
let parentUuid =
|
|
2800
|
+
let parentUuid = projectionSnapshot().cursor.lastEntryId;
|
|
2702
2801
|
for (const outcome of pendingRecoveryHookOutcomes) {
|
|
2703
2802
|
const outcomeEntries = createClaudeHookAttachmentEntries(outcome, {
|
|
2704
2803
|
...this.translationContext(sessionId, projectionSnapshot()),
|
|
@@ -2707,9 +2806,8 @@ export class ClaudeSessionService {
|
|
|
2707
2806
|
});
|
|
2708
2807
|
entries.push(...outcomeEntries);
|
|
2709
2808
|
history = [...history, ...outcomeEntries];
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2712
|
-
parentUuid = lastEntry.uuid;
|
|
2809
|
+
const batchCursor = deriveTurnProjectionCursor(outcomeEntries);
|
|
2810
|
+
parentUuid = batchCursor.lastEntryId ?? parentUuid;
|
|
2713
2811
|
}
|
|
2714
2812
|
if (entries.length === 0) {
|
|
2715
2813
|
pendingRecoveryHookOutcomes.length = 0;
|
|
@@ -3380,8 +3478,32 @@ export class ClaudeSessionService {
|
|
|
3380
3478
|
...(budget ? { budget } : {}),
|
|
3381
3479
|
autoCompact: this.options.autoCompact !== false,
|
|
3382
3480
|
memory: {
|
|
3383
|
-
beforeCompact: () =>
|
|
3384
|
-
|
|
3481
|
+
beforeCompact: async () => {
|
|
3482
|
+
try {
|
|
3483
|
+
await turnMemory.beforeCompact();
|
|
3484
|
+
}
|
|
3485
|
+
catch (error) {
|
|
3486
|
+
throw classifyCompactionError(error, {
|
|
3487
|
+
trigger: 'auto',
|
|
3488
|
+
phase: 'validation',
|
|
3489
|
+
durableState: 'not_committed',
|
|
3490
|
+
recoveryDisposition: 'none',
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
},
|
|
3494
|
+
afterCompact: async () => {
|
|
3495
|
+
try {
|
|
3496
|
+
await turnMemory.afterCompact();
|
|
3497
|
+
}
|
|
3498
|
+
catch (error) {
|
|
3499
|
+
throw classifyCompactionError(error, {
|
|
3500
|
+
trigger: 'auto',
|
|
3501
|
+
phase: 'post_commit',
|
|
3502
|
+
durableState: 'committed',
|
|
3503
|
+
recoveryDisposition: 'reconcile',
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
},
|
|
3385
3507
|
},
|
|
3386
3508
|
});
|
|
3387
3509
|
observeModelRequestUsage = ({ usage, messages, tools }) => {
|
|
@@ -3431,14 +3553,29 @@ export class ClaudeSessionService {
|
|
|
3431
3553
|
}).envelope,
|
|
3432
3554
|
propose: async () => {
|
|
3433
3555
|
if (!budget)
|
|
3434
|
-
throw new Error('Context budget is unavailable')
|
|
3435
|
-
|
|
3436
|
-
|
|
3556
|
+
throw classifyCompactionError(new Error('Context budget is unavailable'), {
|
|
3557
|
+
trigger: 'auto',
|
|
3558
|
+
phase: 'validation',
|
|
3559
|
+
durableState: 'not_committed',
|
|
3560
|
+
recoveryDisposition: 'none',
|
|
3561
|
+
});
|
|
3562
|
+
const definitions = await autoValidation(() => contextPreparation.project().envelope.tools);
|
|
3563
|
+
const historyMessages = await autoValidation(activeTurnMessages);
|
|
3437
3564
|
if (historyMessages.length === 0)
|
|
3438
|
-
throw new Error('Cannot compact an empty native transcript')
|
|
3565
|
+
throw classifyCompactionError(new Error('Cannot compact an empty native transcript'), {
|
|
3566
|
+
trigger: 'auto',
|
|
3567
|
+
phase: 'validation',
|
|
3568
|
+
durableState: 'not_committed',
|
|
3569
|
+
recoveryDisposition: 'none',
|
|
3570
|
+
});
|
|
3439
3571
|
if (unresolvedActiveToolCallIds(historyMessages).length > 0)
|
|
3440
|
-
throw new Error('Cannot compact a native transcript with unresolved tool calls')
|
|
3441
|
-
|
|
3572
|
+
throw classifyCompactionError(new Error('Cannot compact a native transcript with unresolved tool calls'), {
|
|
3573
|
+
trigger: 'auto',
|
|
3574
|
+
phase: 'validation',
|
|
3575
|
+
durableState: 'not_committed',
|
|
3576
|
+
recoveryDisposition: 'none',
|
|
3577
|
+
});
|
|
3578
|
+
const irreducibleMessages = await autoValidation(() => contextPreparation.project({
|
|
3442
3579
|
includeHistory: false,
|
|
3443
3580
|
includeMemory: false,
|
|
3444
3581
|
pendingMessages: [
|
|
@@ -3448,7 +3585,7 @@ export class ClaudeSessionService {
|
|
|
3448
3585
|
content,
|
|
3449
3586
|
})),
|
|
3450
3587
|
],
|
|
3451
|
-
}).envelope.messages;
|
|
3588
|
+
}).envelope.messages);
|
|
3452
3589
|
let compactableMessages = historyMessages;
|
|
3453
3590
|
let preservedMessages = [];
|
|
3454
3591
|
let compactionLogicalParentId;
|
|
@@ -3467,7 +3604,12 @@ export class ClaudeSessionService {
|
|
|
3467
3604
|
}
|
|
3468
3605
|
}
|
|
3469
3606
|
if (found < 0)
|
|
3470
|
-
throw new Error('Native automatic compaction could not match the current-turn suffix')
|
|
3607
|
+
throw classifyCompactionError(new Error('Native automatic compaction could not match the current-turn suffix'), {
|
|
3608
|
+
trigger: 'auto',
|
|
3609
|
+
phase: 'validation',
|
|
3610
|
+
durableState: 'not_committed',
|
|
3611
|
+
recoveryDisposition: 'none',
|
|
3612
|
+
});
|
|
3471
3613
|
matchedIndexes.push(found);
|
|
3472
3614
|
searchFrom = found;
|
|
3473
3615
|
}
|
|
@@ -3493,7 +3635,7 @@ export class ClaudeSessionService {
|
|
|
3493
3635
|
}
|
|
3494
3636
|
}
|
|
3495
3637
|
const memorySelection = sessionMemory
|
|
3496
|
-
? await this.selectMemoryPreservedCompact(sessionId, persistence.view().projectionEntries)
|
|
3638
|
+
? await autoValidation(() => this.selectMemoryPreservedCompact(sessionId, persistence.view().projectionEntries))
|
|
3497
3639
|
: null;
|
|
3498
3640
|
if (memorySelection) {
|
|
3499
3641
|
compactableMessages = [
|
|
@@ -3521,47 +3663,102 @@ export class ClaudeSessionService {
|
|
|
3521
3663
|
message.content === anchor.content))?.id;
|
|
3522
3664
|
}
|
|
3523
3665
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
messages: [
|
|
3536
|
-
...compactableMessages,
|
|
3537
|
-
...successfulHookOutput(await this.runAdvisoryHook(sessionId, 'PreCompact', {
|
|
3538
|
-
trigger: 'auto',
|
|
3539
|
-
custom_instructions: null,
|
|
3540
|
-
}, 'auto', signal)).map((content) => ({
|
|
3666
|
+
await autoValidation(() => {
|
|
3667
|
+
this.options.eventSink?.({
|
|
3668
|
+
type: 'state',
|
|
3669
|
+
state: 'compacting',
|
|
3670
|
+
});
|
|
3671
|
+
});
|
|
3672
|
+
const preTokens = await autoValidation(() => estimateModelRequestTokens(compactableMessages));
|
|
3673
|
+
const compactEnvelope = await autoValidation(() => {
|
|
3674
|
+
const envelope = budget.evaluate([
|
|
3675
|
+
...irreducibleMessages,
|
|
3676
|
+
{
|
|
3541
3677
|
role: 'user',
|
|
3542
|
-
content:
|
|
3543
|
-
}
|
|
3544
|
-
],
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
contextWindowTokens: budget.contextWindowTokens,
|
|
3548
|
-
...(signal ? { signal } : {}),
|
|
3678
|
+
content: formatClaudeCompactSummary(''),
|
|
3679
|
+
},
|
|
3680
|
+
], definitions);
|
|
3681
|
+
budget.assertFits(envelope);
|
|
3682
|
+
return envelope;
|
|
3549
3683
|
});
|
|
3550
|
-
|
|
3684
|
+
let compacted;
|
|
3685
|
+
try {
|
|
3686
|
+
compacted = await (this.options.compactor ?? new ModelCompactor(provider)).compact({
|
|
3687
|
+
messages: [
|
|
3688
|
+
...compactableMessages,
|
|
3689
|
+
...successfulHookOutput(await this.runAdvisoryHook(sessionId, 'PreCompact', {
|
|
3690
|
+
trigger: 'auto',
|
|
3691
|
+
custom_instructions: null,
|
|
3692
|
+
}, 'auto', signal)).map((content) => ({
|
|
3693
|
+
role: 'user',
|
|
3694
|
+
content: `Additional summarization context: ${content}`,
|
|
3695
|
+
})),
|
|
3696
|
+
],
|
|
3697
|
+
targetTokens: Math.min(8192, Math.max(1, compactEnvelope.availableTokens -
|
|
3698
|
+
compactEnvelope.estimatedTokens)),
|
|
3699
|
+
contextWindowTokens: budget.contextWindowTokens,
|
|
3700
|
+
...(signal ? { signal } : {}),
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
catch (error) {
|
|
3704
|
+
throw classifyCompactionError(error, {
|
|
3705
|
+
trigger: 'auto',
|
|
3706
|
+
phase: 'generation',
|
|
3707
|
+
durableState: 'not_committed',
|
|
3708
|
+
recoveryDisposition: 'none',
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
let durationMs;
|
|
3712
|
+
let durationWithoutRetriesMs;
|
|
3713
|
+
try {
|
|
3714
|
+
const durations = requireCompactionDurations(compacted);
|
|
3715
|
+
durationMs = durations.durationMs;
|
|
3716
|
+
durationWithoutRetriesMs = durations.durationWithoutRetriesMs;
|
|
3717
|
+
}
|
|
3718
|
+
catch (error) {
|
|
3719
|
+
throw classifyCompactionError(error, {
|
|
3720
|
+
trigger: 'auto',
|
|
3721
|
+
phase: 'validation',
|
|
3722
|
+
durableState: 'not_committed',
|
|
3723
|
+
recoveryDisposition: 'none',
|
|
3724
|
+
});
|
|
3725
|
+
}
|
|
3551
3726
|
if (signal?.aborted)
|
|
3552
3727
|
throw new AgentRunCancelledError();
|
|
3553
|
-
const postTokens = estimateModelRequestTokens([
|
|
3728
|
+
const postTokens = await autoValidation(() => estimateModelRequestTokens([
|
|
3554
3729
|
{ role: 'user', content: compacted.summary },
|
|
3555
3730
|
...preservedMessages,
|
|
3556
|
-
]);
|
|
3731
|
+
]));
|
|
3557
3732
|
const compactModel = compacted.model !== undefined && compacted.model.trim() !== ''
|
|
3558
3733
|
? compacted.model
|
|
3559
3734
|
: provider.model;
|
|
3560
|
-
|
|
3735
|
+
if (compacted.summary.trim().length === 0)
|
|
3736
|
+
throw classifyCompactionError(new Error('Native compact summary must not be blank'), {
|
|
3737
|
+
trigger: 'auto',
|
|
3738
|
+
phase: 'validation',
|
|
3739
|
+
durableState: 'not_committed',
|
|
3740
|
+
recoveryDisposition: 'none',
|
|
3741
|
+
});
|
|
3742
|
+
const preparedCompaction = await autoValidation(() => turnAccounting.prepareCompaction({
|
|
3561
3743
|
usage: compacted.usage,
|
|
3562
|
-
...(
|
|
3744
|
+
...(!hasNonZeroUsage(compacted.usage) ||
|
|
3745
|
+
compactModel === undefined
|
|
3746
|
+
? {}
|
|
3747
|
+
: { model: compactModel }),
|
|
3563
3748
|
durationApiMs: durationMs,
|
|
3564
3749
|
durationApiWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3750
|
+
}));
|
|
3751
|
+
const preparedTransaction = await compactionAccounting.prepare({
|
|
3752
|
+
trigger: 'auto',
|
|
3753
|
+
metric: {
|
|
3754
|
+
usage: compacted.usage,
|
|
3755
|
+
...(!hasNonZeroUsage(compacted.usage) ||
|
|
3756
|
+
compactModel === undefined
|
|
3757
|
+
? {}
|
|
3758
|
+
: { model: compactModel }),
|
|
3759
|
+
durationApiMs: durationMs,
|
|
3760
|
+
durationApiWithoutRetriesMs: durationWithoutRetriesMs,
|
|
3761
|
+
},
|
|
3565
3762
|
});
|
|
3566
3763
|
const summaryMessage = {
|
|
3567
3764
|
role: 'user',
|
|
@@ -3597,54 +3794,138 @@ export class ClaudeSessionService {
|
|
|
3597
3794
|
commit: async () => {
|
|
3598
3795
|
if (signal?.aborted)
|
|
3599
3796
|
throw new AgentRunCancelledError();
|
|
3600
|
-
const committed = await replacement.commit(() =>
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3797
|
+
const committed = await replacement.commit(async () => {
|
|
3798
|
+
try {
|
|
3799
|
+
return await persistence.commit({
|
|
3800
|
+
kind: 'compaction',
|
|
3801
|
+
input: {
|
|
3802
|
+
summary: compacted.summary,
|
|
3803
|
+
trigger: 'auto',
|
|
3804
|
+
preTokens,
|
|
3805
|
+
postTokens,
|
|
3806
|
+
durationMs,
|
|
3807
|
+
boundaryId: preparedTransaction.boundaryId,
|
|
3808
|
+
summaryId: preparedTransaction.summaryId,
|
|
3809
|
+
preservedMessages: replayMessages,
|
|
3810
|
+
...(compactionLogicalParentId === undefined
|
|
3811
|
+
? {}
|
|
3812
|
+
: { logicalParentId: compactionLogicalParentId }),
|
|
3813
|
+
...(memorySelection
|
|
3814
|
+
? {
|
|
3815
|
+
direction: 'from',
|
|
3816
|
+
messagesSummarized: compactableMessages.length,
|
|
3817
|
+
preservePrefix: false,
|
|
3818
|
+
}
|
|
3819
|
+
: {}),
|
|
3820
|
+
},
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
catch (error) {
|
|
3824
|
+
throw classifyCompactionError(error, {
|
|
3825
|
+
trigger: 'auto',
|
|
3826
|
+
phase: 'transcript_commit',
|
|
3827
|
+
durableState: 'indeterminate',
|
|
3828
|
+
recoveryDisposition: 'reconcile',
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
});
|
|
3621
3832
|
const ids = committed.value;
|
|
3622
3833
|
if (ids.kind !== 'compaction')
|
|
3623
|
-
throw new Error('Turn persistence returned an invalid compaction receipt')
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3834
|
+
throw classifyCompactionError(new Error('Turn persistence returned an invalid compaction receipt'), {
|
|
3835
|
+
trigger: 'auto',
|
|
3836
|
+
phase: 'transcript_commit',
|
|
3837
|
+
durableState: 'indeterminate',
|
|
3838
|
+
recoveryDisposition: 'reconcile',
|
|
3839
|
+
});
|
|
3840
|
+
try {
|
|
3841
|
+
preparedCompaction.commit();
|
|
3842
|
+
await preparedTransaction.commit({
|
|
3843
|
+
kind: 'compaction',
|
|
3844
|
+
boundaryId: ids.boundaryId,
|
|
3845
|
+
summaryId: ids.summaryId,
|
|
3846
|
+
});
|
|
3847
|
+
}
|
|
3848
|
+
catch (error) {
|
|
3849
|
+
throw classifyCompactionError(error, {
|
|
3850
|
+
trigger: 'auto',
|
|
3851
|
+
phase: 'accounting_commit',
|
|
3852
|
+
durableState: 'committed',
|
|
3853
|
+
recoveryDisposition: 'reconcile',
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
try {
|
|
3857
|
+
await this.runAdvisoryHook(sessionId, 'PostCompact', { trigger: 'auto', compact_summary: compacted.summary }, 'auto', signal);
|
|
3858
|
+
}
|
|
3859
|
+
catch (error) {
|
|
3860
|
+
throw classifyCompactionError(error, {
|
|
3861
|
+
trigger: 'auto',
|
|
3862
|
+
phase: 'post_commit',
|
|
3863
|
+
durableState: 'committed',
|
|
3864
|
+
recoveryDisposition: 'reconcile',
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
try {
|
|
3868
|
+
if (this.options.hooks) {
|
|
3869
|
+
const outcome = await this.hookLifecycle.refresh(sessionId, hookSession, signal);
|
|
3870
|
+
if (outcome)
|
|
3871
|
+
await recordHookOutcome(outcome);
|
|
3872
|
+
}
|
|
3873
|
+
this.options.contextAssembler?.invalidate?.({
|
|
3874
|
+
lifecycleId: sessionId,
|
|
3875
|
+
reason: 'compact',
|
|
3876
|
+
});
|
|
3877
|
+
await refreshRuntimeContext?.();
|
|
3878
|
+
this.options.eventSink?.({
|
|
3879
|
+
type: 'compact-boundary',
|
|
3880
|
+
trigger: 'auto',
|
|
3881
|
+
preTokens,
|
|
3882
|
+
uuid: ids.boundaryId,
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
catch (error) {
|
|
3886
|
+
throw classifyCompactionError(error, {
|
|
3887
|
+
trigger: 'auto',
|
|
3888
|
+
phase: 'post_commit',
|
|
3889
|
+
durableState: 'committed',
|
|
3890
|
+
recoveryDisposition: 'reconcile',
|
|
3891
|
+
});
|
|
3629
3892
|
}
|
|
3630
|
-
this.options.contextAssembler?.invalidate?.({
|
|
3631
|
-
lifecycleId: sessionId,
|
|
3632
|
-
reason: 'compact',
|
|
3633
|
-
});
|
|
3634
|
-
await refreshRuntimeContext?.();
|
|
3635
|
-
this.options.eventSink?.({
|
|
3636
|
-
type: 'compact-boundary',
|
|
3637
|
-
trigger: 'auto',
|
|
3638
|
-
preTokens,
|
|
3639
|
-
uuid: ids.boundaryId,
|
|
3640
|
-
});
|
|
3641
|
-
preparedCompaction.commit();
|
|
3642
3893
|
},
|
|
3643
3894
|
};
|
|
3644
3895
|
},
|
|
3645
3896
|
});
|
|
3897
|
+
const autoValidation = async (operation) => {
|
|
3898
|
+
try {
|
|
3899
|
+
return await operation();
|
|
3900
|
+
}
|
|
3901
|
+
catch (error) {
|
|
3902
|
+
if (error instanceof StaleContextGenerationError)
|
|
3903
|
+
throw error;
|
|
3904
|
+
throw classifyCompactionError(error, {
|
|
3905
|
+
trigger: 'auto',
|
|
3906
|
+
phase: 'validation',
|
|
3907
|
+
durableState: 'not_committed',
|
|
3908
|
+
recoveryDisposition: 'none',
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
3912
|
+
const prepareContext = async (port) => {
|
|
3913
|
+
try {
|
|
3914
|
+
await contextEngine.prepare(port, signal);
|
|
3915
|
+
}
|
|
3916
|
+
catch (error) {
|
|
3917
|
+
if (error instanceof StaleContextGenerationError)
|
|
3918
|
+
throw error;
|
|
3919
|
+
throw classifyCompactionError(error, {
|
|
3920
|
+
trigger: 'auto',
|
|
3921
|
+
phase: 'validation',
|
|
3922
|
+
durableState: 'not_committed',
|
|
3923
|
+
recoveryDisposition: 'none',
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
};
|
|
3646
3927
|
if (shellCommand === undefined) {
|
|
3647
|
-
await
|
|
3928
|
+
await prepareContext(contextTransitionPort(pendingUserMessages));
|
|
3648
3929
|
}
|
|
3649
3930
|
for (const [index, message] of expandedMessages.entries()) {
|
|
3650
3931
|
if (shellCommand !== undefined)
|
|
@@ -3826,7 +4107,7 @@ export class ClaudeSessionService {
|
|
|
3826
4107
|
};
|
|
3827
4108
|
}
|
|
3828
4109
|
if (shellCommand === undefined && budget) {
|
|
3829
|
-
await
|
|
4110
|
+
await prepareContext(contextTransitionPort([], currentTurnUserMessages ?? []));
|
|
3830
4111
|
const projection = contextPreparation.project({
|
|
3831
4112
|
includeMemory: false,
|
|
3832
4113
|
});
|
|
@@ -3866,7 +4147,7 @@ export class ClaudeSessionService {
|
|
|
3866
4147
|
}
|
|
3867
4148
|
await refreshRuntimeContext?.();
|
|
3868
4149
|
if (shellCommand === undefined) {
|
|
3869
|
-
await
|
|
4150
|
+
await prepareContext(contextTransitionPort([], currentTurnUserMessages ?? []));
|
|
3870
4151
|
}
|
|
3871
4152
|
const projection = contextPreparation.project();
|
|
3872
4153
|
runtimeRequest.stableSystemMessageCount =
|
|
@@ -4002,7 +4283,20 @@ export class ClaudeSessionService {
|
|
|
4002
4283
|
catch (error) {
|
|
4003
4284
|
if (!budget || !isPromptTooLongError(error))
|
|
4004
4285
|
throw error;
|
|
4005
|
-
|
|
4286
|
+
let recovery;
|
|
4287
|
+
try {
|
|
4288
|
+
recovery = await contextEngine.recover(error, contextTransitionPort([], currentTurnUserMessages ?? []), signal);
|
|
4289
|
+
}
|
|
4290
|
+
catch (recoveryError) {
|
|
4291
|
+
if (recoveryError instanceof StaleContextGenerationError)
|
|
4292
|
+
throw recoveryError;
|
|
4293
|
+
throw classifyCompactionError(recoveryError, {
|
|
4294
|
+
trigger: 'auto',
|
|
4295
|
+
phase: 'validation',
|
|
4296
|
+
durableState: 'not_committed',
|
|
4297
|
+
recoveryDisposition: 'none',
|
|
4298
|
+
});
|
|
4299
|
+
}
|
|
4006
4300
|
if (recovery.kind !== 'retry') {
|
|
4007
4301
|
surfaceExhaustedRecovery(error);
|
|
4008
4302
|
throw error;
|
|
@@ -4141,6 +4435,8 @@ export class ClaudeSessionService {
|
|
|
4141
4435
|
sessionId,
|
|
4142
4436
|
store: nativeStore,
|
|
4143
4437
|
});
|
|
4438
|
+
const compactionAccounting = this.compactionAccounting(sessionId, nativeStore);
|
|
4439
|
+
await compactionAccounting.recover();
|
|
4144
4440
|
const activationKind = requireExisting
|
|
4145
4441
|
? resumeSessionAt === undefined
|
|
4146
4442
|
? { kind: 'resume' }
|
|
@@ -4148,7 +4444,7 @@ export class ClaudeSessionService {
|
|
|
4148
4444
|
: { kind: 'start' };
|
|
4149
4445
|
result = await nativeTranscript.withLease(activationKind, async (nativeLease) => {
|
|
4150
4446
|
const persistence = new TurnPersistence({ native: nativeLease });
|
|
4151
|
-
return runUnderLease(persistence);
|
|
4447
|
+
return runUnderLease(persistence, compactionAccounting);
|
|
4152
4448
|
});
|
|
4153
4449
|
}
|
|
4154
4450
|
return result;
|
|
@@ -4182,7 +4478,7 @@ export class ClaudeSessionService {
|
|
|
4182
4478
|
translationContext(sessionId, snapshot) {
|
|
4183
4479
|
return {
|
|
4184
4480
|
sessionId,
|
|
4185
|
-
parentUuid:
|
|
4481
|
+
parentUuid: snapshot.cursor.lastEntryId,
|
|
4186
4482
|
cwd: this.activeCwd(),
|
|
4187
4483
|
claudeVersion: this.options.claudeVersion,
|
|
4188
4484
|
gitBranch: null,
|
|
@@ -4612,6 +4908,38 @@ export class ClaudeSessionService {
|
|
|
4612
4908
|
}
|
|
4613
4909
|
return this.store(sessionId);
|
|
4614
4910
|
}
|
|
4911
|
+
compactionAccounting(sessionId, nativeStore) {
|
|
4912
|
+
const existing = this.compactionAccountings.get(sessionId);
|
|
4913
|
+
if (existing)
|
|
4914
|
+
return existing;
|
|
4915
|
+
const tracker = this.sessionCostTrackers.get(sessionId);
|
|
4916
|
+
if (!tracker)
|
|
4917
|
+
throw new Error(`Session cost tracker is not active for session ${sessionId}`);
|
|
4918
|
+
const sessionPaths = this.paths(sessionId);
|
|
4919
|
+
const accounting = new CompactionAccounting({
|
|
4920
|
+
sessionId,
|
|
4921
|
+
tracker,
|
|
4922
|
+
...(this.options.pricing ? { pricing: this.options.pricing } : {}),
|
|
4923
|
+
...(this.options.sessionPersistence === false
|
|
4924
|
+
? {}
|
|
4925
|
+
: {
|
|
4926
|
+
receiptStore: new NativeCompactionReceiptFileStore({
|
|
4927
|
+
sidecarRoot: sessionPaths.praxisRoot,
|
|
4928
|
+
}),
|
|
4929
|
+
}),
|
|
4930
|
+
...(this.options.costStateStore
|
|
4931
|
+
? { costStateStore: this.options.costStateStore }
|
|
4932
|
+
: {}),
|
|
4933
|
+
readTranscript: async () => {
|
|
4934
|
+
const loaded = await nativeStore.withLease((lease) => lease.load());
|
|
4935
|
+
if (loaded.status === 'conflict')
|
|
4936
|
+
throw new Error(`native transcript lease conflict: ${loaded.reason}`);
|
|
4937
|
+
return loaded.value.records.map((record) => record.event);
|
|
4938
|
+
},
|
|
4939
|
+
});
|
|
4940
|
+
this.compactionAccountings.set(sessionId, accounting);
|
|
4941
|
+
return accounting;
|
|
4942
|
+
}
|
|
4615
4943
|
assertSessionPersistence() {
|
|
4616
4944
|
if (this.options.sessionPersistence === false) {
|
|
4617
4945
|
throw new Error('Session persistence is disabled');
|
|
@@ -4925,8 +5253,5 @@ export class ClaudeSessionService {
|
|
|
4925
5253
|
}
|
|
4926
5254
|
return factory(base, operations, sessionId, enabledTools);
|
|
4927
5255
|
}
|
|
4928
|
-
logicalTailUuid(tail) {
|
|
4929
|
-
return tail.branchParentId ?? tail.lastEventId;
|
|
4930
|
-
}
|
|
4931
5256
|
}
|
|
4932
5257
|
//# sourceMappingURL=session-service.js.map
|