11import assert from "node:assert/strict"
22import test from "node:test"
3+ import type { PluginConfig } from "../lib/config"
4+ import { createTextCompleteHandler } from "../lib/hooks"
35import { assignMessageRefs } from "../lib/message-ids"
4- import { buildPriorityMap } from "../lib/messages/priority"
56import { injectMessageIds } from "../lib/messages/inject/inject"
67import { applyAnchoredNudges } from "../lib/messages/inject/utils"
8+ import { buildPriorityMap } from "../lib/messages/priority"
79import { stripHallucinationsFromString } from "../lib/messages/utils"
810import { createSessionState , type WithParts } from "../lib/state"
9- import type { PluginConfig } from "../lib/config"
10- import { createTextCompleteHandler } from "../lib/hooks"
1111
12- function buildConfig ( ) : PluginConfig {
12+ function buildConfig ( mode : "message" | "range" = "message" ) : PluginConfig {
1313 return {
1414 enabled : true ,
1515 debug : false ,
@@ -33,7 +33,7 @@ function buildConfig(): PluginConfig {
3333 } ,
3434 protectedFilePatterns : [ ] ,
3535 compress : {
36- mode : "message" ,
36+ mode,
3737 permission : "allow" ,
3838 showCompression : false ,
3939 maxContextLimit : 150000 ,
@@ -68,6 +68,28 @@ function textPart(messageID: string, sessionID: string, id: string, text: string
6868 }
6969}
7070
71+ function toolPart (
72+ messageID : string ,
73+ sessionID : string ,
74+ callID : string ,
75+ toolName : string ,
76+ output : string ,
77+ ) {
78+ return {
79+ id : `${ callID } -part` ,
80+ messageID,
81+ sessionID,
82+ type : "tool" as const ,
83+ tool : toolName ,
84+ callID,
85+ state : {
86+ status : "completed" as const ,
87+ input : { description : "demo" } ,
88+ output,
89+ } ,
90+ }
91+ }
92+
7193function buildMessage (
7294 id : string ,
7395 role : "user" | "assistant" ,
@@ -106,11 +128,28 @@ function repeatedWord(word: string, count: number): string {
106128 return Array . from ( { length : count } , ( ) => word ) . join ( " " )
107129}
108130
109- test ( "injectMessageIds adds priority attributes in message mode" , ( ) => {
131+ test ( "injectMessageIds appends priority tags to existing text parts in message mode" , ( ) => {
110132 const sessionID = "ses_message_priority_tags"
111133 const messages : WithParts [ ] = [
112134 buildMessage ( "msg-user-1" , "user" , sessionID , repeatedWord ( "investigate" , 6000 ) , 1 ) ,
113- buildMessage ( "msg-assistant-1" , "assistant" , sessionID , "Short follow-up note." , 2 ) ,
135+ {
136+ info : {
137+ id : "msg-assistant-1" ,
138+ role : "assistant" ,
139+ sessionID,
140+ agent : "assistant" ,
141+ time : { created : 2 } ,
142+ } as WithParts [ "info" ] ,
143+ parts : [
144+ textPart (
145+ "msg-assistant-1" ,
146+ sessionID ,
147+ "msg-assistant-1-part" ,
148+ "Short follow-up note." ,
149+ ) ,
150+ toolPart ( "msg-assistant-1" , sessionID , "call-task-1" , "task" , "task output body" ) ,
151+ ] ,
152+ } ,
114153 ]
115154 const state = createSessionState ( )
116155 const config = buildConfig ( )
@@ -120,19 +159,28 @@ test("injectMessageIds adds priority attributes in message mode", () => {
120159
121160 injectMessageIds ( state , config , messages , compressionPriorities )
122161
123- const userTag = messages [ 0 ] ?. parts [ messages [ 0 ] . parts . length - 1 ]
124- const assistantTag = messages [ 1 ] ?. parts [ messages [ 1 ] . parts . length - 1 ]
162+ assert . equal ( messages [ 0 ] ?. parts . length , 1 )
163+ assert . equal ( messages [ 1 ] ?. parts . length , 2 )
125164
126- assert . equal ( userTag ?. type , "text" )
127- assert . equal ( assistantTag ?. type , "text" )
128- assert . match ( ( userTag as any ) . text , / < d c p - m e s s a g e - i d p r i o r i t y = " h i g h " > m 0 0 0 1 < \/ d c p - m e s s a g e - i d > / )
165+ const userText = messages [ 0 ] ?. parts [ 0 ]
166+ const assistantText = messages [ 1 ] ?. parts [ 0 ]
167+ const assistantTool = messages [ 1 ] ?. parts [ 1 ]
168+
169+ assert . equal ( userText ?. type , "text" )
170+ assert . equal ( assistantText ?. type , "text" )
171+ assert . equal ( assistantTool ?. type , "tool" )
129172 assert . match (
130- ( assistantTag as any ) . text ,
131- / < d c p - m e s s a g e - i d p r i o r i t y = " l o w " > m 0 0 0 2 < \/ d c p - m e s s a g e - i d > / ,
173+ ( userText as any ) . text ,
174+ / \n \n < d c p - m e s s a g e - i d p r i o r i t y = " h i g h " > m 0 0 0 1 < \/ d c p - m e s s a g e - i d > / ,
132175 )
176+ assert . match (
177+ ( assistantText as any ) . text ,
178+ / \n \n < d c p - m e s s a g e - i d p r i o r i t y = " l o w " > m 0 0 0 2 < \/ d c p - m e s s a g e - i d > / ,
179+ )
180+ assert . equal ( ( assistantTool as any ) . state . output , "task output body" )
133181} )
134182
135- test ( "message-mode nudges list only earlier visible high-priority message IDs" , ( ) => {
183+ test ( "message-mode nudges append to existing text parts and list only earlier visible high-priority message IDs" , ( ) => {
136184 const sessionID = "ses_message_priority_nudges"
137185 const messages : WithParts [ ] = [
138186 buildMessage ( "msg-user-1" , "user" , sessionID , repeatedWord ( "alpha" , 6000 ) , 1 ) ,
@@ -168,15 +216,64 @@ test("message-mode nudges list only earlier visible high-priority message IDs",
168216 compressionPriorities ,
169217 )
170218
171- const injectedNudge = messages [ 2 ] ?. parts [ messages [ 2 ] . parts . length - 1 ]
219+ assert . equal ( messages [ 2 ] ?. parts . length , 1 )
220+
221+ const injectedNudge = messages [ 2 ] ?. parts [ 0 ]
172222 assert . equal ( injectedNudge ?. type , "text" )
223+ assert . match ( ( injectedNudge as any ) . text , / \n \n < d c p - s y s t e m - r e m i n d e r > B a s e c o n t e x t n u d g e / )
173224 assert . match ( ( injectedNudge as any ) . text , / M e s s a g e p r i o r i t y c o n t e x t : / )
174225 assert . match ( ( injectedNudge as any ) . text , / H i g h - p r i o r i t y m e s s a g e I D s b e f o r e t h i s p o i n t : m 0 0 0 1 / )
175226 assert . doesNotMatch ( ( injectedNudge as any ) . text , / m 0 0 0 2 / )
176227 assert . doesNotMatch ( ( injectedNudge as any ) . text , / m 0 0 0 3 / )
177228 assert . doesNotMatch ( ( injectedNudge as any ) . text , / m 0 0 0 4 / )
178229} )
179230
231+ test ( "range-mode nudges append to existing text parts before tool outputs" , ( ) => {
232+ const sessionID = "ses_range_nudge_injection"
233+ const messages : WithParts [ ] = [
234+ buildMessage ( "msg-user-1" , "user" , sessionID , repeatedWord ( "alpha" , 6000 ) , 1 ) ,
235+ {
236+ info : {
237+ id : "msg-assistant-1" ,
238+ role : "assistant" ,
239+ sessionID,
240+ agent : "assistant" ,
241+ time : { created : 2 } ,
242+ } as WithParts [ "info" ] ,
243+ parts : [
244+ textPart ( "msg-assistant-1" , sessionID , "msg-assistant-1-part" , "Working summary." ) ,
245+ toolPart ( "msg-assistant-1" , sessionID , "call-task-2" , "task" , "task output body" ) ,
246+ ] ,
247+ } ,
248+ ]
249+ const state = createSessionState ( )
250+ const config = buildConfig ( "range" )
251+
252+ assignMessageRefs ( state , messages )
253+ state . prune . messages . activeBlockIds . add ( 7 )
254+ state . nudges . contextLimitAnchors . add ( "msg-assistant-1" )
255+
256+ applyAnchoredNudges ( state , config , messages , {
257+ system : "" ,
258+ compressRange : "" ,
259+ compressMessage : "" ,
260+ contextLimitNudge : "<dcp-system-reminder>Base context nudge</dcp-system-reminder>" ,
261+ turnNudge : "<dcp-system-reminder>Base turn nudge</dcp-system-reminder>" ,
262+ iterationNudge : "<dcp-system-reminder>Base iteration nudge</dcp-system-reminder>" ,
263+ } )
264+
265+ assert . equal ( messages [ 1 ] ?. parts . length , 2 )
266+
267+ const injectedNudge = messages [ 1 ] ?. parts [ 0 ]
268+ const toolOutput = messages [ 1 ] ?. parts [ 1 ]
269+ assert . equal ( injectedNudge ?. type , "text" )
270+ assert . equal ( toolOutput ?. type , "tool" )
271+ assert . match ( ( injectedNudge as any ) . text , / \n \n < d c p - s y s t e m - r e m i n d e r > B a s e c o n t e x t n u d g e / )
272+ assert . match ( ( injectedNudge as any ) . text , / C o m p r e s s e d b l o c k c o n t e x t : / )
273+ assert . match ( ( injectedNudge as any ) . text , / A c t i v e c o m p r e s s e d b l o c k s i n t h i s s e s s i o n : 1 \( b 7 \) / )
274+ assert . equal ( ( toolOutput as any ) . state . output , "task output body" )
275+ } )
276+
180277test ( "hallucination stripping removes exact metadata tags and preserves lookalikes" , async ( ) => {
181278 const text =
182279 'alpha<dcp-message-id priority="high">m0007</dcp-message-id>' +
0 commit comments