Heartbeat is now handled exclusively by the Claude-based cron job
(heartbeat-2h in jobs.json), which is more flexible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AsyncIOScheduler now runs in Europe/Bucharest so cron strings in jobs.json
match local wall-clock time.
- New add_shell_job() validates name, cron, command list, channel, report_on
(always|changes|never), and optional timeout (1..3600s). Existing add_job()
stays untouched for the Claude path.
- _execute_job dispatches on job['kind'] (default 'claude'); legacy jobs
without the field still route to the Claude executor. Refactored the
Claude path into _execute_claude_job; new _execute_shell_job runs
subprocess with _safe_env + PROJECT_ROOT cwd.
- Shell semantics: non-zero exit always forwards stderr (trimmed to 500 ch)
as '[cron:NAME] exit CODE: STDERR' regardless of report_on. On exit 0,
'always' forwards stdout (trimmed to 1500 ch), 'never' stays silent, and
'changes' parses the GSTACK-CRON marker (^GSTACK-CRON: changes=\d+$) and
forwards stdout only when N>0; missing/malformed marker logs a warning
and stays silent.
- Timeout honoured per-job (falls back to JOB_TIMEOUT=300s).
Digest summarizes unread emails via Claude CLI; forward sends raw
content (split to 4096 chars). Wired as /email digest and
/email forward slash commands, plus instant per-guild sync on ready.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beehiiv redirects non-existent newsletters to /?404=... with HTTP 302.
With follow_redirects=True, the final 200 was misread as "newsletter exists".
Fix: disable redirect following so only a direct HTTP 200 = newsletter real.
Also reset state back to last_sent=13 (real).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- heartbeat saves unread whitelisted emails via email_process --save --json
- fix: add --add-dir so Claude CLI subprocess can access memory/ symlink
- email_check/process: use BODY.PEEK[] to avoid marking emails as read
- email_process: simplify credential loading via credential_store only
- config: heartbeat interval 30→120min, quiet hours end 08→07
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
KB and embeddings reindex status messages were being sent to Discord
as heartbeat results. These are internal housekeeping — now logged
instead of surfaced to the user.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Switch Bash permission patterns from space to colon separator
- Add memory.bak/ to .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- claude_session: replace 10 individual git command patterns with single Bash(git *) wildcard
- generate_pdf: add italic/bold-oblique font loading and render_rich_text() for inline bold/italic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fast commands for git, email, calendar, notes, search, reminders, and
diagnostics — all execute instantly without Claude CLI. Incremental
embeddings indexing in heartbeat (1h cooldown) + inline indexing after
/note, /jurnal, /email save. Fix Ollama URL (localhost → 10.0.20.161),
fix email_process.py KB path (kb/ → memory/kb/).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Calendar no longer bypasses quiet hours. First run after quiet hours
sends full daily summary, subsequent runs only remind for next event
within 45 min with deduplication. Calendar cooldown set to 30 min.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Heartbeat system overhaul:
- Fix email/calendar checks to parse JSON output correctly
- Add per-check cooldowns and quiet hours config
- Send findings to Discord channel instead of just logging
- Auto-reindex KB when stale files detected
- Claude CLI called only if HEARTBEAT.md has extra instructions
- All settings configurable via config.json heartbeat section
Move hardcoded values to config.json:
- allowed_tools list (claude_session.py)
- Ollama URL/model (memory_search.py now reads ollama.url from config)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Git commands without arguments (git push, git status, git diff, etc.) were not matched by the existing wildcard patterns. Added bare variants and git stash support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Switch from --output-format json to --output-format stream-json --verbose
so that _run_claude() parses all assistant text blocks (not just the final
result field). Discord/Telegram/WhatsApp now receive every intermediate
message Claude writes between tool calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLAUDE.md rewritten to clearly establish Echo's identity and role.
claude_session.py now passes --allowedTools to Claude CLI in both
start_session() and resume_session(), with explicit tool whitelist:
- File tools (Read/Edit/Write/Glob/Grep) + WebFetch/WebSearch (read-only)
- Bash restricted by command prefix (git, python, npm, docker, systemctl)
- SSH/SCP/rsync limited to local network (10.0.20.*)
- curl/wget excluded to prevent data exfiltration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bridge: allow fromMe messages in groups, include participant field in
message queue, bind to 0.0.0.0 for network access, QR served as HTML.
Adapter: process registered group messages (route to Claude), extract
participant for user identification, fix unbound 'phone' variable.
Tested end-to-end: WhatsApp group chat with Claude working. 442 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Node.js bridge (bridge/whatsapp/): Baileys client with Express HTTP API
on localhost:8098 — QR code linking, message queue, reconnection logic.
Python adapter (src/adapters/whatsapp.py): polls bridge every 2s, routes
through router.py, separate whatsapp.owner/admins auth, security logging.
Integrated in main.py alongside Discord + Telegram via asyncio.gather.
CLI: echo whatsapp status/qr. 442 tests pass (32 new, zero failures).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Prompt injection protection: external messages wrapped in [EXTERNAL CONTENT]
markers, system prompt instructs Claude to never follow external instructions
- Invocation logging: all Claude CLI calls logged with channel, model, duration,
token counts to echo-core.invoke logger
- Security logging: separate echo-core.security logger for unauthorized access
attempts (DMs from non-admins, unauthorized admin/owner commands)
- Security log routed to logs/security.log in addition to main log
- Extended echo doctor: Claude CLI functional check, config.json secret scan,
.gitignore completeness, file permissions, Ollama reachability, bot process
- Subprocess env stripping logged at debug level
373 tests pass (10 new security tests).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Semantic search over memory/*.md files using all-minilm embeddings.
Adds /search Discord command and `echo memory search/reindex` CLI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
/model (show/change), /restart (owner), /logs, set_session_model API, model reset on /clear. 20 new tests (161 total).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Subprocess wrapper for Claude CLI with start/resume/clear sessions, personality system prompt, atomic session tracking. 38 new tests (89 total).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>