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

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/CHANGELOG.md +4 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +1 -0
  8. package/dist/builtin/workflows/README.md +1 -1
  9. package/dist/builtin/workflows/package.json +1 -1
  10. package/dist/builtin/workflows/src/runs/foreground/executor.ts +19 -10
  11. package/dist/core/anthropic-thinking-guard.d.ts +15 -0
  12. package/dist/core/anthropic-thinking-guard.d.ts.map +1 -0
  13. package/dist/core/anthropic-thinking-guard.js +205 -0
  14. package/dist/core/anthropic-thinking-guard.js.map +1 -0
  15. package/dist/core/compaction/compaction.d.ts +8 -2
  16. package/dist/core/compaction/compaction.d.ts.map +1 -1
  17. package/dist/core/compaction/compaction.js +19 -3
  18. package/dist/core/compaction/compaction.js.map +1 -1
  19. package/dist/core/compaction/context-compaction.d.ts.map +1 -1
  20. package/dist/core/compaction/context-compaction.js +84 -14
  21. package/dist/core/compaction/context-compaction.js.map +1 -1
  22. package/dist/core/sdk.d.ts.map +1 -1
  23. package/dist/core/sdk.js +15 -3
  24. package/dist/core/sdk.js.map +1 -1
  25. package/dist/core/session-manager.d.ts.map +1 -1
  26. package/dist/core/session-manager.js +22 -28
  27. package/dist/core/session-manager.js.map +1 -1
  28. package/dist/modes/interactive/components/context-compaction-summary-message.d.ts.map +1 -1
  29. package/dist/modes/interactive/components/context-compaction-summary-message.js +2 -2
  30. package/dist/modes/interactive/components/context-compaction-summary-message.js.map +1 -1
  31. package/docs/compaction.md +8 -5
  32. package/docs/session-format.md +1 -1
  33. package/docs/workflows.md +2 -0
  34. package/package.json +2 -2
@@ -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 entire stale assistant entries, EXCEPT do not delete individual content blocks from any retained assistant message that contains thinking or redacted_thinking blocks. Thinking-bearing assistant messages are all-or-nothing for replay safety.
154
154
 
155
155
  <output_format>
156
156
  Call the context_delete tool one or more times with deletion targets in this shape:
@@ -525,7 +525,7 @@ function formatProtectedToolDependencyError(transcript, blockedTarget, context)
525
525
  function isProtectedContextDeletionErrorMessage(message) {
526
526
  return (/\bprotected\b/i.test(message) ||
527
527
  /Cannot delete (?:recent context entry|content block .* because entry .* is one of the last)/u.test(message) ||
528
- /latest assistant message|thinking\/redacted_thinking block in the latest assistant message/u.test(message));
528
+ /latest assistant message|thinking\/redacted_thinking block in (?:the latest|a retained) assistant message/u.test(message));
529
529
  }
530
530
  function assertNoRecentContextDeletionTargets(transcript, targets) {
531
531
  const recentEntryIds = getRecentContextEntryIds(transcript);
@@ -535,29 +535,63 @@ 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 findAssistantThinkingContentBlockDeletionViolation(transcript, targets) {
547
+ const deletedEntryIds = getDeletedEntryIds(targets);
550
548
  for (const target of targets) {
551
- if (target.entryId !== latestAssistant.entryId)
549
+ if (target.kind !== "content_block")
552
550
  continue;
551
+ if (deletedEntryIds.has(target.entryId))
552
+ continue;
553
+ const entry = findTranscriptEntry(transcript, target.entryId);
554
+ if (entry && assistantEntryHasThinkingContentBlock(entry))
555
+ return target;
556
+ }
557
+ return undefined;
558
+ }
559
+ function findLatestAssistantThinkingDeletionViolation(transcript, targets) {
560
+ const deletedEntryIds = getDeletedEntryIds(targets);
561
+ const latestRetainedAssistant = latestAssistantEntry(transcript, deletedEntryIds);
562
+ for (const target of targets) {
553
563
  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`);
564
+ const entry = findTranscriptEntry(transcript, target.entryId);
565
+ if (!entry || !assistantEntryHasThinkingContentBlock(entry))
566
+ continue;
567
+ const deletedEntryIdsIfTargetWereKept = new Set(deletedEntryIds);
568
+ deletedEntryIdsIfTargetWereKept.delete(target.entryId);
569
+ if (latestAssistantEntry(transcript, deletedEntryIdsIfTargetWereKept)?.entryId === target.entryId) {
570
+ return target;
571
+ }
572
+ continue;
555
573
  }
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`);
574
+ if (latestRetainedAssistant?.entryId === target.entryId &&
575
+ assistantEntryHasThinkingContentBlock(latestRetainedAssistant)) {
576
+ return target;
559
577
  }
560
578
  }
579
+ return undefined;
580
+ }
581
+ function assertNoAssistantThinkingContentBlockDeletionTargets(transcript, targets) {
582
+ const violation = findAssistantThinkingContentBlockDeletionViolation(transcript, targets);
583
+ if (!violation)
584
+ return;
585
+ throw new Error(`Cannot delete content block ${violation.entryId}:${violation.blockIndex} because a thinking/redacted_thinking block in a retained assistant message must remain unmodified; retained assistant messages containing thinking/redacted_thinking content blocks are all-or-nothing`);
586
+ }
587
+ function assertNoLatestAssistantThinkingDeletionTargets(transcript, targets) {
588
+ const violation = findLatestAssistantThinkingDeletionViolation(transcript, targets);
589
+ if (!violation)
590
+ return;
591
+ if (violation.kind === "entry") {
592
+ 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`);
593
+ }
594
+ 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
595
  }
562
596
  function isToolCallBlockDeleted(entry, callId, deletedEntryIds, deletedContentBlocks) {
563
597
  if (deletedEntryIds.has(entry.entryId))
@@ -615,6 +649,11 @@ function canonicalizeEntryTargets(transcript, targets, entry) {
615
649
  return deleteEntryTarget(targets, entry.entryId);
616
650
  }
617
651
  function addToolCallDeletion(transcript, targets, entry, callId) {
652
+ if (assistantEntryHasThinkingContentBlock(entry)) {
653
+ if (!canDeleteTarget(transcript, { kind: "entry", entryId: entry.entryId }))
654
+ return false;
655
+ return deleteEntryTarget(targets, entry.entryId);
656
+ }
618
657
  let changed = false;
619
658
  for (const blockIndex of toolCallBlockIndexes(entry, callId)) {
620
659
  const target = { kind: "content_block", entryId: entry.entryId, blockIndex };
@@ -685,7 +724,9 @@ function reconcileToolDependencies(transcript, initialTargets) {
685
724
  continue;
686
725
  recordChange(deleteEntryTarget(targets, result.entryId));
687
726
  const callEntryTarget = { kind: "entry", entryId: callEntry.entryId };
688
- const callBlockTarget = firstToolCallBlockTarget(callEntry, callId) ?? callEntryTarget;
727
+ const callBlockTarget = assistantEntryHasThinkingContentBlock(callEntry)
728
+ ? callEntryTarget
729
+ : firstToolCallBlockTarget(callEntry, callId) ?? callEntryTarget;
689
730
  if (!canDeleteTarget(transcript, callBlockTarget)) {
690
731
  if (isRecentTarget(transcript, callBlockTarget)) {
691
732
  throw new Error(formatRecentContextDeletionError(transcript, callBlockTarget));
@@ -869,6 +910,7 @@ export function validateContextDeletionRequest(request, transcript) {
869
910
  // Tool reconciliation can add targets after the per-request checks above, so
870
911
  // these post-reconcile assertions remain authoritative.
871
912
  assertNoRecentContextDeletionTargets(transcript, reconciledTargets);
913
+ assertNoAssistantThinkingContentBlockDeletionTargets(transcript, reconciledTargets);
872
914
  assertNoLatestAssistantThinkingDeletionTargets(transcript, reconciledTargets);
873
915
  const reconciledDeletedEntryIds = getDeletedEntryIds(reconciledTargets);
874
916
  for (const target of reconciledTargets) {
@@ -1075,6 +1117,34 @@ function filterProtectedGrepCandidates(candidates, matches, currentTargets, tran
1075
1117
  eligibleMatches.push(match);
1076
1118
  }
1077
1119
  }
1120
+ // Some latest-assistant thinking violations only become visible after a grep batch also
1121
+ // deletes newer assistant entries. Classify the newly-unsafe grep candidates as
1122
+ // protected/skipped before maxMatches, expectedMatchCount, stats, or removals are computed.
1123
+ let changed = true;
1124
+ while (changed) {
1125
+ changed = false;
1126
+ const mergedTargets = mergeContextDeletionTargets(currentTargets, eligibleCandidates);
1127
+ const violation = findLatestAssistantThinkingDeletionViolation(transcript, mergedTargets);
1128
+ if (!violation)
1129
+ continue;
1130
+ const violationKey = targetKey(violation);
1131
+ let violationIndex = eligibleCandidates.findIndex((candidate) => targetKey(candidate) === violationKey);
1132
+ if (violationIndex < 0) {
1133
+ violationIndex = eligibleCandidates.findIndex((_candidate, candidateIndex) => {
1134
+ const remainingCandidates = eligibleCandidates.filter((_candidateToKeep, index) => index !== candidateIndex);
1135
+ const remainingTargets = mergeContextDeletionTargets(currentTargets, remainingCandidates);
1136
+ const remainingViolation = findLatestAssistantThinkingDeletionViolation(transcript, remainingTargets);
1137
+ return !remainingViolation || targetKey(remainingViolation) !== violationKey;
1138
+ });
1139
+ }
1140
+ if (violationIndex < 0)
1141
+ continue;
1142
+ const [skippedMatch] = eligibleMatches.splice(violationIndex, 1);
1143
+ eligibleCandidates.splice(violationIndex, 1);
1144
+ if (skippedMatch)
1145
+ pushProtectedGrepSkip(skipped, skippedMatch);
1146
+ changed = true;
1147
+ }
1078
1148
  return { candidates: eligibleCandidates, matches: eligibleMatches };
1079
1149
  }
1080
1150
  function copyDeletionTarget(target) {