feat: NanoClaude Phase 3 — Synology Chat 연동
- POST /webhook/synology: outgoing webhook 수신 + token 검증 - 파이프라인 완료 시 incoming webhook으로 응답 자동 전송 - "분석 중..." typing 메시지 선전송 - 응답 길이 1500자 제한 (Synology Chat 제한 대응) - 에러/실패 시에도 사용자에게 알림 메시지 전송 - 중복 요청 방지 (30초 TTL dedup) - Synology에서 rewrite 이벤트 숨김 (SSE에서만 노출) - callback 구조로 확장 가능 (Slack, Discord 등) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,12 +12,14 @@ from models.schemas import JobStatus
|
||||
from services.backend_registry import backend_registry
|
||||
from services.job_manager import Job, job_manager
|
||||
from services.state_stream import state_stream
|
||||
from services.synology_sender import send_to_synology
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HEARTBEAT_INTERVAL = 4.0
|
||||
REWRITE_HEARTBEAT = 2.0
|
||||
MAX_REWRITE_LENGTH = 1000
|
||||
SYNOLOGY_MAX_LEN = 1500
|
||||
|
||||
|
||||
async def _complete_with_heartbeat(adapter, message: str, job_id: str) -> str:
|
||||
@@ -113,8 +115,9 @@ async def run(job: Job) -> None:
|
||||
rewrite_latency = (time() - rewrite_start) * 1000
|
||||
job.rewritten_message = rewritten_message
|
||||
|
||||
# --- Rewrite 결과 SSE 노출 ---
|
||||
await state_stream.push(job.id, "rewrite", {"content": rewritten_message})
|
||||
# --- Rewrite 결과 SSE 노출 (Synology에서는 숨김) ---
|
||||
if job.callback != "synology":
|
||||
await state_stream.push(job.id, "rewrite", {"content": rewritten_message})
|
||||
|
||||
# --- Cancel 체크 #2 ---
|
||||
if job.status == JobStatus.cancelled:
|
||||
@@ -146,10 +149,18 @@ async def run(job: Job) -> None:
|
||||
job_manager.set_status(job.id, JobStatus.failed)
|
||||
await state_stream.push(job.id, "error", {"message": "응답을 받지 못했습니다."})
|
||||
status = "failed"
|
||||
if job.callback == "synology":
|
||||
await send_to_synology("⚠️ 응답을 받지 못했습니다. 다시 시도해주세요.")
|
||||
else:
|
||||
job_manager.set_status(job.id, JobStatus.completed)
|
||||
await state_stream.push(job.id, "done", {"message": "완료"})
|
||||
status = "completed"
|
||||
# Synology callback: 결과 전송
|
||||
if job.callback == "synology":
|
||||
full_response = "".join(collected)
|
||||
if len(full_response) > SYNOLOGY_MAX_LEN:
|
||||
full_response = full_response[:SYNOLOGY_MAX_LEN] + "\n\n...(생략됨)"
|
||||
await send_to_synology(full_response)
|
||||
|
||||
# --- DB 로깅 ---
|
||||
latency_ms = (time() - start_time) * 1000
|
||||
@@ -176,6 +187,11 @@ async def run(job: Job) -> None:
|
||||
logger.exception("Worker failed for job %s", job.id)
|
||||
job_manager.set_status(job.id, JobStatus.failed)
|
||||
await state_stream.push(job.id, "error", {"message": "내부 오류가 발생했습니다."})
|
||||
if job.callback == "synology":
|
||||
try:
|
||||
await send_to_synology("⚠️ 처리 중 오류가 발생했습니다. 다시 시도해주세요.")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await log_completion(job.id, "failed", 0, (time() - start_time) * 1000, time())
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user