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:
@@ -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(
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user