@bastani/atomic 0.8.30-alpha.1 → 0.8.30-alpha.2

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.
@@ -150,7 +150,7 @@ What Survives:
150
150
  - User instructions: The original task and any clarifications.
151
151
 
152
152
  Conditionally Deleted:
153
- - Old Reasoning decisions: If there is nothing else to remove and the target reduction is not met, you can remove any reasoning steps, EXCEPT thinking or redacted_thinking blocks in the latest assistant message.
153
+ - Old Reasoning decisions: If there is nothing else to remove and the target reduction is not met, you can remove reasoning steps, EXCEPT do not delete any content block from the latest assistant message when that message contains thinking or redacted_thinking blocks.
154
154
 
155
155
  <output_format>
156
156
  Call the context_delete tool one or more times with deletion targets in this shape:
@@ -535,29 +535,44 @@ function assertNoRecentContextDeletionTargets(transcript, targets) {
535
535
  }
536
536
  }
537
537
  }
538
- function latestAssistantEntry(transcript) {
538
+ function latestAssistantEntry(transcript, deletedEntryIds = new Set()) {
539
539
  for (let index = transcript.entries.length - 1; index >= 0; index--) {
540
540
  const entry = transcript.entries[index];
541
- if (entry.role === "assistant")
541
+ if (entry.role === "assistant" && !deletedEntryIds.has(entry.entryId))
542
542
  return entry;
543
543
  }
544
544
  return undefined;
545
545
  }
546
- function assertNoLatestAssistantThinkingDeletionTargets(transcript, targets) {
547
- const latestAssistant = latestAssistantEntry(transcript);
548
- if (!latestAssistant || !assistantEntryHasThinkingContentBlock(latestAssistant))
549
- return;
546
+ function findLatestAssistantThinkingDeletionViolation(transcript, targets) {
547
+ const deletedEntryIds = getDeletedEntryIds(targets);
548
+ const latestRetainedAssistant = latestAssistantEntry(transcript, deletedEntryIds);
550
549
  for (const target of targets) {
551
- if (target.entryId !== latestAssistant.entryId)
552
- continue;
553
550
  if (target.kind === "entry") {
554
- throw new Error(`Cannot delete assistant entry ${target.entryId} because it is the latest assistant message and contains thinking/redacted_thinking content blocks`);
551
+ const entry = findTranscriptEntry(transcript, target.entryId);
552
+ if (!entry || !assistantEntryHasThinkingContentBlock(entry))
553
+ continue;
554
+ const deletedEntryIdsIfTargetWereKept = new Set(deletedEntryIds);
555
+ deletedEntryIdsIfTargetWereKept.delete(target.entryId);
556
+ if (latestAssistantEntry(transcript, deletedEntryIdsIfTargetWereKept)?.entryId === target.entryId) {
557
+ return target;
558
+ }
559
+ continue;
555
560
  }
556
- const block = latestAssistant.contentBlocks.find((candidate) => candidate.blockIndex === target.blockIndex);
557
- if (block && isAssistantThinkingBlockType(block.type)) {
558
- throw new Error(`Cannot delete content block ${target.entryId}:${target.blockIndex} because it is a thinking/redacted_thinking block in the latest assistant message`);
561
+ if (latestRetainedAssistant?.entryId === target.entryId &&
562
+ assistantEntryHasThinkingContentBlock(latestRetainedAssistant)) {
563
+ return target;
559
564
  }
560
565
  }
566
+ return undefined;
567
+ }
568
+ function assertNoLatestAssistantThinkingDeletionTargets(transcript, targets) {
569
+ const violation = findLatestAssistantThinkingDeletionViolation(transcript, targets);
570
+ if (!violation)
571
+ return;
572
+ if (violation.kind === "entry") {
573
+ throw new Error(`Cannot delete assistant entry ${violation.entryId} because it is the latest assistant message retained after other deletions and contains thinking/redacted_thinking content blocks`);
574
+ }
575
+ throw new Error(`Cannot delete content block ${violation.entryId}:${violation.blockIndex} because a thinking/redacted_thinking block in the latest assistant message must remain unmodified; the latest retained assistant message contains thinking/redacted_thinking content blocks`);
561
576
  }
562
577
  function isToolCallBlockDeleted(entry, callId, deletedEntryIds, deletedContentBlocks) {
563
578
  if (deletedEntryIds.has(entry.entryId))
@@ -1075,6 +1090,34 @@ function filterProtectedGrepCandidates(candidates, matches, currentTargets, tran
1075
1090
  eligibleMatches.push(match);
1076
1091
  }
1077
1092
  }
1093
+ // Some latest-assistant thinking violations only become visible after a grep batch also
1094
+ // deletes newer assistant entries. Classify the newly-unsafe grep candidates as
1095
+ // protected/skipped before maxMatches, expectedMatchCount, stats, or removals are computed.
1096
+ let changed = true;
1097
+ while (changed) {
1098
+ changed = false;
1099
+ const mergedTargets = mergeContextDeletionTargets(currentTargets, eligibleCandidates);
1100
+ const violation = findLatestAssistantThinkingDeletionViolation(transcript, mergedTargets);
1101
+ if (!violation)
1102
+ continue;
1103
+ const violationKey = targetKey(violation);
1104
+ let violationIndex = eligibleCandidates.findIndex((candidate) => targetKey(candidate) === violationKey);
1105
+ if (violationIndex < 0) {
1106
+ violationIndex = eligibleCandidates.findIndex((_candidate, candidateIndex) => {
1107
+ const remainingCandidates = eligibleCandidates.filter((_candidateToKeep, index) => index !== candidateIndex);
1108
+ const remainingTargets = mergeContextDeletionTargets(currentTargets, remainingCandidates);
1109
+ const remainingViolation = findLatestAssistantThinkingDeletionViolation(transcript, remainingTargets);
1110
+ return !remainingViolation || targetKey(remainingViolation) !== violationKey;
1111
+ });
1112
+ }
1113
+ if (violationIndex < 0)
1114
+ continue;
1115
+ const [skippedMatch] = eligibleMatches.splice(violationIndex, 1);
1116
+ eligibleCandidates.splice(violationIndex, 1);
1117
+ if (skippedMatch)
1118
+ pushProtectedGrepSkip(skipped, skippedMatch);
1119
+ changed = true;
1120
+ }
1078
1121
  return { candidates: eligibleCandidates, matches: eligibleMatches };
1079
1122
  }
1080
1123
  function copyDeletionTarget(target) {