shared/telegram.py 스텁 복구 + pipeline dedup 일괄 적용
결론
- Part A 완료:
/Users/ron/.openclaw/workspace/shared/telegram.py211B 테스트 스텁을 Hermes 실구현과 동일한 37KB 구현으로 복구했다. - 실발송 검증 완료: OpenClaw root
shared.telegram.send_dm()테스트 발송 성공,cmux_supervisor.py --notify-always도텔레그램: 전송 완료확인. - Part B 완료: Hermes active pipeline direct
*.py기준 Telegram 발송 호출 파일 112개 전체에 shared dedup 가드를 적용했다. - AST 기반 자동 패치: 107개
- custom local sender 수동 패치: 5개
- py_compile 실패: 0개
- dedup dry-run 검증: 같은 메시지 2회 호출 시 underlying send 1회만 발생
Part A — OpenClaw shared/telegram.py 복구
확인된 상태
복구 전 파일:
/Users/ron/.openclaw/workspace/shared/telegram.py
size: 211 bytes
내용: 테스트용 최소 스텁
send_dm(*a, **k): return True
send_dm_chunked(*a, **k): return True
동일 workspace 안에는 실제 구현도 존재했다.
/Users/ron/.openclaw/workspace/scripts/shared/telegram.py 32KB, 실제 구현
/Users/ron/.hermes/workspace/scripts/shared/telegram.py 37KB, 최신 Hermes 구현
git log --follow -- shared/telegram.py에서는 root shared/telegram.py에 대한 의미 있는 이력이 나오지 않았다. 따라서 원인은 “git으로 추적된 정상 교체”라기보다, 테스트용 root package stub이 남아 legacy import 경로에 걸린 것으로 보는 것이 확인 가능한 범위의 결론이다.
조치
Hermes 최신 구현을 OpenClaw root shared로 동기화했다.
src: /Users/ron/.hermes/workspace/scripts/shared/telegram.py
dst: /Users/ron/.openclaw/workspace/shared/telegram.py
backup: /Users/ron/.openclaw/workspace/shared/telegram.py.bak-stub-recovery-20260424_130957
검증:
python3 -m py_compile /Users/ron/.openclaw/workspace/shared/telegram.py
OK
module /Users/ron/.openclaw/workspace/shared/telegram.py
send_dm True
send_sector True
send_document True
config /Users/ron/.hermes/openclaw.json
복구 후 checksum:
/Users/ron/.openclaw/workspace/shared/telegram.py 37394 bytes sha256[:16]=51aa60760b30a7c9
/Users/ron/.hermes/workspace/scripts/shared/telegram.py 37394 bytes sha256[:16]=51aa60760b30a7c9
실제 발송 검증
OpenClaw root shared import 경로로 직접 발송:
{"ok": true, "target": "notification_center", "chat_id": -1003522748967}
Hermes notification log 확인:
223|2026-04-24 13:10:10|info|<stdin>|1||🔧 [TEST 260424] OpenClaw shared.telegram stub recovery 확인 13:10
cmux_supervisor.py --notify-always 검증:
cd /Users/ron/.openclaw/workspace && python3 scripts/cmux_supervisor.py --notify-always --surfaces surface:4 --lines 10
[cmux 감독 결과] 2026-04-24 13:10:16
surface:4 🔵 실행중 준수:0/5
⚠ 스킬/서브에이전트 미사용 — 방법론 미준수 가능
경고 총 1건
텔레그램: 전송 완료
해당 명령은 경고가 있어서 process exit code는 1이었지만, 목적이던 Telegram notify는 성공했다.
Part B — pipeline dedup rollout
전수 검사 범위
대상:
/Users/ron/.hermes/workspace/scripts/pipeline/*.py
발송 호출 기준:
send_dm
send_dm_chunked
send_sector
send_sector_result
send_document
send_document_result
send_group
send_group_chunked
send_group_chunked_result
send_notification_center_chunked
초기 후보:
total_py: 237
candidate_total: 107
이 107개는 AST로 call function expression만 바꿀 수 있는 파일이었다. 별도로 자체 def send_dm(...)을 가진 custom sender 5개가 있어 수동 패치했다.
적용 방식
생성 스크립트:
/Users/ron/.hermes/workspace/scripts/admin/apply_dedup_to_all.py
방식:
- AST parse
- call function expression만 교체
send_sector(...)→_dedup_send_sector(...)telegram.send_sector(...)→_dedup_send_sector(...)send_document(...)→_dedup_send_document(...)- 각 파일에 shared dedup guard block 삽입
- 파일별 백업 생성
- 즉시
py_compile검증
사용 dedup store:
/Users/ron/.hermes/workspace/memory/telegram_send_hashes.json
window:
24시간
배치 적용 결과
5개씩 22회 배치로 적용했다.
batch=1 candidates_before=107 selected=5 patched=5 compile_failed=0
batch=2 candidates_before=102 selected=5 patched=5 compile_failed=0
...
batch=21 candidates_before=7 selected=5 patched=5 compile_failed=0
batch=22 candidates_before=2 selected=2 patched=2 compile_failed=0
batch=23 candidates_before=0 selected=0 patched=0 compile_failed=0
최종 적용 수:
AST auto-patched files: 107
custom local sender patched files: 5
total covered files: 112
backup count: 107 + 5
custom local sender 5개:
/Users/ron/.hermes/workspace/scripts/pipeline/bond_evening_concept.py
/Users/ron/.hermes/workspace/scripts/pipeline/bond_morning_quiz.py
/Users/ron/.hermes/workspace/scripts/pipeline/bond_weekly_review.py
/Users/ron/.hermes/workspace/scripts/pipeline/bond_weekly_score.py
/Users/ron/.hermes/workspace/scripts/pipeline/task_briefing.py
이 5개는 send_dm(chat_id, message) 같은 자체 함수 시그니처를 갖고 있어 AST 자동 교체 대신 local sender 내부에 shared.dedup.should_skip/record_sent를 직접 넣었다.
백업
자동 패치 백업:
*.py.bak-dedup-rollout-20260424
custom sender 백업:
*.py.bak-dedup-rollout-20260424-custom
OpenClaw telegram stub 백업:
/Users/ron/.openclaw/workspace/shared/telegram.py.bak-stub-recovery-20260424_130957
py_compile 전수 검증
python3 -m py_compile 대상 전체
compile_ok 237
compile_fail 0
coverage 검증
AST 기준 발송 호출 파일 coverage:
files_with_calls: 112
covered: 112
uncovered: []
즉 active pipeline/*.py direct 파일 중 Telegram send 호출을 가진 파일은 모두 dedup guard가 들어갔다.
dedup dry-run 검증
네트워크 발송 없이 discovery_digest.py의 _dedup_send_sector를 monkeypatch fake sender로 검증했다.
명령 결과:
FAKE_SEND 1 ops [DEDUP VERIFY 260424] same message 12345
{"first": true, "second": true, "underlying_calls": 1, "store_exists": true}
hash store 기록:
{
"entries": [
{
"chat_id": -1003522748967,
"hash": "ddcdb16a38533f077e79c1730d861d6be9e5243e2923c5b54d01c78188efacf0",
"timestamp": 1777004037.107224,
"topic_id": 9
}
]
}
판정: 같은 text/chat/topic 조합은 두 번째 호출에서 skip되고, 실제 sender는 1회만 호출된다.
변경 파일 요약
핵심 파일:
/Users/ron/.openclaw/workspace/shared/telegram.py
/Users/ron/.hermes/workspace/scripts/admin/apply_dedup_to_all.py
/Users/ron/.hermes/workspace/scripts/pipeline/*.py 112개
실패 수:
patch failure: 0
compile failure: 0
dry-run dedup failure: 0
남은 리스크
pipeline/*.pydirect 파일 기준으로는 전부 커버했지만,pipeline/_archive/,pipeline/webapp/하위, 기타 non-pipeline 스크립트는 이번 rollout 범위에서 제외했다.- Telegram photo/album 계열(
send_sector_photo,send_photo,send_album)은 이번 미션의 명시 대상(send_dm/send_sector/send_document) 밖이라 별도 dedup 적용하지 않았다. - 일부 파일은 local helper가 Telegram API를 직접 호출한다. 이번에 발견된 custom sender 5개는 처리했지만,
_tg_api("sendMessage", ...)같은 저수준 직접 호출은 별도 감사가 필요하다.
자체평가
| 기준 | 점수 | 근거 |
|---|---|---|
| 정확성 | 4.5/5 | stub 복구, 실발송, AST coverage, dedup dry-run 모두 확인 |
| 완성도 | 4.5/5 | 112개 active pipeline sender 파일 전체 커버. 제외 범위 명시 |
| 검증 | 4.5/5 | py_compile 237개 통과, 발송 성공, hash skip 검증 |
| 최소 변경 | 4.0/5 | 대량 변경이지만 call expression만 바꾸고 백업 생성. custom 5개만 수동 최소 패치 |
종합: 4.4/5
DONE
Photo/Album dedup 확장 (추가 작업 — 2026-04-24 13:21)
적용 요약
텍스트/문서 계열 dedup 이후 남아 있던 사진·앨범 발송 계열을 추가로 막았다.
검색 대상 함수:
send_sector_photo
send_photo
send_album
send_sector_album
send_dm_photo
send_group_photo
send_video
검색 결과 send_video/sendVideo 호출은 없었다.
적용된 pipeline 파일 수: 5개
/Users/ron/.hermes/workspace/scripts/pipeline/daily_intelligence_report.py
/Users/ron/.hermes/workspace/scripts/pipeline/geopolitical_monitor.py
/Users/ron/.hermes/workspace/scripts/pipeline/market_indicator_tracker.py
/Users/ron/.hermes/workspace/scripts/pipeline/oil_supply_monitor.py
/Users/ron/.hermes/workspace/scripts/pipeline/urea_price_tracker.py
shared/dedup.py 확장
수정 파일:
/Users/ron/.hermes/workspace/scripts/shared/dedup.py
/Users/ron/.hermes/workspace/shared/dedup.py
백업:
/Users/ron/.hermes/workspace/scripts/shared/dedup.py.bak-media-dedup-20260424
/Users/ron/.hermes/workspace/shared/dedup.py.bak-media-dedup-20260424
추가 함수:
media_dedup_key(media, caption="", kind="media")
should_skip_media(media, chat_id, topic_id=0, caption="", kind="media", window_hours=24)
record_sent_media(media, chat_id, topic_id=0, caption="", kind="media")
key 구성:
- 파일 존재 시: 파일 content SHA256 + 파일명 + caption
- 파일 미존재/읽기 실패 시: 확장 path + caption fallback
- album은 사진 리스트 순서를 보존해 각 파일 hash를 이어 붙임
즉 같은 PNG가 같은 토픽에 다시 발송되면 caption이 같을 때 두 번째 호출은 skip된다. 파일 경로만 같은 경우뿐 아니라, 파일 내용 hash도 key에 들어간다.
apply_dedup_to_all.py 확장
수정 파일:
/Users/ron/.hermes/workspace/scripts/admin/apply_dedup_to_all.py
백업:
/Users/ron/.hermes/workspace/scripts/admin/apply_dedup_to_all.py.bak-media-dedup-20260424
추가 내용:
- image target 추가
--image-only모드 추가- 기존 marker 파일에도 image call만 재패치 가능하도록 확장
- wrapper에
_dedup_send_sector_photo,_dedup_send_photo,_dedup_send_album,_dedup_send_sector_album,_dedup_send_dm_photo,_dedup_send_group_photo추가
실행 결과:
candidate_total: 5
selected_count: 5
patched_count: 5
compile_failed_count: 0
적용된 실제 호출
daily_intelligence_report.py: send_sector_photo → _dedup_send_sector_photo
geopolitical_monitor.py: send_sector_album/send_sector_photo → _dedup_send_sector_album/_dedup_send_sector_photo
market_indicator_tracker.py: send_sector_photo → _dedup_send_sector_photo
oil_supply_monitor.py: send_photo 4곳 → _dedup_send_photo
urea_price_tracker.py: send_sector_photo → _dedup_send_sector_photo
raw image call 잔여 검사:
remaining_raw_image_calls: []
검증
전체 pipeline compile:
compile_ok: 238
compile_fail: 0
동일 chart PNG 2회 호출 dry-run 검증:
FAKE_PHOTO_SEND 1 market media_dedup_verify_chart.png same chart
{"first": true, "second": true, "underlying_photo_calls": 1, "store_exists": true}
hash store 기록:
{
"entries": [
{
"chat_id": -1003522748967,
"hash": "edc43baf4497bd05c41e55f5824b610103023c579847120e4443ab40087245e7",
"timestamp": 1777004475.022471,
"topic_id": 5
}
]
}
판정: 같은 chart PNG + 같은 caption + 같은 sector/topic 조합은 두 번째 호출에서 skip되고, 실제 photo sender는 1회만 호출된다.
차단 효과 추정
중복 가능성이 있던 chart/PNG 계열은 주로 아래 5개였다.
- market_indicator_tracker: 시장 지표 차트 중복
- oil_supply_monitor: 석유/가스 차트 중복
- urea_price_tracker: 요소/비료 차트 중복
- geopolitical_monitor: 지정학 앨범 중복
- daily_intelligence_report: report 첨부 이미지 중복
따라서 기존 텍스트 dedup 112개에 더해, 사진·앨범 계열 5개 파이프라인의 중복 발송도 차단됐다.
추가 자체평가
| 기준 | 점수 | 근거 |
|---|---|---|
| 정확성 | 4.5/5 | 실제 image 호출 grep 후 5개 파일 전체 적용, raw call 0 확인 |
| 완성도 | 4.3/5 | photo/album 대상 완료. send_video는 호출 없음 확인 |
| 검증 | 4.5/5 | py_compile 0 fail, 동일 PNG 2회 호출 시 1회만 sender 호출 검증 |
| 최소 변경 | 4.2/5 | 기존 wrapper 확장 + 5개 파일만 추가 패치 |
추가 종합: 4.4/5
DONE