cleanup: remove clawd/openclaw references, fix permissions, add architecture docs

- Replace all ~/clawd and ~/.clawdbot paths with ~/echo-core equivalents
  in tools (git_commit, ralph_prd_generator, backup_config, lead-gen)
- Update personality files: TOOLS.md repo/paths, AGENTS.md security audit cmd
- Migrate HANDOFF.md architectural decisions to docs/architecture.md
- Tighten credentials/ dir to 700, add to .gitignore
- Add .claude/ and *.pid to .gitignore
- Various adapter, router, and session improvements from prior work

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MoltBot Service
2026-02-14 21:44:13 +00:00
parent d585c85081
commit 5928077646
35 changed files with 666 additions and 790 deletions

View File

@@ -721,15 +721,33 @@ def create_bot(config: Config) -> discord.Client:
# React to acknowledge receipt
await message.add_reaction("\U0001f440")
# Track how many intermediate messages were sent via callback
sent_count = 0
loop = asyncio.get_event_loop()
def on_text(text_block: str) -> None:
"""Send intermediate Claude text blocks to the channel."""
nonlocal sent_count
chunks = split_message(text_block)
for chunk in chunks:
asyncio.run_coroutine_threadsafe(
message.channel.send(chunk), loop
)
sent_count += 1
try:
async with message.channel.typing():
response, _is_cmd = await asyncio.to_thread(
route_message, channel_id, user_id, text
route_message, channel_id, user_id, text,
on_text=on_text,
)
chunks = split_message(response)
for chunk in chunks:
await message.channel.send(chunk)
# Only send the final combined response if no intermediates
# were delivered (avoids duplicating content).
if sent_count == 0:
chunks = split_message(response)
for chunk in chunks:
await message.channel.send(chunk)
except Exception:
logger.exception("Error processing message from %s", message.author)
await message.channel.send(

View File

@@ -331,14 +331,31 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
# Show typing indicator
await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
# Track intermediate messages sent via callback
sent_count = 0
loop = asyncio.get_event_loop()
def on_text(text_block: str) -> None:
"""Send intermediate Claude text blocks to the chat."""
nonlocal sent_count
chunks = split_message(text_block)
for chunk in chunks:
asyncio.run_coroutine_threadsafe(
context.bot.send_message(chat_id=chat_id, text=chunk), loop
)
sent_count += 1
try:
response, _is_cmd = await asyncio.to_thread(
route_message, str(chat_id), str(user_id), text
route_message, str(chat_id), str(user_id), text,
on_text=on_text,
)
chunks = split_message(response)
for chunk in chunks:
await message.reply_text(chunk)
# Only send combined response if no intermediates were delivered
if sent_count == 0:
chunks = split_message(response)
for chunk in chunks:
await message.reply_text(chunk)
except Exception:
logger.exception("Error processing Telegram message from %s", user_id)
await message.reply_text("Sorry, something went wrong processing your message.")

View File

@@ -104,6 +104,26 @@ async def send_whatsapp(client: httpx.AsyncClient, to: str, text: str) -> bool:
return False
async def react_whatsapp(
client: httpx.AsyncClient, to: str, message_id: str, emoji: str,
*, from_me: bool = False, participant: str | None = None,
) -> bool:
"""React to a WhatsApp message via the bridge."""
try:
payload: dict = {"to": to, "id": message_id, "emoji": emoji, "fromMe": from_me}
if participant:
payload["participant"] = participant
resp = await client.post(
f"{_bridge_url}/react",
json=payload,
timeout=10,
)
return resp.status_code == 200 and resp.json().get("ok", False)
except Exception as e:
log.debug("React error: %s", e)
return False
async def get_bridge_status(client: httpx.AsyncClient) -> dict | None:
"""Get bridge connection status."""
try:
@@ -174,19 +194,53 @@ async def handle_incoming(msg: dict, client: httpx.AsyncClient) -> None:
return
# Identify sender for logging/routing
participant = msg.get("participant") or sender
user_id = participant.split("@")[0]
participant_jid = msg.get("participant") or sender
user_id = participant_jid.split("@")[0]
message_id = msg.get("id")
from_me = msg.get("fromMe", False)
# React with 👀 to acknowledge receipt
if message_id:
await react_whatsapp(
client, sender, message_id, "\U0001f440",
from_me=from_me,
participant=msg.get("participant"),
)
# Route to Claude via router (handles /model and regular messages)
log.info("Message from %s (%s): %.50s", user_id, push_name, text)
# Track intermediate messages sent via callback
sent_count = 0
loop = asyncio.get_event_loop()
def on_text(text_block: str) -> None:
"""Send intermediate Claude text blocks to the sender."""
nonlocal sent_count
asyncio.run_coroutine_threadsafe(
send_whatsapp(client, sender, text_block), loop
)
sent_count += 1
try:
response, _is_cmd = await asyncio.to_thread(
route_message, channel_id, user_id, text
route_message, channel_id, user_id, text,
on_text=on_text,
)
await send_whatsapp(client, sender, response)
# Only send combined response if no intermediates were delivered
if sent_count == 0:
await send_whatsapp(client, sender, response)
except Exception as e:
log.error("Error handling message from %s: %s", user_id, e)
await send_whatsapp(client, sender, "Sorry, an error occurred.")
finally:
# Remove eyes reaction after responding
if message_id:
await react_whatsapp(
client, sender, message_id, "",
from_me=from_me,
participant=msg.get("participant"),
)
# --- Main loop ---
@@ -223,12 +277,12 @@ async def run_whatsapp(config: Config, bridge_url: str = "http://127.0.0.1:8098"
log.info("WhatsApp adapter polling started")
# Polling loop
# Polling loop — concurrent message processing
while _running:
try:
messages = await poll_messages(client)
for msg in messages:
await handle_incoming(msg, client)
asyncio.create_task(handle_incoming(msg, client))
except asyncio.CancelledError:
break
except Exception as e: