@things-factory/board-ai 10.1.4 → 10.1.6
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.
- package/client/components/default-catalog-entries.ts +1 -1
- package/client/components/default-slash-templates.ts +1 -1
- package/client/index.ts +27 -6
- package/dist-client/client/components/default-catalog-entries.d.ts +1 -1
- package/dist-client/client/components/default-catalog-entries.js.map +1 -1
- package/dist-client/client/components/default-slash-templates.d.ts +1 -1
- package/dist-client/client/components/default-slash-templates.js.map +1 -1
- package/dist-client/client/index.d.ts +3 -11
- package/dist-client/client/index.js +25 -6
- package/dist-client/client/index.js.map +1 -1
- package/dist-client/server/service/assistant.d.ts +22 -22
- package/dist-client/server/service/assistant.js +32 -70
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/grounding.d.ts +2 -12
- package/dist-client/server/service/grounding.js +15 -23
- package/dist-client/server/service/grounding.js.map +1 -1
- package/dist-client/server/service/types.d.ts +2 -4
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/server/service/validation/board-model-schema.d.ts +270 -270
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/assistant.d.ts +22 -22
- package/dist-server/service/assistant.js +25 -76
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.js +41 -30
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/board-session-resolver.d.ts +12 -0
- package/dist-server/service/board-session-resolver.js +203 -0
- package/dist-server/service/board-session-resolver.js.map +1 -0
- package/dist-server/service/board-tool-rights.d.ts +7 -0
- package/dist-server/service/board-tool-rights.js +62 -0
- package/dist-server/service/board-tool-rights.js.map +1 -0
- package/dist-server/service/grounding.d.ts +2 -12
- package/dist-server/service/grounding.js +16 -24
- package/dist-server/service/grounding.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -10
- package/dist-server/service/index.js +16 -23
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js +2 -2
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
- package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
- package/dist-server/service/patch-entry/patch-entry.js +3 -3
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
- package/dist-server/service/types.d.ts +2 -4
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/server/service/apply-patch-drift.test.ts +3 -1
- package/server/service/assistant.ts +47 -73
- package/server/service/board-ai-resolver.ts +22 -12
- package/server/service/board-session-resolver.ts +183 -0
- package/server/service/board-tool-rights.test.ts +96 -0
- package/server/service/board-tool-rights.ts +73 -0
- package/server/service/dock-contract.test.ts +3 -0
- package/server/service/grounding.test.ts +3 -3
- package/server/service/grounding.ts +15 -34
- package/server/service/index.ts +16 -22
- package/server/service/patch-entry/board-patch-subscription.ts +1 -1
- package/server/service/patch-entry/patch-entry.ts +3 -2
- package/server/service/registry-tool-loop.test.ts +1 -0
- package/server/service/tool-exposure.test.ts +2 -0
- package/server/service/types.ts +3 -4
- package/client/components/board-ai-chat.test.ts +0 -149
- package/client/components/board-ai-chat.ts +0 -3184
- package/client/components/chat-echo-dedup.test.ts +0 -230
- package/client/components/chat-echo-dedup.ts +0 -155
- package/client/components/chat-input-builder.ts +0 -69
- package/client/components/markdown.test.ts +0 -518
- package/client/components/markdown.ts +0 -508
- package/client/components/mention-popup-helpers.ts +0 -433
- package/client/components/mention-popup.test.ts +0 -588
- package/client/components/mention-popup.ts +0 -238
- package/client/utils/board-edit-patch.ts +0 -374
- package/dist-client/client/components/board-ai-chat.d.ts +0 -384
- package/dist-client/client/components/board-ai-chat.js +0 -3008
- package/dist-client/client/components/board-ai-chat.js.map +0 -1
- package/dist-client/client/components/board-ai-chat.test.d.ts +0 -1
- package/dist-client/client/components/board-ai-chat.test.js +0 -138
- package/dist-client/client/components/board-ai-chat.test.js.map +0 -1
- package/dist-client/client/components/chat-echo-dedup.d.ts +0 -54
- package/dist-client/client/components/chat-echo-dedup.js +0 -115
- package/dist-client/client/components/chat-echo-dedup.js.map +0 -1
- package/dist-client/client/components/chat-echo-dedup.test.d.ts +0 -1
- package/dist-client/client/components/chat-echo-dedup.test.js +0 -203
- package/dist-client/client/components/chat-echo-dedup.test.js.map +0 -1
- package/dist-client/client/components/chat-input-builder.d.ts +0 -55
- package/dist-client/client/components/chat-input-builder.js +0 -32
- package/dist-client/client/components/chat-input-builder.js.map +0 -1
- package/dist-client/client/components/markdown.d.ts +0 -60
- package/dist-client/client/components/markdown.js +0 -422
- package/dist-client/client/components/markdown.js.map +0 -1
- package/dist-client/client/components/markdown.test.d.ts +0 -1
- package/dist-client/client/components/markdown.test.js +0 -418
- package/dist-client/client/components/markdown.test.js.map +0 -1
- package/dist-client/client/components/mention-popup-helpers.d.ts +0 -165
- package/dist-client/client/components/mention-popup-helpers.js +0 -330
- package/dist-client/client/components/mention-popup-helpers.js.map +0 -1
- package/dist-client/client/components/mention-popup.d.ts +0 -35
- package/dist-client/client/components/mention-popup.js +0 -223
- package/dist-client/client/components/mention-popup.js.map +0 -1
- package/dist-client/client/components/mention-popup.test.d.ts +0 -1
- package/dist-client/client/components/mention-popup.test.js +0 -474
- package/dist-client/client/components/mention-popup.test.js.map +0 -1
- package/dist-client/client/utils/board-edit-patch.d.ts +0 -157
- package/dist-client/client/utils/board-edit-patch.js +0 -284
- package/dist-client/client/utils/board-edit-patch.js.map +0 -1
- package/dist-server/service/chat-message/chat-message-broadcast.d.ts +0 -13
- package/dist-server/service/chat-message/chat-message-broadcast.js +0 -13
- package/dist-server/service/chat-message/chat-message-broadcast.js.map +0 -1
- package/dist-server/service/chat-message/chat-message-publish.d.ts +0 -6
- package/dist-server/service/chat-message/chat-message-publish.js +0 -27
- package/dist-server/service/chat-message/chat-message-publish.js.map +0 -1
- package/dist-server/service/chat-message/chat-message-subscription.d.ts +0 -8
- package/dist-server/service/chat-message/chat-message-subscription.js +0 -53
- package/dist-server/service/chat-message/chat-message-subscription.js.map +0 -1
- package/dist-server/service/chat-message/chat-message.d.ts +0 -68
- package/dist-server/service/chat-message/chat-message.js +0 -172
- package/dist-server/service/chat-message/chat-message.js.map +0 -1
- package/dist-server/service/chat-message/fold-history.d.ts +0 -43
- package/dist-server/service/chat-message/fold-history.js +0 -33
- package/dist-server/service/chat-message/fold-history.js.map +0 -1
- package/dist-server/service/chat-message/history-summary.d.ts +0 -43
- package/dist-server/service/chat-message/history-summary.js +0 -77
- package/dist-server/service/chat-message/history-summary.js.map +0 -1
- package/dist-server/service/chat-message/index.d.ts +0 -5
- package/dist-server/service/chat-message/index.js +0 -12
- package/dist-server/service/chat-message/index.js.map +0 -1
- package/dist-server/service/chat-message/llm-history.d.ts +0 -46
- package/dist-server/service/chat-message/llm-history.js +0 -87
- package/dist-server/service/chat-message/llm-history.js.map +0 -1
- package/dist-server/service/chat-session/chat-session.d.ts +0 -62
- package/dist-server/service/chat-session/chat-session.js +0 -164
- package/dist-server/service/chat-session/chat-session.js.map +0 -1
- package/dist-server/service/chat-session/index.d.ts +0 -3
- package/dist-server/service/chat-session/index.js +0 -7
- package/dist-server/service/chat-session/index.js.map +0 -1
- package/dist-server/service/chat-session/session-activity-broadcast.d.ts +0 -30
- package/dist-server/service/chat-session/session-activity-broadcast.js +0 -34
- package/dist-server/service/chat-session/session-activity-broadcast.js.map +0 -1
- package/dist-server/service/chat-session/session-activity-publish.d.ts +0 -12
- package/dist-server/service/chat-session/session-activity-publish.js +0 -37
- package/dist-server/service/chat-session/session-activity-publish.js.map +0 -1
- package/dist-server/service/chat-session/session-activity-subscription.d.ts +0 -14
- package/dist-server/service/chat-session/session-activity-subscription.js +0 -87
- package/dist-server/service/chat-session/session-activity-subscription.js.map +0 -1
- package/dist-server/service/chat-session/session-anchor.d.ts +0 -12
- package/dist-server/service/chat-session/session-anchor.js +0 -20
- package/dist-server/service/chat-session/session-anchor.js.map +0 -1
- package/dist-server/service/chat-session/session-inbox.d.ts +0 -80
- package/dist-server/service/chat-session/session-inbox.js +0 -133
- package/dist-server/service/chat-session/session-inbox.js.map +0 -1
- package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +0 -49
- package/dist-server/service/chat-session-participant/chat-session-participant.js +0 -141
- package/dist-server/service/chat-session-participant/chat-session-participant.js.map +0 -1
- package/dist-server/service/chat-session-participant/index.d.ts +0 -4
- package/dist-server/service/chat-session-participant/index.js +0 -10
- package/dist-server/service/chat-session-participant/index.js.map +0 -1
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +0 -27
- package/dist-server/service/chat-session-participant/invite-notification.js +0 -30
- package/dist-server/service/chat-session-participant/invite-notification.js.map +0 -1
- package/dist-server/service/chat-session-participant/presence.d.ts +0 -4
- package/dist-server/service/chat-session-participant/presence.js +0 -22
- package/dist-server/service/chat-session-participant/presence.js.map +0 -1
- package/dist-server/service/chat-session-resolver.d.ts +0 -102
- package/dist-server/service/chat-session-resolver.js +0 -856
- package/dist-server/service/chat-session-resolver.js.map +0 -1
- package/dist-server/service/subscribe-auth.d.ts +0 -14
- package/dist-server/service/subscribe-auth.js +0 -33
- package/dist-server/service/subscribe-auth.js.map +0 -1
- package/server/service/chat-message/chat-message-broadcast.integration.test.ts +0 -73
- package/server/service/chat-message/chat-message-broadcast.test.ts +0 -40
- package/server/service/chat-message/chat-message-broadcast.ts +0 -27
- package/server/service/chat-message/chat-message-publish.ts +0 -22
- package/server/service/chat-message/chat-message-subscription.ts +0 -41
- package/server/service/chat-message/chat-message.ts +0 -195
- package/server/service/chat-message/fold-history.test.ts +0 -98
- package/server/service/chat-message/fold-history.ts +0 -77
- package/server/service/chat-message/history-summary.test.ts +0 -127
- package/server/service/chat-message/history-summary.ts +0 -100
- package/server/service/chat-message/index.ts +0 -12
- package/server/service/chat-message/llm-history.test.ts +0 -194
- package/server/service/chat-message/llm-history.ts +0 -163
- package/server/service/chat-session/chat-session.ts +0 -182
- package/server/service/chat-session/index.ts +0 -5
- package/server/service/chat-session/session-activity-broadcast.ts +0 -57
- package/server/service/chat-session/session-activity-publish.ts +0 -44
- package/server/service/chat-session/session-activity-subscription.ts +0 -67
- package/server/service/chat-session/session-anchor.ts +0 -35
- package/server/service/chat-session/session-inbox.test.ts +0 -185
- package/server/service/chat-session/session-inbox.ts +0 -161
- package/server/service/chat-session-participant/chat-session-participant.ts +0 -135
- package/server/service/chat-session-participant/index.ts +0 -6
- package/server/service/chat-session-participant/invite-notification.test.ts +0 -30
- package/server/service/chat-session-participant/invite-notification.ts +0 -48
- package/server/service/chat-session-participant/presence.test.ts +0 -39
- package/server/service/chat-session-participant/presence.ts +0 -21
- package/server/service/chat-session-resolver.ts +0 -779
- package/server/service/session-anchor.test.ts +0 -65
- package/server/service/subscribe-auth.test.ts +0 -57
- package/server/service/subscribe-auth.ts +0 -34
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildLlmHistory = buildLlmHistory;
|
|
4
|
-
const history_summary_js_1 = require("./history-summary.js");
|
|
5
|
-
/** 사람이 아닌 기록(캔버스 직접 편집 등)의 표기. */
|
|
6
|
-
const SYSTEM_LABEL = '시스템';
|
|
7
|
-
/** 이름을 모르는 참여자의 표기 — 빈 대괄호를 남기지 않는다. */
|
|
8
|
-
const UNKNOWN_LABEL = '참여자';
|
|
9
|
-
/** 상한으로 잘라낸 앞부분을 알리는 표기 — 모델이 남은 첫 줄을 대화의 시작으로 오해하지 않게. */
|
|
10
|
-
const OMITTED_LABEL = '시스템';
|
|
11
|
-
/**
|
|
12
|
-
* 저장된 이력 → LLM 대화.
|
|
13
|
-
*
|
|
14
|
-
* 입력은 **시간 오름차순**을 가정한다(호출자가 createdAt ASC 로 조회). 정렬을 다시 하지 않는다 —
|
|
15
|
-
* 같은 시각의 순서까지 보장하려면 조회 쪽에서 결정해야 하고, 여기서 추측하면 두 곳이 어긋난다.
|
|
16
|
-
*/
|
|
17
|
-
function buildLlmHistory(rows, options = {}) {
|
|
18
|
-
const keep = new Set(options.keepMessageIds ?? []);
|
|
19
|
-
const truncated = truncate(rows, options.truncateAfterMessageId, keep);
|
|
20
|
-
/* 상한 — 최근 것부터 남긴다. 오래된 말보다 최근 상황이 답을 좌우한다. */
|
|
21
|
-
const limit = options.maxTurns && options.maxTurns > 0 ? options.maxTurns : 0;
|
|
22
|
-
const omitted = limit && truncated.length > limit ? truncated.length - limit : 0;
|
|
23
|
-
const kept = omitted ? truncated.slice(-limit) : truncated;
|
|
24
|
-
const label = options.labelSenders ?? new Set(kept.filter(r => isHuman(r)).map(r => r.senderId ?? '')).size > 1;
|
|
25
|
-
const out = [];
|
|
26
|
-
if (omitted) {
|
|
27
|
-
const dropped = truncated.slice(0, truncated.length - kept.length);
|
|
28
|
-
const summaryText = (options.summary?.text ?? '').trim();
|
|
29
|
-
if (summaryText) {
|
|
30
|
-
/* 요약이 있으면 그것을 싣는다. **요약임을 밝힌다** — 모델이 원문처럼 인용하지 않게.
|
|
31
|
-
* 요약이 밀려난 구간을 다 덮지 못하면 남은 개수도 함께 알린다(최신이 아님을 숨기지 않는다). */
|
|
32
|
-
const uncovered = (0, history_summary_js_1.pendingForSummary)(dropped, options.summary).length;
|
|
33
|
-
const tail = uncovered
|
|
34
|
-
? ` 그 뒤 ${uncovered}개 메시지는 요약에 아직 반영되지 않았습니다.`
|
|
35
|
-
: '';
|
|
36
|
-
out.push({
|
|
37
|
-
role: 'user',
|
|
38
|
-
content: `[${OMITTED_LABEL}] 이 대화의 앞부분 ${dropped.length}개 메시지는 길이 제한으로 아래 요약으로 대체되었습니다(원문 아님 — 요약에 없는 사실은 사용자에게 확인하세요).${tail}\n${summaryText}`
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
/* 요약이 아직 없으면 생략 사실만 알린다 — 개수만 밝히고 내용은 지어내지 않는다. */
|
|
43
|
-
out.push({
|
|
44
|
-
role: 'user',
|
|
45
|
-
content: `[${OMITTED_LABEL}] 이 대화의 앞부분 ${omitted}개 메시지는 길이 제한으로 생략되었습니다. 필요하면 사용자에게 다시 확인하세요.`
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const row of kept) {
|
|
50
|
-
const content = (row.content ?? '').trim();
|
|
51
|
-
if (!content)
|
|
52
|
-
continue; // 빈 본문(pending 자리 등)은 모델에 넘길 것이 없다
|
|
53
|
-
if (row.role === 'assistant') {
|
|
54
|
-
out.push({ role: 'assistant', content });
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (row.role === 'system') {
|
|
58
|
-
out.push({ role: 'user', content: `[${SYSTEM_LABEL}] ${content}` });
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
out.push({ role: 'user', content: label ? `[${speaker(row)}] ${content}` : content });
|
|
62
|
-
}
|
|
63
|
-
return out;
|
|
64
|
-
}
|
|
65
|
-
/** 사람이 보낸 줄인가 — 발신자 수 계산 대상. system 기록은 사람이 아니다. */
|
|
66
|
-
function isHuman(row) {
|
|
67
|
-
return row.role !== 'assistant' && row.role !== 'system';
|
|
68
|
-
}
|
|
69
|
-
function speaker(row) {
|
|
70
|
-
const name = (row.senderName ?? '').trim();
|
|
71
|
-
return name || UNKNOWN_LABEL;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* 접은 구간 제거. `truncateAfterMessageId` 가 없거나 이력에서 못 찾으면 **전부 유지**한다
|
|
75
|
-
* (조용히 다 버리는 것보다 다 보내는 쪽이 안전하다 — 문맥 상실은 답을 망친다).
|
|
76
|
-
*/
|
|
77
|
-
function truncate(rows, afterId, keep) {
|
|
78
|
-
if (!afterId)
|
|
79
|
-
return rows;
|
|
80
|
-
const idx = rows.findIndex(r => r.id === afterId);
|
|
81
|
-
if (idx < 0)
|
|
82
|
-
return rows;
|
|
83
|
-
const head = rows.slice(0, idx + 1);
|
|
84
|
-
const tail = rows.slice(idx + 1).filter(r => r.id && keep.has(r.id));
|
|
85
|
-
return [...head, ...tail];
|
|
86
|
-
}
|
|
87
|
-
//# sourceMappingURL=llm-history.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"llm-history.js","sourceRoot":"","sources":["../../../server/service/chat-message/llm-history.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 에이전트에게 넘길 대화 이력 조립 — 순수 로직(리졸버와 분리해 단위 테스트 가능).\n *\n * ── 왜 서버 정본인가 ─────────────────────────────────────────────────────────\n * 예전에는 클라이언트가 화면에 들고 있던 줄들을 그대로 LLM 에 넘겼다. 협의(여러 참여자)에서는\n * 참여자마다 화면이 달라 **에이전트가 사람마다 다른 대화를 본다**. 서버는 ChatMessage 에 정본을\n * 갖고 있으므로 그것을 쓴다. 세션이 없는 즉석 대화(ad-hoc)만 클라이언트 이력을 쓴다.\n *\n * ── 발신자 표기 ─────────────────────────────────────────────────────────────\n * LLMMessage 는 `{ role, content }` 뿐이다 — 발신자 칸이 없다(Anthropic·Gemini 는 message.name 을\n * 지원하지 않아 이식성이 없다). 그래서 발신자는 **본문 맨 앞 표기**로만 전달할 수 있다.\n *\n * [김철수] 승인합니다\n *\n * **이 표기는 추론용 문맥이며 권한 판정 근거가 아니다.** 사용자가 본문에 같은 형식을 직접 적을 수\n * 있으므로, 되돌릴 수 없는 조치의 승인 여부는 반드시 서버 게이트(요청자 신원 기반)가 판정한다.\n * 표기는 \"누가 무슨 맥락에서 말했는지\"를 모델이 이해하게 하는 용도로만 쓴다.\n *\n * 발신자가 **한 명뿐이면 표기하지 않는다** — 혼자 쓰는 대화(대다수)에 노이즈와 토큰을 늘리지 않고,\n * 기존 동작과 같은 프롬프트를 유지한다.\n *\n * ── system 행 ───────────────────────────────────────────────────────────────\n * 캔버스 직접 편집 기록(`User directly edited the board: …`)은 원래 에이전트에게 알리려고 남긴 것인데,\n * 클라이언트가 role='system' 을 걸러내 사장돼 있었다. LLMMessage 에 system 역할이 없으므로\n * `[시스템]` 표기를 붙인 user 메시지로 넘긴다 — 사람이 아니므로 발신자 수 계산에는 넣지 않는다.\n *\n * ── 편집·재생성으로 접은 구간 ───────────────────────────────────────────────\n * 클라이언트의 메시지 편집은 화면에서만 접고 **서버 이력은 유지**한다(board-ai-chat.editUserMessage).\n * 서버 정본으로 바꾸면 접은 말이 에이전트 문맥에 되살아난다. 그래서 호출자가 `truncateAfterMessageId`\n * (마지막으로 남긴 메시지 id)를 주면 그 뒤 행을 버린다. 단 `keepMessageIds` 로 지정한 행(방금 저장한\n * 새 사용자 메시지)은 시간상 뒤에 있어도 항상 남긴다.\n *\n * 알려진 한계(현행 동작과 동일): 공유 세션에서 접은 구간에 **다른 사람의 발언**이 있으면 그것도\n * 함께 제외된다. 오늘 클라이언트가 보내는 이력도 같으므로 이 단계에서는 동등성을 유지하고,\n * \"내 발언만 접기\"는 별도 개선으로 다룬다.\n */\nimport type { LLMMessage } from '../types.js'\nimport { pendingForSummary, type StoredSummary } from './history-summary.js'\n\n/** 이력 한 줄 — 저장된 ChatMessage 에서 필요한 것만(엔티티·typeorm 비의존). */\nexport interface HistoryRow {\n id?: string\n role: string\n /** 멘션 마커(#token{refid:N})는 호출자가 미리 제거해 넘긴다 — 모델 주의 분산·토큰 낭비 방지. */\n content: string\n /** 발신자 식별자(서버 내부용 — 발신자 수 계산에만 쓰고 프롬프트에 넣지 않는다). */\n senderId?: string\n /** 프롬프트에 노출할 표시 이름. 이메일 같은 개인정보는 넣지 않는다. */\n senderName?: string\n}\n\nexport interface BuildLlmHistoryOptions {\n /** 이 메시지까지만 유지하고 이후는 버린다(편집·재생성으로 접은 구간). 미지정이면 전부 사용. */\n truncateAfterMessageId?: string\n /** 잘라내기와 무관하게 항상 유지할 메시지 id(방금 저장한 새 사용자 메시지 등). */\n keepMessageIds?: string[]\n /** 발신자 표기 강제 — 미지정이면 사람 발신자가 둘 이상일 때만 표기. 테스트·특수 호출용. */\n labelSenders?: boolean\n /**\n * 모델에 넘길 **최근 메시지 수 상한**. 초과분(오래된 앞부분)은 버리고 생략 표시를 남긴다.\n *\n * 왜 필요한가: 협의는 며칠에 걸친다. 상한이 없으면 매 턴 전체 이력을 보내 프롬프트가 무한히\n * 자라고(비용·지연·문맥 한계) 오래된 말이 최근 상황을 덮는다. 미지정이면 전부 보낸다(기존 동작).\n *\n * **생략은 반드시 알린다** — 조용히 버리면 모델은 남은 첫 줄을 대화의 시작으로 오해한다.\n * 이건 상한일 뿐 요약이 아니다. 버린 내용을 요약해 실어 보내는 것은 다음 단계다(ChatSession.lastSummary).\n */\n maxTurns?: number\n /**\n * 상한으로 밀려난 앞부분을 대신할 **요약**(세션에 저장된 것).\n *\n * 있으면 \"N개 생략\" 대신 요약을 첫 줄로 싣는다 — 며칠 이어지는 협의에서 앞에서 합의한 것이\n * 없던 일이 되지 않게. 요약이 밀려난 구간을 **전부 덮지 못하면** 덮지 못한 개수도 함께 밝힌다\n * (요약이 최신이 아닐 수 있다는 사실을 숨기지 않는다).\n */\n summary?: StoredSummary\n}\n\n/** 사람이 아닌 기록(캔버스 직접 편집 등)의 표기. */\nconst SYSTEM_LABEL = '시스템'\n/** 이름을 모르는 참여자의 표기 — 빈 대괄호를 남기지 않는다. */\nconst UNKNOWN_LABEL = '참여자'\n/** 상한으로 잘라낸 앞부분을 알리는 표기 — 모델이 남은 첫 줄을 대화의 시작으로 오해하지 않게. */\nconst OMITTED_LABEL = '시스템'\n\n/**\n * 저장된 이력 → LLM 대화.\n *\n * 입력은 **시간 오름차순**을 가정한다(호출자가 createdAt ASC 로 조회). 정렬을 다시 하지 않는다 —\n * 같은 시각의 순서까지 보장하려면 조회 쪽에서 결정해야 하고, 여기서 추측하면 두 곳이 어긋난다.\n */\nexport function buildLlmHistory(rows: HistoryRow[], options: BuildLlmHistoryOptions = {}): LLMMessage[] {\n const keep = new Set(options.keepMessageIds ?? [])\n const truncated = truncate(rows, options.truncateAfterMessageId, keep)\n /* 상한 — 최근 것부터 남긴다. 오래된 말보다 최근 상황이 답을 좌우한다. */\n const limit = options.maxTurns && options.maxTurns > 0 ? options.maxTurns : 0\n const omitted = limit && truncated.length > limit ? truncated.length - limit : 0\n const kept = omitted ? truncated.slice(-limit) : truncated\n\n const label =\n options.labelSenders ?? new Set(kept.filter(r => isHuman(r)).map(r => r.senderId ?? '')).size > 1\n\n const out: LLMMessage[] = []\n if (omitted) {\n const dropped = truncated.slice(0, truncated.length - kept.length)\n const summaryText = (options.summary?.text ?? '').trim()\n if (summaryText) {\n /* 요약이 있으면 그것을 싣는다. **요약임을 밝힌다** — 모델이 원문처럼 인용하지 않게.\n * 요약이 밀려난 구간을 다 덮지 못하면 남은 개수도 함께 알린다(최신이 아님을 숨기지 않는다). */\n const uncovered = pendingForSummary(dropped, options.summary).length\n const tail = uncovered\n ? ` 그 뒤 ${uncovered}개 메시지는 요약에 아직 반영되지 않았습니다.`\n : ''\n out.push({\n role: 'user',\n content: `[${OMITTED_LABEL}] 이 대화의 앞부분 ${dropped.length}개 메시지는 길이 제한으로 아래 요약으로 대체되었습니다(원문 아님 — 요약에 없는 사실은 사용자에게 확인하세요).${tail}\\n${summaryText}`\n })\n } else {\n /* 요약이 아직 없으면 생략 사실만 알린다 — 개수만 밝히고 내용은 지어내지 않는다. */\n out.push({\n role: 'user',\n content: `[${OMITTED_LABEL}] 이 대화의 앞부분 ${omitted}개 메시지는 길이 제한으로 생략되었습니다. 필요하면 사용자에게 다시 확인하세요.`\n })\n }\n }\n for (const row of kept) {\n const content = (row.content ?? '').trim()\n if (!content) continue // 빈 본문(pending 자리 등)은 모델에 넘길 것이 없다\n if (row.role === 'assistant') {\n out.push({ role: 'assistant', content })\n continue\n }\n if (row.role === 'system') {\n out.push({ role: 'user', content: `[${SYSTEM_LABEL}] ${content}` })\n continue\n }\n out.push({ role: 'user', content: label ? `[${speaker(row)}] ${content}` : content })\n }\n return out\n}\n\n/** 사람이 보낸 줄인가 — 발신자 수 계산 대상. system 기록은 사람이 아니다. */\nfunction isHuman(row: HistoryRow): boolean {\n return row.role !== 'assistant' && row.role !== 'system'\n}\n\nfunction speaker(row: HistoryRow): string {\n const name = (row.senderName ?? '').trim()\n return name || UNKNOWN_LABEL\n}\n\n/**\n * 접은 구간 제거. `truncateAfterMessageId` 가 없거나 이력에서 못 찾으면 **전부 유지**한다\n * (조용히 다 버리는 것보다 다 보내는 쪽이 안전하다 — 문맥 상실은 답을 망친다).\n */\nfunction truncate(rows: HistoryRow[], afterId: string | undefined, keep: Set<string>): HistoryRow[] {\n if (!afterId) return rows\n const idx = rows.findIndex(r => r.id === afterId)\n if (idx < 0) return rows\n const head = rows.slice(0, idx + 1)\n const tail = rows.slice(idx + 1).filter(r => r.id && keep.has(r.id))\n return [...head, ...tail]\n}\n"]}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Domain } from '@things-factory/shell';
|
|
2
|
-
import { User } from '@things-factory/auth-base';
|
|
3
|
-
import { ChatMessage } from '../chat-message/chat-message.js';
|
|
4
|
-
import { PatchEntry } from '../patch-entry/patch-entry.js';
|
|
5
|
-
export declare class ChatSession {
|
|
6
|
-
readonly id?: string;
|
|
7
|
-
domain?: Domain;
|
|
8
|
-
domainId?: string;
|
|
9
|
-
/** Board.id 와 연결. Board 가 owning side 가 될 예정. 1:N — 한 보드에 여러 세션 가능.
|
|
10
|
-
*
|
|
11
|
-
* **앵커 일반화 이후에도 유지되는 하위호환 컬럼**이다(제거하지 말 것). 앵커가 board 인 세션은
|
|
12
|
-
* anchorId 와 함께 이 컬럼에도 같은 값을 쓴다(dual-write) — 기존 조회/인덱스/소비자가 그대로 동작한다. */
|
|
13
|
-
boardId?: string;
|
|
14
|
-
/**
|
|
15
|
-
* 대화가 매달린 대상의 **종류**. 세션은 보드에만 매달리지 않는다 —
|
|
16
|
-
* 저작 대화는 보드에, 운영 대화는 공간(사이트)에 매달린다. 앵커가 대화의 성격과 이력 경계를 정한다.
|
|
17
|
-
*
|
|
18
|
-
* 'board' | 'space' | 'instance' | 'none'. 미설정(기존 데이터)은 boardId 가 있으면 board 로 읽는다 —
|
|
19
|
-
* 백필 마이그레이션 없이 lazy 하위호환(컬럼 추가만).
|
|
20
|
-
*/
|
|
21
|
-
anchorType?: string;
|
|
22
|
-
/** 앵커 대상의 id(보드 id · 공간 id · 인스턴스 id). anchorType 과 함께 의미를 갖는다. */
|
|
23
|
-
anchorId?: string;
|
|
24
|
-
/**
|
|
25
|
-
* 세션의 사용자 정의 이름 (탭 레이블 / 세션 식별 표시용).
|
|
26
|
-
*
|
|
27
|
-
* nullable — 미설정 시 UI 가 fallback 라벨 ("세션 N" 또는 createdAt 기반) 사용. 사용자가
|
|
28
|
-
* 다중 세션 운영 시 의미 부여 (예: "초기 설계", "운영 시뮬레이션", "사용자A 의 사적 세션").
|
|
29
|
-
*/
|
|
30
|
-
name?: string;
|
|
31
|
-
creator?: User;
|
|
32
|
-
creatorId?: string;
|
|
33
|
-
updater?: User;
|
|
34
|
-
updaterId?: string;
|
|
35
|
-
/** 토큰 절감용 — 오래된 메시지를 LLM 으로 압축한 요약. Phase 2 에서 작성. */
|
|
36
|
-
/**
|
|
37
|
-
* 마지막 메시지 시각 — **비정규화**. 목록 정렬(최근 활동순)과 안 읽음 판정의 기준.
|
|
38
|
-
*
|
|
39
|
-
* 왜 비정규화인가: 세션마다 메시지를 집계하면(MAX/COUNT + group by) 목록 한 번에 집계 쿼리가 붙고,
|
|
40
|
-
* 이 프레임워크는 5개 DB 드라이버를 지원해야 한다. 세션 컬럼 하나로 두면 정렬·비교가 값싸게 끝나고
|
|
41
|
-
* 드라이버 차이가 없다. 메시지를 저장할 때 함께 갱신한다(activityPatch).
|
|
42
|
-
*/
|
|
43
|
-
lastMessageAt?: Date;
|
|
44
|
-
/** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */
|
|
45
|
-
lastMessagePreview?: string;
|
|
46
|
-
lastSummary?: string;
|
|
47
|
-
/**
|
|
48
|
-
* `lastSummary` 가 **어디까지 덮는지** — 이 메시지까지 요약에 반영됐다.
|
|
49
|
-
*
|
|
50
|
-
* 이게 없으면 누적 요약을 할 수 없다: 매 턴 전체를 다시 요약하거나(비용) 이미 요약한 구간을
|
|
51
|
-
* 또 이어 붙인다(중복). 이력에서 이 id 를 못 찾으면(삭제·편집) 덮은 범위를 알 수 없으므로
|
|
52
|
-
* 전부 미반영으로 보고 다시 요약한다 — 빠뜨리는 쪽보다 안전하다.
|
|
53
|
-
*/
|
|
54
|
-
summaryUpToMessageId?: string;
|
|
55
|
-
/** 사용 모델 식별 — 'anthropic:claude-sonnet-4-6' 등 */
|
|
56
|
-
aiClientId?: string;
|
|
57
|
-
createdAt?: Date;
|
|
58
|
-
updatedAt?: Date;
|
|
59
|
-
deletedAt?: Date;
|
|
60
|
-
messages?: ChatMessage[];
|
|
61
|
-
patches?: PatchEntry[];
|
|
62
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ChatSession = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
/**
|
|
6
|
-
* ChatSession — Board 와 결합되는 AI 협력 세션.
|
|
7
|
-
*
|
|
8
|
-
* 한 보드에 여러 세션 가능 — DB 레벨 unique 제약 없음.
|
|
9
|
-
*
|
|
10
|
-
* 향후 폭증할 세션 분화 차원 (예: thread / 사용자별 / 컨텍스트별 design vs ops /
|
|
11
|
-
* archived / forked from clone / agent별) 을 미리 차단하지 않기 위해 구조 자체는 1:N
|
|
12
|
-
* 으로 열어둠. 단일 세션 정책 (현재 UX) 은 app 레벨 idempotent 로직 — `startBoardAISession`
|
|
13
|
-
* 이 boardId 매칭 시 기존 세션 반환, `chatSessionByBoard` 가 첫 매칭 반환.
|
|
14
|
-
*
|
|
15
|
-
* 미래에 다중 세션 활성화 시 discriminator 컬럼 (kind / name / threadId / creatorId 등)
|
|
16
|
-
* 을 그때 추가. 마이그레이션 부담 최소화 — 현재는 unique 만 풀어 자리 마련.
|
|
17
|
-
*
|
|
18
|
-
* board-service 의 Board 가 owning side 가 될 예정 (ChatSession FK).
|
|
19
|
-
*/
|
|
20
|
-
const typeorm_1 = require("typeorm");
|
|
21
|
-
const type_graphql_1 = require("type-graphql");
|
|
22
|
-
const shell_1 = require("@things-factory/shell");
|
|
23
|
-
const auth_base_1 = require("@things-factory/auth-base");
|
|
24
|
-
const env_1 = require("@things-factory/env");
|
|
25
|
-
const chat_message_js_1 = require("../chat-message/chat-message.js");
|
|
26
|
-
const patch_entry_js_1 = require("../patch-entry/patch-entry.js");
|
|
27
|
-
const ORMCONFIG = env_1.config.get('ormconfig', {});
|
|
28
|
-
const DATABASE_TYPE = ORMCONFIG.type;
|
|
29
|
-
let ChatSession = class ChatSession {
|
|
30
|
-
};
|
|
31
|
-
exports.ChatSession = ChatSession;
|
|
32
|
-
tslib_1.__decorate([
|
|
33
|
-
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
34
|
-
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { nullable: true }),
|
|
35
|
-
tslib_1.__metadata("design:type", String)
|
|
36
|
-
], ChatSession.prototype, "id", void 0);
|
|
37
|
-
tslib_1.__decorate([
|
|
38
|
-
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
39
|
-
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true }),
|
|
40
|
-
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
41
|
-
], ChatSession.prototype, "domain", void 0);
|
|
42
|
-
tslib_1.__decorate([
|
|
43
|
-
(0, typeorm_1.RelationId)((session) => session.domain),
|
|
44
|
-
tslib_1.__metadata("design:type", String)
|
|
45
|
-
], ChatSession.prototype, "domainId", void 0);
|
|
46
|
-
tslib_1.__decorate([
|
|
47
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
48
|
-
(0, type_graphql_1.Field)({
|
|
49
|
-
nullable: true,
|
|
50
|
-
description: 'Connected Board id. Multiple sessions per board allowed (future: threads / per-user / contexts). Kept for backward compatibility — board-anchored sessions dual-write this alongside anchorId.'
|
|
51
|
-
}),
|
|
52
|
-
tslib_1.__metadata("design:type", String)
|
|
53
|
-
], ChatSession.prototype, "boardId", void 0);
|
|
54
|
-
tslib_1.__decorate([
|
|
55
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
56
|
-
(0, type_graphql_1.Field)({
|
|
57
|
-
nullable: true,
|
|
58
|
-
description: "What this conversation is anchored to: 'board' (authoring a board) | 'space' (a site and the twins running there) | 'instance' | 'none'. Absent on legacy rows — treated as 'board' when boardId is present."
|
|
59
|
-
}),
|
|
60
|
-
tslib_1.__metadata("design:type", String)
|
|
61
|
-
], ChatSession.prototype, "anchorType", void 0);
|
|
62
|
-
tslib_1.__decorate([
|
|
63
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
64
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Id of the anchored target (board id / space id / instance id).' }),
|
|
65
|
-
tslib_1.__metadata("design:type", String)
|
|
66
|
-
], ChatSession.prototype, "anchorId", void 0);
|
|
67
|
-
tslib_1.__decorate([
|
|
68
|
-
(0, typeorm_1.Column)({ nullable: true, length: 200 }),
|
|
69
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'User-given name of this session (tab label / identification).' }),
|
|
70
|
-
tslib_1.__metadata("design:type", String)
|
|
71
|
-
], ChatSession.prototype, "name", void 0);
|
|
72
|
-
tslib_1.__decorate([
|
|
73
|
-
(0, typeorm_1.ManyToOne)(type => auth_base_1.User),
|
|
74
|
-
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'User who created this session.' }),
|
|
75
|
-
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
76
|
-
], ChatSession.prototype, "creator", void 0);
|
|
77
|
-
tslib_1.__decorate([
|
|
78
|
-
(0, typeorm_1.RelationId)((session) => session.creator),
|
|
79
|
-
tslib_1.__metadata("design:type", String)
|
|
80
|
-
], ChatSession.prototype, "creatorId", void 0);
|
|
81
|
-
tslib_1.__decorate([
|
|
82
|
-
(0, typeorm_1.ManyToOne)(type => auth_base_1.User),
|
|
83
|
-
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'User who last updated this session.' }),
|
|
84
|
-
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
85
|
-
], ChatSession.prototype, "updater", void 0);
|
|
86
|
-
tslib_1.__decorate([
|
|
87
|
-
(0, typeorm_1.RelationId)((session) => session.updater),
|
|
88
|
-
tslib_1.__metadata("design:type", String)
|
|
89
|
-
], ChatSession.prototype, "updaterId", void 0);
|
|
90
|
-
tslib_1.__decorate([
|
|
91
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
92
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Timestamp of the last message in this session (denormalized for list ordering and unread comparison — avoids per-session aggregation across the supported DB drivers).' }),
|
|
93
|
-
tslib_1.__metadata("design:type", Date
|
|
94
|
-
/** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */
|
|
95
|
-
)
|
|
96
|
-
], ChatSession.prototype, "lastMessageAt", void 0);
|
|
97
|
-
tslib_1.__decorate([
|
|
98
|
-
(0, typeorm_1.Column)({ nullable: true, length: 200 }),
|
|
99
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Short preview of the last message, stored so the session list does not need to query messages.' }),
|
|
100
|
-
tslib_1.__metadata("design:type", String)
|
|
101
|
-
], ChatSession.prototype, "lastMessagePreview", void 0);
|
|
102
|
-
tslib_1.__decorate([
|
|
103
|
-
(0, typeorm_1.Column)({
|
|
104
|
-
nullable: true,
|
|
105
|
-
type: DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
106
|
-
? 'longtext'
|
|
107
|
-
: DATABASE_TYPE == 'oracle'
|
|
108
|
-
? 'clob'
|
|
109
|
-
: DATABASE_TYPE == 'mssql'
|
|
110
|
-
? 'nvarchar'
|
|
111
|
-
: 'text',
|
|
112
|
-
length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
|
|
113
|
-
}),
|
|
114
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Compressed summary of older messages (for token saving).' }),
|
|
115
|
-
tslib_1.__metadata("design:type", String)
|
|
116
|
-
], ChatSession.prototype, "lastSummary", void 0);
|
|
117
|
-
tslib_1.__decorate([
|
|
118
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
119
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Id of the last message folded into lastSummary — enables incremental summarization instead of re-reading the whole history.' }),
|
|
120
|
-
tslib_1.__metadata("design:type", String)
|
|
121
|
-
], ChatSession.prototype, "summaryUpToMessageId", void 0);
|
|
122
|
-
tslib_1.__decorate([
|
|
123
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
124
|
-
(0, type_graphql_1.Field)({ nullable: true }),
|
|
125
|
-
tslib_1.__metadata("design:type", String)
|
|
126
|
-
], ChatSession.prototype, "aiClientId", void 0);
|
|
127
|
-
tslib_1.__decorate([
|
|
128
|
-
(0, typeorm_1.CreateDateColumn)(),
|
|
129
|
-
(0, type_graphql_1.Field)({ nullable: true }),
|
|
130
|
-
tslib_1.__metadata("design:type", Date)
|
|
131
|
-
], ChatSession.prototype, "createdAt", void 0);
|
|
132
|
-
tslib_1.__decorate([
|
|
133
|
-
(0, typeorm_1.UpdateDateColumn)(),
|
|
134
|
-
(0, type_graphql_1.Field)({ nullable: true }),
|
|
135
|
-
tslib_1.__metadata("design:type", Date)
|
|
136
|
-
], ChatSession.prototype, "updatedAt", void 0);
|
|
137
|
-
tslib_1.__decorate([
|
|
138
|
-
(0, typeorm_1.DeleteDateColumn)(),
|
|
139
|
-
tslib_1.__metadata("design:type", Date
|
|
140
|
-
// ── 관계 (TypeORM 만, GraphQL 노출은 별도 query 통해 페이징) ──
|
|
141
|
-
)
|
|
142
|
-
], ChatSession.prototype, "deletedAt", void 0);
|
|
143
|
-
tslib_1.__decorate([
|
|
144
|
-
(0, typeorm_1.OneToMany)(type => chat_message_js_1.ChatMessage, message => message.session),
|
|
145
|
-
tslib_1.__metadata("design:type", Array)
|
|
146
|
-
], ChatSession.prototype, "messages", void 0);
|
|
147
|
-
tslib_1.__decorate([
|
|
148
|
-
(0, typeorm_1.OneToMany)(type => patch_entry_js_1.PatchEntry, patch => patch.session),
|
|
149
|
-
tslib_1.__metadata("design:type", Array)
|
|
150
|
-
], ChatSession.prototype, "patches", void 0);
|
|
151
|
-
exports.ChatSession = ChatSession = tslib_1.__decorate([
|
|
152
|
-
(0, typeorm_1.Entity)()
|
|
153
|
-
// 인덱스는 조회 성능용으로 유지 (chatSessionByBoard 가 [domain, boardId] 로 자주 쿼리).
|
|
154
|
-
// unique 제약은 의도적으로 제거 — 한 보드에 여러 세션을 미래에 허용하기 위함.
|
|
155
|
-
,
|
|
156
|
-
(0, typeorm_1.Index)('ix_chat_session_1', (session) => [session.domain, session.boardId])
|
|
157
|
-
// 앵커 조회용 — chatSessionsByAnchor 가 [domain, anchorType, anchorId] 로 질의.
|
|
158
|
-
,
|
|
159
|
-
(0, typeorm_1.Index)('ix_chat_session_2', (session) => [session.domain, session.anchorType, session.anchorId]),
|
|
160
|
-
(0, type_graphql_1.ObjectType)({
|
|
161
|
-
description: 'AI 협력 세션 — Board 와 결합. 한 보드에 여러 세션 가능 (thread / 사용자별 / 컨텍스트별 등 미래 확장).'
|
|
162
|
-
})
|
|
163
|
-
], ChatSession);
|
|
164
|
-
//# sourceMappingURL=chat-session.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chat-session.js","sourceRoot":"","sources":["../../../server/service/chat-session/chat-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSession — Board 와 결합되는 AI 협력 세션.\n *\n * 한 보드에 여러 세션 가능 — DB 레벨 unique 제약 없음.\n *\n * 향후 폭증할 세션 분화 차원 (예: thread / 사용자별 / 컨텍스트별 design vs ops /\n * archived / forked from clone / agent별) 을 미리 차단하지 않기 위해 구조 자체는 1:N\n * 으로 열어둠. 단일 세션 정책 (현재 UX) 은 app 레벨 idempotent 로직 — `startBoardAISession`\n * 이 boardId 매칭 시 기존 세션 반환, `chatSessionByBoard` 가 첫 매칭 반환.\n *\n * 미래에 다중 세션 활성화 시 discriminator 컬럼 (kind / name / threadId / creatorId 등)\n * 을 그때 추가. 마이그레이션 부담 최소화 — 현재는 unique 만 풀어 자리 마련.\n *\n * board-service 의 Board 가 owning side 가 될 예정 (ChatSession FK).\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n OneToMany,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatMessage } from '../chat-message/chat-message.js'\nimport { PatchEntry } from '../patch-entry/patch-entry.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n// 인덱스는 조회 성능용으로 유지 (chatSessionByBoard 가 [domain, boardId] 로 자주 쿼리).\n// unique 제약은 의도적으로 제거 — 한 보드에 여러 세션을 미래에 허용하기 위함.\n@Index('ix_chat_session_1', (session: ChatSession) => [session.domain, session.boardId])\n// 앵커 조회용 — chatSessionsByAnchor 가 [domain, anchorType, anchorId] 로 질의.\n@Index('ix_chat_session_2', (session: ChatSession) => [session.domain, session.anchorType, session.anchorId])\n@ObjectType({\n description: 'AI 협력 세션 — Board 와 결합. 한 보드에 여러 세션 가능 (thread / 사용자별 / 컨텍스트별 등 미래 확장).'\n})\nexport class ChatSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ChatSession) => session.domain)\n domainId?: string\n\n /** Board.id 와 연결. Board 가 owning side 가 될 예정. 1:N — 한 보드에 여러 세션 가능.\n *\n * **앵커 일반화 이후에도 유지되는 하위호환 컬럼**이다(제거하지 말 것). 앵커가 board 인 세션은\n * anchorId 와 함께 이 컬럼에도 같은 값을 쓴다(dual-write) — 기존 조회/인덱스/소비자가 그대로 동작한다. */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'Connected Board id. Multiple sessions per board allowed (future: threads / per-user / contexts). Kept for backward compatibility — board-anchored sessions dual-write this alongside anchorId.'\n })\n boardId?: string\n\n /**\n * 대화가 매달린 대상의 **종류**. 세션은 보드에만 매달리지 않는다 —\n * 저작 대화는 보드에, 운영 대화는 공간(사이트)에 매달린다. 앵커가 대화의 성격과 이력 경계를 정한다.\n *\n * 'board' | 'space' | 'instance' | 'none'. 미설정(기존 데이터)은 boardId 가 있으면 board 로 읽는다 —\n * 백필 마이그레이션 없이 lazy 하위호환(컬럼 추가만).\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n \"What this conversation is anchored to: 'board' (authoring a board) | 'space' (a site and the twins running there) | 'instance' | 'none'. Absent on legacy rows — treated as 'board' when boardId is present.\"\n })\n anchorType?: string\n\n /** 앵커 대상의 id(보드 id · 공간 id · 인스턴스 id). anchorType 과 함께 의미를 갖는다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the anchored target (board id / space id / instance id).' })\n anchorId?: string\n\n /**\n * 세션의 사용자 정의 이름 (탭 레이블 / 세션 식별 표시용).\n *\n * nullable — 미설정 시 UI 가 fallback 라벨 (\"세션 N\" 또는 createdAt 기반) 사용. 사용자가\n * 다중 세션 운영 시 의미 부여 (예: \"초기 설계\", \"운영 시뮬레이션\", \"사용자A 의 사적 세션\").\n */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'User-given name of this session (tab label / identification).' })\n name?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this session.' })\n creator?: User\n\n @RelationId((session: ChatSession) => session.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who last updated this session.' })\n updater?: User\n\n @RelationId((session: ChatSession) => session.updater)\n updaterId?: string\n\n /** 토큰 절감용 — 오래된 메시지를 LLM 으로 압축한 요약. Phase 2 에서 작성. */\n /**\n * 마지막 메시지 시각 — **비정규화**. 목록 정렬(최근 활동순)과 안 읽음 판정의 기준.\n *\n * 왜 비정규화인가: 세션마다 메시지를 집계하면(MAX/COUNT + group by) 목록 한 번에 집계 쿼리가 붙고,\n * 이 프레임워크는 5개 DB 드라이버를 지원해야 한다. 세션 컬럼 하나로 두면 정렬·비교가 값싸게 끝나고\n * 드라이버 차이가 없다. 메시지를 저장할 때 함께 갱신한다(activityPatch).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last message in this session (denormalized for list ordering and unread comparison — avoids per-session aggregation across the supported DB drivers).' })\n lastMessageAt?: Date\n\n /** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'Short preview of the last message, stored so the session list does not need to query messages.' })\n lastMessagePreview?: string\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'Compressed summary of older messages (for token saving).' })\n lastSummary?: string\n\n /**\n * `lastSummary` 가 **어디까지 덮는지** — 이 메시지까지 요약에 반영됐다.\n *\n * 이게 없으면 누적 요약을 할 수 없다: 매 턴 전체를 다시 요약하거나(비용) 이미 요약한 구간을\n * 또 이어 붙인다(중복). 이력에서 이 id 를 못 찾으면(삭제·편집) 덮은 범위를 알 수 없으므로\n * 전부 미반영으로 보고 다시 요약한다 — 빠뜨리는 쪽보다 안전하다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the last message folded into lastSummary — enables incremental summarization instead of re-reading the whole history.' })\n summaryUpToMessageId?: string\n\n /** 사용 모델 식별 — 'anthropic:claude-sonnet-4-6' 등 */\n @Column({ nullable: true })\n @Field({ nullable: true })\n aiClientId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n deletedAt?: Date\n\n // ── 관계 (TypeORM 만, GraphQL 노출은 별도 query 통해 페이징) ──\n @OneToMany(type => ChatMessage, message => message.session)\n messages?: ChatMessage[]\n\n @OneToMany(type => PatchEntry, patch => patch.session)\n patches?: PatchEntry[]\n}\n"]}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.entities = exports.ChatSession = void 0;
|
|
4
|
-
const chat_session_js_1 = require("./chat-session.js");
|
|
5
|
-
Object.defineProperty(exports, "ChatSession", { enumerable: true, get: function () { return chat_session_js_1.ChatSession; } });
|
|
6
|
-
exports.entities = [chat_session_js_1.ChatSession];
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/chat-session/index.ts"],"names":[],"mappings":";;;AAAA,uDAA+C;AAEtC,4FAFA,6BAAW,OAEA;AAEP,QAAA,QAAQ,GAAG,CAAC,6BAAW,CAAC,CAAA","sourcesContent":["import { ChatSession } from './chat-session.js'\n\nexport { ChatSession }\n\nexport const entities = [ChatSession]\n"]}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** 세션 활동 방송 토픽. */
|
|
2
|
-
export declare const SESSION_ACTIVITY_TOPIC = "board-ai-session-activity";
|
|
3
|
-
/** 방송 페이로드 — 필터용 메타(도메인·참여자) + 목록 갱신용 최소 정보. 본문은 싣지 않는다. */
|
|
4
|
-
/**
|
|
5
|
-
* 활동 종류.
|
|
6
|
-
* message — 새 메시지. **안 읽음 배지를 올린다.**
|
|
7
|
-
* meta — 이름 변경·참여자 변동 등 세션 자체의 변화. 목록은 갱신하지만 배지는 올리지 않는다.
|
|
8
|
-
* (이름이 바뀐 것을 "새 소식" 으로 알리면 소음이 된다.)
|
|
9
|
-
*/
|
|
10
|
-
export type SessionActivityKind = 'message' | 'meta';
|
|
11
|
-
export interface SessionActivityPayload {
|
|
12
|
-
domainId: string;
|
|
13
|
-
sessionId: string;
|
|
14
|
-
kind: SessionActivityKind;
|
|
15
|
-
/** 이 세션 참여자 user id — 필터가 이 배열로 수신자를 정한다(필터에서 DB 를 읽지 않기 위함). */
|
|
16
|
-
participantIds: string[];
|
|
17
|
-
anchorType?: string;
|
|
18
|
-
anchorId?: string;
|
|
19
|
-
/** 활동 시각(ISO) — 클라가 목록 정렬·안 읽음 판정에 쓴다. */
|
|
20
|
-
at: string;
|
|
21
|
-
/** 한 줄 미리보기(짧게). 본문 전체가 아니다. */
|
|
22
|
-
preview?: string;
|
|
23
|
-
/** 이 활동을 만든 사용자 — 자기 발신은 배지를 올리지 않도록 클라가 쓴다. */
|
|
24
|
-
actorId?: string;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* 같은 도메인 + **내가 참여자인 세션**의 활동만 통과. 도메인 격리가 보안 핵심이고,
|
|
28
|
-
* 참여자 검사가 "내 것만" 을 성립시킨다(참여하지 않은 대화의 활동을 받지 않는다).
|
|
29
|
-
*/
|
|
30
|
-
export declare function sessionActivityFilter(domainId: string, userId: string): (payload: SessionActivityPayload) => boolean;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* 세션 활동 방송 — 순수 로직(토픽·페이로드·필터). 데코레이터·pubsub import 없음 → 단위 테스트 가능.
|
|
4
|
-
*
|
|
5
|
-
* ── 왜 메시지 방송(chat-message)과 따로 두는가 ──────────────────────────────
|
|
6
|
-
* 기존 `chatMessageActivity` 는 **그 세션을 열어 둔** 구독자에게 메시지 본문을 보낸다. 그래서 창을 닫아
|
|
7
|
-
* 두면 새 메시지를 알 수 없다 — 초대 알림 한 번 받고 나면 이후 대화는 사용자가 스스로 들어와 봐야 안다.
|
|
8
|
-
*
|
|
9
|
-
* 이 토픽은 **내가 참여한 모든 세션**의 활동을 알린다. 목적이 배지·목록 갱신이므로 본문을 싣지 않고
|
|
10
|
-
* 최소 정보(세션·앵커·시각·미리보기)만 보낸다:
|
|
11
|
-
* · 열지도 않은 세션들의 본문이 한 채널로 쏟아지는 것을 피한다(대역·주의 낭비).
|
|
12
|
-
* · 본문이 필요한 시점은 그 세션을 열 때이고, 그때는 기존 메시지 구독이 담당한다.
|
|
13
|
-
*
|
|
14
|
-
* ── 구독자 판정을 필터에서 DB 로 하지 않는다 ────────────────────────────────
|
|
15
|
-
* 필터는 **구독자 × 메시지마다** 돈다. 여기서 DB 를 읽으면 방송 한 번이 구독자 수만큼의 질의가 된다.
|
|
16
|
-
* 그래서 발행 시점에 참여자 id 목록을 페이로드에 실어 보내고, 필터는 배열 포함 검사만 한다.
|
|
17
|
-
* 참여자 조회는 발행당 1회 — 사람 대화 빈도에서 감당 가능한 비용이다.
|
|
18
|
-
*/
|
|
19
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.SESSION_ACTIVITY_TOPIC = void 0;
|
|
21
|
-
exports.sessionActivityFilter = sessionActivityFilter;
|
|
22
|
-
/** 세션 활동 방송 토픽. */
|
|
23
|
-
exports.SESSION_ACTIVITY_TOPIC = 'board-ai-session-activity';
|
|
24
|
-
/**
|
|
25
|
-
* 같은 도메인 + **내가 참여자인 세션**의 활동만 통과. 도메인 격리가 보안 핵심이고,
|
|
26
|
-
* 참여자 검사가 "내 것만" 을 성립시킨다(참여하지 않은 대화의 활동을 받지 않는다).
|
|
27
|
-
*/
|
|
28
|
-
function sessionActivityFilter(domainId, userId) {
|
|
29
|
-
return (payload) => !!payload &&
|
|
30
|
-
payload.domainId === domainId &&
|
|
31
|
-
Array.isArray(payload.participantIds) &&
|
|
32
|
-
payload.participantIds.includes(userId);
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=session-activity-broadcast.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-activity-broadcast.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-activity-broadcast.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;AAkCH,sDAMC;AAtCD,mBAAmB;AACN,QAAA,sBAAsB,GAAG,2BAA2B,CAAA;AA2BjE;;;GAGG;AACH,SAAgB,qBAAqB,CAAC,QAAgB,EAAE,MAAc;IACpE,OAAO,CAAC,OAA+B,EAAW,EAAE,CAClD,CAAC,CAAC,OAAO;QACT,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC7B,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC;QACrC,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAC3C,CAAC","sourcesContent":["/*\n * 세션 활동 방송 — 순수 로직(토픽·페이로드·필터). 데코레이터·pubsub import 없음 → 단위 테스트 가능.\n *\n * ── 왜 메시지 방송(chat-message)과 따로 두는가 ──────────────────────────────\n * 기존 `chatMessageActivity` 는 **그 세션을 열어 둔** 구독자에게 메시지 본문을 보낸다. 그래서 창을 닫아\n * 두면 새 메시지를 알 수 없다 — 초대 알림 한 번 받고 나면 이후 대화는 사용자가 스스로 들어와 봐야 안다.\n *\n * 이 토픽은 **내가 참여한 모든 세션**의 활동을 알린다. 목적이 배지·목록 갱신이므로 본문을 싣지 않고\n * 최소 정보(세션·앵커·시각·미리보기)만 보낸다:\n * · 열지도 않은 세션들의 본문이 한 채널로 쏟아지는 것을 피한다(대역·주의 낭비).\n * · 본문이 필요한 시점은 그 세션을 열 때이고, 그때는 기존 메시지 구독이 담당한다.\n *\n * ── 구독자 판정을 필터에서 DB 로 하지 않는다 ────────────────────────────────\n * 필터는 **구독자 × 메시지마다** 돈다. 여기서 DB 를 읽으면 방송 한 번이 구독자 수만큼의 질의가 된다.\n * 그래서 발행 시점에 참여자 id 목록을 페이로드에 실어 보내고, 필터는 배열 포함 검사만 한다.\n * 참여자 조회는 발행당 1회 — 사람 대화 빈도에서 감당 가능한 비용이다.\n */\n\n/** 세션 활동 방송 토픽. */\nexport const SESSION_ACTIVITY_TOPIC = 'board-ai-session-activity'\n\n/** 방송 페이로드 — 필터용 메타(도메인·참여자) + 목록 갱신용 최소 정보. 본문은 싣지 않는다. */\n/**\n * 활동 종류.\n * message — 새 메시지. **안 읽음 배지를 올린다.**\n * meta — 이름 변경·참여자 변동 등 세션 자체의 변화. 목록은 갱신하지만 배지는 올리지 않는다.\n * (이름이 바뀐 것을 \"새 소식\" 으로 알리면 소음이 된다.)\n */\nexport type SessionActivityKind = 'message' | 'meta'\n\nexport interface SessionActivityPayload {\n domainId: string\n sessionId: string\n kind: SessionActivityKind\n /** 이 세션 참여자 user id — 필터가 이 배열로 수신자를 정한다(필터에서 DB 를 읽지 않기 위함). */\n participantIds: string[]\n anchorType?: string\n anchorId?: string\n /** 활동 시각(ISO) — 클라가 목록 정렬·안 읽음 판정에 쓴다. */\n at: string\n /** 한 줄 미리보기(짧게). 본문 전체가 아니다. */\n preview?: string\n /** 이 활동을 만든 사용자 — 자기 발신은 배지를 올리지 않도록 클라가 쓴다. */\n actorId?: string\n}\n\n/**\n * 같은 도메인 + **내가 참여자인 세션**의 활동만 통과. 도메인 격리가 보안 핵심이고,\n * 참여자 검사가 \"내 것만\" 을 성립시킨다(참여하지 않은 대화의 활동을 받지 않는다).\n */\nexport function sessionActivityFilter(domainId: string, userId: string) {\n return (payload: SessionActivityPayload): boolean =>\n !!payload &&\n payload.domainId === domainId &&\n Array.isArray(payload.participantIds) &&\n payload.participantIds.includes(userId)\n}\n"]}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type SessionActivityKind } from './session-activity-broadcast.js';
|
|
2
|
-
export declare function publishSessionActivity(input: {
|
|
3
|
-
domainId: string;
|
|
4
|
-
sessionId: string;
|
|
5
|
-
/** message = 새 메시지(배지 올림) · meta = 이름 변경 등(목록만 갱신). 기본 message. */
|
|
6
|
-
kind?: SessionActivityKind;
|
|
7
|
-
anchorType?: string;
|
|
8
|
-
anchorId?: string;
|
|
9
|
-
at?: Date;
|
|
10
|
-
preview?: string;
|
|
11
|
-
actorId?: string;
|
|
12
|
-
}): Promise<void>;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.publishSessionActivity = publishSessionActivity;
|
|
4
|
-
/*
|
|
5
|
-
* 세션 활동 발행 — pubsub 의존만(데코레이터 없음). 순수 로직은 session-activity-broadcast.ts.
|
|
6
|
-
*
|
|
7
|
-
* 참여자 목록을 여기서 읽어 페이로드에 싣는다 — 필터는 구독자 × 메시지마다 돌기 때문에 거기서 DB 를
|
|
8
|
-
* 읽으면 방송 한 번이 구독자 수만큼의 질의가 된다. 발행당 1회로 끝낸다.
|
|
9
|
-
*
|
|
10
|
-
* best-effort: 실패해도 대화·저장을 막지 않는다(배지가 한 번 늦을 뿐).
|
|
11
|
-
*/
|
|
12
|
-
const shell_1 = require("@things-factory/shell");
|
|
13
|
-
const chat_session_participant_js_1 = require("../chat-session-participant/chat-session-participant.js");
|
|
14
|
-
const session_activity_broadcast_js_1 = require("./session-activity-broadcast.js");
|
|
15
|
-
async function publishSessionActivity(input) {
|
|
16
|
-
try {
|
|
17
|
-
const participants = await (0, shell_1.getRepository)(chat_session_participant_js_1.ChatSessionParticipant).find({
|
|
18
|
-
where: { domain: { id: input.domainId }, session: { id: input.sessionId } }
|
|
19
|
-
});
|
|
20
|
-
const payload = {
|
|
21
|
-
domainId: input.domainId,
|
|
22
|
-
sessionId: input.sessionId,
|
|
23
|
-
kind: input.kind ?? 'message',
|
|
24
|
-
participantIds: participants.map(p => p.userId).filter(Boolean),
|
|
25
|
-
anchorType: input.anchorType,
|
|
26
|
-
anchorId: input.anchorId,
|
|
27
|
-
at: (input.at ?? new Date()).toISOString(),
|
|
28
|
-
preview: input.preview,
|
|
29
|
-
actorId: input.actorId
|
|
30
|
-
};
|
|
31
|
-
shell_1.pubsub.publish(session_activity_broadcast_js_1.SESSION_ACTIVITY_TOPIC, payload);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
/* 방송 실패는 대화를 막지 않는다 */
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=session-activity-publish.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-activity-publish.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-activity-publish.ts"],"names":[],"mappings":";;AAaA,wDA8BC;AA3CD;;;;;;;GAOG;AACH,iDAA6D;AAE7D,yGAAgG;AAChG,mFAA+H;AAExH,KAAK,UAAU,sBAAsB,CAAC,KAU5C;IACC,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,IAAA,qBAAa,EAAC,oDAAsB,CAAC,CAAC,IAAI,CAAC;YACpE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAS,EAAE;SAC1F,CAAC,CAAA;QACF,MAAM,OAAO,GAA2B;YACtC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS;YAC7B,cAAc,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,CAAS,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;YACxE,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;YAC1C,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAA;QACD,cAAM,CAAC,OAAO,CAAC,sDAAsB,EAAE,OAAO,CAAC,CAAA;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,uBAAuB;IACzB,CAAC;AACH,CAAC","sourcesContent":["/*\n * 세션 활동 발행 — pubsub 의존만(데코레이터 없음). 순수 로직은 session-activity-broadcast.ts.\n *\n * 참여자 목록을 여기서 읽어 페이로드에 싣는다 — 필터는 구독자 × 메시지마다 돌기 때문에 거기서 DB 를\n * 읽으면 방송 한 번이 구독자 수만큼의 질의가 된다. 발행당 1회로 끝낸다.\n *\n * best-effort: 실패해도 대화·저장을 막지 않는다(배지가 한 번 늦을 뿐).\n */\nimport { pubsub, getRepository } from '@things-factory/shell'\n\nimport { ChatSessionParticipant } from '../chat-session-participant/chat-session-participant.js'\nimport { SESSION_ACTIVITY_TOPIC, type SessionActivityPayload, type SessionActivityKind } from './session-activity-broadcast.js'\n\nexport async function publishSessionActivity(input: {\n domainId: string\n sessionId: string\n /** message = 새 메시지(배지 올림) · meta = 이름 변경 등(목록만 갱신). 기본 message. */\n kind?: SessionActivityKind\n anchorType?: string\n anchorId?: string\n at?: Date\n preview?: string\n actorId?: string\n}): Promise<void> {\n try {\n const participants = await getRepository(ChatSessionParticipant).find({\n where: { domain: { id: input.domainId } as any, session: { id: input.sessionId } as any }\n })\n const payload: SessionActivityPayload = {\n domainId: input.domainId,\n sessionId: input.sessionId,\n kind: input.kind ?? 'message',\n participantIds: participants.map(p => (p as any).userId).filter(Boolean),\n anchorType: input.anchorType,\n anchorId: input.anchorId,\n at: (input.at ?? new Date()).toISOString(),\n preview: input.preview,\n actorId: input.actorId\n }\n pubsub.publish(SESSION_ACTIVITY_TOPIC, payload)\n } catch {\n /* 방송 실패는 대화를 막지 않는다 */\n }\n}\n"]}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type SessionActivityPayload } from './session-activity-broadcast.js';
|
|
2
|
-
export { publishSessionActivity } from './session-activity-publish.js';
|
|
3
|
-
export declare class SessionActivity {
|
|
4
|
-
sessionId: string;
|
|
5
|
-
kind: string;
|
|
6
|
-
anchorType?: string;
|
|
7
|
-
anchorId?: string;
|
|
8
|
-
at: string;
|
|
9
|
-
preview?: string;
|
|
10
|
-
actorId?: string;
|
|
11
|
-
}
|
|
12
|
-
export declare class MySessionActivitySubscription {
|
|
13
|
-
mySessionActivity(payload: SessionActivityPayload): SessionActivity;
|
|
14
|
-
}
|