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:
112
bot.py
112
bot.py
@@ -138,6 +138,38 @@ ATLASSIAN_TOOLS = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "confluence_update_page",
|
||||
"description": "Update a section of a Confluence page by heading. Use when the user asks to change, edit, or update part of a wiki page.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page_id": {"type": "string", "description": "The Confluence page ID"},
|
||||
"section_heading": {"type": "string", "description": "The heading text of the section to update"},
|
||||
"new_content": {"type": "string", "description": "New content for the section (paragraphs separated by newlines)"},
|
||||
},
|
||||
"required": ["page_id", "section_heading", "new_content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "confluence_create_page",
|
||||
"description": "Create a new Confluence page. Use when the user asks to create a new wiki page or document.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string", "description": "Page title"},
|
||||
"content": {"type": "string", "description": "Page body text (paragraphs separated by newlines)"},
|
||||
"space_key": {"type": "string", "description": "Confluence space key (default: AG)", "default": "AG"},
|
||||
},
|
||||
"required": ["title", "content"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -476,6 +508,80 @@ class AtlassianClient:
|
||||
except Exception as e:
|
||||
return f"Failed to read Confluence page {page_id}: {e}"
|
||||
|
||||
async def confluence_update_page(self, token: str, page_id: str,
|
||||
section_heading: str, new_content: str) -> str:
|
||||
cloud_id = await self._get_cloud_id(token)
|
||||
if not cloud_id:
|
||||
return "Error: Could not determine Atlassian Cloud instance."
|
||||
try:
|
||||
base_url = f"https://api.atlassian.com/ex/confluence/{cloud_id}/wiki"
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||
# Fetch current page
|
||||
resp = await client.get(
|
||||
f"{base_url}/rest/api/content/{page_id}",
|
||||
params={"expand": "body.storage,version"},
|
||||
headers=headers,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
page = resp.json()
|
||||
title = page["title"]
|
||||
version = page["version"]["number"]
|
||||
body_html = page["body"]["storage"]["value"]
|
||||
|
||||
# Find and replace section
|
||||
from confluence_collab.parser import parse_sections, find_section, replace_section_content
|
||||
sections = parse_sections(body_html)
|
||||
section = find_section(sections, section_heading)
|
||||
if section is None:
|
||||
return f"Section '{section_heading}' not found on page '{title}'"
|
||||
|
||||
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>"
|
||||
new_body = replace_section_content(body_html, section, new_html)
|
||||
|
||||
# Update page
|
||||
resp = await client.put(
|
||||
f"{base_url}/rest/api/content/{page_id}",
|
||||
json={
|
||||
"version": {"number": version + 1},
|
||||
"title": title,
|
||||
"type": "page",
|
||||
"body": {"storage": {"value": new_body, "representation": "storage"}},
|
||||
},
|
||||
headers=headers,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return f"Section '{section_heading}' updated successfully on '{title}'"
|
||||
except Exception as e:
|
||||
return f"Failed to update Confluence page: {e}"
|
||||
|
||||
async def confluence_create_page(self, token: str, title: str, content: str,
|
||||
space_key: str = "AG") -> str:
|
||||
cloud_id = await self._get_cloud_id(token)
|
||||
if not cloud_id:
|
||||
return "Error: Could not determine Atlassian Cloud instance."
|
||||
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>"
|
||||
async with httpx.AsyncClient(timeout=15.0) as client:
|
||||
resp = await client.post(
|
||||
f"https://api.atlassian.com/ex/confluence/{cloud_id}/wiki/rest/api/content",
|
||||
json={
|
||||
"type": "page",
|
||||
"title": title,
|
||||
"space": {"key": space_key},
|
||||
"body": {"storage": {"value": body_html, "representation": "storage"}},
|
||||
},
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
page_id = data["id"]
|
||||
return f"Page created: **{title}** (ID: {page_id})"
|
||||
except Exception as e:
|
||||
return f"Failed to create Confluence page: {e}"
|
||||
|
||||
async def jira_search(self, token: str, jql: str, limit: int = 10) -> str:
|
||||
cloud_id = await self._get_cloud_id(token)
|
||||
if not cloud_id:
|
||||
@@ -1909,6 +2015,12 @@ class Bot:
|
||||
return await self.atlassian.confluence_search(token, args["query"], args.get("limit", 5))
|
||||
elif tool_name == "confluence_read_page":
|
||||
return await self.atlassian.confluence_read_page(token, args["page_id"])
|
||||
elif tool_name == "confluence_update_page":
|
||||
return await self.atlassian.confluence_update_page(
|
||||
token, args["page_id"], args["section_heading"], args["new_content"])
|
||||
elif tool_name == "confluence_create_page":
|
||||
return await self.atlassian.confluence_create_page(
|
||||
token, args["title"], args["content"], args.get("space_key", "AG"))
|
||||
elif tool_name == "jira_search":
|
||||
return await self.atlassian.jira_search(token, args["jql"], args.get("limit", 10))
|
||||
elif tool_name == "jira_get_issue":
|
||||
|
||||
Reference in New Issue
Block a user