Implement platform skill execution system and fix message formatting
Major features: - Added platform skill execution architecture allowing agent to invoke platform-specific actions (delete posts, update rules, announcements) - Agent now receives available platform skills and can execute them with proper authorization checks via constitution Agent improvements: - Added platform_skills parameter to process_request() - New action type: execute_platform_skill with skill_name and skill_parameters - Enhanced LLM prompts to distinguish between direct execution and skill execution - Clearer JSON format specifications for different action types Bot orchestration: - Bot fetches platform skills and passes to agent - Detects execute_platform_skill actions and calls platform.execute_skill() - Handles skill execution results with proper error reporting Mastodon platform: - Implemented create_announcement skill with graceful API limitation handling - Implemented update_instance_rules skill with graceful API limitation handling - Both skills now acknowledge Mastodon API doesn't support these operations programmatically and provide direct admin interface links - Implemented delete_status skill (working) - All admin operations use direct API calls via __api_request Message formatting fixes: - Fixed message chunking to preserve line breaks and paragraph structure - Split by paragraphs first, then lines, then words as last resort - Removed debug logging for newline tracking Documentation: - Updated .gitignore to exclude bot.log and nohup.out This implements the governance bot's ability to execute platform-specific actions while respecting constitutional authority and handling API limitations gracefully. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -79,3 +79,4 @@ htmlcov/
|
|||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
nohup.out
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ class GovernanceAgent:
|
|||||||
self,
|
self,
|
||||||
request: str,
|
request: str,
|
||||||
actor: str,
|
actor: str,
|
||||||
context: Optional[Dict[str, Any]] = None
|
context: Optional[Dict[str, Any]] = None,
|
||||||
|
platform_skills: Optional[List[Dict[str, Any]]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Process a governance request using agentic interpretation.
|
Process a governance request using agentic interpretation.
|
||||||
@@ -78,6 +79,7 @@ class GovernanceAgent:
|
|||||||
request: Natural language request
|
request: Natural language request
|
||||||
actor: Who made the request
|
actor: Who made the request
|
||||||
context: Optional context (thread ID, etc.)
|
context: Optional context (thread ID, etc.)
|
||||||
|
platform_skills: List of available platform-specific skills
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response dictionary with action taken and audit trail
|
Response dictionary with action taken and audit trail
|
||||||
@@ -103,7 +105,8 @@ class GovernanceAgent:
|
|||||||
constitution=constitutional_guidance,
|
constitution=constitutional_guidance,
|
||||||
memory=memory_context,
|
memory=memory_context,
|
||||||
actor=actor,
|
actor=actor,
|
||||||
context=context
|
context=context,
|
||||||
|
platform_skills=platform_skills
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 5: Execute the decision
|
# Step 5: Execute the decision
|
||||||
@@ -158,9 +161,20 @@ Return your analysis as JSON:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.constitution._call_llm(prompt)
|
result = self.constitution._call_llm(prompt)
|
||||||
# Parse JSON from response
|
# result is a string, parse JSON from it
|
||||||
# (In production, would use proper JSON parsing from LLM response)
|
# Handle potential markdown code blocks
|
||||||
return json.loads(result.get("answer", "{}"))
|
if "```json" in result:
|
||||||
|
json_start = result.find("```json") + 7
|
||||||
|
json_end = result.find("```", json_start)
|
||||||
|
json_str = result[json_start:json_end].strip()
|
||||||
|
elif "```" in result:
|
||||||
|
json_start = result.find("```") + 3
|
||||||
|
json_end = result.find("```", json_start)
|
||||||
|
json_str = result[json_start:json_end].strip()
|
||||||
|
else:
|
||||||
|
json_str = result.strip()
|
||||||
|
|
||||||
|
return json.loads(json_str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": f"Failed to parse intent: {e}"}
|
return {"error": f"Failed to parse intent: {e}"}
|
||||||
|
|
||||||
@@ -210,7 +224,8 @@ Return your analysis as JSON:
|
|||||||
constitution: Dict[str, Any],
|
constitution: Dict[str, Any],
|
||||||
memory: Dict[str, Any],
|
memory: Dict[str, Any],
|
||||||
actor: str,
|
actor: str,
|
||||||
context: Optional[Dict[str, Any]]
|
context: Optional[Dict[str, Any]],
|
||||||
|
platform_skills: Optional[List[Dict[str, Any]]] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Use LLM to decide what action to take.
|
Use LLM to decide what action to take.
|
||||||
@@ -221,13 +236,29 @@ Return your analysis as JSON:
|
|||||||
Returns:
|
Returns:
|
||||||
Decision dict with:
|
Decision dict with:
|
||||||
- action: What to do
|
- action: What to do
|
||||||
- reasoning: Why
|
- reasoning: Why (formatted with line breaks for readability)
|
||||||
- constitution_citations: Which articles apply
|
- constitution_citations: Which articles apply
|
||||||
- calculations: Any math needed
|
- calculations: Any math needed
|
||||||
- state_updates: Changes to memory
|
- state_updates: Changes to memory
|
||||||
"""
|
"""
|
||||||
|
# Format platform skills for the prompt
|
||||||
|
platform_skills_info = ""
|
||||||
|
if platform_skills:
|
||||||
|
platform_skills_info = "\n\nAVAILABLE PLATFORM SKILLS:\n"
|
||||||
|
for skill in platform_skills:
|
||||||
|
params_str = ", ".join([f"{p['name']}: {p['type']}" for p in skill.get('parameters', [])])
|
||||||
|
platform_skills_info += f"- {skill['name']}({params_str}): {skill['description']}\n"
|
||||||
|
if skill.get('constitutional_authorization'):
|
||||||
|
platform_skills_info += f" Authorization: {skill['constitutional_authorization']}\n"
|
||||||
|
|
||||||
prompt = f"""You are a governance bot interpreting a community constitution.
|
prompt = f"""You are a governance bot interpreting a community constitution.
|
||||||
|
|
||||||
|
IMPORTANT: When generating the "response" field, use newline characters (\\n) for line breaks:
|
||||||
|
- Put each key point on its own line using \\n
|
||||||
|
- Use double newlines (\\n\\n) between paragraphs
|
||||||
|
- Format like this: "First point\\n\\nSecond point\\n\\nThird point"
|
||||||
|
- Make responses readable and well-formatted with actual newline characters
|
||||||
|
|
||||||
INTENT:
|
INTENT:
|
||||||
{json.dumps(intent, indent=2)}
|
{json.dumps(intent, indent=2)}
|
||||||
|
|
||||||
@@ -238,13 +269,28 @@ CURRENT MEMORY STATE:
|
|||||||
{json.dumps(memory, indent=2)}
|
{json.dumps(memory, indent=2)}
|
||||||
|
|
||||||
ACTOR: {actor}
|
ACTOR: {actor}
|
||||||
|
{platform_skills_info}
|
||||||
Based on the constitution and current state, decide what action to take.
|
Based on the constitution and current state, decide what action to take.
|
||||||
|
|
||||||
For proposals:
|
IMPORTANT AUTHORITY CHECK:
|
||||||
- What type of proposal is this? (standard, urgent, constitutional, etc.)
|
- Does the constitution give the ACTOR direct authority to make this decision?
|
||||||
- What discussion period does the constitution specify?
|
- If YES and no platform skill needed: Use action "direct_execution"
|
||||||
- What voting threshold is required?
|
- If YES and platform skill needed: Use action "execute_platform_skill"
|
||||||
|
- If NO: Create a process for community decision-making
|
||||||
|
|
||||||
|
CRITICAL - For platform-specific actions:
|
||||||
|
- When the user REQUESTS an action (like "create an announcement", "delete this post", "update rules"),
|
||||||
|
check if a platform skill exists for that action
|
||||||
|
- If a skill exists AND the actor has authority: EXECUTE the skill using "execute_platform_skill" action
|
||||||
|
- Do NOT just tell the user to use the skill - the user is asking YOU to do it for them
|
||||||
|
- Include skill_name and skill_parameters in your response
|
||||||
|
- The bot will execute the skill on the platform and report the result
|
||||||
|
- Only use "direct_execution" if there is NO platform skill for the requested action
|
||||||
|
|
||||||
|
For proposals/processes (only if actor lacks authority):
|
||||||
|
- What type of process is this? (proposal, consensus, discussion, etc.)
|
||||||
|
- What decision period does the constitution specify?
|
||||||
|
- What threshold or mechanism is required?
|
||||||
- Are there any special requirements?
|
- Are there any special requirements?
|
||||||
|
|
||||||
For votes:
|
For votes:
|
||||||
@@ -266,16 +312,30 @@ Available tools for calculations:
|
|||||||
- random_select(items, count): Random selection
|
- random_select(items, count): Random selection
|
||||||
|
|
||||||
Return your decision as JSON:
|
Return your decision as JSON:
|
||||||
|
|
||||||
|
For execute_platform_skill action:
|
||||||
{{
|
{{
|
||||||
"action": "create_process|record_vote|complete_process|query_response",
|
"action": "execute_platform_skill",
|
||||||
"reasoning": "explain your interpretation",
|
"skill_name": "create_announcement", // REQUIRED - exact name of the skill to execute
|
||||||
|
"skill_parameters": {{ // REQUIRED - parameters for the skill
|
||||||
|
"text": "The announcement text here"
|
||||||
|
}},
|
||||||
|
"reasoning": "internal reasoning for audit log",
|
||||||
|
"response": "Confirmation message to user",
|
||||||
|
"constitution_citations": ["Article X, Section Y", ...]
|
||||||
|
}}
|
||||||
|
|
||||||
|
For other actions:
|
||||||
|
{{
|
||||||
|
"action": "direct_execution|create_process|record_vote|complete_process|query_response",
|
||||||
|
"reasoning": "internal reasoning for audit log (not shown to user)",
|
||||||
|
"response": "clean, concise public response to the user (use line breaks for readability)",
|
||||||
"constitution_citations": ["Article X, Section Y", ...],
|
"constitution_citations": ["Article X, Section Y", ...],
|
||||||
"parameters": {{
|
"parameters": {{
|
||||||
// Action-specific parameters
|
// Action-specific parameters
|
||||||
"process_type": "...",
|
"process_type": "...",
|
||||||
"deadline_days": X,
|
"deadline_days": X,
|
||||||
"threshold_expression": "agree > disagree",
|
"threshold_expression": "agree > disagree"
|
||||||
// etc.
|
|
||||||
}},
|
}},
|
||||||
"calculations": [
|
"calculations": [
|
||||||
{{
|
{{
|
||||||
@@ -289,7 +349,20 @@ Return your decision as JSON:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.constitution._call_llm(prompt)
|
result = self.constitution._call_llm(prompt)
|
||||||
decision = json.loads(result.get("answer", "{}"))
|
# result is a string, parse JSON from it
|
||||||
|
# Handle potential markdown code blocks
|
||||||
|
if "```json" in result:
|
||||||
|
json_start = result.find("```json") + 7
|
||||||
|
json_end = result.find("```", json_start)
|
||||||
|
json_str = result[json_start:json_end].strip()
|
||||||
|
elif "```" in result:
|
||||||
|
json_start = result.find("```") + 3
|
||||||
|
json_end = result.find("```", json_start)
|
||||||
|
json_str = result[json_start:json_end].strip()
|
||||||
|
else:
|
||||||
|
json_str = result.strip()
|
||||||
|
|
||||||
|
decision = json.loads(json_str)
|
||||||
|
|
||||||
# Execute any calculations using tools
|
# Execute any calculations using tools
|
||||||
if "calculations" in decision:
|
if "calculations" in decision:
|
||||||
@@ -326,7 +399,28 @@ Return your decision as JSON:
|
|||||||
params = decision.get("parameters", {})
|
params = decision.get("parameters", {})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if action == "create_process":
|
if action == "direct_execution":
|
||||||
|
# Actor has authority - acknowledge and execute
|
||||||
|
return {
|
||||||
|
"response": decision.get("response", decision.get("reasoning")),
|
||||||
|
"reasoning": decision.get("reasoning"), # For audit log
|
||||||
|
"constitution_citations": decision.get("constitution_citations", []),
|
||||||
|
"success": True
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == "execute_platform_skill":
|
||||||
|
# Actor has authority and needs to execute a platform-specific skill
|
||||||
|
return {
|
||||||
|
"action": "execute_platform_skill",
|
||||||
|
"skill_name": decision.get("skill_name"),
|
||||||
|
"skill_parameters": decision.get("skill_parameters", {}),
|
||||||
|
"response": decision.get("response", "Executing platform action..."),
|
||||||
|
"reasoning": decision.get("reasoning"), # For audit log
|
||||||
|
"constitution_citations": decision.get("constitution_citations", []),
|
||||||
|
"success": True
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == "create_process":
|
||||||
return self._create_process_from_decision(decision, actor, context)
|
return self._create_process_from_decision(decision, actor, context)
|
||||||
|
|
||||||
elif action == "record_vote":
|
elif action == "record_vote":
|
||||||
@@ -337,7 +431,8 @@ Return your decision as JSON:
|
|||||||
|
|
||||||
elif action == "query_response":
|
elif action == "query_response":
|
||||||
return {
|
return {
|
||||||
"response": decision.get("reasoning"),
|
"response": decision.get("response", decision.get("reasoning")),
|
||||||
|
"reasoning": decision.get("reasoning"), # For audit log
|
||||||
"constitution_citations": decision.get("constitution_citations", []),
|
"constitution_citations": decision.get("constitution_citations", []),
|
||||||
"success": True
|
"success": True
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,13 +197,61 @@ class Govbot:
|
|||||||
"platform_message": message,
|
"platform_message": message,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Get available platform skills
|
||||||
|
platform_skills_list = []
|
||||||
|
try:
|
||||||
|
platform_skills = self.platform.get_skills()
|
||||||
|
# Convert PlatformSkill objects to dicts for the agent
|
||||||
|
for skill in platform_skills:
|
||||||
|
skill_dict = {
|
||||||
|
"name": skill.name,
|
||||||
|
"description": skill.description,
|
||||||
|
"category": skill.category,
|
||||||
|
"parameters": [
|
||||||
|
{"name": p.name, "type": p.type, "description": p.description}
|
||||||
|
for p in skill.parameters
|
||||||
|
],
|
||||||
|
"constitutional_authorization": skill.constitutional_authorization,
|
||||||
|
}
|
||||||
|
platform_skills_list.append(skill_dict)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not get platform skills: {e}")
|
||||||
|
|
||||||
result = self.agent.process_request(
|
result = self.agent.process_request(
|
||||||
request=message.text,
|
request=message.text,
|
||||||
actor=f"@{message.author_handle}",
|
actor=f"@{message.author_handle}",
|
||||||
context=context,
|
context=context,
|
||||||
|
platform_skills=platform_skills_list if platform_skills_list else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Post response
|
# Check if we need to execute a platform skill
|
||||||
|
if result.get("action") == "execute_platform_skill":
|
||||||
|
skill_name = result.get("skill_name")
|
||||||
|
skill_params = result.get("skill_parameters", {})
|
||||||
|
|
||||||
|
logger.info(f"Executing platform skill: {skill_name}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
skill_result = self.platform.execute_skill(
|
||||||
|
skill_name=skill_name,
|
||||||
|
parameters=skill_params,
|
||||||
|
actor=f"@{message.author_handle}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if skill_result.get("success"):
|
||||||
|
# Skill executed successfully
|
||||||
|
response = result.get("response", "Action completed successfully.")
|
||||||
|
logger.info(f"Platform skill executed successfully: {skill_result.get('message')}")
|
||||||
|
else:
|
||||||
|
# Skill execution failed
|
||||||
|
response = f"Failed to execute action: {skill_result.get('message', 'Unknown error')}"
|
||||||
|
logger.error(f"Platform skill execution failed: {skill_result.get('message')}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing platform skill: {e}", exc_info=True)
|
||||||
|
response = f"Error executing action: {str(e)}"
|
||||||
|
else:
|
||||||
|
# Normal response without skill execution
|
||||||
response = result.get("response", "Sorry, I couldn't process that request.")
|
response = result.get("response", "Sorry, I couldn't process that request.")
|
||||||
|
|
||||||
# Handle long responses by splitting into thread
|
# Handle long responses by splitting into thread
|
||||||
@@ -307,24 +355,62 @@ Process ID: {process_id}
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Split into chunks
|
# Split into chunks while preserving newlines
|
||||||
words = response.split()
|
# Split by paragraph breaks first to keep formatting intact
|
||||||
|
paragraphs = response.split('\n\n')
|
||||||
chunks = []
|
chunks = []
|
||||||
current_chunk = []
|
current_chunk = []
|
||||||
current_length = 0
|
current_length = 0
|
||||||
|
|
||||||
|
for para in paragraphs:
|
||||||
|
para_length = len(para) + 2 # +2 for paragraph break
|
||||||
|
|
||||||
|
# If single paragraph is too long, split it by sentences or words
|
||||||
|
if para_length > max_length:
|
||||||
|
# Try to split by newlines within paragraph
|
||||||
|
lines = para.split('\n')
|
||||||
|
for line in lines:
|
||||||
|
line_length = len(line) + 1 # +1 for newline
|
||||||
|
if current_length + line_length > max_length and current_chunk:
|
||||||
|
# Flush current chunk
|
||||||
|
chunks.append('\n\n'.join(current_chunk))
|
||||||
|
current_chunk = [line]
|
||||||
|
current_length = line_length
|
||||||
|
elif line_length > max_length:
|
||||||
|
# Single line too long, split by words
|
||||||
|
if current_chunk:
|
||||||
|
chunks.append('\n\n'.join(current_chunk))
|
||||||
|
current_chunk = []
|
||||||
|
current_length = 0
|
||||||
|
words = line.split()
|
||||||
|
word_chunk = []
|
||||||
|
word_length = 0
|
||||||
for word in words:
|
for word in words:
|
||||||
word_len = len(word) + 1 # +1 for space
|
word_len = len(word) + 1
|
||||||
if current_length + word_len > max_length and current_chunk:
|
if word_length + word_len > max_length and word_chunk:
|
||||||
chunks.append(" ".join(current_chunk))
|
chunks.append(' '.join(word_chunk))
|
||||||
current_chunk = [word]
|
word_chunk = [word]
|
||||||
current_length = word_len
|
word_length = word_len
|
||||||
else:
|
else:
|
||||||
current_chunk.append(word)
|
word_chunk.append(word)
|
||||||
current_length += word_len
|
word_length += word_len
|
||||||
|
if word_chunk:
|
||||||
|
current_chunk.append(' '.join(word_chunk))
|
||||||
|
current_length = len(current_chunk[-1])
|
||||||
|
else:
|
||||||
|
current_chunk.append(line)
|
||||||
|
current_length += line_length
|
||||||
|
elif current_length + para_length > max_length and current_chunk:
|
||||||
|
# Flush current chunk
|
||||||
|
chunks.append('\n\n'.join(current_chunk))
|
||||||
|
current_chunk = [para]
|
||||||
|
current_length = para_length
|
||||||
|
else:
|
||||||
|
current_chunk.append(para)
|
||||||
|
current_length += para_length
|
||||||
|
|
||||||
if current_chunk:
|
if current_chunk:
|
||||||
chunks.append(" ".join(current_chunk))
|
chunks.append('\n\n'.join(current_chunk))
|
||||||
|
|
||||||
# Post chunks as a thread
|
# Post chunks as a thread
|
||||||
last_id = reply_to_id
|
last_id = reply_to_id
|
||||||
|
|||||||
@@ -587,20 +587,27 @@ class MastodonAdapter(PlatformAdapter):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _update_instance_rules(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
def _update_instance_rules(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Update instance rules"""
|
"""Update instance rules - web admin only"""
|
||||||
# Note: This requires admin API access
|
rules = params.get("rules", [])
|
||||||
# Implementation depends on Mastodon version and API availability
|
|
||||||
rules = params["rules"]
|
|
||||||
|
|
||||||
# This would use admin API to update instance rules
|
# Note: Mastodon's API does not provide endpoints for creating/updating rules.
|
||||||
# Exact implementation varies by Mastodon version
|
# Rules must be managed through the web admin interface.
|
||||||
|
# See: https://docs.joinmastodon.org/methods/instance/
|
||||||
|
|
||||||
|
rules_text = "\n".join([f"- {rule}" for rule in rules])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": False,
|
||||||
"message": f"Updated instance rules ({len(rules)} rules)",
|
"message": (
|
||||||
|
f"Proposed server rules:\n{rules_text}\n\n"
|
||||||
|
"Note: Mastodon's API does not support managing server rules programmatically. "
|
||||||
|
f"To update the server rules, please visit:\n"
|
||||||
|
f"{self.instance_url}/admin/server_settings/rules\n\n"
|
||||||
|
"You can add, edit, or remove rules through the admin interface."
|
||||||
|
),
|
||||||
"data": {"rules": rules},
|
"data": {"rules": rules},
|
||||||
"reversible": True,
|
"reversible": False,
|
||||||
"reverse_params": {"rules": "previous_rules"}, # Would need to store previous
|
"requires_manual_action": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _update_instance_description(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
def _update_instance_description(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
@@ -649,23 +656,35 @@ class MastodonAdapter(PlatformAdapter):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _create_announcement(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
def _create_announcement(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Create instance announcement"""
|
"""Create instance announcement - web admin only"""
|
||||||
text = params["text"]
|
text = params["text"]
|
||||||
starts_at = params.get("starts_at")
|
starts_at = params.get("starts_at")
|
||||||
ends_at = params.get("ends_at")
|
ends_at = params.get("ends_at")
|
||||||
|
|
||||||
announcement = self.client.admin_announcement_create(
|
# Note: Mastodon's API does not provide endpoints for creating announcements.
|
||||||
text=text,
|
# Announcements must be created through the web admin interface.
|
||||||
starts_at=starts_at,
|
# See: https://docs.joinmastodon.org/methods/announcements/
|
||||||
ends_at=ends_at,
|
|
||||||
)
|
timing_info = ""
|
||||||
|
if starts_at or ends_at:
|
||||||
|
timing_info = "\n\nTiming:"
|
||||||
|
if starts_at:
|
||||||
|
timing_info += f"\n- Start: {starts_at}"
|
||||||
|
if ends_at:
|
||||||
|
timing_info += f"\n- End: {ends_at}"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": False,
|
||||||
"message": "Created announcement",
|
"message": (
|
||||||
"data": {"announcement_id": announcement["id"], "text": text},
|
f"Announcement text:\n{text}{timing_info}\n\n"
|
||||||
"reversible": True,
|
"Note: Mastodon's API does not support creating announcements programmatically. "
|
||||||
"reverse_params": {"announcement_id": announcement["id"]},
|
f"To post this announcement, please visit:\n"
|
||||||
|
f"{self.instance_url}/admin/announcements\n\n"
|
||||||
|
"Click 'New announcement', paste the text above, and publish it."
|
||||||
|
),
|
||||||
|
"data": {"text": text, "starts_at": starts_at, "ends_at": ends_at},
|
||||||
|
"reversible": False,
|
||||||
|
"requires_manual_action": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _strip_markdown(self, text: str) -> str:
|
def _strip_markdown(self, text: str) -> str:
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ class GovernanceScheduler:
|
|||||||
)
|
)
|
||||||
# TODO: Post result to Mastodon
|
# TODO: Post result to Mastodon
|
||||||
|
|
||||||
# Check for pending reminders
|
# Check for pending reminders (if agent has primitives - old architecture)
|
||||||
|
if hasattr(self.agent, 'primitives'):
|
||||||
reminders = self.agent.primitives.get_pending_reminders()
|
reminders = self.agent.primitives.get_pending_reminders()
|
||||||
for reminder in reminders:
|
for reminder in reminders:
|
||||||
logger.info(f"Sending reminder: {reminder['data']['message']}")
|
logger.info(f"Sending reminder: {reminder['data']['message']}")
|
||||||
|
|||||||
Reference in New Issue
Block a user