feat(ralph): unified slash commands /p /a /l /k cu legacy aliases
Restructurează comenzile Ralph într-un dispatcher unificat (_try_ralph_dispatch) care suportă atât comenzile noi scurte (/p /a /l /k) cât și aliasurile legacy (!propose !approve !status !stop). Pe Discord adaugă slash commands native cu autocomplete dinamic pentru pending (/a) și running (/k). Pe Telegram apar în meniul /. WhatsApp le parsează ca text plain. Activează cron jobs morning-report (08:30) și evening-report (21:00) și adaugă night-execute (23:00) pentru execuția autonomă a proiectelor aprobate. Foundation pentru W1 din planul "Echo Core conversational planning agent". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,14 @@ from src.claude_session import (
|
||||
VALID_MODELS,
|
||||
)
|
||||
from src.fast_commands import dispatch as fast_dispatch
|
||||
from src.router import route_message
|
||||
from src.router import (
|
||||
route_message,
|
||||
_ralph_propose,
|
||||
_ralph_approve,
|
||||
_ralph_status,
|
||||
_ralph_stop,
|
||||
_load_approved_tasks,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("echo-core.discord")
|
||||
_security_log = logging.getLogger("echo-core.security")
|
||||
@@ -150,6 +157,12 @@ def create_bot(config: Config) -> discord.Client:
|
||||
"`/heartbeat` — Health checks",
|
||||
"`/restart` — Restart bot (owner)",
|
||||
"",
|
||||
"**Ralph (autonomous projects)**",
|
||||
"`/p <slug> <description>` — Propose new project",
|
||||
"`/a [slug]` — Approve for tonight (autocomplete)",
|
||||
"`/l` — List projects status",
|
||||
"`/k <slug>` — Stop a running project (autocomplete)",
|
||||
"",
|
||||
"**Admin**",
|
||||
"`/setup` — Claim ownership",
|
||||
"`/channel add <alias>` — Register channel",
|
||||
@@ -886,6 +899,68 @@ def create_bot(config: Config) -> discord.Client:
|
||||
f"Error reading logs: {e}", ephemeral=True
|
||||
)
|
||||
|
||||
# --- Ralph commands (autonomous project execution) ---
|
||||
|
||||
async def _autocomplete_by_status(
|
||||
interaction: discord.Interaction, current: str, statuses: tuple[str, ...]
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
try:
|
||||
data = _load_approved_tasks()
|
||||
except Exception:
|
||||
return []
|
||||
current_low = (current or "").lower()
|
||||
choices: list[app_commands.Choice[str]] = []
|
||||
for p in data.get("projects", []):
|
||||
if p.get("status") not in statuses:
|
||||
continue
|
||||
name = p.get("name", "")
|
||||
if current_low and current_low not in name.lower():
|
||||
continue
|
||||
desc = (p.get("description") or "").strip()
|
||||
label = f"{name} — {desc}"[:100] if desc else name
|
||||
choices.append(app_commands.Choice(name=label, value=name))
|
||||
if len(choices) >= 25:
|
||||
break
|
||||
return choices
|
||||
|
||||
async def _ralph_autocomplete_pending(
|
||||
interaction: discord.Interaction, current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
return await _autocomplete_by_status(interaction, current, ("pending",))
|
||||
|
||||
async def _ralph_autocomplete_running(
|
||||
interaction: discord.Interaction, current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
return await _autocomplete_by_status(interaction, current, ("running", "approved"))
|
||||
|
||||
@tree.command(name="p", description="Propose new Ralph project")
|
||||
@app_commands.describe(slug="Project slug (e.g. game-library)", description="Short description of what to do")
|
||||
async def ralph_p(
|
||||
interaction: discord.Interaction, slug: str, description: str
|
||||
) -> None:
|
||||
await interaction.response.send_message(_ralph_propose(slug, description))
|
||||
|
||||
@tree.command(name="a", description="Approve Ralph project for tonight (no slug = list pending)")
|
||||
@app_commands.describe(slug="Project slug to approve (leave empty to list pending)")
|
||||
@app_commands.autocomplete(slug=_ralph_autocomplete_pending)
|
||||
async def ralph_a(
|
||||
interaction: discord.Interaction, slug: str | None = None
|
||||
) -> None:
|
||||
slugs = [slug] if slug else []
|
||||
await interaction.response.send_message(_ralph_approve(slugs))
|
||||
|
||||
@tree.command(name="l", description="List Ralph projects status")
|
||||
async def ralph_l(interaction: discord.Interaction) -> None:
|
||||
await interaction.response.send_message(_ralph_status())
|
||||
|
||||
@tree.command(name="k", description="Stop a running Ralph project")
|
||||
@app_commands.describe(slug="Project slug to stop")
|
||||
@app_commands.autocomplete(slug=_ralph_autocomplete_running)
|
||||
async def ralph_k(
|
||||
interaction: discord.Interaction, slug: str
|
||||
) -> None:
|
||||
await interaction.response.send_message(_ralph_stop(slug))
|
||||
|
||||
# --- Events ---
|
||||
|
||||
@client.event
|
||||
|
||||
Reference in New Issue
Block a user