Commit Graph

6 Commits

Author SHA1 Message Date
e0155d0423 [feat] solution/backend: 카톡 승인 알림에 [승인] 버튼 — 연결된 계정이 권한 (Event API 2-2)
2-1 로 카톡에 글과 [수정하기] 가 오게 됐고, 이제 카톡 안에서 바로 올린다.
승인 권한은 링크가 아니라 연결된 계정이다 — 버튼이 들고 온 글 ID 는 믿지 않는다.

- kakao_bot: 알림 카드에 [승인](action: block, blockId=KAKAO_APPROVE_BLOCK_ID,
  extra={kind:approve, post_id}) 을 그리고, 클릭은 action.clientExtra 로 받는다.
  블록 ID 가 비면 버튼을 그리지 않는다(눌러도 안 되는 버튼을 보내지 않는다)
- channel.approve_post: 누른 발화자 → 사장님 → 그 가게의 미처리·기한 전 글인지 재확인 후
  approve_by_owner(메일·'바로 발행' 과 같은 경로라 재발행 잡·쓰레드 공유까지 동일).
  연결 안 됨·남의 글·이미 올림·기한 지남은 같은 안내로 끝나고 두 번 올라가지 않는다.
  답장은 "올렸습니다" + [사이트 보기](#blog). LLM 을 부르지 않는다
- 5초를 넘겨도 승인 작업은 취소하지 않는다(asyncio.shield) — 여러 번 커밋하는 작업이라
  중간에 끊기면 승인만 되고 재발행이 안 걸린 글이 남는다
- PostService._blog_url → blog_url(채널이 [사이트 보기] 에 쓴다)

test_kakao_webhook 10건 추가(구현 전 5건 실패 확인), 관련 11개 스위트 239 passed.
실제 클릭 본문(action.clientExtra 위치)은 첫 클릭의 로그로 확인한다.
2026-09-29 16:50:40 +09:00
f1b473968d [feat] solution/backend: 승인 알림을 카톡으로도 발송하고 웹훅이 메시지를 그린다 (Event API 2-1)
결정(2026-09-29): 카톡과 메일 둘 다, 승인은 링크가 아니라 연결된 계정 신원으로.
이 커밋은 발송과 메시지 그리기까지고 [승인] 버튼은 다음 단계(2-2)다.

- blog_jobs._send_one: 메일에 더해 Event API 로 보낸다. 하나라도 나가면 SENT,
  아무 데도 안 나가면 SENT 로 표시하지 않아 다음 스윕이 다시 시도한다. 카톡은 채널
  친구가 아니거나 차단했으면 실패하므로 메일을 빼지 않는다. send_now 도 같은 경로
- 카톡 params 로 post_id 와 수정용 일회용 코드(edit_token)를 넘긴다. 코드 평문은
  발송 시점에만 알아서다. 로그에는 params 의 키만 남기고 값은 남기지 않는다
- channel.approval_notice: 발화자 키 → 사장님 → 그 글이 그 사장님 가게 것·미처리·
  기한 전일 때만 본문과 [수정하기] 를 준다. 연결 안 됨·남의 글·처리됨·만료·이상한
  ID 는 구분 없이 같은 안내(구분해 주면 글 ID 를 탐색할 수 있다)
- kakao_bot: userRequest.params.post_id 가 있으면 승인 알림 요청으로 처리하고 링크
  버튼은 본문과 따로 textCard 로 그린다(카드 설명 길이 제한을 피한다)
- KAKAO_APPROVAL_PUSH_ENABLED(기본 0), KAKAO_APPROVAL_EVENT_NAME 추가 —
  오픈빌더 이벤트 블록(스킬 연결)과 배포가 끝나기 전에는 켜지 않는다

test_blog_owner 6건·test_kakao_webhook 6건 추가, 카카오·미니블로그 스위트 119 passed,
인접 스위트 110 passed. 실제 카톡 수신은 콘솔 설정·운영 배포 뒤에 확인한다.
2026-09-29 16:13:07 +09:00
c4aa1794e2 [feat] solution/backend: 한 발화에 여러 가지 — 응답을 actions 배열로
사장님은 "체크인 3시로 바꾸고 후기도 빼줘" 처럼 한 번에 시킨다.

- prompts/agent: 응답 스키마를 actions 배열로
- runtime: 시킨 순서대로 실행. MAX_ACTIONS=5 — 무한정이면 "다 지워줘" 한 마디에
  연쇄 실행된다
- ★ publish(SEMI)가 섞이면 그 앞까지만 하고 확인을 받는다. 확인이 필요한 행위를
  다른 일에 묻어 실행하면 확인의 의미가 없다
- ★ 중간에 실패해도 앞의 것을 되돌리지 않는다(사장님 결정). 되돌리는 것도 시키지
  않은 변경이다 — 대신 무엇이 됐고 무엇이 안 됐는지 그대로 말한다
- Tool.republish 플래그로 재발행 안내를 런타임이 한 번만 붙인다. 도구 문장에
  박아 두면 셋을 고쳤을 때 같은 말이 세 번 나왔다

실모델 4/4 정확히 쪼갬(1.8~2.8초).
test_agent_runtime·test_kakao_webhook 63 passed.
전체 878 passed / 53 failed — 53 은 기존과 동일. npm run lint 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-28 17:00:30 +09:00
f500210cd8 [fix] solution/backend: 카톡 5초 벽을 콜백으로 넘는다
실사용 첫날 "시설 편의에서 바비큐 이용 문구 빼줘" 가 타임아웃으로 끝났다.

★ 작은 표본으로 잰 수치를 상한 근거로 삼은 것이 틀렸다. 개발 중 잰 1.3~2.4초는
업종 필드 두 개짜리 장난감 프롬프트였고, 진짜 요청에는 필드 43개 + fact 수십 개가
실린다. "여유가 있다" 고 적어 둔 판단이 하루 만에 깨졌다.

- userRequest.callbackUrl 이 오면 {"useCallback": true} 로 즉답하고 백그라운드에서
  답을 만든 뒤 그 주소로 POST. 콜백 주소는 1분·1회라 재시도하지 않는다 —
  두 번째 POST 는 거절되고 사장님에게는 이미 "확인하고 있어요" 가 가 있다
- 콜백이 꺼져 있으면 예전처럼 동기, 상한만 4.0 → 4.5 (카카오가 5초에 끊는다)

★ 오픈빌더 스킬 설정에서 '콜백 사용' 을 켜야 열린다. 안 켜면 callbackUrl 이 안 와서
조용히 예전 경로로만 돈다.

test_kakao_webhook.py 24 passed(콜백 3건 추가)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:33:16 +09:00
1300652b09 [feat] solution/backend: 카톡 대화에 홈페이지 목록·가게 고르기
실제로 붙여 보니 빠진 것이 드러났다 — 연결은 됐는데 **어느 홈페이지를 다루는
대화인지** 말해 주지 않았다. 가게가 하나면 말없이 자동 선택돼 더 모호했다.

- 연결 직후 목록을 보여준다. 하나면 이름+발행 여부를, 여럿이면 바로가기 버튼으로
- 목록 줄에 발행 여부를 적는다 — 안 그러면 고친 것이 손님에게 보이는 줄 안다
- "목록"·"가게 바꿔줘" 로 언제든 돌아와 바꾼다. ★ 이 경로는 LLM 을 부르지 않는다:
  대화가 막혔을 때 처음 찾는 길이라 늘 통해야 하고, 목록에 돈을 쓸 이유가 없다
- 사업장 목록이 아니라 list_my_sites 를 쓴다 — 사장님이 알아야 하는 건
  "가게가 있다" 가 아니라 "발행돼 있나" 다(/sites 화면이 같은 이유로 그걸 쓴다)

test_kakao_webhook.py 21 passed(목록·전환 4건 추가).
전체 845 passed / 53 failed — 53 은 이번 변경 전과 동일

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:26:40 +09:00
d32df10cf7 [feat] solution/backend: 카카오 채널 웹훅 — 에이전트 4단계
런타임은 한 줄도 안 바뀌었다. 채널을 모르게 만들어 둔 것이 여기서 값을 했다 —
새로 생긴 것은 형식 변환(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>
2026-09-22 15:13:33 +09:00