런타임은 한 줄도 안 바뀌었다. 채널을 모르게 만들어 둔 것이 여기서 값을 했다 — 새로 생긴 것은 형식 변환(kakao_bot)과 대화 상태(channel)뿐이다. ★★ 오픈빌더는 서명을 주지 않는다. URL 만 알면 누구나 때릴 수 있고 userRequest.user.id 를 위조하면 그 사장님 행세를 한다 — 1단계의 신원 연결이 통째로 무의미해지는 자리다. 공유 시크릿(헤더 X-Agent-Secret, compare_digest) + 선택적 KAKAO_BOT_ID 대조로 막고, 시크릿이 없으면 엔드포인트가 404 다 (401 은 "여기 뭔가 있다" 를 알려 준다). - router/v1/agent/kakao_bot: 카카오 형식을 아는 유일한 파일. 헤더·경로 두 경로 - services/agent/channel: 신원(★ 토큰을 발급하지 않는다) · 가게 고르기 · 확인 - 0022: owner_kakao_links 에 current_place_id · pending_* 빌더 화면과 다른 것 셋: - 로그인 토큰이 없다 → 발화자 키로 사장님을 찾는다 - place_id 가 URL 에 없다 → 여럿이면 추측하지 않고 되묻는다. 임의로 첫 가게를 고르면 사장님은 엉뚱한 가게를 고쳐 놓고도 모른다 - 확인을 되돌려 줄 프론트가 없다 → 서버가 pending 을 든다. ★ 3분 만료가 없으면 한참 뒤의 "네" 한 마디에 묵은 발행이 돈다 5초 벽은 DEADLINE_SEC=4.0 으로 끊고, 어떤 실패도 200+안내다 — 메신저에서는 500 도 침묵으로 보인다. 밟은 것: execute_lambda 는 람다 반환값을 그대로 준다(CRUD 관례가 (ErrorType,값)). 우리 람다가 객체만 돌려주자 언패킹 TypeError 가 났고, 라우터가 예외를 삼켜 화면에는 안내 한 줄만 보였다 — 원인이 안 보이는 종류다. test_kakao_webhook.py 17 passed. 전체 841 passed / 53 failed(이전과 동일). npm run lint 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
64 lines
3.1 KiB
Python
64 lines
3.1 KiB
Python
"""사장님 에이전트 설정 — 루트 .env 하나만 읽는다(APP_ENV=test 면 .env 를 읽지 않는다).
|
|
|
|
★ SNS 게재(social_config)와 파일을 가른 이유는 도메인이 다르기 때문이다.
|
|
SNS 게재는 **되돌릴 수 없는** 대외 발화이고, 에이전트는 사장님이 자기 사이트를
|
|
고치는 창구다. 승인 강도도 보관하는 것도 다르다 — 설정이 한 파일에 섞이면
|
|
"이 값이 무엇을 여는가" 가 흐려진다.
|
|
"""
|
|
|
|
from pydantic_settings import BaseSettings
|
|
|
|
from config.config_models import _BASE
|
|
|
|
|
|
class AgentConfig(BaseSettings):
|
|
model_config = _BASE
|
|
|
|
# 카카오톡 채널 공개 ID(`_xaBcD` 형태). 사장님이 채널을 찾아 코드를 입력해야 하므로
|
|
# ★ 이 값이 없으면 연결 화면 자체를 열지 않는다 — 어디에 코드를 칠지 말해 줄 수
|
|
# 없는데 코드만 발급하면, 사장님에게는 고장난 화면이다(Threads 카드와 같은 규칙).
|
|
KAKAO_CHANNEL_PUBLIC_ID: str = ""
|
|
# 코드 수명. 사장님이 화면을 보고 카톡을 열어 치는 동작이라 짧아도 된다.
|
|
KAKAO_LINK_CODE_TTL_MIN: int = 10
|
|
# 코드가 짧아서(사람이 손으로 친다) 무차별 대입이 가능하다. 시도 수로 끊는다.
|
|
KAKAO_LINK_MAX_ATTEMPTS: int = 5
|
|
|
|
# 빌더 화면의 대화창. 2026-09-21 에 한 번 닫았다가(카카오 채널 보류) 채널 인증이
|
|
# 끝나 다시 열었다(2026-09-22).
|
|
# ★ 이 값이 "1" 이어도 **LLM 키가 없으면 안 열린다**(runtime.is_configured 가 둘 다 본다) —
|
|
# 키 없는 환경에서 켜 둔 채 잊어도 "눌러도 안 되는 입구" 가 생기지 않는다.
|
|
# 다시 닫을 일이 생기면 이 값만 "0" 으로 되돌린다. 코드를 되짚지 않는다.
|
|
AGENT_CHAT_ENABLED: str = "1"
|
|
|
|
# ★ 카카오 웹훅 인증. **오픈빌더는 서명을 주지 않는다** — URL 만 알면 누구나 이 엔드포인트를
|
|
# 때릴 수 있고, user.id 를 아무 값이나 넣으면 **그 사장님 행세를 한다.** 신원 연결
|
|
# (owner_kakao_links)이 통째로 무의미해진다.
|
|
# 그래서 이 값이 없으면 **엔드포인트 자체를 띄우지 않는다**(404). 반쯤 열린 상태를
|
|
# 만들지 않는 것은 Threads 연결과 같은 규칙이다.
|
|
# 만드는 법: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
KAKAO_WEBHOOK_SECRET: str = ""
|
|
# 우리 봇이 맞는지 한 겹 더 본다. 시크릿이 아니라 오발송을 거르는 용도라 비워도 된다.
|
|
KAKAO_BOT_ID: str = ""
|
|
|
|
|
|
def get(name, default=""):
|
|
return getattr(AgentConfig(), name, default) or default
|
|
|
|
|
|
def chat_enabled() -> bool:
|
|
return get("AGENT_CHAT_ENABLED", "0") == "1"
|
|
|
|
|
|
def webhook_secret() -> str:
|
|
return get("KAKAO_WEBHOOK_SECRET")
|
|
|
|
|
|
def kakao_link_enabled() -> bool:
|
|
return bool(get("KAKAO_CHANNEL_PUBLIC_ID"))
|
|
|
|
|
|
def channel_url() -> str:
|
|
"""사장님이 눌러서 채널로 가는 주소. 공개 ID 가 없으면 빈 문자열이다."""
|
|
public_id = get("KAKAO_CHANNEL_PUBLIC_ID")
|
|
return f"http://pf.kakao.com/{public_id}" if public_id else ""
|