@myagentroam/agent 0.10.29 → 0.10.31

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.
Files changed (34) hide show
  1. package/dist/agent-core/extensions/session-input.d.ts +2 -0
  2. package/dist/agent-core/extensions/session-input.js +21 -3
  3. package/dist/agent-core/model/contracts.d.ts +1 -0
  4. package/dist/agent-core/model/openai-responses.js +6 -2
  5. package/dist/agent-core/runtime/compaction/coordinator.d.ts +15 -0
  6. package/dist/agent-core/runtime/compaction/coordinator.js +88 -0
  7. package/dist/agent-core/runtime/compaction/requests.d.ts +16 -0
  8. package/dist/agent-core/runtime/compaction/requests.js +114 -0
  9. package/dist/agent-core/runtime/compaction/retained-context.d.ts +6 -0
  10. package/dist/agent-core/runtime/compaction/retained-context.js +52 -0
  11. package/dist/agent-core/runtime/compaction/smart.d.ts +14 -0
  12. package/dist/agent-core/runtime/compaction/smart.js +59 -0
  13. package/dist/agent-core/runtime/context/restore.d.ts +1 -0
  14. package/dist/agent-core/runtime/context/restore.js +14 -4
  15. package/dist/agent-core/runtime/context/usage.js +3 -1
  16. package/dist/agent-core/runtime/event-projection.js +1 -1
  17. package/dist/agent-core/runtime/events.d.ts +21 -0
  18. package/dist/agent-core/runtime/execution-policy.d.ts +2 -0
  19. package/dist/agent-core/runtime/execution-policy.js +2 -0
  20. package/dist/agent-core/runtime/execution.d.ts +1 -0
  21. package/dist/agent-core/runtime/execution.js +1 -0
  22. package/dist/agent-core/sdk/extension-session.js +4 -2
  23. package/dist/agent-core/sdk/session.d.ts +2 -0
  24. package/dist/agent-core/sdk/session.js +350 -192
  25. package/dist/agent-core/sdk/types.d.ts +4 -1
  26. package/dist/agent-core/session/catalog.js +2 -2
  27. package/dist/agent-core/session/jsonl-store.d.ts +2 -0
  28. package/dist/agent-core/session/jsonl-store.js +9 -1
  29. package/dist/agent-core/tools/agent/smart-compact.d.ts +9 -0
  30. package/dist/agent-core/tools/agent/smart-compact.js +20 -0
  31. package/dist/agent-core/tools/registry.js +4 -1
  32. package/dist/agent-core/tools/runtime-context.d.ts +1 -0
  33. package/dist/index.d.ts +1 -1
  34. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { isProviderContextOverflow, recoverModelStream } from '../model/recovery
2
2
  import { estimatedRequestTokens } from '../runtime/context/request-budget.js';
3
3
  import { restoreMessages } from '../runtime/context/restore.js';
4
4
  import { lastConfigurationEffort, latestModelUsage, reasoningEffortBaseline, shouldUseNativeCompaction } from '../runtime/context/usage.js';
5
- import { splitContextualPrompt, projectCompactPromptMessages } from '../runtime/context/contextual-blocks.js';
5
+ import { splitContextualPrompt } from '../runtime/context/contextual-blocks.js';
6
6
  import { contextualInstructionInsertIndex, insertContextualWorldState, latestContextualWorldStateGeneration, parsePersistedContextualWorldState, persistContextualWorldState, resetContextualWorldState, updateContextualWorldState, updatePromptWorldState } from '../runtime/context/world-state.js';
7
7
  import { eventForPersistence, publicModelUsage } from '../runtime/event-projection.js';
8
8
  import { ExecutionLoop } from '../runtime/execution.js';
@@ -32,12 +32,16 @@ import { retainedSessionResourcesSnapshot } from '../prompts/resources.js';
32
32
  import { childSubagentInstruction } from '../prompts/subagent.js';
33
33
  import { AsyncQueue } from '../runtime/async-queue.js';
34
34
  import { CompactionOperation } from '../runtime/compaction/operation.js';
35
+ import { CompactCoordinator } from '../runtime/compaction/coordinator.js';
36
+ import { CompactRequests } from '../runtime/compaction/requests.js';
37
+ import { compactSnapshotAnchor } from '../runtime/compaction/retained-context.js';
35
38
  import { DEFAULT_COMPACTION_FOCUS, buildCompactedHistory, buildCompactionInput, buildCompactionMessages, buildNativeCompactionMessages, compactedHistoryMessages } from '../runtime/compaction/history.js';
36
39
  import { currentLocalTimeContext, environmentContextUpdate, restoreEnvironmentState } from '../runtime/context/environment.js';
37
40
  import { ContextGcRuntime } from '../runtime/context/gc-runtime.js';
38
41
  import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
39
42
  import { loadAgentInstructions, renderAgentInstructions } from '../runtime/context/instructions.js';
40
43
  import { normalizeMessagesForModel } from '../runtime/context/normalize.js';
44
+ import { latestSmartContextTokens, projectSmartCompactionMessage, requestSmartCompaction } from '../runtime/compaction/smart.js';
41
45
  import { compactSummaryFromCheckpoint, inputMetadataFromRecord } from '../session/jsonl-store.js';
42
46
  import { SubagentSessionController } from '../subagent/session-controller.js';
43
47
  import { agentStartToolDefinitionWithModels } from '../tools/agent/agent-start.js';
@@ -197,6 +201,11 @@ export function createSessionOpener(services) {
197
201
  executionModels.get(input.modelId ?? executionDefaultModelId)?.conversationModel === false)
198
202
  throw new MarAgentError('MAR_AGENT_MODEL_DISABLED', 'The selected model is not a conversation model.');
199
203
  const executionId = randomUUID(), turnId = internal.turnId ?? randomUUID(), queue = new AsyncQueue(), controller = new AbortController();
204
+ const compactCoordinator = new CompactCoordinator();
205
+ const messageSources = new WeakMap();
206
+ let liveModelMessages = [];
207
+ const compactRequests = new CompactRequests(executionId, () => compactCoordinator.assertNotReentrant());
208
+ sessionRuntime.compactRequests = compactRequests;
200
209
  const permissionPolicy = new ExecutionToolPermissions(mode === 'compact' ? 'full-access' : resolvePermissionMode(input.permissionMode), { sessionId, executionId }, host.requestToolApproval
201
210
  ? {
202
211
  request: (request, signal) => host.requestToolApproval(request, signal),
@@ -265,7 +274,6 @@ export function createSessionOpener(services) {
265
274
  acceptParent(message);
266
275
  let acceptsMailbox = sessionSource.type === 'subagent' && mode !== 'compact';
267
276
  let sequence = 0;
268
- let manualCompactionStarted = false;
269
277
  let emitTail = Promise.resolve();
270
278
  let releaseSession;
271
279
  let exhaustedModelAttempt;
@@ -284,14 +292,21 @@ export function createSessionOpener(services) {
284
292
  const operation = emitTail.then(async () => {
285
293
  assertLive?.();
286
294
  const persistent = eventForPersistence(full);
287
- if (persistent !== undefined && releaseSession !== undefined)
288
- await store.append(sessionId, {
295
+ if (persistent !== undefined && releaseSession !== undefined) {
296
+ const stored = await store.append(sessionId, {
289
297
  type: full.type,
290
298
  payload: persistent,
291
299
  executionId,
292
300
  turnId
293
301
  });
302
+ if (full.type === 'message.completed') {
303
+ const message = liveModelMessages.findLast((item) => item.role === 'assistant' && item.content === full.text);
304
+ if (message)
305
+ messageSources.set(message, stored);
306
+ }
307
+ }
294
308
  queue.push(full);
309
+ compactRequests.publish(full);
295
310
  if (internal.publishHostEvents !== false)
296
311
  await host.emit(full);
297
312
  });
@@ -337,7 +352,6 @@ export function createSessionOpener(services) {
337
352
  const result = withToolPermissions(permissionPolicy, async () => {
338
353
  let finalAnswer = '';
339
354
  let executionLoop;
340
- let manualCompaction;
341
355
  let executionToolSources;
342
356
  let selectedTurn;
343
357
  const contextGc = new ContextGcRuntime();
@@ -392,9 +406,11 @@ export function createSessionOpener(services) {
392
406
  }
393
407
  const compactMayContainAgentInstructions = contextRecords.some((record) => compactSummaryFromCheckpoint(record) !== undefined);
394
408
  const restored = await restoreMessages(store, sessionId, contextRecords, {
409
+ messageSources,
395
410
  includeImages: mode !== 'compact'
396
411
  });
397
412
  const messages = restored.messages;
413
+ liveModelMessages = messages;
398
414
  const restoredRecordIds = new Set(contextRecords.map((record) => record.recordId));
399
415
  for (const pending of pendingAppends)
400
416
  if (!restoredRecordIds.has(pending.recordId))
@@ -402,13 +418,25 @@ export function createSessionOpener(services) {
402
418
  pendingAppends.length = 0;
403
419
  immediateTarget = messages;
404
420
  let recordedEnvironment = restored.environmentState;
421
+ let smartInput;
405
422
  let compactContext;
423
+ let startCompact;
406
424
  const writeInputRecord = async (content, type, metadata, call, committed) => {
407
425
  call.signal.throwIfAborted();
408
426
  if (content.images?.length && !selectedModel.inputCapabilities.includes('IMAGE'))
409
427
  throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'Selected model does not support image input.');
410
428
  await withRecordProjection(async () => {
411
429
  call.signal.throwIfAborted();
430
+ const smartPercent = smartContextTokens === undefined
431
+ ? undefined
432
+ : (smartContextTokens * 100) / selectedModel.contextWindowTokens;
433
+ const smartCompaction = mode !== 'compact' &&
434
+ sessionSource.type === 'user' &&
435
+ type === 'turn.user' &&
436
+ metadata.inputSource === undefined &&
437
+ smartPercent !== undefined &&
438
+ smartPercent >= AGENT_EXECUTION_POLICY.smartCompactionPercent &&
439
+ smartPercent < AGENT_EXECUTION_POLICY.automaticCompactionPercent;
412
440
  const images = content.images?.length
413
441
  ? await store.persistInputImages(sessionId, content.images)
414
442
  : [];
@@ -417,6 +445,7 @@ export function createSessionOpener(services) {
417
445
  payload: {
418
446
  ...metadata,
419
447
  prompt: content.text,
448
+ ...(smartCompaction ? { smartCompaction: true } : {}),
420
449
  ...(content.modelText === undefined ? {} : { modelText: content.modelText }),
421
450
  ...(content.context === undefined ? {} : { context: content.context }),
422
451
  ...(content.modelContext === undefined
@@ -428,6 +457,11 @@ export function createSessionOpener(services) {
428
457
  turnId
429
458
  }, { signal: call.signal });
430
459
  const inputMetadata = inputMetadataFromRecord(record);
460
+ smartInput = {
461
+ recordId: record.recordId,
462
+ eligible: smartCompaction,
463
+ requested: false
464
+ };
431
465
  compactionUserMessages.push({
432
466
  content: content.modelText ?? content.text,
433
467
  metadata: inputMetadata
@@ -439,8 +473,14 @@ export function createSessionOpener(services) {
439
473
  };
440
474
  const appendMailboxMessages = async () => inputs.drain(async (entry, request, call) => {
441
475
  call.signal.throwIfAborted();
442
- if (request.kind === 'compact')
476
+ if (request.kind === 'compact') {
477
+ if (request.input.async) {
478
+ const flight = startCompact(request.input);
479
+ entry.start();
480
+ return { changed: await flight.result, executionId };
481
+ }
443
482
  return { changed: await compactContext(request.input), executionId };
483
+ }
444
484
  if (request.kind === 'append') {
445
485
  const result = await appendExtensionMessage(entry, request, call);
446
486
  return { ...result, executionId };
@@ -527,6 +567,7 @@ export function createSessionOpener(services) {
527
567
  const selected = createModelTurnSession(selectedAdapter);
528
568
  selectedTurn = selected;
529
569
  let lastServerUsage = latestModelUsage(contextRecords, selectedModel.id);
570
+ let smartContextTokens = latestSmartContextTokens(contextRecords, selectedModel.id, selectedModel.contextWindowTokens);
530
571
  const compactUserTokenLimit = Math.min(20_000, Math.floor(selectedModel.contextWindowTokens / 8));
531
572
  const reasoningEffort = input.reasoningEffort ?? selectedModel.defaultReasoningEffort;
532
573
  const useEffortUpdates = usesResponsesConfigurationUpdates(selectedModel);
@@ -765,14 +806,31 @@ export function createSessionOpener(services) {
765
806
  contextualWorldState = next;
766
807
  sessionRuntime.contextualWorldState = next;
767
808
  };
768
- const resetCompactWorldState = async (generation) => {
809
+ const resetCompactWorldState = async (generation, committed) => {
769
810
  agentInstructionInsertIndex = contextualInstructionInsertIndex(messages);
770
811
  const next = resetContextualWorldState(generation, agentInstructionInsertIndex, {
771
812
  instructionSnapshot: currentInstructionSnapshot,
772
813
  instructionContent: renderAgentInstructions(instructionEntries, true),
773
- resourceSnapshot: retainedSessionResourcesSnapshot(await loadRetainedSessionResources())
814
+ resourceSnapshot: committed
815
+ ? committed.resources
816
+ : retainedSessionResourcesSnapshot(await loadRetainedSessionResources())
774
817
  });
775
- await commitWorldState(next);
818
+ if (!committed)
819
+ await commitWorldState(next);
820
+ else {
821
+ // The checkpoint is authoritative; its sidecar is recoverable on restart.
822
+ contextualWorldState = next;
823
+ sessionRuntime.contextualWorldState = next;
824
+ try {
825
+ await persistContextualWorldState(store, sessionId, next);
826
+ }
827
+ catch {
828
+ coreExtensions.diagnose({
829
+ code: 'MAR_AGENT_COMPACT_SIDECAR_PERSIST_FAILED',
830
+ sessionId
831
+ });
832
+ }
833
+ }
776
834
  };
777
835
  // A missing sidecar is rebuilt only for the latest Compact, never for a GC boundary.
778
836
  if (!contextualWorldState.initialized &&
@@ -848,8 +906,24 @@ export function createSessionOpener(services) {
848
906
  const parentFrame = subagents === undefined || mode === 'compact'
849
907
  ? undefined
850
908
  : subagents.captureParentFrame(executionId);
909
+ const roundSmartInput = smartInput;
851
910
  const toolContext = {
852
911
  host: host,
912
+ requestSmartCompact: () => {
913
+ controller.signal.throwIfAborted();
914
+ requestSmartCompaction(roundSmartInput === smartInput ? roundSmartInput : undefined, () => {
915
+ // The Session owns this background operation, not the short-lived tool callback.
916
+ services.extensions.operations.enter({
917
+ sessionId,
918
+ sessionEpoch: coreExtensions.epoch,
919
+ executionId,
920
+ turnId,
921
+ actor: 'sdk',
922
+ entry: 'native-api',
923
+ signal: controller.signal
924
+ }, () => startCompact({ async: true }));
925
+ });
926
+ },
853
927
  permissionPolicy,
854
928
  writeToolResultResource: (text) => store.writeToolResultResource(sessionId, text),
855
929
  todo,
@@ -1062,8 +1136,6 @@ export function createSessionOpener(services) {
1062
1136
  const initialSystemPrompt = mode === 'compact'
1063
1137
  ? buildExecutionSystemPrompt(executionTools, 'run')
1064
1138
  : buildExecutionSystemPrompt();
1065
- let useManualCompactionFallback;
1066
- const manualAppendBoundary = pendingAppends.length;
1067
1139
  const generateCompactExtension = async (history, reason, focus, compaction) => {
1068
1140
  if (!coreExtensions.hooks.hasInterceptors('compact.generate'))
1069
1141
  return undefined;
@@ -1114,50 +1186,6 @@ export function createSessionOpener(services) {
1114
1186
  acceptsDiagnostics = false;
1115
1187
  }
1116
1188
  };
1117
- let manualExtension;
1118
- if (mode === 'compact' && coreExtensions.hooks.hasInterceptors('compact.generate')) {
1119
- manualCompactionStarted = true;
1120
- manualCompaction = createCompactionOperation(manualCompactionId, compactSignal);
1121
- await emit({ type: 'context.compacting', compactionId: manualCompactionId });
1122
- manualExtension = await generateCompactExtension(messages, 'manual', compactFocus, manualCompaction);
1123
- }
1124
- if (mode === 'compact' && manualExtension === undefined) {
1125
- preparedCompactPrompt = await operation((call) => coreExtensions.composeModelSystem(promptComposition(executionTools, 'run'), call));
1126
- const compactComposition = splitContextualPrompt(preparedCompactPrompt);
1127
- const compactSystemPrompt = compactComposition.system;
1128
- const projectCompact = (input) => projectCompactPromptMessages(input, compactComposition.parts, contextualWorldState.promptSnapshot ?? []);
1129
- const compactHistory = [...messages];
1130
- const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries), { supportsImages: false });
1131
- const nativeMessages = buildNativeCompactionMessages(projectedHistory, {
1132
- focus: compactFocus
1133
- });
1134
- const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, projectCompact(buildCompactionMessages('')), executionTools);
1135
- const compactInputTokens = selectedModel.contextWindowTokens - compactOverheadTokens;
1136
- if (compactInputTokens <= 0)
1137
- throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Compaction prompt exceeds the selected model context window.');
1138
- const fallbackMessages = () => buildCompactionMessages(buildCompactionInput(compactHistory, {
1139
- focus: compactFocus,
1140
- maxTokens: compactInputTokens
1141
- }));
1142
- const useNative = shouldUseNativeCompaction({
1143
- estimatedRequestTokens: estimatedRequestTokens(compactSystemPrompt, projectCompact(nativeMessages), executionTools),
1144
- contextWindowTokens: selectedModel.contextWindowTokens,
1145
- latestUsage: lastServerUsage,
1146
- historyMessages: compactHistory,
1147
- appendedMessages: projectCompact(nativeMessages).slice(projectedHistory.length)
1148
- });
1149
- messages.splice(0, messages.length, ...(useNative ? nativeMessages : fallbackMessages()));
1150
- if (useNative) {
1151
- let available = true;
1152
- useManualCompactionFallback = () => {
1153
- if (!available)
1154
- return false;
1155
- available = false;
1156
- messages.splice(0, messages.length, ...fallbackMessages(), ...pendingAppends.slice(manualAppendBoundary).map((entry) => entry.message));
1157
- return true;
1158
- };
1159
- }
1160
- }
1161
1189
  await store.append(sessionId, {
1162
1190
  type: 'execution.header',
1163
1191
  payload: {
@@ -1173,29 +1201,53 @@ export function createSessionOpener(services) {
1173
1201
  turnId
1174
1202
  });
1175
1203
  let lastServerContextTokens = lastServerUsage?.contextInputTokens;
1176
- const compactLeaf = async (input = {}, signal = controller.signal) => {
1204
+ let compactBoundary = messages.length;
1205
+ const compactLeaf = async (input, signal, prepare, compactionId) => {
1177
1206
  if (messages.length < 2)
1178
1207
  return false;
1179
1208
  let compactSummary = '';
1180
- const current = messages.at(-1);
1181
- const retainCurrent = current.role === 'user' && current.contextKind === undefined;
1182
- const compactHistory = retainCurrent ? messages.slice(0, -1) : [...messages];
1183
- const appendBoundary = pendingAppends.length;
1184
- const compactComposition = splitContextualPrompt(await operation((call) => coreExtensions.composeModelSystem(promptComposition(executionTools, 'run'), call)));
1209
+ const cutoff = Math.min(compactBoundary, messages.length);
1210
+ const current = messages[cutoff - 1];
1211
+ const retainCurrent = mode !== 'compact' && current.role === 'user' && current.contextKind === undefined;
1212
+ const prefix = messages.slice(0, cutoff - (retainCurrent ? 1 : 0));
1213
+ if (!prefix.some((message) => message.role !== 'user' ||
1214
+ message.contextKind === undefined ||
1215
+ message.contextKind === 'summary'))
1216
+ return false;
1217
+ const compactHistory = structuredClone(prefix);
1218
+ const prefixInputs = compactionUserMessages.filter((entry) => prefix.some((message) => message.role === 'user' &&
1219
+ message.contextKind === undefined &&
1220
+ (entry.metadata?.recordId
1221
+ ? message.metadata?.recordId === entry.metadata.recordId
1222
+ : message.content === entry.content)));
1223
+ const snapshotTools = structuredClone(projectTools(executionTools));
1224
+ const generation = contextualWorldState.generation;
1225
+ const snapshotUsage = lastServerUsage;
1226
+ const snapshotEffort = requestReasoningEffort ?? reasoningEffort;
1227
+ const anchorRecordId = prefix
1228
+ .map((message) => messageSources.get(message)?.recordId ?? message.metadata?.recordId)
1229
+ .findLast((id) => id !== undefined);
1230
+ const visibleAnchor = compactSnapshotAnchor(prefix, messageSources);
1231
+ const anchor = {
1232
+ ...(anchorRecordId ? { anchorRecordId } : {}),
1233
+ ...(visibleAnchor ? { anchor: visibleAnchor } : {})
1234
+ };
1235
+ const compactComposition = splitContextualPrompt(await operation((call) => coreExtensions.composeModelSystem(promptComposition(snapshotTools, 'run'), call)));
1185
1236
  const compactSystemPrompt = compactComposition.system;
1186
- const projectCompact = (input) => projectCompactPromptMessages(input, compactComposition.parts, contextualWorldState.promptSnapshot ?? []);
1187
- const projectedHistory = normalizeMessagesForModel(insertContextualWorldState(compactHistory, contextualWorldState.entries), { supportsImages: false });
1237
+ const projectedHistory = normalizeMessagesForModel(compactHistory, {
1238
+ supportsImages: false
1239
+ }).map(projectSmartCompactionMessage);
1188
1240
  const nativeCompactMessages = buildNativeCompactionMessages(projectedHistory, {
1189
1241
  ...(input.focus === undefined ? {} : { focus: input.focus })
1190
1242
  });
1191
- const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, projectCompact(buildCompactionMessages('')), executionTools);
1243
+ const compactOverheadTokens = estimatedRequestTokens(compactSystemPrompt, buildCompactionMessages(''), snapshotTools);
1192
1244
  const compactInputTokens = Math.max(0, selectedModel.contextWindowTokens - compactOverheadTokens);
1193
1245
  let usingNativeMessages = shouldUseNativeCompaction({
1194
- estimatedRequestTokens: estimatedRequestTokens(compactSystemPrompt, projectCompact(nativeCompactMessages), executionTools),
1246
+ estimatedRequestTokens: estimatedRequestTokens(compactSystemPrompt, nativeCompactMessages, snapshotTools),
1195
1247
  contextWindowTokens: selectedModel.contextWindowTokens,
1196
- latestUsage: lastServerUsage,
1248
+ latestUsage: snapshotUsage,
1197
1249
  historyMessages: compactHistory,
1198
- appendedMessages: projectCompact(nativeCompactMessages).slice(projectedHistory.length)
1250
+ appendedMessages: nativeCompactMessages.slice(projectedHistory.length)
1199
1251
  });
1200
1252
  const fallbackMessages = () => buildCompactionMessages(buildCompactionInput(compactHistory, {
1201
1253
  ...(input.focus === undefined ? {} : { focus: input.focus }),
@@ -1204,18 +1256,28 @@ export function createSessionOpener(services) {
1204
1256
  let compactMessages = usingNativeMessages
1205
1257
  ? nativeCompactMessages
1206
1258
  : fallbackMessages();
1207
- const compactionId = randomUUID();
1208
- await emit({ type: 'context.compacting', compactionId });
1259
+ await emit({
1260
+ type: 'context.compacting',
1261
+ compactionId,
1262
+ async: input.async === true,
1263
+ stage: 'GENERATING',
1264
+ ...anchor
1265
+ });
1209
1266
  let latestUsage;
1210
1267
  const compactionRetryState = { attempts: 0 };
1211
1268
  const compaction = createCompactionOperation(compactionId, signal);
1269
+ const compactTurn = createModelTurnSession(selectedAdapter);
1212
1270
  try {
1213
- const extensionResult = await generateCompactExtension(compactHistory, 'automatic', input.focus, compaction);
1271
+ const extensionResult = await generateCompactExtension(compactHistory, mode === 'compact' ? 'manual' : 'automatic', input.focus, compaction);
1214
1272
  if (extensionResult) {
1215
1273
  compactSummary = extensionResult.summary;
1216
1274
  latestUsage = extensionResult.usage;
1217
1275
  if (latestUsage)
1218
- await emit({ type: 'usage.updated', ...publicModelUsage(latestUsage) });
1276
+ await emit({
1277
+ type: 'usage.updated',
1278
+ purpose: 'compact',
1279
+ ...publicModelUsage(latestUsage)
1280
+ });
1219
1281
  }
1220
1282
  while (extensionResult === undefined) {
1221
1283
  if (compactInputTokens === 0)
@@ -1229,20 +1291,24 @@ export function createSessionOpener(services) {
1229
1291
  ...(compactComposition.systemBlocks === undefined
1230
1292
  ? {}
1231
1293
  : { systemBlocks: compactComposition.systemBlocks }),
1232
- messages: projectCompact(compactMessages),
1233
- tools: executionTools,
1294
+ messages: compactMessages,
1295
+ tools: snapshotTools,
1234
1296
  toolChoice: 'none',
1235
1297
  promptCacheKey: sessionId,
1236
- reasoningEffort: requestReasoningEffort ?? reasoningEffort,
1298
+ reasoningEffort: snapshotEffort,
1237
1299
  onAttemptDiagnostic: recordModelAttempt('COMPACTION')
1238
1300
  }, undefined, selectedModel.id, 'compact');
1239
- for await (const event of streamModel((signal) => compaction.start({ start: (request) => selected.start(request, signal) }, request), compaction.signal)) {
1301
+ for await (const event of streamModel((signal) => compaction.start({ start: (request) => compactTurn.start(request, signal) }, request), compaction.signal)) {
1240
1302
  if (event.type === 'text.completed' &&
1241
1303
  (event.phase ?? 'final_answer') === 'final_answer')
1242
1304
  compactSummary = event.text.trim();
1243
1305
  else if (event.type === 'usage') {
1244
1306
  latestUsage = event.usage;
1245
- await emit({ type: 'usage.updated', ...publicModelUsage(event.usage) });
1307
+ await emit({
1308
+ type: 'usage.updated',
1309
+ purpose: 'compact',
1310
+ ...publicModelUsage(event.usage)
1311
+ });
1246
1312
  }
1247
1313
  else if (event.type === 'client_tool.call')
1248
1314
  throw new MarAgentError('MAR_AGENT_MODEL_PROTOCOL_ERROR', 'Compaction returned a tool call while tools were disabled.');
@@ -1267,32 +1333,36 @@ export function createSessionOpener(services) {
1267
1333
  }
1268
1334
  finally {
1269
1335
  await compaction.dispose();
1336
+ await compactTurn.close();
1270
1337
  }
1271
1338
  if (!compactSummary)
1272
1339
  throw new MarAgentError('MAR_AGENT_COMPACT_FAILED', 'Automatic compact failed.');
1273
- let replacementHistory = buildCompactedHistory(compactSummary, retainCurrent ? compactionUserMessages.slice(0, -1) : compactionUserMessages, compactUserTokenLimit, retainCurrent
1274
- ? {
1275
- content: current.content,
1276
- ...(current.metadata ? { metadata: current.metadata } : {})
1277
- }
1278
- : undefined);
1340
+ await emit({
1341
+ type: 'context.compacting',
1342
+ compactionId,
1343
+ async: !compactCoordinator.waiting && input.async === true,
1344
+ stage: compactCoordinator.waiting ? 'WAITING' : 'READY',
1345
+ ...anchor
1346
+ });
1347
+ await prepare();
1279
1348
  signal.throwIfAborted();
1349
+ const compactResources = retainedSessionResourcesSnapshot(await loadRetainedSessionResources());
1350
+ const replacementHistory = buildCompactedHistory(compactSummary, prefixInputs, compactUserTokenLimit);
1351
+ let retainedModelContext = [];
1280
1352
  const compactRecord = await writeCompactionRecord(compactSummary, true, signal, async (actual, options) => {
1281
- compactSummary = actual;
1282
- replacementHistory = buildCompactedHistory(actual, retainCurrent ? compactionUserMessages.slice(0, -1) : compactionUserMessages, compactUserTokenLimit, retainCurrent
1283
- ? {
1284
- content: current.content,
1285
- ...(current.metadata ? { metadata: current.metadata } : {})
1286
- }
1287
- : undefined);
1288
- const retainedModelContext = pendingAppends
1289
- .slice(appendBoundary)
1290
- .map((entry) => entry.message);
1353
+ if (contextualWorldState.generation !== generation ||
1354
+ prefix.some((message, index) => messages[index] !== message))
1355
+ throw new MarAgentError('MAR_AGENT_COMPACT_STALE', 'The compaction prefix has changed.');
1356
+ retainedModelContext = messages
1357
+ .slice(prefix.length)
1358
+ .filter((message) => message.contextKind !== 'private' || message.role === 'provider');
1291
1359
  const record = await store.compact(sessionId, {
1292
- summary: compactSummary,
1360
+ summary: actual,
1293
1361
  replacementHistory,
1294
1362
  retainedModelContext,
1295
- automatic: true,
1363
+ snapshotMessageCount: prefix.length,
1364
+ ...(anchorRecordId ? { snapshotRecordId: anchorRecordId } : {}),
1365
+ automatic: mode !== 'compact',
1296
1366
  environment: executionEnvironment,
1297
1367
  modelId: selectedModel.id,
1298
1368
  contextWindowTokens: selectedModel.contextWindowTokens,
@@ -1314,30 +1384,119 @@ export function createSessionOpener(services) {
1314
1384
  content: message.content,
1315
1385
  ...(message.metadata ? { metadata: { ...message.metadata } } : {})
1316
1386
  }));
1317
- await resetCompactWorldState(compactRecord.recordId);
1387
+ compactionUserMessages.push(...retainedModelContext
1388
+ .filter((message) => message.role === 'user' && message.contextKind === undefined)
1389
+ .map((message) => ({
1390
+ content: message.content,
1391
+ ...(message.metadata ? { metadata: message.metadata } : {})
1392
+ })));
1393
+ await resetCompactWorldState(compactRecord.recordId, { resources: compactResources });
1318
1394
  lastServerContextTokens = undefined;
1319
1395
  lastServerUsage = undefined;
1396
+ smartContextTokens = undefined;
1320
1397
  requestReasoningEffort = reasoningEffort;
1321
1398
  effectiveReasoningEffort = reasoningEffort;
1322
- await emit({ type: 'context.compacted', compactionId });
1399
+ await emit({
1400
+ type: 'context.compacted',
1401
+ compactionId,
1402
+ async: input.async === true,
1403
+ retainedContext: retainedModelContext.length > 0,
1404
+ ...anchor
1405
+ });
1323
1406
  return true;
1324
1407
  };
1408
+ startCompact = (input = {}) => {
1409
+ const id = manualCompactionId ?? randomUUID();
1410
+ const flight = compactCoordinator.start(id, input.focus, async (prepare, signal) => {
1411
+ const linked = AbortSignal.any([signal, controller.signal, compactSignal]);
1412
+ try {
1413
+ if (mode === 'compact' && inputs.currentCompact())
1414
+ return await compactLeaf(input, linked, prepare, id);
1415
+ return (await performCompact(input, async (effective, call) => ({
1416
+ changed: await compactLeaf({ ...effective, async: input.async === true }, AbortSignal.any([linked, call.signal]), prepare, id),
1417
+ executionId
1418
+ }))).changed;
1419
+ }
1420
+ catch (error) {
1421
+ const diagnostic = describeMarAgentError(error, 'MAR_AGENT_COMPACT_FAILED');
1422
+ await emit({
1423
+ type: 'context.compact_failed',
1424
+ compactionId: id,
1425
+ code: diagnostic.code,
1426
+ async: input.async === true,
1427
+ cancelled: linked.aborted
1428
+ });
1429
+ throw error;
1430
+ }
1431
+ });
1432
+ if (smartInput)
1433
+ smartInput.requested = true;
1434
+ return flight;
1435
+ };
1325
1436
  compactContext = async (input = {}) => {
1326
- sources.bind(captureRoundToolScope().toolScope);
1327
- return (await performCompact(input, async (effective, call) => ({
1328
- changed: await compactLeaf(effective, call.signal),
1329
- executionId
1330
- }))).changed;
1437
+ const flight = startCompact(input);
1438
+ if (input.async)
1439
+ return false;
1440
+ await emit({
1441
+ type: 'context.compacting',
1442
+ compactionId: flight.id,
1443
+ stage: 'WAITING',
1444
+ async: false
1445
+ });
1446
+ return (await compactCoordinator.checkpoint(true)) ?? false;
1331
1447
  };
1448
+ compactRequests.bind((input) => compactCoordinator.pending
1449
+ ? startCompact({
1450
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1451
+ async: input.async === true
1452
+ })
1453
+ : undefined);
1454
+ if (mode === 'compact') {
1455
+ startCompact({ focus: compactFocus });
1456
+ compactRequests.drain((input) => startCompact({
1457
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1458
+ async: input.async === true
1459
+ }));
1460
+ await compactCoordinator.checkpoint(true);
1461
+ compactRequests.seal();
1462
+ compactRequests.drain((input) => startCompact({
1463
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1464
+ async: input.async === true
1465
+ }));
1466
+ await compactCoordinator.checkpoint(true);
1467
+ return;
1468
+ }
1332
1469
  const ensureContextBudget = async () => {
1333
- if (mode === 'compact' || lastServerContextTokens === undefined)
1334
- return;
1335
- const hardLimit = selectedModel.contextWindowTokens;
1336
- const compactLimit = Math.floor((hardLimit * AGENT_EXECUTION_POLICY.automaticCompactionPercent) / 100);
1337
- if (lastServerContextTokens < compactLimit)
1338
- return;
1339
- if (!(await compactContext()))
1340
- throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Provider usage reached the compaction threshold but no history can be compacted.');
1470
+ const percent = lastServerContextTokens === undefined
1471
+ ? undefined
1472
+ : (lastServerContextTokens * 100) / selectedModel.contextWindowTokens;
1473
+ if (percent !== undefined &&
1474
+ percent >= AGENT_EXECUTION_POLICY.automaticCompactionPercent) {
1475
+ const hadPending = compactCoordinator.pending;
1476
+ let compacted;
1477
+ try {
1478
+ compacted = await compactContext();
1479
+ }
1480
+ catch (error) {
1481
+ controller.signal.throwIfAborted();
1482
+ if (!hadPending)
1483
+ throw error;
1484
+ compactBoundary = messages.length;
1485
+ compacted = await compactContext();
1486
+ }
1487
+ if (!compacted)
1488
+ throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Provider usage reached the compaction threshold but no history can be compacted.');
1489
+ }
1490
+ else {
1491
+ const adopted = await compactCoordinator.checkpoint(false).catch(() => {
1492
+ controller.signal.throwIfAborted();
1493
+ });
1494
+ if (!adopted &&
1495
+ percent !== undefined &&
1496
+ percent >= AGENT_EXECUTION_POLICY.backgroundCompactionPercent &&
1497
+ !compactCoordinator.pending)
1498
+ startCompact({ async: true });
1499
+ }
1341
1500
  };
1342
1501
  executionLoop = new ExecutionLoop({
1343
1502
  mode,
@@ -1349,27 +1508,30 @@ export function createSessionOpener(services) {
1349
1508
  toolImages: currentExecutionToolImages,
1350
1509
  publish: emit,
1351
1510
  appendContext: (message) => withRecordProjection(async () => {
1352
- await store.append(sessionId, {
1511
+ const stored = await store.append(sessionId, {
1353
1512
  type: 'model.context',
1354
1513
  payload: message,
1355
1514
  executionId,
1356
1515
  turnId
1357
1516
  });
1517
+ messageSources.set(message, stored);
1358
1518
  }),
1359
- prepareRound: async (pendingOutputContinuation) => {
1360
- if (mode !== 'compact')
1361
- await appendMailboxMessages();
1519
+ prepareRound: async function prepareRound(pendingOutputContinuation) {
1520
+ await appendMailboxMessages();
1362
1521
  updateMcpRegistry();
1363
1522
  executionTools = mcpRegistry.project(baseTools());
1364
- if (mode !== 'compact')
1365
- await syncMcpWorldState(messages.length);
1523
+ await syncMcpWorldState(messages.length);
1524
+ compactBoundary = messages.length;
1525
+ const blocking = compactRequests.drain((input) => startCompact({
1526
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1527
+ async: input.async === true
1528
+ }));
1529
+ if (blocking)
1530
+ await compactCoordinator.checkpoint(true);
1366
1531
  await ensureContextBudget();
1367
- if (mode !== 'compact')
1368
- await syncMcpWorldState(messages.length);
1369
- if (mode !== 'compact') {
1370
- executionEnvironment = { ...currentEnvironment, ...currentLocalTimeContext() };
1371
- await recordEnvironment();
1372
- }
1532
+ await syncMcpWorldState(messages.length);
1533
+ executionEnvironment = { ...currentEnvironment, ...currentLocalTimeContext() };
1534
+ await recordEnvironment();
1373
1535
  const { roundTools, roundBindings, toolScope, composeSystemPrompt } = captureRoundToolScope();
1374
1536
  const codeModeEnabled = roundTools.some((tool) => tool.name === 'code_exec');
1375
1537
  const residentTools = projectTools(mcpRegistry.projectDirect(baseTools()));
@@ -1385,13 +1547,8 @@ export function createSessionOpener(services) {
1385
1547
  : [...messages, pendingOutputContinuation];
1386
1548
  sources.bind(toolScope);
1387
1549
  const composedSystem = splitContextualPrompt(await composeSystemPrompt());
1388
- const candidateWorld = mode === 'compact'
1389
- ? contextualWorldState
1390
- : updatePromptWorldState(contextualWorldState, messages.length, composedSystem.parts);
1391
- const projectedMessages = projectSessionRequestMessages(requestContextMessages, mode === 'compact' ? [] : candidateWorld.entries, mode !== 'compact' && selectedModel.inputCapabilities.includes('IMAGE'), codeModeEnabled, permissionPolicy.mode === 'auto');
1392
- const modelMessages = mode === 'compact'
1393
- ? projectCompactPromptMessages(projectedMessages, composedSystem.parts, contextualWorldState.promptSnapshot ?? [])
1394
- : projectedMessages;
1550
+ const candidateWorld = updatePromptWorldState(contextualWorldState, messages.length, composedSystem.parts);
1551
+ const modelMessages = projectSessionRequestMessages(requestContextMessages, candidateWorld.entries, selectedModel.inputCapabilities.includes('IMAGE'), codeModeEnabled, permissionPolicy.mode === 'auto');
1395
1552
  const systemPrompt = composedSystem.system;
1396
1553
  const candidateRequest = {
1397
1554
  system: systemPrompt,
@@ -1401,12 +1558,11 @@ export function createSessionOpener(services) {
1401
1558
  messages: modelMessages,
1402
1559
  tools: requestTools,
1403
1560
  ...(permissionPolicy.mode === 'auto' ? { allowHostedTools: false } : {}),
1404
- ...(mode === 'compact' ? { toolChoice: 'none' } : {}),
1405
1561
  promptCacheKey: sessionId,
1406
1562
  reasoningEffort,
1407
1563
  ...(requestReasoningEffort === undefined ? {} : { requestReasoningEffort }),
1408
1564
  ...(modelInputImages?.length ? { images: modelInputImages } : {}),
1409
- onAttemptDiagnostic: recordModelAttempt(mode === 'compact' ? 'COMPACTION' : 'EXECUTION')
1565
+ onAttemptDiagnostic: recordModelAttempt('EXECUTION')
1410
1566
  };
1411
1567
  preparingRequestMessages = modelMessages;
1412
1568
  preparingRequestBaseLength = messages.length;
@@ -1422,44 +1578,65 @@ export function createSessionOpener(services) {
1422
1578
  // Commit only after request middleware accepts the entire candidate.
1423
1579
  await commitWorldState(candidateWorld);
1424
1580
  }
1581
+ const lateCoreChange = inputs.hasBoundaryCompactPending && (await appendMailboxMessages());
1582
+ const lateBlocking = compactRequests.drain((input) => startCompact({
1583
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1584
+ async: input.async === true
1585
+ }));
1586
+ const lateAdopted = lateBlocking && (await compactCoordinator.checkpoint(true));
1587
+ if (lateCoreChange || lateAdopted) {
1588
+ // Middleware may admit a blocking join after the initial checkpoint.
1589
+ // Never sample the candidate assembled against the replaced context.
1590
+ return prepareRound(pendingOutputContinuation);
1591
+ }
1425
1592
  return { request, toolScope };
1426
1593
  },
1427
1594
  startModel: async function* (request) {
1428
- const snapshots = mode === 'compact'
1429
- ? undefined
1430
- : (sessionRuntime.requestSnapshots ??= new ExtensionRequestSnapshots());
1431
- const snapshot = snapshots?.record(selectedModel.id, request);
1432
- if (manualCompactionId !== undefined && !manualCompactionStarted) {
1433
- manualCompactionStarted = true;
1434
- manualCompaction = createCompactionOperation(manualCompactionId, compactSignal);
1435
- await emit({ type: 'context.compacting', compactionId: manualCompactionId });
1436
- }
1437
- const compaction = manualCompaction;
1595
+ const snapshots = (sessionRuntime.requestSnapshots ??=
1596
+ new ExtensionRequestSnapshots());
1597
+ const snapshot = snapshots.record(selectedModel.id, request);
1438
1598
  yield* streamModel(async function* (nativeSignal) {
1439
- const events = compaction
1440
- ? compaction.start({ start: (value) => selected.start(value, nativeSignal) }, request)
1441
- : selected.start(request, nativeSignal);
1442
- for await (const event of events) {
1599
+ for await (const event of selected.start(request, nativeSignal)) {
1443
1600
  if (snapshot && event.type === 'completed' && event.reason !== 'cancelled')
1444
1601
  snapshots.complete(snapshot);
1445
1602
  yield event;
1446
1603
  }
1447
- }, compaction?.signal ?? controller.signal);
1604
+ }, controller.signal);
1448
1605
  },
1449
- recoverySignal: () => manualCompaction?.signal ?? controller.signal,
1606
+ recoverySignal: () => controller.signal,
1450
1607
  compactContext,
1451
- compactionFallback: useManualCompactionFallback,
1608
+ afterModel: async (closedPrefixLength) => {
1609
+ compactBoundary = closedPrefixLength;
1610
+ if (lastServerContextTokens !== undefined &&
1611
+ (lastServerContextTokens * 100) / selectedModel.contextWindowTokens >=
1612
+ AGENT_EXECUTION_POLICY.backgroundCompactionPercent &&
1613
+ !compactCoordinator.pending)
1614
+ startCompact({ async: true });
1615
+ },
1452
1616
  observeContextTokens: (tokens) => {
1453
1617
  lastServerContextTokens = tokens;
1618
+ smartContextTokens = tokens;
1454
1619
  },
1455
1620
  observeUsage: (usage) => {
1456
1621
  if (usage !== undefined) {
1457
1622
  lastServerContextTokens = usage.contextInputTokens;
1623
+ smartContextTokens = usage.contextInputTokens;
1458
1624
  lastServerUsage = { ...usage, historyMessages: [...messages] };
1459
1625
  }
1460
1626
  },
1461
1627
  checkpoint: async (round, latestUsage) => {
1462
- if (mode !== 'compact' &&
1628
+ compactBoundary = messages.length;
1629
+ const blocking = compactRequests.drain((input) => startCompact({
1630
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1631
+ async: input.async === true
1632
+ }));
1633
+ if (blocking)
1634
+ await compactCoordinator.checkpoint(true);
1635
+ else
1636
+ await compactCoordinator.checkpoint(false).catch(() => {
1637
+ controller.signal.throwIfAborted();
1638
+ });
1639
+ if (!compactCoordinator.pending &&
1463
1640
  sessionSource.type !== 'subagent' &&
1464
1641
  latestUsage !== undefined) {
1465
1642
  // Only a committed GC checkpoint may replace active context and invalidate continuation.
@@ -1486,6 +1663,7 @@ export function createSessionOpener(services) {
1486
1663
  }
1487
1664
  });
1488
1665
  if (committed) {
1666
+ smartContextTokens = undefined;
1489
1667
  selected.resetContinuation?.();
1490
1668
  const refreshed = await restoreMessages(store, sessionId, committed.records, {
1491
1669
  includeImages: true,
@@ -1511,51 +1689,23 @@ export function createSessionOpener(services) {
1511
1689
  }
1512
1690
  },
1513
1691
  mailbox: {
1514
- hasPending: () => inputs.hasBoundaryPending && !inputs.executing && mode !== 'compact',
1692
+ hasPending: () => inputs.hasBoundaryPending && !inputs.executing,
1515
1693
  append: appendMailboxMessages,
1516
1694
  close: () => {
1517
1695
  acceptsMailbox = false;
1518
1696
  }
1519
1697
  }
1520
1698
  });
1521
- if (manualExtension) {
1522
- if (manualExtension.usage)
1523
- await emit({ type: 'usage.updated', ...publicModelUsage(manualExtension.usage) });
1524
- }
1525
- const completed = manualExtension
1526
- ? { finalAnswer: manualExtension.summary, usage: manualExtension.usage }
1527
- : await executionLoop.run();
1699
+ const completed = await executionLoop.run();
1700
+ compactRequests.seal();
1701
+ compactRequests.drain((input) => startCompact({
1702
+ ...(input.prompt === undefined ? {} : { focus: input.prompt }),
1703
+ async: input.async === true
1704
+ }));
1705
+ await compactCoordinator.checkpoint(true).catch(() => {
1706
+ controller.signal.throwIfAborted();
1707
+ });
1528
1708
  finalAnswer = completed.finalAnswer;
1529
- const compactSummary = mode === 'compact' ? finalAnswer.trim() : '';
1530
- if (manualCompaction) {
1531
- manualCompaction.signal.throwIfAborted();
1532
- await manualCompaction.dispose();
1533
- }
1534
- if (mode === 'compact') {
1535
- const compactRecord = await writeCompactionRecord(compactSummary, false, compactSignal, async (actual, options) => {
1536
- const retainedModelContext = pendingAppends
1537
- .slice(manualAppendBoundary)
1538
- .map((entry) => entry.message);
1539
- const replacementHistory = buildCompactedHistory(actual, compactionUserMessages, compactUserTokenLimit);
1540
- const record = await store.compact(sessionId, {
1541
- summary: actual,
1542
- retainedModelContext,
1543
- replacementHistory,
1544
- modelId: selectedModel.id,
1545
- contextWindowTokens: selectedModel.contextWindowTokens,
1546
- ...(completed.usage === undefined ? {} : { usage: completed.usage }),
1547
- executionId,
1548
- turnId
1549
- }, options);
1550
- messages.splice(0, messages.length, ...compactedHistoryMessages(replacementHistory), ...retainedModelContext);
1551
- pendingAppends.length = 0;
1552
- selected.resetContinuation?.();
1553
- return record;
1554
- });
1555
- await resetCompactWorldState(compactRecord.recordId);
1556
- }
1557
- if (mode === 'compact' && manualCompactionId !== undefined)
1558
- await emit({ type: 'context.compacted', compactionId: manualCompactionId });
1559
1709
  };
1560
1710
  if (mode === 'compact')
1561
1711
  await performCompact({ focus: input.prompt }, async (effective, call) => {
@@ -1579,6 +1729,10 @@ export function createSessionOpener(services) {
1579
1729
  catch (error) {
1580
1730
  finalAnswer = executionLoop?.finalAnswer ?? finalAnswer;
1581
1731
  acceptsMailbox = false;
1732
+ await compactCoordinator.close(error);
1733
+ compactRequests.close(new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
1734
+ if (sessionRuntime.compactRequests === compactRequests)
1735
+ delete sessionRuntime.compactRequests;
1582
1736
  if (controller.signal.aborted) {
1583
1737
  subagents?.endParentExecution(executionId);
1584
1738
  await emit({ type: 'execution.cancelled' });
@@ -1602,6 +1756,9 @@ export function createSessionOpener(services) {
1602
1756
  finally {
1603
1757
  if (sessionRuntime.requestMessages?.executionId === executionId)
1604
1758
  delete sessionRuntime.requestMessages;
1759
+ compactRequests.close(new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
1760
+ if (sessionRuntime.compactRequests === compactRequests)
1761
+ delete sessionRuntime.compactRequests;
1605
1762
  if (sessionRuntime.appendedMessages?.executionId === executionId)
1606
1763
  delete sessionRuntime.appendedMessages;
1607
1764
  if (promptDefaults?.executionId === executionId)
@@ -1611,7 +1768,7 @@ export function createSessionOpener(services) {
1611
1768
  if (sessionRuntime.toolSources === executionToolSources)
1612
1769
  delete sessionRuntime.toolSources;
1613
1770
  internal.signal?.removeEventListener('abort', abortSource);
1614
- await manualCompaction?.dispose();
1771
+ await compactCoordinator.close(controller.signal.reason ?? new MarAgentError('MAR_AGENT_CANCELLED', 'Execution ended.'));
1615
1772
  await selectedTurn?.close().catch(() => undefined);
1616
1773
  try {
1617
1774
  await closeModelAdapters(executionScopedAdapters);
@@ -1900,10 +2057,11 @@ export function createSessionOpener(services) {
1900
2057
  id: sessionId,
1901
2058
  run: (input) => execution('run', input),
1902
2059
  plan: (input) => execution('plan', input),
1903
- compact: (input = {}) => execution('compact', {
1904
- prompt: input.prompt ?? DEFAULT_COMPACTION_FOCUS,
1905
- ...input
1906
- }),
2060
+ compact: (input = {}) => sessionRuntime.compactRequests?.request(input) ??
2061
+ execution('compact', {
2062
+ prompt: input.prompt ?? DEFAULT_COMPACTION_FOCUS,
2063
+ ...input
2064
+ }),
1907
2065
  cancel: async (executionId) => active.get(`${sessionId}:${executionId}`)?.abort(),
1908
2066
  startSubagentTurn: (turn) => execution(turn.role === 'explore' ? 'plan' : 'run', {
1909
2067
  prompt: turn.prompt,
@@ -1954,7 +2112,7 @@ function projectSessionRequestMessages(messages, entries, supportsImages, codeMo
1954
2112
  supportsImages
1955
2113
  }).map((message) => projectCodeModeSearchResult(autoPermission && message.toolSearchTools
1956
2114
  ? { ...message, toolSearchTools: message.toolSearchTools.map(projectPermissionTool) }
1957
- : message, codeMode));
2115
+ : projectSmartCompactionMessage(message), codeMode));
1958
2116
  }
1959
2117
  function projectAppendedModelMessage(input) {
1960
2118
  if (input.visibility === 'transcript')