@vibexnpm/talkx 4.0.1 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -65,7 +65,16 @@ const CHAT_DELEGATE_METHODS = [
65
65
  'deactivateRoomPmPrompt',
66
66
  'getRoomPmPromptVersions',
67
67
  'activateRoomPmPromptVersion',
68
- 'previewRoomPmPrompt'
68
+ 'previewRoomPmPrompt',
69
+ // 투표 — 투표 서버는 클러스터 내부에만 있어 클라이언트가 직접 부를 수 없다. 전부 채팅 서버 경유.
70
+ 'createVote',
71
+ 'getVotes',
72
+ 'getVote',
73
+ 'updateVote',
74
+ 'deleteVote',
75
+ 'closeVote',
76
+ 'submitVote',
77
+ 'uploadVoteImage'
69
78
  ];
70
79
 
71
80
  const WEBRTC_DELEGATE_METHODS = [
@@ -24,6 +24,7 @@ const CHAT_EVENT_MAP = [
24
24
  ['messageRead', 'messageRead'],
25
25
  ['typing', 'typing'],
26
26
  ['assistantProgress', 'assistantProgress'],
27
+ ['assistantRoomProgress', 'assistantRoomProgress'],
27
28
  ['summaryUpdate', 'summaryUpdate'],
28
29
  ['memberJoined', 'memberJoined'],
29
30
  ['memberLeft', 'memberLeft'],
@@ -33,6 +33,7 @@ export function initializeSubClients(client) {
33
33
  apiClient: client.apiClient,
34
34
  userId: client._userId,
35
35
  syncOnReconnect: client.options.syncOnReconnect,
36
+ assistantStreamReplay: client.options.assistantStreamReplay,
36
37
  rewindMillis: client.options.rewindMillis,
37
38
  maxSyncPages: client.options.maxSyncPages,
38
39
  syncPageSize: client.options.syncPageSize,
@@ -62,10 +62,12 @@ class ApiClient {
62
62
  * @param {Object} [options] - 옵션
63
63
  * @param {Object} [options.body] - 요청 본문
64
64
  * @param {Object} [options.params] - URL 파라미터
65
+ * @param {Object} [options.headers] - 이 요청에만 얹는 추가 헤더. 기본 헤더(Authorization 등)와 병합된다.
66
+ * 같은 이름이면 이 값이 이긴다 — 인증 헤더를 덮어쓰지 않도록 호출자가 주의한다.
65
67
  * @returns {Promise<Object>}
66
68
  */
67
69
  async request(method, path, options = {}) {
68
- const { body, params } = options;
70
+ const { body, params, headers } = options;
69
71
 
70
72
  // URL 빌드
71
73
  let url = `${this.baseUrl}${path}`;
@@ -92,7 +94,7 @@ class ApiClient {
92
94
 
93
95
  response = await fetch(url, {
94
96
  method,
95
- headers: this._getHeaders(),
97
+ headers: headers ? { ...this._getHeaders(), ...headers } : this._getHeaders(),
96
98
  body: body ? JSON.stringify(body) : undefined,
97
99
  signal: controller.signal
98
100
  });
@@ -152,10 +154,12 @@ class ApiClient {
152
154
  * @param {string} path - API 경로
153
155
  * @param {Object} [body] - 요청 본문
154
156
  * @param {Object} [params] - URL 파라미터
157
+ * @param {Object} [headers] - 이 요청에만 얹는 추가 헤더 (예: {@code X-Idempotency-Key}).
158
+ * 기존 호출자는 인자를 넘기지 않으므로 동작이 바뀌지 않는다.
155
159
  * @returns {Promise<Object>}
156
160
  */
157
- post(path, body, params) {
158
- return this.request('POST', path, { body, params });
161
+ post(path, body, params, headers) {
162
+ return this.request('POST', path, { body, params, headers });
159
163
  }
160
164
 
161
165
  /**
package/types/index.d.ts CHANGED
@@ -753,6 +753,177 @@ export interface ChatMessage {
753
753
  decisionRequest?: DecisionRequest | null;
754
754
  /** 사용자의 선택. 어느 카드인지는 {@code replyTo} 가 특정한다. */
755
755
  decisionResponse?: DecisionResponse | null;
756
+ /**
757
+ * 투표 카드 — 투표가 열리거나 마감·취소됐음을 알리는 메시지의 payload.
758
+ *
759
+ * <p><b>모르면 그냥 텍스트로 그려라 — 그게 정상 동작이다.</b> {@code content} 에 같은 내용이
760
+ * 사람이 읽는 형태로 들어 있다. 반대로 카드를 그린다면 {@code content} 를 다시 그리지 마라.</p>
761
+ *
762
+ * <p>카드에는 집계가 없다. 결과는 {@code voteId} 로 {@link ChatClient.getVote} 를 호출해 받는다 —
763
+ * 마스킹이 호출자마다 다르게 계산되기 때문이다.</p>
764
+ */
765
+ voteCard?: VoteCard | null;
766
+ }
767
+
768
+ /**
769
+ * 투표 카드 payload — 이벤트 하나당 메시지 하나다(개설·마감·취소).
770
+ *
771
+ * <p>이미 발행된 카드는 고쳐지지 않는다. 투표가 삭제돼도 개설 카드는 그대로 남고 조회가 404 가 되며,
772
+ * 「취소됨」 표시는 화면 몫이다(별도의 취소 카드도 함께 발행된다).</p>
773
+ */
774
+ export interface VoteCard {
775
+ voteId: string;
776
+ /**
777
+ * {@code 'OPENED'} | {@code 'CLOSED'} | {@code 'CANCELED'}.
778
+ *
779
+ * <p>🔴 <b>모르는 값이 올 수 있다</b> — 서버가 나중에 종류를 늘려도 구버전이 깨지지 않도록 열린 문자열이다.
780
+ * switch 의 default 는 텍스트 폴백으로 둔다.</p>
781
+ */
782
+ kind: 'OPENED' | 'CLOSED' | 'CANCELED' | (string & {});
783
+ /**
784
+ * 발행 시점의 질문 <b>스냅샷</b>. 참여자가 0명인 동안 투표가 수정될 수 있으므로 최신값이 필요하면
785
+ * {@link ChatClient.getVote} 로 조회한다. 마감·취소 카드에서는 비어 있을 수 있다.
786
+ */
787
+ question?: string | null;
788
+ /** 마감 예정 시각 (epoch millis, UTC). 마감·취소 카드에서는 null 일 수 있다. */
789
+ closeAt?: number | null;
790
+ }
791
+
792
+ // ── 투표 (createVote / getVotes / getVote / submitVote ...) ──
793
+
794
+ /** 투표 등록 폼 — 생성과 수정이 같은 타입이다({@code PATCH} 지만 부분 수정이 아니다). */
795
+ export interface VoteForm {
796
+ /** 최대 500자. 공백만이면 거절. */
797
+ question: string;
798
+ type: 'SINGLE' | 'MULTIPLE';
799
+ /** 양수. {@code SINGLE} 이면 1. */
800
+ maxSelections: number;
801
+ /** {@code 'IMAGE'} 라는 값은 없다. */
802
+ optionType: 'TEXT' | 'TEXT_WITH_IMAGE';
803
+ /** 마감 시각 (epoch millis). 과거·현재 불가 · 최대 30일 뒤. 무기한 투표는 없다. */
804
+ closeAt: number;
805
+ /** 2~10개. */
806
+ options: VoteFormOption[];
807
+ }
808
+
809
+ export interface VoteFormOption {
810
+ /** {@code .} {@code $} NUL 사용 불가 · 최대 100자 · 중복 불가 (집계 필드명으로 쓰인다). */
811
+ id: string;
812
+ /** 최대 200자. */
813
+ content: string;
814
+ /** 표시 순서. */
815
+ order: number;
816
+ /**
817
+ * {@link ChatClient.uploadVoteImage} 가 돌려준 값을 <b>가공 없이</b> 넣는다 — 서버가 문자열로 비교한다.
818
+ * 채팅 첨부 URL 은 쓸 수 없다. {@code optionType: 'TEXT'} 면 넣으면 안 되고, 빈 문자열도 거절된다.
819
+ */
820
+ imageUrl?: string | null;
821
+ }
822
+
823
+ /** 투표 단건 + 집계 — 생성 · 수정 · 단건조회 · 접수 응답. */
824
+ export interface Vote {
825
+ voteId: string;
826
+ roomId: string;
827
+ question: string;
828
+ type: 'SINGLE' | 'MULTIPLE';
829
+ optionType: 'TEXT' | 'TEXT_WITH_IMAGE';
830
+ maxSelections: number;
831
+ /**
832
+ * {@code 'OPEN'} | {@code 'CLOSED'}.
833
+ *
834
+ * <p>🔴 <b>이 값만으로 마감을 판정하면 안 된다</b> — 서버 자동 마감이 미구현이라 {@code closeAt} 이
835
+ * 지났는데도 {@code 'OPEN'} 인 투표가 있고, 그건 마감된 것으로 동작한다. 둘을 함께 본다.</p>
836
+ */
837
+ status: 'OPEN' | 'CLOSED' | (string & {});
838
+ /** epoch millis. */
839
+ closeAt: number;
840
+ /** 마감 전이면 null. epoch millis. */
841
+ closedAt?: number | null;
842
+ creatorId: string;
843
+ createdAt: number;
844
+ updatedAt: number;
845
+ /**
846
+ * 결과 공개 여부. 공개 조건은 「내가 응답했다」 또는 「마감됐다」.
847
+ *
848
+ * <p>🔴 {@code false} 일 때의 {@code count}/{@code ratio} 0 은 「참여자가 없다」가 아니라
849
+ * <b>서버가 가린 값</b>이다. 「아직 0표」로 표시하면 거짓말이 된다.</p>
850
+ */
851
+ revealed: boolean;
852
+ /** 응답자 수. <b>득표 합이 아니다</b> — 복수 선택에서 값이 다르다. */
853
+ totalResponses: number;
854
+ /** 호출자가 고른 선택지. 없으면 빈 배열. 비공개 상태에서도 채워진다(재투표 화면용). */
855
+ myOptionIds: string[];
856
+ options: VoteOptionResult[];
857
+ }
858
+
859
+ export interface VoteOptionResult {
860
+ optionId: string;
861
+ content: string;
862
+ order: number;
863
+ /** {@code optionType} 이 {@code 'TEXT'} 면 항상 null. */
864
+ imageUrl?: string | null;
865
+ /** 득표 수. 비공개면 0(가려진 값). */
866
+ count: number;
867
+ /**
868
+ * 응답자 대비 %, 소수 첫째 자리.
869
+ *
870
+ * <p>분모가 <b>응답자 수</b>이지 총 선택 수가 아니다 — 복수 선택 투표에서는 합이 100을 넘는 것이
871
+ * 정상이고, 화면이 「복수 응답」임을 표기해야 한다.</p>
872
+ */
873
+ ratio: number;
874
+ }
875
+
876
+ /** {@link ChatClient.getVotes} 옵션. */
877
+ export interface GetVotesOptions {
878
+ /** 이전 응답의 {@code nextCursor} 를 <b>그대로</b>. 불투명 문자열이라 파싱하지 않는다. */
879
+ cursor?: string;
880
+ /** 기본 20, 최대 100. 초과분은 서버가 조용히 100 으로 보정한다(에러 아님). */
881
+ limit?: number;
882
+ }
883
+
884
+ /** {@link ChatClient.getVotes} 반환. */
885
+ export interface VoteList {
886
+ /** 최신순. 비면 끝이다. */
887
+ items: VoteListItem[];
888
+ /** null 이면 끝. 마지막 페이지가 정확히 {@code limit} 개면 채워진 채 오고, 그 커서로 부르면 빈 목록이 온다. */
889
+ nextCursor?: string | null;
890
+ }
891
+
892
+ /** 목록 항목 — {@code optionType} · {@code options} · {@code myOptionIds} · {@code revealed} 가 <b>없다</b>. */
893
+ export interface VoteListItem {
894
+ voteId: string;
895
+ question: string;
896
+ type: 'SINGLE' | 'MULTIPLE';
897
+ status: 'OPEN' | 'CLOSED' | (string & {});
898
+ closeAt: number;
899
+ creatorId: string;
900
+ createdAt: number;
901
+ /** 참여자 수. 선택지별 집계는 목록에 없다 — 마스킹이 사용자마다 다르기 때문이다. */
902
+ responseCount: number;
903
+ }
904
+
905
+ /** {@link ChatClient.createVote} 옵션. */
906
+ export interface CreateVoteOptions {
907
+ /**
908
+ * 🔴 <b>필수.</b> SDK 가 대신 만들지 않는다 — 만들면 그 값이 호출자에게 남지 않아 응답만 유실된
909
+ * 타임아웃에서 복구가 불가능하다(다음 호출이 새 키를 만들어 투표가 두 개 생긴다).
910
+ *
911
+ * <p>한 투표당 한 번 만들어 보관하고, 같은 시도의 재시도에는 <b>같은 값</b>을 넘긴다 —
912
+ * 그러면 서버가 기존 투표를 돌려준다(201, 같은 voteId).</p>
913
+ */
914
+ idempotencyKey: string;
915
+ }
916
+
917
+ /** {@link ChatClient.uploadVoteImage} 옵션. */
918
+ export interface UploadVoteImageOptions {
919
+ onProgress?: (progress: { loaded: number; total: number; percent: number }) => void;
920
+ signal?: AbortSignal;
921
+ }
922
+
923
+ /** {@link ChatClient.uploadVoteImage} 반환. */
924
+ export interface VoteImage {
925
+ /** 생성·수정 본문의 {@code options[].imageUrl} 에 <b>가공 없이</b> 넣는다. */
926
+ imageUrl: string;
756
927
  }
757
928
 
758
929
  /**
@@ -990,35 +1161,103 @@ export interface TypingEvent {
990
1161
  *
991
1162
  * typing 과 별개의 가산 UX 레이어(설계 ASSISTANT_PROGRESS_STREAMING_DESIGN.md). 서버가 typing 도 이중발행하므로
992
1163
  * 구버전 SDK 는 본 이벤트를 못 받아도 기존 typing 으로 정상 동작한다. UI 는 `active:true` 면 `label`(또는 `phase`)
993
- * 로 단계를 표시하고, `active:false`(DONE·어시 메시지 도착·TTL 만료) 면 해제한다.
1164
+ * 로 단계를 표시하고, `active:false`(DONE·TTL 만료) 면 해제한다.
1165
+ *
1166
+ * ⚠️ **v5.0.0 breaking — 이 이벤트는 `streamId` 단위다.** 한 방에 동시 스트림이 실재하므로(PM 방에서 트리거가
1167
+ * 겹치면 각각 streamId 를 받는다) `active:false` 는 **그 stream 하나**를 뜻한다. 방마다 버블 하나를 그리는
1168
+ * 기존 코드에 그대로 물리면 **같은 방의 다른 살아 있는 버블까지 숨긴다**. 둘 중 하나를 택한다.
1169
+ *
1170
+ * - (권장) `roomId + streamId` 를 키로 버블을 관리한다 — 최종 목표 형태.
1171
+ * - (이행용 다리) {@link AssistantRoomProgressEvent} 를 대신 구독한다. 리스너 이름만 바꾸면 기존 단일 버블
1172
+ * 렌더가 그대로 동작한다.
994
1173
  *
995
1174
  * 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행 — 멘션/버튼/14인 경로는 본 이벤트 없이 typing 만.
996
1175
  */
997
1176
  export interface AssistantProgressEvent {
998
1177
  /** 채팅방 ID. */
999
1178
  roomId: string;
1000
- /** 어시 응답 1회분 식별(멀티 응답/중복 dedup). 일부 종료 emit 에선 null. */
1001
- streamId: string | null;
1179
+ /**
1180
+ * 어시 응답 1회분 식별 — **v5.0.0 부터 항상 non-null**.
1181
+ *
1182
+ * UI 는 `roomId + streamId` 로 버블을 키잉한다. SDK 는 `streamId` 없는 서버 이벤트를 아예 버리므로
1183
+ * 이 값이 비어 오는 emit 은 없다(v4.x 는 일부 종료 emit 에서 null 이 올 수 있었다).
1184
+ */
1185
+ streamId: string;
1002
1186
  /** 진행 주체 페르소나 ID. */
1003
1187
  personaId: string | null;
1004
1188
  /** 진행 단계 — `active:true` 일 때. 종료 시 null. */
1005
1189
  phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
1006
1190
  /** phase 의 기본 표시 라벨(예: "생각 중…"). SDK 매핑 — 커스텀하려면 `phase` 사용. 종료 시 null. */
1007
1191
  label: string | null;
1008
- /** true=진행중(PHASE/DELTA) / false=종료(DONE·메시지 도착·TTL). */
1192
+ /** true=진행중(PHASE/DELTA) / false=종료(DONE·TTL). */
1009
1193
  active: boolean;
1010
- /** 토큰 스트림 누적 텍스트(B) — DELTA 수신 시 지금까지 모인 전체. 버블에 그대로 렌더. PHASE-only/종료 시 null. */
1194
+ /**
1195
+ * 토큰 스트림 누적 텍스트(B) — DELTA 수신 시 지금까지 모인 전체. 버블에 그대로 렌더.
1196
+ * PHASE-only/종료 시 null. **`textAvailable:false` 면 항상 null** — 그 땐 `label` 을 그린다.
1197
+ */
1011
1198
  text?: string | null;
1012
- /** 이번 토큰 청크(B) — append 렌더를 선호할 때. DELTA 수신 시만. */
1199
+ /** 이번 토큰 청크(B) — append 렌더를 선호할 때. DELTA 수신 시만. `textAvailable:false` 면 null. */
1013
1200
  delta?: string | null;
1014
1201
  /** 토큰 청크 순서/누락 감지(B) — DELTA 수신 시만. */
1015
1202
  seq?: number | null;
1203
+ /**
1204
+ * 본문을 신뢰할 수 있는가 (v4.2.0 추가).
1205
+ *
1206
+ * `false` = 앞부분을 받은 적이 없다 — 스트리밍 도중 재입장/새로고침(late join)했거나 WS 순단으로 `seq` 에
1207
+ * 구멍이 났다는 뜻. 이때 `text`/`delta` 는 null 로 오고 **이후 청크도 누적되지 않는다**.
1208
+ * UI 는 본문 대신 `label`(예: "작성 중…")을 표시하고 최종 메시지를 기다리면 된다.
1209
+ *
1210
+ * 예전 동작은 이 경우에도 받은 조각을 누적본처럼 넘겨서 **잘린 답변**이 화면에 그려졌다.
1211
+ */
1212
+ textAvailable?: boolean;
1016
1213
  /** 종료(DONE) 결과 — DONE 수신 시만. */
1017
1214
  status?: 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT' | null;
1018
1215
  /** 종료(DONE, SUCCEEDED) 시 영속된 최종 메시지 ID. */
1019
1216
  messageId?: string | null;
1020
- /** 종료 사유 — 'message'(어시 메시지 도착) / 'timeout'(TTL). DONE 수신 해제엔 없음. */
1021
- reason?: 'message' | 'timeout' | null;
1217
+ /**
1218
+ * 종료 사유 — 'timeout'(TTL 만료). DONE 수신 해제엔 없음.
1219
+ *
1220
+ * v4.2.0 부터 `'message'` 는 emit 되지 않았고(어시 메시지 도착은 진행 표시를 끄지 않는다 —
1221
+ * 종료 SSOT = DONE + TTL), **v5.0.0 에서 유니온에서도 제거**했다.
1222
+ */
1223
+ reason?: 'timeout' | null;
1224
+ /** 이 stream 의 마지막 seq — 스냅샷 병합 커서(v5.0.0). DELTA 0건이면 -1. */
1225
+ lastSeq?: number;
1226
+ /**
1227
+ * `true` = 서버 스냅샷으로 **복원된** 진행분(v5.1.0).
1228
+ *
1229
+ * 스트리밍 도중 재입장했거나 새로 들어와서, 구독 전에 이미 발행된 앞부분을 REST 스냅샷으로 받아온
1230
+ * 경우다. UI 가 "이어보는 중" 같은 표시를 하고 싶을 때만 쓰면 되고, 렌더 로직은 평소와 같다.
1231
+ */
1232
+ resumed?: boolean;
1233
+ }
1234
+
1235
+ /**
1236
+ * 방 단위 AI 진행 표시 집계 (v5.0.0 신설).
1237
+ *
1238
+ * <p>{@link AssistantProgressEvent} 가 `streamId` 단위로 바뀌면서, **방마다 임시 버블 하나**를 그리는 기존
1239
+ * UI 를 위한 이행용 다리로 제공한다. 리스너 이름만 `assistantProgress` → `assistantRoomProgress` 로 바꾸면
1240
+ * 기존 렌더 코드가 그대로 동작한다.</p>
1241
+ *
1242
+ * <p>`active` 는 방에 살아 있는 stream 이 **하나라도** 있으면 true 다. 그래서 동시 응답 중 하나가 끝나도
1243
+ * 버블이 사라지지 않는다. 표시 내용(`text`/`label`/`phase`)은 **가장 최근에 갱신된** stream 을 따른다.</p>
1244
+ */
1245
+ export interface AssistantRoomProgressEvent {
1246
+ /** 채팅방 ID. */
1247
+ roomId: string;
1248
+ /** 방에 살아 있는 stream 이 하나라도 있으면 true. */
1249
+ active: boolean;
1250
+ /** 살아 있는 stream 개수. 2 이상이면 동시 응답 중이다. */
1251
+ streamCount: number;
1252
+ /** 표시 기준이 된 stream (가장 최근 갱신). 없으면 null. */
1253
+ streamId: string | null;
1254
+ personaId: string | null;
1255
+ phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
1256
+ label: string | null;
1257
+ /** 표시 기준 stream 의 누적 본문. `textAvailable:false` 거나 진행 중이 아니면 null. */
1258
+ text?: string | null;
1259
+ /** 표시 기준 stream 의 본문 신뢰 여부 — false 면 `label` 을 그린다. */
1260
+ textAvailable?: boolean;
1022
1261
  }
1023
1262
 
1024
1263
  // ============================================================================
@@ -1061,6 +1300,13 @@ export interface TalkFlowClientOptions {
1061
1300
  * <p>복구된 메시지는 일반 `chatMessage` 이벤트로 재생되므로 수신 코드를 바꿀 필요가 없다.</p>
1062
1301
  */
1063
1302
  syncOnReconnect?: boolean;
1303
+ /**
1304
+ * 방 구독·재연결 시 진행 중 AI 스트림을 서버 스냅샷으로 이어받을지. 기본 true (v5.1.0).
1305
+ *
1306
+ * <p>스트림은 Pub/Sub 라 구독 시점 이후만 오므로, 스트리밍 도중 재입장하면 앞부분을 받을 방법이
1307
+ * 스냅샷뿐이다. `syncOnReconnect`(메시지 갭 복구)와 <b>별개 경로</b>다 — 그쪽을 꺼도 이건 돈다.</p>
1308
+ */
1309
+ assistantStreamReplay?: boolean;
1064
1310
  /** 갭 복구 첫 페이지에만 적용되는 되감기 폭 (ms). 기본 3000. 서버가 상한 clamp 후 1회 차감. */
1065
1311
  rewindMillis?: number;
1066
1312
  /** 갭 복구 페이지 상한. 기본 5. 초과 시 최신 페이지 재로드로 전환하고 `gapTruncated` 를 발행. */
@@ -1438,6 +1684,7 @@ export interface TalkFlowClientEvents {
1438
1684
  messageRead: MessageReadEventPayload;
1439
1685
  typing: TypingEvent;
1440
1686
  assistantProgress: AssistantProgressEvent;
1687
+ assistantRoomProgress: AssistantRoomProgressEvent;
1441
1688
  /** Rolling 누적 요약 완료/실패 통지 (transient) — refreshSummary 후. */
1442
1689
  summaryUpdate: SummaryUpdateEvent;
1443
1690
  memberJoined: MemberChangePayload;
@@ -1573,6 +1820,7 @@ export interface ChatClientEvents {
1573
1820
  messageRead: MessageReadEventPayload;
1574
1821
  typing: TypingEvent;
1575
1822
  assistantProgress: AssistantProgressEvent;
1823
+ assistantRoomProgress: AssistantRoomProgressEvent;
1576
1824
  /** Rolling 누적 요약 완료/실패 통지 (transient) — refreshSummary 후. */
1577
1825
  summaryUpdate: SummaryUpdateEvent;
1578
1826
  }
@@ -1644,6 +1892,24 @@ export class ChatClient implements SdkEventEmitter<ChatClientEvents> {
1644
1892
  refreshSummary(roomId: string, options?: RefreshSummaryOptions): Promise<RefreshSummaryResult>;
1645
1893
  /** Rolling 누적 요약 조회 — rollup + segment 페이지. 저장된 요약의 단일 진실 소스. */
1646
1894
  getSummary(roomId: string, options?: GetSummaryOptions): Promise<RoomSummaryView>;
1895
+
1896
+ // ── 투표 ──
1897
+ /** 투표 생성. {@code idempotencyKey} 필수 — 재시도에 같은 값을 넘겨야 투표가 두 개 안 생긴다. */
1898
+ createVote(roomId: string, vote: VoteForm, options: CreateVoteOptions): Promise<Vote>;
1899
+ /** 방의 투표 목록(최신순). 집계는 없고 참여자 수만 있다. */
1900
+ getVotes(roomId: string, options?: GetVotesOptions): Promise<VoteList>;
1901
+ /** 투표 단건 + 집계. 마스킹이 호출자마다 다르므로 공유 캐시에 넣지 않는다. */
1902
+ getVote(roomId: string, voteId: string): Promise<Vote>;
1903
+ /** 투표 수정 — 폼 전체를 보낸다. 개설자 + 참여자 0명일 때만. */
1904
+ updateVote(roomId: string, voteId: string, vote: VoteForm): Promise<Vote>;
1905
+ /** 투표 삭제(204). 이미 발행된 카드는 남고 조회가 404 가 된다. */
1906
+ deleteVote(roomId: string, voteId: string): Promise<void>;
1907
+ /** 수동 마감(204). 결과를 보려면 {@link getVote} 를 다시 호출한다. */
1908
+ closeVote(roomId: string, voteId: string): Promise<void>;
1909
+ /** 투표 접수 · 재투표 — 같은 메서드. 응답에 갱신된 집계가 함께 온다. */
1910
+ submitVote(roomId: string, voteId: string, optionIds: string[]): Promise<Vote>;
1911
+ /** 선택지 이미지 업로드. 반환 {@code imageUrl} 을 가공 없이 폼에 넣는다. */
1912
+ uploadVoteImage(roomId: string, file: File | Blob, options?: UploadVoteImageOptions): Promise<VoteImage>;
1647
1913
  /** 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용, 참여자 전체. 미등록 시 404. */
1648
1914
  getRoomPmPrompt(roomId: string): Promise<PmPromptLayerResponse>;
1649
1915
  /** 방 PM 프롬프트 레이어 등록/수정 (upsert, 새 버전 자동 기록) — 방 주인(OWNER) 전용. content 1~2,000자. */
@@ -2012,6 +2278,24 @@ export default class TalkFlowClient implements SdkEventEmitter<TalkFlowClientEve
2012
2278
  refreshSummary(roomId: string, options?: RefreshSummaryOptions): Promise<RefreshSummaryResult>;
2013
2279
  /** Rolling 누적 요약 조회 — rollup + segment 페이지. 저장된 요약의 단일 진실 소스. */
2014
2280
  getSummary(roomId: string, options?: GetSummaryOptions): Promise<RoomSummaryView>;
2281
+
2282
+ // ── 투표 ──
2283
+ /** 투표 생성. {@code idempotencyKey} 필수 — 재시도에 같은 값을 넘겨야 투표가 두 개 안 생긴다. */
2284
+ createVote(roomId: string, vote: VoteForm, options: CreateVoteOptions): Promise<Vote>;
2285
+ /** 방의 투표 목록(최신순). 집계는 없고 참여자 수만 있다. */
2286
+ getVotes(roomId: string, options?: GetVotesOptions): Promise<VoteList>;
2287
+ /** 투표 단건 + 집계. 마스킹이 호출자마다 다르므로 공유 캐시에 넣지 않는다. */
2288
+ getVote(roomId: string, voteId: string): Promise<Vote>;
2289
+ /** 투표 수정 — 폼 전체를 보낸다. 개설자 + 참여자 0명일 때만. */
2290
+ updateVote(roomId: string, voteId: string, vote: VoteForm): Promise<Vote>;
2291
+ /** 투표 삭제(204). 이미 발행된 카드는 남고 조회가 404 가 된다. */
2292
+ deleteVote(roomId: string, voteId: string): Promise<void>;
2293
+ /** 수동 마감(204). 결과를 보려면 {@link getVote} 를 다시 호출한다. */
2294
+ closeVote(roomId: string, voteId: string): Promise<void>;
2295
+ /** 투표 접수 · 재투표 — 같은 메서드. 응답에 갱신된 집계가 함께 온다. */
2296
+ submitVote(roomId: string, voteId: string, optionIds: string[]): Promise<Vote>;
2297
+ /** 선택지 이미지 업로드. 반환 {@code imageUrl} 을 가공 없이 폼에 넣는다. */
2298
+ uploadVoteImage(roomId: string, file: File | Blob, options?: UploadVoteImageOptions): Promise<VoteImage>;
2015
2299
  /** 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용, 참여자 전체. 미등록 시 404. */
2016
2300
  getRoomPmPrompt(roomId: string): Promise<PmPromptLayerResponse>;
2017
2301
  /** 방 PM 프롬프트 레이어 등록/수정 (upsert, 새 버전 자동 기록) — 방 주인(OWNER) 전용. content 1~2,000자. */