]*>',
+ re.IGNORECASE,
+ )
+ next_match = next_heading.search(body_html, section_start)
+ section_end = next_match.start() if next_match else len(body_html)
+
+ # Replace section content
+ new_body = body_html[:section_start] + new_html + body_html[section_end:]
+
+ # PUT updated page
+ put_data = {
+ "version": {"number": version + 1},
+ "title": title,
+ "type": "page",
+ "body": {
+ "storage": {
+ "value": new_body,
+ "representation": "storage",
+ }
+ },
+ }
+ async with httpx.AsyncClient(timeout=15.0) as client:
+ resp = await client.put(
+ url,
+ json=put_data,
+ auth=(CONFLUENCE_USER, CONFLUENCE_TOKEN),
+ )
+ resp.raise_for_status()
+ return f"Section '{section_heading}' updated successfully."
+
+
def _build_e2ee_options() -> rtc.E2EEOptions:
"""Build E2EE options — let Rust FFI apply HKDF internally (KDF_HKDF=1).
@@ -698,12 +791,48 @@ class VoiceSession:
await _store_user_pref(caller_uid, "timezone", iana_timezone)
return f"Timezone set to {iana_timezone}"
+ @function_tool
+ async def read_confluence_page(page_id: str) -> str:
+ """Read a Confluence page. Use when user asks to read, review,
+ or check a document. Returns page title and content as text."""
+ logger.info("CONFLUENCE_READ: page_id=%s", page_id)
+ try:
+ title, text, _ver = await _confluence_read_page(page_id)
+ result = f"Page: {title}\n\n{text}"
+ logger.info("CONFLUENCE_READ_OK: %s (%d chars)", title, len(text))
+ return result
+ except Exception as exc:
+ logger.warning("CONFLUENCE_READ_FAIL: %s", exc)
+ return f"Failed to read page: {exc}"
+
+ @function_tool
+ async def update_confluence_page(page_id: str, section_heading: str, new_content: str) -> str:
+ """Update a section of a Confluence page. Use when user asks to
+ change, update, or rewrite part of a document.
+ - page_id: Confluence page ID
+ - section_heading: heading text of the section to update
+ - new_content: new plain text for the section (will be wrapped in tags)
+ Human sees changes instantly in their browser via Live Docs."""
+ logger.info("CONFLUENCE_UPDATE: page=%s section='%s'", page_id, section_heading)
+ try:
+ new_html = f"
{new_content}
"
+ result = await _confluence_update_section(page_id, section_heading, new_html)
+ logger.info("CONFLUENCE_UPDATE_OK: %s", result)
+ return result
+ except Exception as exc:
+ logger.warning("CONFLUENCE_UPDATE_FAIL: %s", exc)
+ return f"Failed to update page: {exc}"
+
instructions = _build_voice_prompt(model=self.model, timezone=user_timezone) + memory_section
if self._document_context:
instructions += f"\n\nDokument-Kontext (im Raum hochgeladen):\n{self._document_context}"
+ # Extract Confluence page IDs from document context for tool use
+ conf_ids = re.findall(r'confluence_page_id:(\d+)', self._document_context)
+ if conf_ids:
+ instructions += f"\n\nAktive Confluence-Seite(n): {', '.join(conf_ids)}. Nutze diese page_id fuer read_confluence_page und update_confluence_page."
agent = _NoiseFilterAgent(
instructions=instructions,
- tools=[search_web, set_user_timezone],
+ tools=[search_web, set_user_timezone, read_confluence_page, update_confluence_page],
)
io_opts = room_io.RoomOptions(
participant_identity=remote_identity,