@@ -1634,7 +1634,7 @@ describe('context-pruner PASS 0 instructions removal', () => {
16341634 return results
16351635 }
16361636
1637- test ( 'removes messages with INSTRUCTIONS_PROMPT tag ' , ( ) => {
1637+ test ( 'removes last INSTRUCTIONS_PROMPT message in PASS 0 ' , ( ) => {
16381638 const messages : Message [ ] = [
16391639 {
16401640 role : 'user' ,
@@ -1721,6 +1721,90 @@ describe('context-pruner PASS 0 instructions removal', () => {
17211721 expect ( texts ) . toContain ( 'Start' )
17221722 expect ( texts ) . toContain ( 'End' )
17231723 } )
1724+
1725+ test ( 'keeps only last INSTRUCTIONS_PROMPT when pruning passes run (over token limit)' , ( ) => {
1726+ // Use a lower maxContextLength to trigger pruning without needing massive content
1727+ // This ensures PASS 0.5 runs (past the initial check)
1728+ const mockAgentState = {
1729+ messageHistory : [ ] as Message [ ] ,
1730+ }
1731+
1732+ const runHandleStepsWithLimit = ( messages : Message [ ] , maxContextLength : number ) => {
1733+ mockAgentState . messageHistory = messages
1734+ const mockLogger = {
1735+ debug : ( ) => { } ,
1736+ info : ( ) => { } ,
1737+ warn : ( ) => { } ,
1738+ error : ( ) => { } ,
1739+ }
1740+ const generator = contextPruner . handleSteps ! ( {
1741+ agentState : mockAgentState ,
1742+ logger : mockLogger ,
1743+ params : { maxContextLength } ,
1744+ } )
1745+ const results : any [ ] = [ ]
1746+ let result = generator . next ( )
1747+ while ( ! result . done ) {
1748+ if ( typeof result . value === 'object' ) {
1749+ results . push ( result . value )
1750+ }
1751+ result = generator . next ( )
1752+ }
1753+ return results
1754+ }
1755+
1756+ // Content that's ~1000 tokens each (3000 chars / 3)
1757+ const mediumContent = 'x' . repeat ( 3000 )
1758+
1759+ const messages : Message [ ] = [
1760+ {
1761+ role : 'user' ,
1762+ content : [ { type : 'text' , text : mediumContent } ] ,
1763+ } ,
1764+ {
1765+ role : 'user' ,
1766+ content : [ { type : 'text' , text : 'Old instructions 1' } ] ,
1767+ tags : [ 'INSTRUCTIONS_PROMPT' ] ,
1768+ } ,
1769+ {
1770+ role : 'user' ,
1771+ content : [ { type : 'text' , text : mediumContent } ] ,
1772+ } ,
1773+ {
1774+ role : 'user' ,
1775+ content : [ { type : 'text' , text : 'Old instructions 2' } ] ,
1776+ tags : [ 'INSTRUCTIONS_PROMPT' ] ,
1777+ } ,
1778+ {
1779+ role : 'user' ,
1780+ content : [ { type : 'text' , text : mediumContent } ] ,
1781+ } ,
1782+ {
1783+ role : 'user' ,
1784+ content : [ { type : 'text' , text : 'Latest instructions' } ] ,
1785+ tags : [ 'INSTRUCTIONS_PROMPT' ] ,
1786+ } ,
1787+ ]
1788+
1789+ // Use a very low limit (1000 tokens) to ensure we exceed it and trigger PASS 0.5
1790+ // Messages are ~3000+ tokens total, so 1000 limit will be exceeded
1791+ const results = runHandleStepsWithLimit ( messages , 1000 )
1792+ const resultMessages = results [ 0 ] . input . messages
1793+
1794+ // PASS 0 removes the last INSTRUCTIONS_PROMPT ('Latest instructions')
1795+ // PASS 0.5 keeps only the last remaining one ('Old instructions 2') and removes 'Old instructions 1'
1796+ // So we should have at most 1 INSTRUCTIONS_PROMPT message remaining
1797+ const instructionsPrompts = resultMessages . filter (
1798+ ( m : any ) => m . tags ?. includes ( 'INSTRUCTIONS_PROMPT' ) ,
1799+ )
1800+ // Either 0 (if aggressive pruning removed it) or 1 (kept the last one)
1801+ // The key assertion is that we don't have 2 (both old ones kept)
1802+ expect ( instructionsPrompts . length ) . toBeLessThanOrEqual ( 1 )
1803+ if ( instructionsPrompts . length === 1 ) {
1804+ // If one remains, it should be 'Old instructions 2' (the last remaining after PASS 0)
1805+ expect ( instructionsPrompts [ 0 ] . content [ 0 ] . text ) . toBe ( 'Old instructions 2' )
1806+ }
1807+ } )
17241808} )
17251809
17261810describe ( 'context-pruner orphan removal' , ( ) => {
0 commit comments