From d90c5141598fcb039102fab9b900fa01be4f9c4d Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Mon, 20 Apr 2026 16:40:36 +0800 Subject: [PATCH] fix(communication): fix errors while import external xml node into input xml --- .../partner/core/cognition/CognitionCore.java | 2 +- .../communication/CommunicationProducer.java | 19 ++++- .../summarizer/MessageSummarizer.java | 2 +- .../experimental/ExternalNodeImportTest.java | 80 +++++++++++++++++++ .../CommunicationProducerTest.java | 34 +++++++- 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 Partner-Core/src/test/java/experimental/ExternalNodeImportTest.java diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java b/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java index 0ee40e97..b2a0a881 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/cognition/CognitionCore.java @@ -127,7 +127,7 @@ public class CognitionCore implements StateSerializable { new BlockContent("recent_chat_messages", "communication_producer") { @Override protected void fillXml(@NotNull Document document, @NotNull Element root) { - document.appendChild(document.importNode(messageNotesElement(), true)); + root.appendChild(document.importNode(messageNotesElement(), true)); Element chatMessagesElement = document.createElement("chat_messages"); root.appendChild(chatMessagesElement); appendRepeatedElements(document, chatMessagesElement, "chat_message", resolveRecentChatMessages(), (messageElement, message) -> { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java index c6610939..52b568ef 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/communication/CommunicationProducer.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; import work.slhaf.partner.core.cognition.*; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; @@ -171,7 +172,7 @@ public class CommunicationProducer extends AbstractAgentModule.Running entry : runningFlowContext.getAdditionalUserInfo().entrySet()) { appendTextElement(document, root, sanitizeTagName(entry.getKey()), entry.getValue()); @@ -242,11 +243,25 @@ public class CommunicationProducer extends AbstractAgentModule.Running, Re return new TaskBlock() { @Override protected void fillXml(@NotNull Document document, @NotNull Element root) { - document.appendChild(document.importNode(cognitionCapability.messageNotesElement(), true)); + root.appendChild(document.importNode(cognitionCapability.messageNotesElement(), true)); appendListElement(document, root, "chat_messages", "message", messages, (element, message) -> { element.setAttribute("role", message.roleValue()); element.setTextContent(message.getContent()); diff --git a/Partner-Core/src/test/java/experimental/ExternalNodeImportTest.java b/Partner-Core/src/test/java/experimental/ExternalNodeImportTest.java new file mode 100644 index 00000000..a65ce16c --- /dev/null +++ b/Partner-Core/src/test/java/experimental/ExternalNodeImportTest.java @@ -0,0 +1,80 @@ +package experimental; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilderFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ExternalNodeImportTest { + + private static Node importExternalNode(Document targetDocument, Node externalNode) { + Node nodeToImport = externalNode; + if (externalNode != null && externalNode.getNodeType() == Node.DOCUMENT_NODE) { + nodeToImport = ((Document) externalNode).getDocumentElement(); + } + if (nodeToImport == null) { + throw new IllegalArgumentException("nodeToImport must not be null"); + } + if (nodeToImport.getOwnerDocument() == targetDocument) { + return nodeToImport.cloneNode(true); + } + return targetDocument.importNode(nodeToImport, true); + } + + private static Document newDocument() throws Exception { + return DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .newDocument(); + } + + @Test + void appendForeignElementDirectlyShouldThrow() throws Exception { + Document targetDocument = newDocument(); + Element inputRoot = targetDocument.createElement("input"); + targetDocument.appendChild(inputRoot); + + Document externalDocument = newDocument(); + Element externalInputs = externalDocument.createElement("inputs"); + externalDocument.appendChild(externalInputs); + + assertThrows(DOMException.class, () -> inputRoot.appendChild(externalInputs)); + } + + @Test + void importForeignElementShouldWork() throws Exception { + Document targetDocument = newDocument(); + Element inputRoot = targetDocument.createElement("input"); + targetDocument.appendChild(inputRoot); + + Document externalDocument = newDocument(); + Element externalInputs = externalDocument.createElement("inputs"); + externalDocument.appendChild(externalInputs); + + Node imported = importExternalNode(targetDocument, externalInputs); + inputRoot.appendChild(imported); + + assertEquals("inputs", inputRoot.getFirstChild().getNodeName()); + } + + @Test + void importExternalDocumentShouldUseDocumentElement() throws Exception { + Document targetDocument = newDocument(); + Element inputRoot = targetDocument.createElement("input"); + targetDocument.appendChild(inputRoot); + + Document externalDocument = newDocument(); + Element externalInputs = externalDocument.createElement("inputs"); + externalDocument.appendChild(externalInputs); + + Node imported = importExternalNode(targetDocument, externalDocument); + inputRoot.appendChild(imported); + + assertEquals("inputs", inputRoot.getFirstChild().getNodeName()); + } +} diff --git a/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java b/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java index da356eec..a71fac34 100644 --- a/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java +++ b/Partner-Core/src/test/java/work/slhaf/partner/module/communication/CommunicationProducerTest.java @@ -14,7 +14,7 @@ import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; class CommunicationProducerTest { @@ -32,6 +32,20 @@ class CommunicationProducerTest { method.invoke(producer, context, response); } + private static String invokeBuildInputXml( + CommunicationProducer producer, + PartnerRunningFlowContext context, + List communicationBlocks + ) throws Exception { + Method method = CommunicationProducer.class.getDeclaredMethod( + "buildInputXml", + PartnerRunningFlowContext.class, + List.class + ); + method.setAccessible(true); + return (String) method.invoke(producer, context, communicationBlocks); + } + private static void setField(Object target, String fieldName, Object value) throws Exception { Field field = target.getClass().getDeclaredField(fieldName); field.setAccessible(true); @@ -72,6 +86,24 @@ class CommunicationProducerTest { assertEquals("[[AGENT]: self]:\n\nnormal reply", chatMessages.get(1).getContent()); } + @Test + void shouldBuildInputXmlWithoutExtraWrapper() throws Exception { + StubCognitionCapability cognitionCapability = new StubCognitionCapability(); + CommunicationProducer producer = new CommunicationProducer(); + setField(producer, "cognitionCapability", cognitionCapability); + + String xml = invokeBuildInputXml( + producer, + PartnerRunningFlowContext.fromUser("user-1", "hello"), + List.of() + ); + + assertTrue(xml.contains("")); + assertTrue(xml.contains("")); + assertTrue(xml.contains("hello")); + assertFalse(xml.contains("")); + } + private static final class StubCognitionCapability implements CognitionCapability { private final ContextWorkspace contextWorkspace = new ContextWorkspace(); private final List chatMessages = new ArrayList<>();