Skip to content

Commit 75e4114

Browse files
Trinity Agentclaude
andcommitted
feat(cloud): Stream all container logs to Telegram in realtime (#131)
- Add Telegram streaming functions with batch buffering (5s interval) - Replace Claude Code launch with streaming pipe for all output - Stream self-review steps (format check, generated files, diff size) - Stream push and PR creation steps - Add TELEGRAM_STREAM=true env var to cloud_orchestrator Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c16ed8f commit 75e4114

2 files changed

Lines changed: 92 additions & 1 deletion

File tree

deploy/agent-entrypoint.sh

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,52 @@ update_telegram_dashboard() {
166166
fi
167167
}
168168

169+
# ═══════════════════════════════════════════════════════════════════════════════
170+
# TELEGRAM LOG STREAMING — Batch streaming every 5 seconds to avoid rate limits
171+
# ═══════════════════════════════════════════════════════════════════════════════
172+
173+
TELEGRAM_BUFFER=""
174+
TELEGRAM_LAST_SEND=0
175+
TELEGRAM_STREAM="${TELEGRAM_STREAM:-true}"
176+
TELEGRAM_BATCH_INTERVAL="${TELEGRAM_BATCH_INTERVAL:-5}"
177+
178+
stream_to_telegram() {
179+
[ "$TELEGRAM_STREAM" != "true" ] && return
180+
local line="$1"
181+
TELEGRAM_BUFFER="${TELEGRAM_BUFFER}${line}
182+
"
183+
184+
local now=$(date +%s)
185+
local diff=$((now - TELEGRAM_LAST_SEND))
186+
187+
if [ $diff -ge $TELEGRAM_BATCH_INTERVAL ] || [ ${#TELEGRAM_BUFFER} -gt 3000 ]; then
188+
if [ -n "$TELEGRAM_BUFFER" ] && [ -n "$TELEGRAM_BOT_TOKEN" ]; then
189+
local msg=$(echo -e "$TELEGRAM_BUFFER" | head -c 3900)
190+
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
191+
-d "chat_id=${TELEGRAM_CHAT_ID}" \
192+
-d "parse_mode=HTML" \
193+
-d "text=<pre>🤖 #${ISSUE} LOG
194+
${msg}</pre>" \
195+
--max-time 5 || true
196+
TELEGRAM_BUFFER=""
197+
TELEGRAM_LAST_SEND=$now
198+
fi
199+
fi
200+
}
201+
202+
flush_telegram() {
203+
if [ -n "$TELEGRAM_BUFFER" ] && [ -n "$TELEGRAM_BOT_TOKEN" ]; then
204+
local msg=$(echo -e "$TELEGRAM_BUFFER" | head -c 3900)
205+
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
206+
-d "chat_id=${TELEGRAM_CHAT_ID}" \
207+
-d "parse_mode=HTML" \
208+
-d "text=<pre>🤖 #${ISSUE} LOG
209+
${msg}</pre>" \
210+
--max-time 5 || true
211+
TELEGRAM_BUFFER=""
212+
fi
213+
}
214+
169215
# ═══════════════════════════════════════════════════════════════════════════════
170216
# EVENT STREAM (OpenHands-style structured events)
171217
# ═══════════════════════════════════════════════════════════════════════════════
@@ -405,7 +451,19 @@ Comment on the issue at each major step."
405451

406452
emit_event "status" '{"status":"CODING","detail":"Claude Code starting"}'
407453
CLAUDE_EXIT=0
408-
timeout "${AGENT_TIMEOUT}" claude -p "${PROMPT}" --allowedTools "Bash,Read,Write,Edit,Glob,Grep" 2>&1 || CLAUDE_EXIT=$?
454+
timeout "${AGENT_TIMEOUT}" claude -p "${PROMPT}" --allowedTools "Bash,Read,Write,Edit,Glob,Grep" 2>&1 | \
455+
while IFS= read -r line; do
456+
echo "$line"
457+
stream_to_telegram "$line"
458+
echo "{\"type\":\"log\",\"issue\":${ISSUE},\"line\":\"$(echo "$line" | sed 's/"/\\"/g' | head -c 500)\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" >> /tmp/agent_events.jsonl
459+
case "$line" in
460+
*"Read("*|*"cat "*) report_status "READING" "$line" ;;
461+
*"Write("*|*"Edit("*) report_status "CODING" "$(echo $line | head -c 100)" ;;
462+
*"Bash("*|*"zig build"*) report_status "TESTING" "$(echo $line | head -c 100)" ;;
463+
*"error"*|*"Error"*) report_status "ERROR" "$(echo $line | head -c 200)" ;;
464+
esac
465+
done || CLAUDE_EXIT=$?
466+
flush_telegram
409467
emit_event "command" "{\"cmd\":\"claude\",\"exit_code\":${CLAUDE_EXIT},\"timeout\":${AGENT_TIMEOUT}}"
410468

411469
if [ "${CLAUDE_EXIT}" -eq 124 ]; then
@@ -417,46 +475,66 @@ fi
417475

418476
# === 6b. Self-review (advisory only — never blocks push) ===
419477
report_status "REVIEWING" "Self-review (advisory)"
478+
stream_to_telegram "Running self-review..."
420479
REVIEW_WARNINGS=0
421480

422481
# 7a. Format check — auto-fix silently
482+
stream_to_telegram "Checking zig fmt format..."
423483
if ! zig fmt --check src/ 2>/dev/null; then
484+
stream_to_telegram "Running zig fmt to fix formatting..."
424485
zig fmt src/ 2>/dev/null || true
425486
git add -A
426487
git commit -m "style: zig fmt (#${ISSUE})" 2>/dev/null || true
488+
stream_to_telegram "Formatting fixed and committed."
489+
else
490+
stream_to_telegram "Format check passed."
427491
fi
428492

429493
# 7b. Generated files check (only real blocker)
494+
stream_to_telegram "Checking for generated files..."
430495
if git diff --name-only main..HEAD 2>/dev/null | grep -qE 'trinity/output/|generated/'; then
431496
emit_event "error" '{"msg":"Modified generated files"}'
432497
REVIEW_WARNINGS=$((REVIEW_WARNINGS + 1))
498+
stream_to_telegram "Warning: Generated files modified."
433499
fi
434500

435501
# 7c. Diff size warning (advisory)
502+
stream_to_telegram "Checking diff size..."
436503
DIFF_LINES=$(git diff --stat main..HEAD 2>/dev/null | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
437504
if [ "${DIFF_LINES:-0}" -gt 500 ]; then
438505
emit_event "error" "{\"msg\":\"Diff large: ${DIFF_LINES} lines\"}"
439506
REVIEW_WARNINGS=$((REVIEW_WARNINGS + 1))
507+
stream_to_telegram "Warning: Large diff (${DIFF_LINES} lines)."
508+
else
509+
stream_to_telegram "Diff size OK: ${DIFF_LINES} lines."
440510
fi
441511

442512
# NOTE: zig build skipped — too heavy for Railway containers, always fails
443513
# Tests run by CI after PR is created
444514
if [ $REVIEW_WARNINGS -gt 0 ]; then
515+
stream_to_telegram "Self-review: ${REVIEW_WARNINGS} warning(s) (advisory, not blocking)."
445516
log "Self-review: ${REVIEW_WARNINGS} warning(s) (advisory, not blocking)"
517+
else
518+
stream_to_telegram "Self-review: All checks passed."
446519
fi
447520

448521
# === 8. Push and create PR if not already done ===
449522
report_status "TESTING" "Checking/creating PR"
523+
stream_to_telegram "Checking for existing PR..."
450524
EXISTING_PR=$(gh pr list --head "feat/issue-${ISSUE}" --json number --jq '.[0].number' 2>/dev/null || echo "")
451525

452526
if [ -z "${EXISTING_PR}" ]; then
453527
# Check if there are actually commits to push
454528
COMMIT_COUNT=$(git log --oneline main..HEAD 2>/dev/null | wc -l | tr -d ' ')
529+
stream_to_telegram "Commit count: ${COMMIT_COUNT}"
455530
if [ "${COMMIT_COUNT}" -gt 0 ]; then
456531
log "Pushing ${COMMIT_COUNT} commit(s)..."
532+
stream_to_telegram "Pushing ${COMMIT_COUNT} commit(s) to origin..."
457533
retry "git push -u origin 'feat/issue-${ISSUE}' 2>/dev/null" || true
534+
stream_to_telegram "Push completed."
458535

459536
log "Creating PR..."
537+
stream_to_telegram "Creating pull request..."
460538
PR_URL=$(gh pr create \
461539
--title "feat: solve issue #${ISSUE}" \
462540
--body "Closes #${ISSUE}
@@ -466,13 +544,15 @@ Commits: ${COMMIT_COUNT}" \
466544
--head "feat/issue-${ISSUE}" 2>/dev/null || true)
467545

468546
if [ -n "${PR_URL}" ]; then
547+
stream_to_telegram "PR created: ${PR_URL}"
469548
emit_event "pr" "{\"url\":\"${PR_URL}\",\"commits\":${COMMIT_COUNT}}"
470549
report_status "PR_CREATED" "PR: ${PR_URL}"
471550
# Send metrics to monitor
472551
report_metrics
473552
# Post final summary comment
474553
DIFF_STAT=$(git diff --stat main..HEAD 2>/dev/null || echo "N/A")
475554
FINAL_ELAPSED=$(( $(date +%s) - START_TIME ))
555+
stream_to_telegram "Posting final summary..."
476556
gh issue comment "${ISSUE}" --body "🚀 **Trinity Agent — Summary**
477557
478558
| Field | Value |
@@ -485,17 +565,25 @@ Commits: ${COMMIT_COUNT}" \
485565
\`\`\`
486566
${DIFF_STAT}
487567
\`\`\`" 2>/dev/null || true
568+
stream_to_telegram "Summary posted."
488569

489570
# Cleanup worktree after PR creation (keeps shared bare repo intact)
490571
log "Cleaning up worktree..."
572+
stream_to_telegram "Cleaning up worktree..."
491573
cd /bare-repo.git
492574
git worktree remove "${WORKTREE_PATH}" --force 2>/dev/null || true
493575
log "Worktree removed: ${WORKTREE_PATH}"
576+
stream_to_telegram "Worktree removed."
577+
else
578+
stream_to_telegram "Failed to create PR."
494579
fi
495580
else
581+
stream_to_telegram "No commits produced — agent could not solve issue."
496582
report_status "FAILED" "No commits produced — agent could not solve issue"
497583
gh issue comment "${ISSUE}" --body "❌ **Trinity Agent**: No solution produced. Issue may need manual attention." 2>/dev/null || true
498584
fi
585+
else
586+
stream_to_telegram "PR already exists: #${EXISTING_PR}"
499587
fi
500588

501589
# === 8. Report final status ===

src/tri/cloud_orchestrator.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ pub fn spawnAgent(allocator: Allocator, issue_number: u32) !SpawnResult {
132132
allocator.free(tg_chat);
133133
}
134134

135+
// Enable Telegram log streaming by default
136+
_ = api.upsertVariable(service_id, env_id, "TELEGRAM_STREAM", "true") catch {};
137+
135138
const mon_token = std.process.getEnvVarOwned(allocator, "MONITOR_TOKEN") catch "";
136139
if (mon_token.len > 0) {
137140
_ = api.upsertVariable(service_id, env_id, "MONITOR_TOKEN", mon_token) catch {};

0 commit comments

Comments
 (0)