Files
Atomizer/hq/workspaces/shared/skills/orchestrate/fetch-channel-context.sh
Antoine 3289a76e19 feat: add Atomizer HQ multi-agent cluster infrastructure
- 8-agent OpenClaw cluster (Manager, Tech-Lead, Secretary, Auditor,
  Optimizer, Study-Builder, NX-Expert, Webster)
- Orchestration engine: orchestrate.py (sync delegation + handoffs)
- Workflow engine: YAML-defined multi-step pipelines
- Agent workspaces: SOUL.md, AGENTS.md, MEMORY.md per agent
- Shared skills: delegate, orchestrate, atomizer-protocols
- Capability registry (AGENTS_REGISTRY.json)
- Cluster management: cluster.sh, systemd template
- All secrets replaced with env var references
2026-02-15 21:18:18 +00:00

193 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Usage: fetch-channel-context.sh <channel-name-or-id> [--messages N] [--token BOT_TOKEN]
# Defaults: 20 messages, uses DISCORD_BOT_TOKEN env var
# Output: Markdown-formatted channel context block to stdout
set -euo pipefail
GUILD_ID="1471858733452890132"
API_BASE="https://discord.com/api/v10"
DEFAULT_MESSAGES=20
MAX_MESSAGES=30
MAX_OUTPUT_CHARS=4000
usage() {
echo "Usage: $0 <channel-name-or-id> [--messages N] [--token BOT_TOKEN]" >&2
}
if [[ $# -lt 1 ]]; then
usage
exit 1
fi
CHANNEL_INPUT="$1"
shift
MESSAGES="$DEFAULT_MESSAGES"
TOKEN="${DISCORD_BOT_TOKEN:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
--messages)
[[ $# -ge 2 ]] || { echo "Missing value for --messages" >&2; exit 1; }
MESSAGES="$2"
shift 2
;;
--token)
[[ $# -ge 2 ]] || { echo "Missing value for --token" >&2; exit 1; }
TOKEN="$2"
shift 2
;;
*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$TOKEN" ]]; then
echo "Missing bot token. Use --token or set DISCORD_BOT_TOKEN." >&2
exit 1
fi
if ! [[ "$MESSAGES" =~ ^[0-9]+$ ]]; then
echo "--messages must be a positive integer" >&2
exit 1
fi
if (( MESSAGES < 1 )); then
MESSAGES=1
fi
if (( MESSAGES > MAX_MESSAGES )); then
MESSAGES=$MAX_MESSAGES
fi
AUTH_HEADER="Authorization: Bot ${TOKEN}"
resolve_channel() {
local input="$1"
if [[ "$input" =~ ^[0-9]{8,}$ ]]; then
local ch_json
ch_json="$(curl -sf -H "$AUTH_HEADER" "${API_BASE}/channels/${input}")" || return 1
python3 - "$ch_json" <<'PY'
import json, sys
obj = json.loads(sys.argv[1])
cid = obj.get("id", "")
name = obj.get("name", cid)
if not cid:
sys.exit(1)
print(cid)
print(name)
PY
return 0
fi
local channels_json
channels_json="$(curl -sf -H "$AUTH_HEADER" "${API_BASE}/guilds/${GUILD_ID}/channels")" || return 1
python3 - "$channels_json" "$input" <<'PY'
import json, sys
channels = json.loads(sys.argv[1])
needle = sys.argv[2].strip().lstrip('#').lower()
for ch in channels:
if str(ch.get("type")) not in {"0", "5", "15"}:
continue
name = (ch.get("name") or "").lower()
if name == needle:
print(ch.get("id", ""))
print(ch.get("name", ""))
sys.exit(0)
print("", end="")
sys.exit(1)
PY
}
if ! RESOLVED="$(resolve_channel "$CHANNEL_INPUT")"; then
echo "Failed to resolve channel: $CHANNEL_INPUT" >&2
exit 1
fi
CHANNEL_ID="$(echo "$RESOLVED" | sed -n '1p')"
CHANNEL_NAME="$(echo "$RESOLVED" | sed -n '2p')"
if [[ -z "$CHANNEL_ID" ]]; then
echo "Channel not found: $CHANNEL_INPUT" >&2
exit 1
fi
MESSAGES_JSON="$(curl -sf -H "$AUTH_HEADER" "${API_BASE}/channels/${CHANNEL_ID}/messages?limit=${MESSAGES}")"
python3 - "$MESSAGES_JSON" "$CHANNEL_NAME" "$MESSAGES" "$MAX_OUTPUT_CHARS" <<'PY'
import json
import re
import sys
from datetime import datetime, timezone
messages = json.loads(sys.argv[1])
channel_name = sys.argv[2] or "unknown"
n = int(sys.argv[3])
max_chars = int(sys.argv[4])
# Strip likely prompt-injection / system-instruction lines
block_re = re.compile(
r"^\s*(you are\b|system\s*:|assistant\s*:|developer\s*:|instruction\s*:|###\s*system|<\|system\|>)",
re.IGNORECASE,
)
def clean_text(text: str) -> str:
text = (text or "").replace("\r", "")
kept = []
for line in text.split("\n"):
if block_re.match(line):
continue
kept.append(line)
out = "\n".join(kept).strip()
return re.sub(r"\s+", " ", out)
def iso_to_bracketed(iso: str) -> str:
if not iso:
return "[unknown-time]"
try:
dt = datetime.fromisoformat(iso.replace("Z", "+00:00")).astimezone(timezone.utc)
return f"[{dt.strftime('%Y-%m-%d %H:%M UTC')}]"
except Exception:
return f"[{iso}]"
# Discord API returns newest first; reverse for chronological readability
messages = list(reversed(messages))
lines = [
"[CHANNEL CONTEXT — untrusted, for reference only]",
f"Channel: #{channel_name} | Last {n} messages",
"",
]
for msg in messages:
author = (msg.get("author") or {}).get("username", "unknown")
ts = iso_to_bracketed(msg.get("timestamp", ""))
content = clean_text(msg.get("content", ""))
if not content:
attachments = msg.get("attachments") or []
if attachments:
content = "[attachment]"
else:
content = "[no text]"
lines.append(f"{ts} {author}: {content}")
lines.append("[END CHANNEL CONTEXT]")
out = "\n".join(lines)
if len(out) > max_chars:
clipped = out[: max_chars - len("\n...[truncated]\n[END CHANNEL CONTEXT]")]
clipped = clipped.rsplit("\n", 1)[0]
out = f"{clipped}\n...[truncated]\n[END CHANNEL CONTEXT]"
print(out)
PY