@fcg-labs/cx-agent-hook
v0.6.0
Published
FCG CX Agent 후킹 SDK — 서빙 답변 수신 + CS팀 교정(점수·수정·발송) 후킹. 의존성 0, CMS에 install만으로 이식
Readme
@fcg-labs/cx-agent-hook
FCG CX Agent 후킹 SDK — 상담 화면에 AI 답변 제안을 띄우고, 상담사의 판단(채택· 수정·발송)을 학습 광물로 돌려받는다.
전송 계층은 의존성 0 (내장 fetch, 브라우저·Node 18+). React 는 ./react
서브패스에서만 쓰는 optional peer 다.
설계 원칙 — 고객사 저장소에 제품 로직을 두지 않는다
고객사(관리자 화면) 코드에 들어가도 되는 것은 주소·토큰·도메인 세 값을 자기 빌드 방식으로 읽어 넘기는 일과 컴포넌트를 어디에 놓을지뿐이다.
| 고객사 코드 | 이 라이브러리 |
|---|---|
| env 세 값 읽기 | 미설정 판정·널 가드·실패 시 돌려줄 모양 |
| 패널 배치 위치 | 패널 UI·상태 전이·기본 스타일 |
| 발송 시 훅 호출 | 사유 코드 목록과 상담사 안내 문구 |
| — | 채택한 제안 ↔ 발송 최종본을 잇는 answer_id 기록 |
이 선을 넘어가면 서버가 사유를 하나 늘릴 때마다 고객사 저장소를 고쳐 재배포해야 하고, 2호 고객이 올 때 같은 것을 또 짜게 된다.
설치
npm install @fcg-labs/cx-agent-hookReact·Vue 어댑터를 쓸 때만 그 프레임워크가 필요하다 (optional peer). 전송 계층만 쓰면 의존성은 0 이다.
AI 코딩 에이전트에게 맡기기 (권장)
패키지 안에 에이전트 스킬이 들어 있다 — 설치→환경변수→훅(adminApi 포함)→답변
슬롯→처리 카드→확인까지, 프로젝트를 먼저 실측하고(프레임워크·번들러·기존 관리자 API
헬퍼·엔드포인트 맵·세션 만료 처리·문의 화면) 그 성격에 맞춰 배선하는 절차와, 인용해도
되는 API 이름 표(reference.md, 시험이 실제 export 와 대조)다.
npx cx-agent-hook skills install # Claude Code: .claude/skills/cx-agent-hook-setup/
npx cx-agent-hook skills install --all # + Cursor 규칙(.cursor/rules) + AGENTS.md 한 줄
npx cx-agent-hook skills path # 그냥 "이 파일 읽고 따라줘" 라고 줄 때그 뒤 에이전트에게 "cx-agent-hook 셋업 도와줘" — 답변 패널만이 아니라 처리 카드
(adminApi)까지가 셋업이라고 스킬이 못박는다. 사람이 읽을 때는 아래 절과
skills/cx-agent-hook-setup/SKILL.md 가 같은 내용이다.
0.3.0 — 문의별 세션 (권장 표면)
createCxHook 은 그대로 동작한다(호환층). 문의별 초안 상태·영속이 필요한
관리자 화면은 세션 표면으로 올라온다 — 초안의 정본이 세션 버퍼라 메뉴
이탈·문의 전환에도 AI 초안이 살아남고, 스트림은 화면과 무관하게 완주한다:
import { createCxAgent } from "@fcg-labs/cx-agent-hook/agent";
const agent = createCxAgent({ baseUrl, token, domain });
const session = agent.session(inquiryId); // 같은 id = 같은 세션
session.attachUi({ getDraft, setDraft, setStatus });
session.restore(); // 복귀 시 초안 복원 한 줄
session.compose({ inquiry, context }); // 이탈해도 버퍼로 완주
session.remember(editorText); // 상담사 편집 반영
session.answerSent(finalText, agentId); // 발송 후킹 + 세션 소멸테넌트 특수분은 파라미터다: source(인그레스 채널명, 기본 "cms"),
draftDecorators(초안 후처리 — 기본 한국어 호칭 개인화, 비한국어는 []),
storage(sessionStorage TTL·LRU 정책). 상세와 훅→세션 대응표는
MIGRATION.md.
CSS 는 사용처 var(--fcx-*, 폴백) 직참조라 소비처 :root 한 줄로 팔레트를
바꾼다. 다크는 조상에 data-fcx-theme="dark". headless 2단계(스타일 0 /
UI 0 — aiSuggestTree)도 공식 표면이다.
0.5.0 — 처리 액션 (브라우저 실행기: 실행기는 CMS 자신)
답변 제안 옆에 처리 선택지가 실릴 수 있다 (payload.suggested_actions). 이 SDK 는
그 선택지를 보관·표시하고, 상담사 확인 뒤 CMS 의 기존 관리자 API 를 CMS 세션으로
불러 처리하고, 결과를 허브 원장에 보고한다. 허브는 어떤 고객사 서버도 부르지 않는다.
고객사 개발자가 새로 짜는 코드는 없다 — 이미 있는 요청 헬퍼와 엔드포인트 맵의
참조 두 개를 옵션으로 넘길 뿐이다.
import TempAdminApi, { EndPoint } from "@/constant/TempAdminApi"; // CMS 에 이미 있는 것
import TempAdminApiMap from "@/constant/TempAdminApiMap";
const agent = createCxAgent({
baseUrl, token, domain,
adminApi: { request: TempAdminApi.request, endpoints: EndPoint, map: TempAdminApiMap,
onAuthExpired: handleAuthExpiredResponse }, // 없으면 액션 표면은 닫힌다
});
const session = agent.session(inquiryId);
session.attachActionUi({ setOffers: (offers, pending) => render(offers, pending) });
// 사람 확인(확인 문구·고위험은 2단) 뒤에만:
const r = await session.executeAction(offer, { actorClaimed: agentDisplayName });
// r.state: succeeded | failed | unknown — unknown 은 재실행 없음("확인 필요"로 남는다)
// 보고가 안 됐으면(r.reported=false) session.resendResult(r.requestId) — 재실행 아님3단: ① 허브 선점(request_id ULID, 전송 전 영속, 재시도 0 — 네트워크 오류면 상태를
묻고 기록이 없을 때만 같은 request_id 로 1회 재전송) → ② adminApi.request 1회 —
카탈로그의 실행 사양(offer.execution)이 $name 을 params_bound 로 치환하고
response_rule 이 성공/실패를 읽는다 → ③ 결과 보고(허브가 상태 단조·멱등 보장).
초기화 시 SDK 가 엔드포인트 맵의 키·메서드·경로 템플릿(호스트 없음) 을 허브에
1회 발행한다 — 공장 시스템 탭의 "이 CMS 가 이미 하는 처리" 목록이 여기서 온다.
같은 지문이면 네트워크 0(agent.candidatesReady, publishActionCandidates({force})).
처리 카드 슬롯은 어댑터 한 줄이다 — React ActionOffersPanel(/react), Vue ActionOffersPanel
(/vue), 그 밖은 <cx-action-offers>(/element, defineCxActionOffers()), 스타일은 같은
styles.css(.fcx-act-*). 확인 2단·고위험 체크·재실행 금지·미보고 재보고 규약을 컴포넌트가 갖는다:
import { ActionOffersPanel } from "@fcg-labs/cx-agent-hook/react";
<ActionOffersPanel session={agent.session(csId)} agent={agent} actorClaimed={answerWriter}
onResult={(r) => r.state === "succeeded" && refresh()} />카드 상태: locked(같은 대상·같은 입력의 최근 성공 — "이미 처리됨 · 시각 · 행위자")
· unavailable(이 CMS 가 그 API 를 모름 — fail-closed) · 실행 중 · 완료 · 실패(재시도
가능) · 확인 필요(unknown) · 미보고(결과 다시 보고). 눌렀던 처리는
session.pendingActions() 로 새로고침 뒤에도 안다(정본은 허브 원장). 헤드리스 뷰:
actionOffersView / actionOffersTree(view.js), 클래스 fcx-act-*, 문구는
ui_action_* 로케일 키. 순수 함수 buildCandidateSnapshot · resolveExecution ·
judgeResponse · executeViaAdminApi (actions.js) 는 시험·커스텀 경로용으로 공개.
지원 프레임워크
| 진입점 | 대상 | 필요한 peer |
|---|---|---|
| /react | React 16.8+ | react |
| /next | Next.js App Router ("use client" 선언 포함) | react |
| /vue | Vue 3 | vue |
| /astro | Astro — 표준 커스텀 엘리먼트 | 없음 |
| /angular | Angular — 표준 커스텀 엘리먼트 | 없음 |
| /element | 순수 HTML·그 밖 | 없음 |
표시 판단은 하나다. 다섯 어댑터가 같은 aiSuggestView() 와 같은 클래스 이름을
쓰므로 styles.css 한 벌이 전부를 덮고, 규약이 바뀌어도 한 곳만 고친다.
Astro·Angular 에 프레임워크 컴포넌트를 따로 내주지 않는 이유: 메이저 버전마다 규약이 흔들려 우리가 고객 버전을 따라다니게 되고, "빌드 단계 없음" 성질도 깨진다. 브라우저 표준 하나로 덮는 편이 오래 간다.
화면 언어
setup 에서 한 번 정하면 이후 문구는 전부 훅이 낸다 — 고객사가 사유별 문구를 알
필요가 없다. 지원: ko · en · ja · zh-TW. 모르는 값이면 조용히 en 으로
떨어진다(화면이 깨지지 않는다).
createCxHook({ ..., locale: "ja" });
createCxHook({ ..., locale: "ko", messages: { ui_request: "답변 초안 받기" } });간체 중국어(zh-CN)를 zh-TW 로 붙이지 않는다 — 잘못 안내하느니 영어가 낫다.
통합 (전부)
주소는 하나다. 허브의 ingress·feedback·answer 세 라우트는 같은 호스트에
같은 권한 등급이라 갈릴 수 없다.
// cxAgentHook.js — 고객사 저장소에 두는 배선 파일. 이게 전부다.
import { createCxHook } from "@fcg-labs/cx-agent-hook";
export const cxHook = createCxHook({
baseUrl: import.meta.env.VITE_CX_HUB_URL,
token: import.meta.env.VITE_CX_HUB_BROWSER_TOKEN, // browser 등급 (적재 전용)
domain: import.meta.env.VITE_CX_DOMAIN,
api: "hub",
});// 상담 화면 (Vue·Astro·Angular 는 위 표의 진입점으로 바꾸면 된다)
import { AiSuggestPanel } from "@fcg-labs/cx-agent-hook/react";
import "@fcg-labs/cx-agent-hook/styles.css";
import { cxHook } from "./cxAgentHook";
<AiSuggestPanel hook={cxHook} inquiry={selected?.content} onAdopt={setAnswerText} />
// 발송 버튼에서 두 줄
cxHook.answerSent(editor.value, session.userId); // ① 채택본이었을 때만 동작
cxHook.inquirySent({ // ② 문의·답변 쌍 적재
externalId: inquiry.id, inquiry: inquiry.text,
reply: editor.value, agent: session.userId,
meta: { category: inquiry.category },
});②가 인입의 유일한 통로다. AI 제안을 안 쓰더라도 ②만 붙이면 문의·답변 쌍이 쌓이고, 그게 교재·평가 데이터가 된다. ①은 AI 제안을 쓸 때만 의미가 있다.
켜고 끄기는 서버가 정한다
"적재만 받고 AI 제안은 안 함"은 서버 설정이다(허브의 answer 업스트림 미설정). 고객사 빌드에 스위치를 두지 않는다 — 두면 진실의 주인이 둘이 되고, 켜는 데 관리자 화면 재배포가 필요해진다. 꺼져 있으면 패널이 눌렀을 때 그 사실을 평문으로 안내한다.
상담사 판단 후킹 — 학습의 재료
채택한 제안에 대해서만 동작한다. answer_id 는 고객사가 들지 않는다 — 패널이
채택 시점에 기록하고, 아래 호출이 그 기록을 쓴다.
cxHook.scored(4, agent); // 제안 품질 1~5
cxHook.edited(editor.value, agent); // 고쳐 씀 (초안↔최종본 델타)
cxHook.discarded(agent, "톤이 어색함"); // 안 씀 + 이유
cxHook.answerSent(editor.value, agent); // 발송answerSent·discarded 는 그 제안에 대한 마지막 판단이라 보낸 뒤 채택 기록을
비운다(재발송해도 중복되지 않는다). scored·edited 는 발송 전에 여러 번 올 수
있어 비우지 않는다.
전송 계층은 공개하지 않는다
CxAgentClient(HTTP 왕복·재시도·경로 규약)는 client.js 에 있고 exports 맵에
없다. 소비처가 @fcg-labs/cx-agent-hook/client.js 로 닿을 수 없다.
의도된 제약이다. 저수준을 열어 두면 새 능력을 붙일 때 자연히 그리로 내려가고,
그 순간 answer_id 수명 관리 같은 제품 지식이 다시 고객사 코드로 흩어진다.
능력이 모자라면 저수준을 노출하는 게 아니라 createCxHook 표면을 채운다.
능력별로 주소가 정말 갈려야 하면(예: 답변만 공장 직결) createCxHook 설정에
answer·ingress·feedback 대상을 넘긴다 — 전송 계층에 그대로 전달된다.
계약 보증
- 어떤 메서드도 throw 하지 않는다 — 발송 UX 를 막지 않고
{ ok:false }로 보고.getAnswer도 대상 미설정·네트워크 오류를 throw 없이 돌려준다(상담사는 수동 작성으로 계속 간다). 설정 실수는onError로 개발자에게만 알린다. - 서버가 준 거절 사유를 뭉개지 않는다 —
answer_disabled(아직 안 켬)와answer_unavailable(장애)이 같은 503 이지만 다른 안내로 갈린다. sendFeedback·logInquiry는 네트워크 오류를 재시도(기본 2회, 백오프) 후 보고- 4xx(계약 위반)는 재시도하지 않고 즉시 보고 —
onError콜백으로 관측 logInquiry는externalId기준 멱등 — 같은 문의를 두 번 보내도 중복되지 않는다answerSent는 한 번 보내면 채택 기록을 비운다 — 재발송해도 교정이 중복되지 않는다
라이선스와 접속 자격
공개 배포이고, 받아서 쓰셔도 됩니다 — 여러분의 애플리케이션에 넣어 함께
배포하는 것도 됩니다. SDK 자체의 재배포·개작, 경쟁 서비스 구축은 제외입니다
(LICENSE 전문 참조).
여는 데 부담이 없는 이유는 이 패키지에 자산이 없기 때문입니다. 지식베이스도 코퍼스도 학습 데이터도 가중치도 들어 있지 않고, 접속 자격이 없으면 아무 동작도 하지 않습니다 — 요청을 보내지 않고 조용히 꺼져 있습니다(시험으로 고정).
접속 토큰
token 은 도메인에 묶인 값이다 (cx1_<domain>_<mac>). 게이트웨이가 요청
경로의 도메인과 대조하므로, 한 고객의 토큰으로 다른 고객의 수집함에 쓸 수 없다.
적재·교정·답변만 되고 학습 산출물에는 닿지 못하는 등급이라 번들에 실려도
안전하다. 값은 플랫폼의 도메인 화면에서 받는다.
테스트
npm test # 65건 — 전송 계약·훅 표면·다국어·5종 어댑터패널의 표시 판단은 aiSuggestView() 순수 함수로 떼어 두었다 — 이 패키지는
react-dom 을 의존하지 않으므로 렌더러 없이 검증한다. Vue 는 SSR 렌더로,
커스텀 엘리먼트는 실제 DOM 으로 확인한다 (둘 다 devDependency — 배포물에는 없다).
배포 전에는 npm publish 가 scripts/prepublish-guard.mjs 를 먼저 돌린다:
화이트리스트 밖 파일·진입점 누락·비밀/실주소/고객 식별자·시험 실패를 막는다.
