diff --git a/interfaces/admin/static/chat.js b/interfaces/admin/static/chat.js
index 0f5f0b3c..9c168406 100644
--- a/interfaces/admin/static/chat.js
+++ b/interfaces/admin/static/chat.js
@@ -1547,10 +1547,12 @@ function hydrateMermaidIn(root) {
const RE_REDACTED_THINKING = new RegExp("\\s*([\\s\\S]*?)\\s*", "i");
const RE_THINK_TAG = new RegExp("\\s*([\\s\\S]*?)\\s*", "i");
+const RE_THINKING_TAG = new RegExp("\\s*([\\s\\S]*?)\\s*", "i");
+const RE_THOUGHT_TAG = new RegExp("\\s*([\\s\\S]*?)\\s*", "i");
function _findEarliestReasoningBlock(remaining) {
let best = null;
- for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG]) {
+ for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG, RE_THINKING_TAG, RE_THOUGHT_TAG]) {
const m = re.exec(remaining);
re.lastIndex = 0;
if (!m) continue;
@@ -1657,7 +1659,7 @@ function extractThinkingFromText(text) {
const raw = String(text || "");
if (!raw) return "";
const blocks = [];
- for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG]) {
+ for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG, RE_THINKING_TAG, RE_THOUGHT_TAG]) {
const rr = new RegExp(re.source, "gi");
for (const m of raw.matchAll(rr)) {
const inner = String(m[1] || "").trim();
diff --git a/runtime/direct_loop.py b/runtime/direct_loop.py
index 82b28569..43584e72 100644
--- a/runtime/direct_loop.py
+++ b/runtime/direct_loop.py
@@ -35,7 +35,10 @@ _DIRECT_LOOP_OC_STAGE: dict[str, str] = {
"tool_result_context_guard": "tool_context_guard",
"tool_pairing_guard": "tool_pairing_guard",
}
-_THINK_BLOCK_RE = re.compile(r"<(think|redacted_thinking)>\s*(.*?)\s*\1>\s*", flags=re.IGNORECASE | re.DOTALL)
+_THINK_BLOCK_RE = re.compile(
+ r"<\s*(?:antml:)?(think|thinking|thought|redacted_thinking)\s*>\s*(.*?)\s*\s*(?:antml:)?\1\s*>",
+ flags=re.IGNORECASE | re.DOTALL,
+)
_DSML_INVOKE_NAME_RE = re.compile(r"invoke\s+name\s*=\s*['\"]([^'\"\s>]+)['\"]", flags=re.IGNORECASE)
_JSON_TOOL_NAME_RE = re.compile(r"['\"]name['\"]\s*:\s*['\"]([^'\"\s]{1,120})['\"]", flags=re.IGNORECASE)
_TOOL_WIRE_CACHE_LOCK = threading.Lock()
diff --git a/tests/test_direct_loop_reasoning_split.py b/tests/test_direct_loop_reasoning_split.py
new file mode 100644
index 00000000..14210415
--- /dev/null
+++ b/tests/test_direct_loop_reasoning_split.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from oclaw.runtime.direct_loop import _split_reasoning_and_body
+
+
+def test_split_reasoning_extracts_thinking_tags() -> None:
+ chunks, body = _split_reasoning_and_body("\ninner\n\nvisible")
+ assert chunks == ["inner"]
+ assert body == "visible"
+
+
+def test_split_reasoning_extracts_thought_tags() -> None:
+ chunks, body = _split_reasoning_and_body("xafter")
+ assert chunks == ["x"]
+ assert body == "after"
+
+
+def test_split_reasoning_explicit_when_no_tags() -> None:
+ chunks, body = _split_reasoning_and_body("body only", explicit_reasoning=" rc ")
+ assert chunks == ["rc"]
+ assert body == "body only"
+
+
+def test_split_reasoning_antml_optional_prefix() -> None:
+ raw = "\nZ\n\nok"
+ chunks, body = _split_reasoning_and_body(raw)
+ assert chunks == ["Z"]
+ assert body == "ok"