feat(MAT-57): Add Confluence write & create tools to voice and text chat

- Add create_confluence_page tool to voice mode (basic auth)
- Add confluence_update_page and confluence_create_page tools to text chat (OAuth)
- Fix update tool: wrap each paragraph in <p> tags instead of single wrapper
- Update system prompt to mention create capability

Previously only search/read were available. User reported bot couldn't
write to or create Confluence pages — because the tools didn't exist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-02-27 08:04:01 +02:00
parent 9833c89aa6
commit 10762a53da
3 changed files with 153 additions and 5 deletions

View File

@@ -51,7 +51,7 @@ STRIKTE Regeln:
- Bei zeitrelevanten Fragen (Uhrzeit, Termine, Geschaeftszeiten): frage kurz nach ob der Nutzer noch in seiner gespeicherten Zeitzone ist, bevor du antwortest. Nutze set_user_timezone wenn sich der Standort geaendert hat.
- Wenn der Nutzer seinen Standort oder seine Stadt erwaehnt, nutze set_user_timezone um die Zeitzone zu speichern.
- IGNORIERE alle Texte in Sternchen wie *Störgeräusche*, *Schlechte Qualität*, *Fernsehgeräusche*, *Schrei* usw. — das sind KEINE echten Nutzereingaben sondern technische Annotationen. Antworte NIEMALS darauf und tue so als haette niemand etwas gesagt.
- Du kannst Confluence-Seiten suchen, lesen und bearbeiten. Nutze search_confluence um Seiten zu finden, read_confluence_page zum Lesen und update_confluence_page zum Bearbeiten.
- Du kannst Confluence-Seiten suchen, lesen, bearbeiten und erstellen. Nutze search_confluence um Seiten zu finden, read_confluence_page zum Lesen, update_confluence_page zum Bearbeiten und create_confluence_page zum Erstellen neuer Seiten.
- Du kannst den Bildschirm oder die Kamera des Nutzers sehen wenn er sie teilt. Nutze look_at_screen wenn der Nutzer etwas zeigen moechte oder fragt ob du etwas sehen kannst."""
@@ -334,6 +334,22 @@ async def _confluence_update_section(page_id: str, section_heading: str, new_htm
return result.message
async def _confluence_create_page(space_key: str, title: str, body_html: str,
parent_id: str | None = None) -> dict:
"""Create a new Confluence page. Returns {id, title, url}."""
if not CONFLUENCE_URL or not CONFLUENCE_USER or not CONFLUENCE_TOKEN:
raise RuntimeError("Confluence credentials not configured")
from confluence_collab.client import Auth, create_page
auth = Auth(base_url=CONFLUENCE_URL, username=CONFLUENCE_USER, api_token=CONFLUENCE_TOKEN)
page = await create_page(space_key, title, body_html, auth, parent_id=parent_id)
return {
"id": page.page_id,
"title": page.title,
"url": f"{CONFLUENCE_URL}/pages/viewpage.action?pageId={page.page_id}",
}
def _build_e2ee_options() -> rtc.E2EEOptions:
"""Build E2EE options — let Rust FFI apply HKDF internally (KDF_HKDF=1).
@@ -868,7 +884,7 @@ class VoiceSession:
"""Update a section of a Confluence page. Use when user asks to
change, update, or rewrite part of a document.
- section_heading: heading text of the section to update
- new_content: new plain text for the section (will be wrapped in <p> tags)
- new_content: new plain text for the section (paragraphs separated by newlines)
- page_id: leave empty to use the active document from the room
Human sees changes instantly in their browser via Live Docs."""
pid = page_id or _active_conf_id
@@ -876,7 +892,9 @@ class VoiceSession:
return "No Confluence page ID available. Ask the user to share a Confluence link first."
logger.info("CONFLUENCE_UPDATE: page=%s section='%s'", pid, section_heading)
try:
new_html = f"<p>{new_content}</p>"
# Wrap each paragraph in <p> tags for proper formatting
paragraphs = [p.strip() for p in new_content.split("\n") if p.strip()]
new_html = "".join(f"<p>{p}</p>" for p in paragraphs) if paragraphs else f"<p>{new_content}</p>"
result = await _confluence_update_section(pid, section_heading, new_html)
logger.info("CONFLUENCE_UPDATE_OK: %s", result)
return result
@@ -884,6 +902,24 @@ class VoiceSession:
logger.warning("CONFLUENCE_UPDATE_FAIL: %s", exc)
return f"Failed to update page: {exc}"
@function_tool
async def create_confluence_page(title: str, content: str, space_key: str = "AG") -> str:
"""Create a new Confluence page. Use when user asks to create a new document,
write a new page, or make a new wiki entry.
- title: page title
- content: page body text (paragraphs separated by newlines)
- space_key: Confluence space key (default: AG for Agiliton)"""
logger.info("CONFLUENCE_CREATE: title='%s' space=%s", title, space_key)
try:
paragraphs = [p.strip() for p in content.split("\n") if p.strip()]
body_html = "".join(f"<p>{p}</p>" for p in paragraphs) if paragraphs else f"<p>{content}</p>"
result = await _confluence_create_page(space_key, title, body_html)
logger.info("CONFLUENCE_CREATE_OK: id=%s title='%s'", result["id"], result["title"])
return f"Page created: {result['title']} (ID: {result['id']})\nURL: {result['url']}"
except Exception as exc:
logger.warning("CONFLUENCE_CREATE_FAIL: %s", exc)
return f"Failed to create page: {exc}"
# Deep thinking tool — escalates to Opus for complex questions
_transcript_ref = self._transcript
_doc_context_ref = self._document_context
@@ -1042,7 +1078,7 @@ class VoiceSession:
instructions += f"\n\nAktive Confluence-Seite: {_active_conf_id}. Du brauchst den Nutzer NICHT nach der page_id zu fragen — nutze automatisch diese ID fuer read_confluence_page und update_confluence_page."
agent = _NoiseFilterAgent(
instructions=instructions,
tools=[search_web, set_user_timezone, search_confluence, read_confluence_page, update_confluence_page, think_deeper, look_at_screen],
tools=[search_web, set_user_timezone, search_confluence, read_confluence_page, update_confluence_page, create_confluence_page, think_deeper, look_at_screen],
)
io_opts = room_io.RoomOptions(
participant_identity=remote_identity,