@@ -32,11 +32,12 @@ def _mock_agent():
3232 agent ._system_prompt = "You are an agent."
3333 agent ._system_prompt_content = [{"text" : "You are an agent." }]
3434
35- # Make system_prompt property behave like the real Agent
35+ # Make system_prompt and system_prompt_content properties behave like the real Agent
3636 type(agent ).system_prompt = property (
3737 lambda self : self ._system_prompt ,
3838 lambda self , value : _set_system_prompt (self , value ),
3939 )
40+ type(agent ).system_prompt_content = property (lambda self : self ._system_prompt_content )
4041
4142 agent .hooks = HookRegistry ()
4243 agent .add_hook = MagicMock (
@@ -59,11 +60,15 @@ def _mock_tool_context(agent: MagicMock) -> ToolContext:
5960 return ToolContext (tool_use = tool_use , agent = agent , invocation_state = {"agent" : agent })
6061
6162
62- def _set_system_prompt (agent : MagicMock , value : str | None ) -> None :
63+ def _set_system_prompt (agent : MagicMock , value : str | list | None ) -> None :
6364 """Simulate the Agent.system_prompt setter."""
6465 if isinstance (value , str ):
6566 agent ._system_prompt = value
6667 agent ._system_prompt_content = [{"text" : value }]
68+ elif isinstance (value , list ):
69+ text_parts = [block ["text" ] for block in value if "text" in block ]
70+ agent ._system_prompt = "\n " .join (text_parts ) if text_parts else None
71+ agent ._system_prompt_content = value
6772 elif value is None :
6873 agent ._system_prompt = None
6974 agent ._system_prompt_content = None
@@ -417,9 +422,41 @@ def test_uses_public_system_prompt_setter(self):
417422 event = BeforeInvocationEvent (agent = agent )
418423 plugin ._on_before_invocation (event )
419424
420- # The public setter should have been used, so _system_prompt_content
421- # should be consistent with _system_prompt
422- assert agent ._system_prompt_content == [{"text" : agent ._system_prompt }]
425+ # The public setter should have been used via the content-block path:
426+ # original block is preserved and the skills XML is appended as a new block.
427+ assert len (agent .system_prompt_content ) == 2
428+ assert agent .system_prompt_content [0 ] == {"text" : "Original." }
429+ assert "<available_skills>" in agent .system_prompt_content [1 ]["text" ]
430+
431+ def test_preserves_cache_points_in_system_prompt (self ):
432+ """Test that cachePoint blocks in the system prompt are preserved after injection."""
433+ plugin = AgentSkills (skills = [_make_skill ()])
434+ agent = _mock_agent ()
435+ agent ._system_prompt = "Base instructions."
436+ agent ._system_prompt_content = [
437+ {"text" : "Base instructions." },
438+ {"cachePoint" : {"type" : "default" }},
439+ ]
440+
441+ expected_skills_xml = plugin ._generate_skills_xml ()
442+
443+ event = BeforeInvocationEvent (agent = agent )
444+ plugin ._on_before_invocation (event )
445+
446+ # Exact block structure: original text, cachePoint, skills XML
447+ assert agent .system_prompt_content == [
448+ {"text" : "Base instructions." },
449+ {"cachePoint" : {"type" : "default" }},
450+ {"text" : expected_skills_xml },
451+ ]
452+
453+ # Repeated invocation: identical result, no accumulation
454+ plugin ._on_before_invocation (event )
455+ assert agent .system_prompt_content == [
456+ {"text" : "Base instructions." },
457+ {"cachePoint" : {"type" : "default" }},
458+ {"text" : expected_skills_xml },
459+ ]
423460
424461 def test_warns_when_previous_xml_not_found (self , caplog ):
425462 """Test that a warning is logged when the previously injected XML is missing from the prompt."""
@@ -441,6 +478,43 @@ def test_warns_when_previous_xml_not_found(self, caplog):
441478 assert "<available_skills>" in agent .system_prompt
442479
443480
481+ class TestStringPathInjection :
482+ """Tests for the string-path branch of _on_before_invocation (system_prompt_content is None)."""
483+
484+ def test_string_path_replaces_previous_xml (self ):
485+ """Test that old injected XML is replaced when found in the string prompt."""
486+ plugin = AgentSkills (skills = [_make_skill ()])
487+ agent = _mock_agent ()
488+
489+ old_xml = "\n \n <old>xml</old>"
490+ agent ._system_prompt = f"Base prompt.{ old_xml } "
491+ agent ._system_prompt_content = None
492+ agent .state .set (plugin ._state_key , {"last_injected_xml" : old_xml })
493+
494+ event = BeforeInvocationEvent (agent = agent )
495+ plugin ._on_before_invocation (event )
496+
497+ assert "<old>xml</old>" not in agent .system_prompt
498+ assert "<available_skills>" in agent .system_prompt
499+ assert agent .system_prompt .startswith ("Base prompt." )
500+
501+ def test_string_path_warns_when_previous_xml_not_found (self , caplog ):
502+ """Test that a warning is logged when old XML is missing from the string prompt."""
503+ plugin = AgentSkills (skills = [_make_skill ()])
504+ agent = _mock_agent ()
505+
506+ agent ._system_prompt = "Totally new prompt."
507+ agent ._system_prompt_content = None
508+ agent .state .set (plugin ._state_key , {"last_injected_xml" : "\n \n <old>xml</old>" })
509+
510+ event = BeforeInvocationEvent (agent = agent )
511+ with caplog .at_level (logging .WARNING ):
512+ plugin ._on_before_invocation (event )
513+
514+ assert "unable to find previously injected skills XML in system prompt" in caplog .text
515+ assert "<available_skills>" in agent .system_prompt
516+
517+
444518class TestSkillsXmlGeneration :
445519 """Tests for _generate_skills_xml."""
446520
0 commit comments