diff --git a/voice.py b/voice.py index d4bd25a..6a6c561 100644 --- a/voice.py +++ b/voice.py @@ -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 lesen und bearbeiten. Nutze read_confluence_page und update_confluence_page wenn der Nutzer Dokumente besprechen oder aendern moechte. +- 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 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.""" @@ -253,6 +253,31 @@ async def _store_voice_exchange(user_text: str, agent_text: str, logger.warning("Voice memory store failed: %s", exc) +async def _confluence_search(query: str, limit: int = 5) -> list[dict]: + """Search Confluence pages by CQL query. Returns list of {id, title, space, url}.""" + if not CONFLUENCE_URL or not CONFLUENCE_USER or not CONFLUENCE_TOKEN: + raise RuntimeError("Confluence credentials not configured") + cql = f'type=page AND (title~"{query}" OR text~"{query}")' + url = f"{CONFLUENCE_URL}/rest/api/content/search" + async with httpx.AsyncClient(timeout=15.0) as client: + resp = await client.get( + url, + params={"cql": cql, "limit": limit}, + auth=(CONFLUENCE_USER, CONFLUENCE_TOKEN), + ) + resp.raise_for_status() + data = resp.json() + results = [] + for r in data.get("results", []): + results.append({ + "id": r["id"], + "title": r.get("title", ""), + "space": r.get("space", {}).get("name", "") if "space" in r else "", + "url": f"{CONFLUENCE_URL}{r.get('_links', {}).get('webui', '')}", + }) + return results + + async def _confluence_read_page(page_id: str) -> tuple[str, str, int]: """Read a Confluence page and return (title, plain_text, version_number).""" if not CONFLUENCE_URL or not CONFLUENCE_USER or not CONFLUENCE_TOKEN: @@ -785,6 +810,24 @@ class VoiceSession: if _conf_ids: _active_conf_id = _conf_ids[0] + @function_tool + async def search_confluence(query: str) -> str: + """Search Confluence for pages matching a query. Use when user asks + to find, search, or look up documents or pages in Confluence. + Returns a list of matching pages with titles and IDs.""" + logger.info("CONFLUENCE_SEARCH: query=%s", query) + try: + results = await _confluence_search(query, limit=5) + if not results: + return f"No Confluence pages found for '{query}'." + lines = [f"Found {len(results)} pages:"] + for r in results: + lines.append(f"- {r['title']} (ID: {r['id']}, Space: {r['space']})") + return "\n".join(lines) + except Exception as exc: + logger.warning("CONFLUENCE_SEARCH_FAIL: %s", exc) + return f"Search failed: {exc}" + @function_tool async def read_confluence_page(page_id: str = "") -> str: """Read a Confluence page. Use when user asks to read, review, @@ -982,7 +1025,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, 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, think_deeper, look_at_screen], ) io_opts = room_io.RoomOptions( participant_identity=remote_identity,