@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.
Files changed (199) hide show
  1. package/client/components/default-catalog-entries.ts +1 -1
  2. package/client/components/default-slash-templates.ts +1 -1
  3. package/client/index.ts +27 -6
  4. package/dist-client/client/components/default-catalog-entries.d.ts +1 -1
  5. package/dist-client/client/components/default-catalog-entries.js.map +1 -1
  6. package/dist-client/client/components/default-slash-templates.d.ts +1 -1
  7. package/dist-client/client/components/default-slash-templates.js.map +1 -1
  8. package/dist-client/client/index.d.ts +3 -11
  9. package/dist-client/client/index.js +25 -6
  10. package/dist-client/client/index.js.map +1 -1
  11. package/dist-client/server/service/assistant.d.ts +22 -22
  12. package/dist-client/server/service/assistant.js +32 -70
  13. package/dist-client/server/service/assistant.js.map +1 -1
  14. package/dist-client/server/service/grounding.d.ts +2 -12
  15. package/dist-client/server/service/grounding.js +15 -23
  16. package/dist-client/server/service/grounding.js.map +1 -1
  17. package/dist-client/server/service/types.d.ts +2 -4
  18. package/dist-client/server/service/types.js.map +1 -1
  19. package/dist-client/server/service/validation/board-model-schema.d.ts +270 -270
  20. package/dist-client/tsconfig.tsbuildinfo +1 -1
  21. package/dist-server/service/assistant.d.ts +22 -22
  22. package/dist-server/service/assistant.js +25 -76
  23. package/dist-server/service/assistant.js.map +1 -1
  24. package/dist-server/service/board-ai-resolver.js +41 -30
  25. package/dist-server/service/board-ai-resolver.js.map +1 -1
  26. package/dist-server/service/board-session-resolver.d.ts +12 -0
  27. package/dist-server/service/board-session-resolver.js +203 -0
  28. package/dist-server/service/board-session-resolver.js.map +1 -0
  29. package/dist-server/service/board-tool-rights.d.ts +7 -0
  30. package/dist-server/service/board-tool-rights.js +62 -0
  31. package/dist-server/service/board-tool-rights.js.map +1 -0
  32. package/dist-server/service/grounding.d.ts +2 -12
  33. package/dist-server/service/grounding.js +16 -24
  34. package/dist-server/service/grounding.js.map +1 -1
  35. package/dist-server/service/index.d.ts +4 -10
  36. package/dist-server/service/index.js +16 -23
  37. package/dist-server/service/index.js.map +1 -1
  38. package/dist-server/service/patch-entry/board-patch-subscription.js +2 -2
  39. package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
  40. package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
  41. package/dist-server/service/patch-entry/patch-entry.js +3 -3
  42. package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
  43. package/dist-server/service/types.d.ts +2 -4
  44. package/dist-server/service/types.js.map +1 -1
  45. package/dist-server/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +6 -5
  47. package/server/service/apply-patch-drift.test.ts +3 -1
  48. package/server/service/assistant.ts +47 -73
  49. package/server/service/board-ai-resolver.ts +22 -12
  50. package/server/service/board-session-resolver.ts +183 -0
  51. package/server/service/board-tool-rights.test.ts +96 -0
  52. package/server/service/board-tool-rights.ts +73 -0
  53. package/server/service/dock-contract.test.ts +3 -0
  54. package/server/service/grounding.test.ts +3 -3
  55. package/server/service/grounding.ts +15 -34
  56. package/server/service/index.ts +16 -22
  57. package/server/service/patch-entry/board-patch-subscription.ts +1 -1
  58. package/server/service/patch-entry/patch-entry.ts +3 -2
  59. package/server/service/registry-tool-loop.test.ts +1 -0
  60. package/server/service/tool-exposure.test.ts +2 -0
  61. package/server/service/types.ts +3 -4
  62. package/client/components/board-ai-chat.test.ts +0 -149
  63. package/client/components/board-ai-chat.ts +0 -3184
  64. package/client/components/chat-echo-dedup.test.ts +0 -230
  65. package/client/components/chat-echo-dedup.ts +0 -155
  66. package/client/components/chat-input-builder.ts +0 -69
  67. package/client/components/markdown.test.ts +0 -518
  68. package/client/components/markdown.ts +0 -508
  69. package/client/components/mention-popup-helpers.ts +0 -433
  70. package/client/components/mention-popup.test.ts +0 -588
  71. package/client/components/mention-popup.ts +0 -238
  72. package/client/utils/board-edit-patch.ts +0 -374
  73. package/dist-client/client/components/board-ai-chat.d.ts +0 -384
  74. package/dist-client/client/components/board-ai-chat.js +0 -3008
  75. package/dist-client/client/components/board-ai-chat.js.map +0 -1
  76. package/dist-client/client/components/board-ai-chat.test.d.ts +0 -1
  77. package/dist-client/client/components/board-ai-chat.test.js +0 -138
  78. package/dist-client/client/components/board-ai-chat.test.js.map +0 -1
  79. package/dist-client/client/components/chat-echo-dedup.d.ts +0 -54
  80. package/dist-client/client/components/chat-echo-dedup.js +0 -115
  81. package/dist-client/client/components/chat-echo-dedup.js.map +0 -1
  82. package/dist-client/client/components/chat-echo-dedup.test.d.ts +0 -1
  83. package/dist-client/client/components/chat-echo-dedup.test.js +0 -203
  84. package/dist-client/client/components/chat-echo-dedup.test.js.map +0 -1
  85. package/dist-client/client/components/chat-input-builder.d.ts +0 -55
  86. package/dist-client/client/components/chat-input-builder.js +0 -32
  87. package/dist-client/client/components/chat-input-builder.js.map +0 -1
  88. package/dist-client/client/components/markdown.d.ts +0 -60
  89. package/dist-client/client/components/markdown.js +0 -422
  90. package/dist-client/client/components/markdown.js.map +0 -1
  91. package/dist-client/client/components/markdown.test.d.ts +0 -1
  92. package/dist-client/client/components/markdown.test.js +0 -418
  93. package/dist-client/client/components/markdown.test.js.map +0 -1
  94. package/dist-client/client/components/mention-popup-helpers.d.ts +0 -165
  95. package/dist-client/client/components/mention-popup-helpers.js +0 -330
  96. package/dist-client/client/components/mention-popup-helpers.js.map +0 -1
  97. package/dist-client/client/components/mention-popup.d.ts +0 -35
  98. package/dist-client/client/components/mention-popup.js +0 -223
  99. package/dist-client/client/components/mention-popup.js.map +0 -1
  100. package/dist-client/client/components/mention-popup.test.d.ts +0 -1
  101. package/dist-client/client/components/mention-popup.test.js +0 -474
  102. package/dist-client/client/components/mention-popup.test.js.map +0 -1
  103. package/dist-client/client/utils/board-edit-patch.d.ts +0 -157
  104. package/dist-client/client/utils/board-edit-patch.js +0 -284
  105. package/dist-client/client/utils/board-edit-patch.js.map +0 -1
  106. package/dist-server/service/chat-message/chat-message-broadcast.d.ts +0 -13
  107. package/dist-server/service/chat-message/chat-message-broadcast.js +0 -13
  108. package/dist-server/service/chat-message/chat-message-broadcast.js.map +0 -1
  109. package/dist-server/service/chat-message/chat-message-publish.d.ts +0 -6
  110. package/dist-server/service/chat-message/chat-message-publish.js +0 -27
  111. package/dist-server/service/chat-message/chat-message-publish.js.map +0 -1
  112. package/dist-server/service/chat-message/chat-message-subscription.d.ts +0 -8
  113. package/dist-server/service/chat-message/chat-message-subscription.js +0 -53
  114. package/dist-server/service/chat-message/chat-message-subscription.js.map +0 -1
  115. package/dist-server/service/chat-message/chat-message.d.ts +0 -68
  116. package/dist-server/service/chat-message/chat-message.js +0 -172
  117. package/dist-server/service/chat-message/chat-message.js.map +0 -1
  118. package/dist-server/service/chat-message/fold-history.d.ts +0 -43
  119. package/dist-server/service/chat-message/fold-history.js +0 -33
  120. package/dist-server/service/chat-message/fold-history.js.map +0 -1
  121. package/dist-server/service/chat-message/history-summary.d.ts +0 -43
  122. package/dist-server/service/chat-message/history-summary.js +0 -77
  123. package/dist-server/service/chat-message/history-summary.js.map +0 -1
  124. package/dist-server/service/chat-message/index.d.ts +0 -5
  125. package/dist-server/service/chat-message/index.js +0 -12
  126. package/dist-server/service/chat-message/index.js.map +0 -1
  127. package/dist-server/service/chat-message/llm-history.d.ts +0 -46
  128. package/dist-server/service/chat-message/llm-history.js +0 -87
  129. package/dist-server/service/chat-message/llm-history.js.map +0 -1
  130. package/dist-server/service/chat-session/chat-session.d.ts +0 -62
  131. package/dist-server/service/chat-session/chat-session.js +0 -164
  132. package/dist-server/service/chat-session/chat-session.js.map +0 -1
  133. package/dist-server/service/chat-session/index.d.ts +0 -3
  134. package/dist-server/service/chat-session/index.js +0 -7
  135. package/dist-server/service/chat-session/index.js.map +0 -1
  136. package/dist-server/service/chat-session/session-activity-broadcast.d.ts +0 -30
  137. package/dist-server/service/chat-session/session-activity-broadcast.js +0 -34
  138. package/dist-server/service/chat-session/session-activity-broadcast.js.map +0 -1
  139. package/dist-server/service/chat-session/session-activity-publish.d.ts +0 -12
  140. package/dist-server/service/chat-session/session-activity-publish.js +0 -37
  141. package/dist-server/service/chat-session/session-activity-publish.js.map +0 -1
  142. package/dist-server/service/chat-session/session-activity-subscription.d.ts +0 -14
  143. package/dist-server/service/chat-session/session-activity-subscription.js +0 -87
  144. package/dist-server/service/chat-session/session-activity-subscription.js.map +0 -1
  145. package/dist-server/service/chat-session/session-anchor.d.ts +0 -12
  146. package/dist-server/service/chat-session/session-anchor.js +0 -20
  147. package/dist-server/service/chat-session/session-anchor.js.map +0 -1
  148. package/dist-server/service/chat-session/session-inbox.d.ts +0 -80
  149. package/dist-server/service/chat-session/session-inbox.js +0 -133
  150. package/dist-server/service/chat-session/session-inbox.js.map +0 -1
  151. package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +0 -49
  152. package/dist-server/service/chat-session-participant/chat-session-participant.js +0 -141
  153. package/dist-server/service/chat-session-participant/chat-session-participant.js.map +0 -1
  154. package/dist-server/service/chat-session-participant/index.d.ts +0 -4
  155. package/dist-server/service/chat-session-participant/index.js +0 -10
  156. package/dist-server/service/chat-session-participant/index.js.map +0 -1
  157. package/dist-server/service/chat-session-participant/invite-notification.d.ts +0 -27
  158. package/dist-server/service/chat-session-participant/invite-notification.js +0 -30
  159. package/dist-server/service/chat-session-participant/invite-notification.js.map +0 -1
  160. package/dist-server/service/chat-session-participant/presence.d.ts +0 -4
  161. package/dist-server/service/chat-session-participant/presence.js +0 -22
  162. package/dist-server/service/chat-session-participant/presence.js.map +0 -1
  163. package/dist-server/service/chat-session-resolver.d.ts +0 -102
  164. package/dist-server/service/chat-session-resolver.js +0 -856
  165. package/dist-server/service/chat-session-resolver.js.map +0 -1
  166. package/dist-server/service/subscribe-auth.d.ts +0 -14
  167. package/dist-server/service/subscribe-auth.js +0 -33
  168. package/dist-server/service/subscribe-auth.js.map +0 -1
  169. package/server/service/chat-message/chat-message-broadcast.integration.test.ts +0 -73
  170. package/server/service/chat-message/chat-message-broadcast.test.ts +0 -40
  171. package/server/service/chat-message/chat-message-broadcast.ts +0 -27
  172. package/server/service/chat-message/chat-message-publish.ts +0 -22
  173. package/server/service/chat-message/chat-message-subscription.ts +0 -41
  174. package/server/service/chat-message/chat-message.ts +0 -195
  175. package/server/service/chat-message/fold-history.test.ts +0 -98
  176. package/server/service/chat-message/fold-history.ts +0 -77
  177. package/server/service/chat-message/history-summary.test.ts +0 -127
  178. package/server/service/chat-message/history-summary.ts +0 -100
  179. package/server/service/chat-message/index.ts +0 -12
  180. package/server/service/chat-message/llm-history.test.ts +0 -194
  181. package/server/service/chat-message/llm-history.ts +0 -163
  182. package/server/service/chat-session/chat-session.ts +0 -182
  183. package/server/service/chat-session/index.ts +0 -5
  184. package/server/service/chat-session/session-activity-broadcast.ts +0 -57
  185. package/server/service/chat-session/session-activity-publish.ts +0 -44
  186. package/server/service/chat-session/session-activity-subscription.ts +0 -67
  187. package/server/service/chat-session/session-anchor.ts +0 -35
  188. package/server/service/chat-session/session-inbox.test.ts +0 -185
  189. package/server/service/chat-session/session-inbox.ts +0 -161
  190. package/server/service/chat-session-participant/chat-session-participant.ts +0 -135
  191. package/server/service/chat-session-participant/index.ts +0 -6
  192. package/server/service/chat-session-participant/invite-notification.test.ts +0 -30
  193. package/server/service/chat-session-participant/invite-notification.ts +0 -48
  194. package/server/service/chat-session-participant/presence.test.ts +0 -39
  195. package/server/service/chat-session-participant/presence.ts +0 -21
  196. package/server/service/chat-session-resolver.ts +0 -779
  197. package/server/service/session-anchor.test.ts +0 -65
  198. package/server/service/subscribe-auth.test.ts +0 -57
  199. package/server/service/subscribe-auth.ts +0 -34
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BoardSessionResolver = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /*
6
+ * 보드에 매인 대화 op — **보드 편집기의 것이라 여기 남는다.**
7
+ *
8
+ * ── 왜 갈랐나 (2026-09-10) ──────────────────────────────────────────────────
9
+ * `chat-session-resolver.ts` 에 스물넷이 함께 있었다. 그 중 열여덟은 대화 자체의 일(세션·참가자·
10
+ * 인박스·메시지)이라 보드가 없는 제품에도 뜻이 있고, 여섯은 보드의 일이다. 한 파일에 있는 동안은
11
+ * 앞엣것을 중립 패키지로 옮길 수 없다 — 옮기면 `ai-assistant → board-ai` 의존이 생긴다.
12
+ *
13
+ * 그래서 **여섯을 이쪽으로 뗐다.** 남은 열여덟이 옮겨질 수 있는 상태가 되는 것이 이 갈이의 목적이다.
14
+ *
15
+ * ── 왜 여섯인가 ─────────────────────────────────────────────────────────────
16
+ * chatSessionByBoard · chatSessionsByBoard 보드 id 로 찾는다 — 보드를 아는 질의
17
+ * startBoardAISession 보드 앵커로 시작하는 얇은 래퍼
18
+ * chatPatches · recordDirectPatch · revertPatch PatchEntry — 보드 편집 이력
19
+ *
20
+ * ── 위임을 `this` 로 하지 않는다 ────────────────────────────────────────────
21
+ * 앞의 셋은 원래 `this.startAISession(...)` 처럼 같은 클래스의 중립 메서드를 불렀다. 이제 클래스가
22
+ * 둘이므로 중립 리졸버 인스턴스에 위임한다. 리졸버는 상태가 없다(전부 `context.state` 에서 읽는다).
23
+ *
24
+ * **로직을 베끼지 않는다** — 같은 글이 두 곳에 있으면 한쪽만 고쳐진다.
25
+ */
26
+ const type_graphql_1 = require("type-graphql");
27
+ const graphql_scalars_1 = require("graphql-scalars");
28
+ const shell_1 = require("@things-factory/shell");
29
+ const ai_assistant_1 = require("@things-factory/ai-assistant");
30
+ const ai_assistant_2 = require("@things-factory/ai-assistant");
31
+ const ai_assistant_3 = require("@things-factory/ai-assistant");
32
+ const ai_assistant_4 = require("@things-factory/ai-assistant");
33
+ const ai_assistant_5 = require("@things-factory/ai-assistant");
34
+ const ai_assistant_6 = require("@things-factory/ai-assistant");
35
+ const patch_entry_js_1 = require("./patch-entry/patch-entry.js");
36
+ const board_patch_subscription_js_1 = require("./patch-entry/board-patch-subscription.js");
37
+ /** 중립 op 로의 위임 대상. 리졸버는 상태가 없으므로 하나면 된다. */
38
+ const sessions = new ai_assistant_6.ChatSessionResolver();
39
+ let BoardSessionResolver = class BoardSessionResolver {
40
+ async chatSessionByBoard(boardId, context) {
41
+ const { domain } = context.state;
42
+ return ((await (0, shell_1.getRepository)(ai_assistant_1.ChatSession).findOneBy({ boardId, domain: { id: domain.id } })) ?? null);
43
+ }
44
+ /** 보드 전용 진입점 — 앵커 조회의 얇은 래퍼(기존 소비자 무변경). */
45
+ async chatSessionsByBoard(boardId, context) {
46
+ return await sessions.chatSessionsByAnchor('board', boardId, context);
47
+ }
48
+ async chatPatches(sessionId, limit, context) {
49
+ const { domain } = context.state;
50
+ const session = await (0, shell_1.getRepository)(ai_assistant_1.ChatSession).findOneBy({
51
+ id: sessionId,
52
+ domain: { id: domain.id }
53
+ });
54
+ if (!session)
55
+ return [];
56
+ return await (0, shell_1.getRepository)(patch_entry_js_1.PatchEntry).find({
57
+ where: { session: { id: sessionId } },
58
+ order: { createdAt: 'DESC' },
59
+ take: limit
60
+ });
61
+ }
62
+ /** 보드 전용 진입점 — 앵커 시작의 얇은 래퍼(기존 소비자 무변경). */
63
+ async startBoardAISession(boardId, context) {
64
+ return await sessions.startAISession('board', boardId, context);
65
+ }
66
+ async recordDirectPatch(sessionId, ops, summary, context) {
67
+ const { domain, tx, user } = context.state;
68
+ const session = await (0, shell_1.getRepository)(ai_assistant_1.ChatSession, tx).findOneBy({
69
+ id: sessionId,
70
+ domain: { id: domain.id }
71
+ });
72
+ if (!session)
73
+ throw new Error(`ChatSession ${sessionId} not found`);
74
+ const entry = await (0, shell_1.getRepository)(patch_entry_js_1.PatchEntry, tx).save({
75
+ session: { id: session.id },
76
+ source: 'user-direct',
77
+ ops: JSON.stringify(ops || []),
78
+ summary,
79
+ reverted: false
80
+ });
81
+ // AI 컨텍스트 흐름 — system 메시지 자동 추가. creator = 직접 편집한 사용자.
82
+ // parentMessage = 직전 in-session 메시지 (timeline 연속성 유지 — system 도 chain 에 합류).
83
+ const previousLast = await (0, shell_1.getRepository)(ai_assistant_4.ChatMessage, tx).findOne({
84
+ where: { session: { id: session.id } },
85
+ order: { createdAt: 'DESC' }
86
+ });
87
+ const sysMsg = await (0, shell_1.getRepository)(ai_assistant_4.ChatMessage, tx).save({
88
+ session: { id: session.id },
89
+ role: 'system',
90
+ content: `User directly edited the board: ${summary ?? `${(ops || []).length} op(s)`}`,
91
+ relatedPatchId: entry.id,
92
+ creator: user,
93
+ updater: user,
94
+ parentMessage: previousLast ? { id: previousLast.id } : undefined
95
+ });
96
+ /* 세션 활동 갱신 — 직접 편집도 다른 참여자에게는 새 소식이다(목록 정렬·안 읽음 판정). */
97
+ try {
98
+ await (0, shell_1.getRepository)(ai_assistant_1.ChatSession, tx).update(session.id, (0, ai_assistant_2.activityPatch)(sysMsg.content));
99
+ }
100
+ catch {
101
+ /* best-effort — 편집 기록을 막지 않는다 */
102
+ }
103
+ (0, ai_assistant_3.publishSessionActivity)({
104
+ domainId: domain.id,
105
+ sessionId: session.id,
106
+ kind: 'message',
107
+ anchorType: session.anchorType,
108
+ anchorId: session.anchorId,
109
+ preview: sysMsg.content,
110
+ actorId: user?.id
111
+ });
112
+ // C1: user-direct 편집도 같은 board 구독자에게 방송 (AI·타참여자 실시간 반영). best-effort.
113
+ (0, board_patch_subscription_js_1.publishBoardPatch)({ domainId: domain.id, boardId: session.boardId, patch: entry });
114
+ // C4: 직접편집 system 메시지를 같은 세션 구독자에게 방송 (transcript 실시간 일관성). best-effort.
115
+ (0, ai_assistant_5.publishChatMessage)({ domainId: domain.id, sessionId: session.id, message: sysMsg });
116
+ return entry;
117
+ }
118
+ async revertPatch(patchId, context) {
119
+ const { tx } = context.state;
120
+ await (0, shell_1.getRepository)(patch_entry_js_1.PatchEntry, tx).update(patchId, { reverted: true });
121
+ return true;
122
+ }
123
+ };
124
+ exports.BoardSessionResolver = BoardSessionResolver;
125
+ tslib_1.__decorate([
126
+ (0, type_graphql_1.Query)(() => ai_assistant_1.ChatSession, { nullable: true, description: 'Get AI chat session by board id (returns first match — backward compat single-session lookup).' }),
127
+ (0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query", legacy: "board-ai")'),
128
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('boardId')),
129
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
130
+ tslib_1.__metadata("design:type", Function),
131
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
132
+ tslib_1.__metadata("design:returntype", Promise)
133
+ ], BoardSessionResolver.prototype, "chatSessionByBoard", null);
134
+ tslib_1.__decorate([
135
+ (0, type_graphql_1.Query)(() => [ai_assistant_1.ChatSession], {
136
+ description: 'List all AI chat sessions for a board (oldest first). Thin wrapper over chatSessionsByAnchor(board).'
137
+ }),
138
+ (0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "query", legacy: "board-ai")'),
139
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('boardId')),
140
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
141
+ tslib_1.__metadata("design:type", Function),
142
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
143
+ tslib_1.__metadata("design:returntype", Promise)
144
+ ], BoardSessionResolver.prototype, "chatSessionsByBoard", null);
145
+ tslib_1.__decorate([
146
+ (0, type_graphql_1.Query)(() => [patch_entry_js_1.PatchEntry], { description: 'List patch entries of a session, newest first.' })
147
+ /*
148
+ * ⚠ 이 셋은 **보드 편집기의 것**이라 `board-ai` 에 남는다 (2026-09-10).
149
+ *
150
+ * 나머지 스물둘을 `ai-assistant` 로 옮길 때 이것들도 함께 갔다가, 도는 스키마를 확인하면서
151
+ * 되돌렸다 — `revertPatch` 가 `ai-assistant:mutation` 으로 떠 있었다. 즉 **대화 권한만 가진
152
+ * 사람이 보드 패치를 되돌릴 수 있었다.** 좁히려던 변경이 넓히는 변경이 된 자리다.
153
+ *
154
+ * 가르는 물음: **이것이 보드를 바꾸나.** 패치는 바꾼다. 세션 조회는 안 바꾼다.
155
+ */
156
+ ,
157
+ (0, type_graphql_1.Directive)('@privilege(category: "board-ai", privilege: "query")'),
158
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
159
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('limit', () => type_graphql_1.Int, { nullable: true, defaultValue: 100 })),
160
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
161
+ tslib_1.__metadata("design:type", Function),
162
+ tslib_1.__metadata("design:paramtypes", [String, Number, Object]),
163
+ tslib_1.__metadata("design:returntype", Promise)
164
+ ], BoardSessionResolver.prototype, "chatPatches", null);
165
+ tslib_1.__decorate([
166
+ (0, type_graphql_1.Mutation)(() => ai_assistant_1.ChatSession, {
167
+ description: 'Start (or get existing) AI chat session for a board. Thin wrapper over startAISession(board).'
168
+ }),
169
+ (0, type_graphql_1.Directive)('@privilege(category: "ai-assistant", privilege: "mutation", legacy: "board-ai")'),
170
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('boardId')),
171
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
172
+ tslib_1.__metadata("design:type", Function),
173
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
174
+ tslib_1.__metadata("design:returntype", Promise)
175
+ ], BoardSessionResolver.prototype, "startBoardAISession", null);
176
+ tslib_1.__decorate([
177
+ (0, type_graphql_1.Directive)('@transaction'),
178
+ (0, type_graphql_1.Mutation)(() => patch_entry_js_1.PatchEntry, {
179
+ description: 'Record a patch from user direct edit. Adds a system message so AI sees the change next turn.'
180
+ }),
181
+ (0, type_graphql_1.Directive)('@privilege(category: "board-ai", privilege: "mutation")'),
182
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('sessionId')),
183
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('ops', () => graphql_scalars_1.GraphQLJSON)),
184
+ tslib_1.__param(2, (0, type_graphql_1.Arg)('summary', { nullable: true })),
185
+ tslib_1.__param(3, (0, type_graphql_1.Ctx)()),
186
+ tslib_1.__metadata("design:type", Function),
187
+ tslib_1.__metadata("design:paramtypes", [String, Array, String, Object]),
188
+ tslib_1.__metadata("design:returntype", Promise)
189
+ ], BoardSessionResolver.prototype, "recordDirectPatch", null);
190
+ tslib_1.__decorate([
191
+ (0, type_graphql_1.Directive)('@transaction'),
192
+ (0, type_graphql_1.Mutation)(() => Boolean, { description: 'Mark a patch as reverted (does not undo, only flags).' }),
193
+ (0, type_graphql_1.Directive)('@privilege(category: "board-ai", privilege: "mutation")'),
194
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('patchId')),
195
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
196
+ tslib_1.__metadata("design:type", Function),
197
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
198
+ tslib_1.__metadata("design:returntype", Promise)
199
+ ], BoardSessionResolver.prototype, "revertPatch", null);
200
+ exports.BoardSessionResolver = BoardSessionResolver = tslib_1.__decorate([
201
+ (0, type_graphql_1.Resolver)()
202
+ ], BoardSessionResolver);
203
+ //# sourceMappingURL=board-session-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-session-resolver.js","sourceRoot":"","sources":["../../server/service/board-session-resolver.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,+CAAkF;AAClF,qDAA6C;AAE7C,iDAAqD;AAErD,+DAA0D;AAC1D,+DAA4D;AAC5D,+DAAqE;AACrE,+DAA0D;AAC1D,+DAAiE;AACjE,+DAAkE;AAClE,iEAAyD;AACzD,2FAA6E;AAE7E,4CAA4C;AAC5C,MAAM,QAAQ,GAAG,IAAI,kCAAmB,EAAE,CAAA;AAGnC,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAGzB,AAAN,KAAK,CAAC,kBAAkB,CACN,OAAe,EACxB,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAChC,OAAO,CACL,CAAC,MAAM,IAAA,qBAAa,EAAC,0BAAW,CAAC,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAC7F,CAAA;IACH,CAAC;IAED,4CAA4C;IAKtC,AAAN,KAAK,CAAC,mBAAmB,CACP,OAAe,EACxB,OAAwB;QAE/B,OAAO,MAAM,QAAQ,CAAC,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACvE,CAAC;IAaK,AAAN,KAAK,CAAC,WAAW,CACG,SAAiB,EAC6B,KAAa,EACtE,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAChC,MAAM,OAAO,GAAG,MAAM,IAAA,qBAAa,EAAC,0BAAW,CAAC,CAAC,SAAS,CAAC;YACzD,EAAE,EAAE,SAAS;YACb,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE;SAC1B,CAAC,CAAA;QACF,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAA;QACvB,OAAO,MAAM,IAAA,qBAAa,EAAC,2BAAU,CAAC,CAAC,IAAI,CAAC;YAC1C,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAS,EAAE;YAC5C,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;YAC5B,IAAI,EAAE,KAAK;SACZ,CAAC,CAAA;IACJ,CAAC;IAED,4CAA4C;IAKtC,AAAN,KAAK,CAAC,mBAAmB,CACP,OAAe,EACxB,OAAwB;QAE/B,OAAO,MAAM,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACjE,CAAC;IAOK,AAAN,KAAK,CAAC,iBAAiB,CACH,SAAiB,EACJ,GAAU,EACL,OAA2B,EACxD,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAC1C,MAAM,OAAO,GAAG,MAAM,IAAA,qBAAa,EAAC,0BAAW,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC;YAC7D,EAAE,EAAE,SAAS;YACb,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE;SAC1B,CAAC,CAAA;QACF,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,SAAS,YAAY,CAAC,CAAA;QAEnE,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,2BAAU,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC;YACrD,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS;YAClC,MAAM,EAAE,aAAa;YACrB,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,CAAC;YAC9B,OAAO;YACP,QAAQ,EAAE,KAAK;SACT,CAAC,CAAA;QAET,uDAAuD;QACvD,6EAA6E;QAC7E,MAAM,YAAY,GAAG,MAAM,IAAA,qBAAa,EAAC,0BAAW,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC;YAChE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS,EAAE;YAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;SAC7B,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,IAAA,qBAAa,EAAC,0BAAW,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC;YACvD,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS;YAClC,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,mCAAmC,OAAO,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,MAAM,QAAQ,EAAE;YACtF,cAAc,EAAE,KAAK,CAAC,EAAE;YACxB,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI;YACb,aAAa,EAAE,YAAY,CAAC,CAAC,CAAE,EAAE,EAAE,EAAE,YAAY,CAAC,EAAE,EAAU,CAAC,CAAC,CAAC,SAAS;SACpE,CAAC,CAAA;QAET,wDAAwD;QACxD,IAAI,CAAC;YACH,MAAM,IAAA,qBAAa,EAAC,0BAAW,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAG,EAAE,IAAA,4BAAa,EAAC,MAAM,CAAC,OAAO,CAAQ,CAAC,CAAA;QAChG,CAAC;QAAC,MAAM,CAAC;YACP,iCAAiC;QACnC,CAAC;QACD,IAAA,qCAAsB,EAAC;YACrB,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,SAAS,EAAE,OAAO,CAAC,EAAG;YACtB,IAAI,EAAE,SAAS;YACf,UAAU,EAAG,OAAe,CAAC,UAAU;YACvC,QAAQ,EAAG,OAAe,CAAC,QAAQ;YACnC,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,IAAI,EAAE,EAAE;SAClB,CAAC,CAAA;QAEF,uEAAuE;QACvE,IAAA,+CAAiB,EAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;QAClF,yEAAyE;QACzE,IAAA,iCAAkB,EAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;QAEnF,OAAO,KAAK,CAAA;IACd,CAAC;IAKK,AAAN,KAAK,CAAC,WAAW,CACC,OAAe,EACxB,OAAwB;QAE/B,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAC5B,MAAM,IAAA,qBAAa,EAAC,2BAAU,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACvE,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAA;AA/IY,oDAAoB;AAGzB;IAFL,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,0BAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gGAAgG,EAAE,CAAC;IAC3J,IAAA,wBAAS,EAAC,8EAA8E,CAAC;IAEvF,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IACd,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;8DAMP;AAOK;IAJL,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,CAAC,0BAAW,CAAC,EAAE;QAC1B,WAAW,EAAE,sGAAsG;KACpH,CAAC;IACD,IAAA,wBAAS,EAAC,8EAA8E,CAAC;IAEvF,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IACd,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+DAGP;AAaK;IAXL,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,CAAC,2BAAU,CAAC,EAAE,EAAE,WAAW,EAAE,gDAAgD,EAAE,CAAC;IAC7F;;;;;;;;OAQG;;IACF,IAAA,wBAAS,EAAC,sDAAsD,CAAC;IAE/D,mBAAA,IAAA,kBAAG,EAAC,WAAW,CAAC,CAAA;IAChB,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAA;IAC9D,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;uDAaP;AAOK;IAJL,IAAA,uBAAQ,EAAC,GAAG,EAAE,CAAC,0BAAW,EAAE;QAC3B,WAAW,EAAE,+FAA+F;KAC7G,CAAC;IACD,IAAA,wBAAS,EAAC,iFAAiF,CAAC;IAE1F,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IACd,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+DAGP;AAOK;IALL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,GAAG,EAAE,CAAC,2BAAU,EAAE;QAC1B,WAAW,EAAE,8FAA8F;KAC5G,CAAC;IACD,IAAA,wBAAS,EAAC,yDAAyD,CAAC;IAElE,mBAAA,IAAA,kBAAG,EAAC,WAAW,CAAC,CAAA;IAChB,mBAAA,IAAA,kBAAG,EAAC,KAAK,EAAE,GAAG,EAAE,CAAC,6BAAW,CAAC,CAAA;IAC7B,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAClC,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6DAuDP;AAKK;IAHL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,uBAAQ,EAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,uDAAuD,EAAE,CAAC;IACjG,IAAA,wBAAS,EAAC,yDAAyD,CAAC;IAElE,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;IACd,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;uDAKP;+BA9IU,oBAAoB;IADhC,IAAA,uBAAQ,GAAE;GACE,oBAAoB,CA+IhC","sourcesContent":["/*\n * 보드에 매인 대화 op — **보드 편집기의 것이라 여기 남는다.**\n *\n * ── 왜 갈랐나 (2026-09-10) ──────────────────────────────────────────────────\n * `chat-session-resolver.ts` 에 스물넷이 함께 있었다. 그 중 열여덟은 대화 자체의 일(세션·참가자·\n * 인박스·메시지)이라 보드가 없는 제품에도 뜻이 있고, 여섯은 보드의 일이다. 한 파일에 있는 동안은\n * 앞엣것을 중립 패키지로 옮길 수 없다 — 옮기면 `ai-assistant → board-ai` 의존이 생긴다.\n *\n * 그래서 **여섯을 이쪽으로 뗐다.** 남은 열여덟이 옮겨질 수 있는 상태가 되는 것이 이 갈이의 목적이다.\n *\n * ── 왜 여섯인가 ─────────────────────────────────────────────────────────────\n * chatSessionByBoard · chatSessionsByBoard 보드 id 로 찾는다 — 보드를 아는 질의\n * startBoardAISession 보드 앵커로 시작하는 얇은 래퍼\n * chatPatches · recordDirectPatch · revertPatch PatchEntry — 보드 편집 이력\n *\n * ── 위임을 `this` 로 하지 않는다 ────────────────────────────────────────────\n * 앞의 셋은 원래 `this.startAISession(...)` 처럼 같은 클래스의 중립 메서드를 불렀다. 이제 클래스가\n * 둘이므로 중립 리졸버 인스턴스에 위임한다. 리졸버는 상태가 없다(전부 `context.state` 에서 읽는다).\n *\n * **로직을 베끼지 않는다** — 같은 글이 두 곳에 있으면 한쪽만 고쳐진다.\n */\nimport { Resolver, Query, Mutation, Arg, Ctx, Directive, Int } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { ChatSession } from '@things-factory/ai-assistant'\nimport { activityPatch } from '@things-factory/ai-assistant'\nimport { publishSessionActivity } from '@things-factory/ai-assistant'\nimport { ChatMessage } from '@things-factory/ai-assistant'\nimport { publishChatMessage } from '@things-factory/ai-assistant'\nimport { ChatSessionResolver } from '@things-factory/ai-assistant'\nimport { PatchEntry } from './patch-entry/patch-entry.js'\nimport { publishBoardPatch } from './patch-entry/board-patch-subscription.js'\n\n/** 중립 op 로의 위임 대상. 리졸버는 상태가 없으므로 하나면 된다. */\nconst sessions = new ChatSessionResolver()\n\n@Resolver()\nexport class BoardSessionResolver {\n @Query(() => ChatSession, { nullable: true, description: 'Get AI chat session by board id (returns first match — backward compat single-session lookup).' })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\", legacy: \"board-ai\")')\n async chatSessionByBoard(\n @Arg('boardId') boardId: string,\n @Ctx() context: ResolverContext\n ): Promise<ChatSession | null> {\n const { domain } = context.state\n return (\n (await getRepository(ChatSession).findOneBy({ boardId, domain: { id: domain.id } })) ?? null\n )\n }\n\n /** 보드 전용 진입점 — 앵커 조회의 얇은 래퍼(기존 소비자 무변경). */\n @Query(() => [ChatSession], {\n description: 'List all AI chat sessions for a board (oldest first). Thin wrapper over chatSessionsByAnchor(board).'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"query\", legacy: \"board-ai\")')\n async chatSessionsByBoard(\n @Arg('boardId') boardId: string,\n @Ctx() context: ResolverContext\n ): Promise<ChatSession[]> {\n return await sessions.chatSessionsByAnchor('board', boardId, context)\n }\n\n @Query(() => [PatchEntry], { description: 'List patch entries of a session, newest first.' })\n /*\n * ⚠ 이 셋은 **보드 편집기의 것**이라 `board-ai` 에 남는다 (2026-09-10).\n *\n * 나머지 스물둘을 `ai-assistant` 로 옮길 때 이것들도 함께 갔다가, 도는 스키마를 확인하면서\n * 되돌렸다 — `revertPatch` 가 `ai-assistant:mutation` 으로 떠 있었다. 즉 **대화 권한만 가진\n * 사람이 보드 패치를 되돌릴 수 있었다.** 좁히려던 변경이 넓히는 변경이 된 자리다.\n *\n * 가르는 물음: **이것이 보드를 바꾸나.** 패치는 바꾼다. 세션 조회는 안 바꾼다.\n */\n @Directive('@privilege(category: \"board-ai\", privilege: \"query\")')\n async chatPatches(\n @Arg('sessionId') sessionId: string,\n @Arg('limit', () => Int, { nullable: true, defaultValue: 100 }) limit: number,\n @Ctx() context: ResolverContext\n ): Promise<PatchEntry[]> {\n const { domain } = context.state\n const session = await getRepository(ChatSession).findOneBy({\n id: sessionId,\n domain: { id: domain.id }\n })\n if (!session) return []\n return await getRepository(PatchEntry).find({\n where: { session: { id: sessionId } as any },\n order: { createdAt: 'DESC' },\n take: limit\n })\n }\n\n /** 보드 전용 진입점 — 앵커 시작의 얇은 래퍼(기존 소비자 무변경). */\n @Mutation(() => ChatSession, {\n description: 'Start (or get existing) AI chat session for a board. Thin wrapper over startAISession(board).'\n })\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\", legacy: \"board-ai\")')\n async startBoardAISession(\n @Arg('boardId') boardId: string,\n @Ctx() context: ResolverContext\n ): Promise<ChatSession> {\n return await sessions.startAISession('board', boardId, context)\n }\n\n @Directive('@transaction')\n @Mutation(() => PatchEntry, {\n description: 'Record a patch from user direct edit. Adds a system message so AI sees the change next turn.'\n })\n @Directive('@privilege(category: \"board-ai\", privilege: \"mutation\")')\n async recordDirectPatch(\n @Arg('sessionId') sessionId: string,\n @Arg('ops', () => GraphQLJSON) ops: any[],\n @Arg('summary', { nullable: true }) summary: string | undefined,\n @Ctx() context: ResolverContext\n ): Promise<PatchEntry> {\n const { domain, tx, user } = context.state\n const session = await getRepository(ChatSession, tx).findOneBy({\n id: sessionId,\n domain: { id: domain.id }\n })\n if (!session) throw new Error(`ChatSession ${sessionId} not found`)\n\n const entry = await getRepository(PatchEntry, tx).save({\n session: { id: session.id } as any,\n source: 'user-direct',\n ops: JSON.stringify(ops || []),\n summary,\n reverted: false\n } as any)\n\n // AI 컨텍스트 흐름 — system 메시지 자동 추가. creator = 직접 편집한 사용자.\n // parentMessage = 직전 in-session 메시지 (timeline 연속성 유지 — system 도 chain 에 합류).\n const previousLast = await getRepository(ChatMessage, tx).findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const sysMsg = await getRepository(ChatMessage, tx).save({\n session: { id: session.id } as any,\n role: 'system',\n content: `User directly edited the board: ${summary ?? `${(ops || []).length} op(s)`}`,\n relatedPatchId: entry.id,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n } as any)\n\n /* 세션 활동 갱신 — 직접 편집도 다른 참여자에게는 새 소식이다(목록 정렬·안 읽음 판정). */\n try {\n await getRepository(ChatSession, tx).update(session.id!, activityPatch(sysMsg.content) as any)\n } catch {\n /* best-effort — 편집 기록을 막지 않는다 */\n }\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: sysMsg.content,\n actorId: user?.id\n })\n\n // C1: user-direct 편집도 같은 board 구독자에게 방송 (AI·타참여자 실시간 반영). best-effort.\n publishBoardPatch({ domainId: domain.id, boardId: session.boardId, patch: entry })\n // C4: 직접편집 system 메시지를 같은 세션 구독자에게 방송 (transcript 실시간 일관성). best-effort.\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: sysMsg })\n\n return entry\n }\n\n @Directive('@transaction')\n @Mutation(() => Boolean, { description: 'Mark a patch as reverted (does not undo, only flags).' })\n @Directive('@privilege(category: \"board-ai\", privilege: \"mutation\")')\n async revertPatch(\n @Arg('patchId') patchId: string,\n @Ctx() context: ResolverContext\n ): Promise<boolean> {\n const { tx } = context.state\n await getRepository(PatchEntry, tx).update(patchId, { reverted: true })\n return true\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 이 요청에 코어 보드 편집 도구를 켤 것인가.
3
+ *
4
+ * @param requested 대화면이 보낸 값. `false` 는 「끄고 싶다」는 뜻이고 그대로 존중한다.
5
+ * `undefined` 는 「기본대로」이고, 기본은 켜짐이었다 — 이제 권한이 그것을 정한다.
6
+ */
7
+ export declare function effectiveBoardTools(requested: boolean | undefined, context: any): Promise<boolean | undefined>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.effectiveBoardTools = effectiveBoardTools;
4
+ /*
5
+ * 이 대화가 보드를 고칠 수 있나 — **문을 지난 것과 별개로** 묻는다.
6
+ *
7
+ * ── 왜 나뉘어야 하나 (2026-09-10) ───────────────────────────────────────────
8
+ * `boardAIChat` 의 문이 `board-ai:mutation` 이었다. 그래서 공장 현황을 묻는 화면이 보드 편집
9
+ * 권한을 요구했고, 그것이 이 분리의 시작이었다.
10
+ *
11
+ * 문만 `ai-assistant:mutation` 으로 바꾸면 **거꾸로 넓어진다.** 코어 보드 편집 도구는 기본으로
12
+ * 켜져 있으므로, 대화 권한만 가진 사람이 같은 문으로 보드를 고치게 된다. 권한을 좁히려던 변경이
13
+ * 권한을 넓히는 변경이 되는 것이다.
14
+ *
15
+ * 그래서 둘로 본다.
16
+ *
17
+ * 묻는다 ai-assistant:mutation 문이 본다
18
+ * 보드를 고친다 board-ai:mutation 이 파일이 본다
19
+ *
20
+ * ── 왜 게이트가 아니라 능력으로 두나 ────────────────────────────────────────
21
+ * 권한이 없다고 요청을 거절하면, 보드 편집 권한이 없는 사람은 **보드가 열린 화면에서 아무것도
22
+ * 물을 수 없게** 된다. 그건 과하다 — 읽고 묻는 것은 편집이 아니다. 그래서 거절하지 않고 **편집
23
+ * 도구만 끈다.** 모델은 그 도구를 아예 못 보고, 고쳐 달라는 말에는 할 수 없다고 답한다.
24
+ *
25
+ * 도구를 숨기는 것만으로 충분하지 않다는 것은 이미 판정돼 있다 — 정의를 숨겨도 이름을 짐작하거나
26
+ * 옛 대화가 재생되면 호출된다. 그래서 이 값은 `buildToolGate` 로 내려가 **정의 조립과 dispatch
27
+ * 판정 양쪽**에 쓰인다(`assistant.ts`).
28
+ */
29
+ const auth_base_1 = require("@things-factory/auth-base");
30
+ const env_1 = require("@things-factory/env");
31
+ /**
32
+ * 이 요청에 코어 보드 편집 도구를 켤 것인가.
33
+ *
34
+ * @param requested 대화면이 보낸 값. `false` 는 「끄고 싶다」는 뜻이고 그대로 존중한다.
35
+ * `undefined` 는 「기본대로」이고, 기본은 켜짐이었다 — 이제 권한이 그것을 정한다.
36
+ */
37
+ async function effectiveBoardTools(requested, context) {
38
+ /* 끄겠다고 한 것을 권한으로 되살리지 않는다. plant 처럼 애초에 보드가 없는 대화면이 이 값을 준다. */
39
+ if (requested === false)
40
+ return false;
41
+ const { domain, user, unsafeIP, prohibitedPrivileges } = context?.state ?? {};
42
+ /*
43
+ * 소유자·슈퍼유저 우회를 주지 않는다 — 옮기기 전 이 리졸버의 지시자에도 없었다. 여기서 새로
44
+ * 주면 권한을 좁히는 변경이 우회를 하나 늘리는 변경이 된다.
45
+ */
46
+ const mayEdit = await (0, auth_base_1.checkPermission)({ category: 'board-ai', privilege: 'mutation' }, user, domain, unsafeIP, prohibitedPrivileges);
47
+ if (mayEdit)
48
+ return requested;
49
+ /*
50
+ * **달라고 했는데 못 준 경우에만 남긴다.**
51
+ *
52
+ * 안 물어본 좁힘(=`undefined`)까지 매번 적으면, 보드가 없는 대화면이 질문을 던질 때마다 한 줄씩
53
+ * 쌓여 로그가 소음이 된다. 소음이 되면 사람이 로그를 안 본다. 반대로 **명시로 켜 달라고 한 것을
54
+ * 조용히 끄면** 대화면 쪽에서 원인을 찾을 길이 없다 — 그 경우만 적는다.
55
+ */
56
+ if (requested === true) {
57
+ env_1.logger.warn(`[board-ai] board edit tools were requested but withheld — ` +
58
+ `${user?.email || user?.id} has no board-ai:mutation in ${domain?.subdomain || domain?.id}`);
59
+ }
60
+ return false;
61
+ }
62
+ //# sourceMappingURL=board-tool-rights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-tool-rights.js","sourceRoot":"","sources":["../../server/service/board-tool-rights.ts"],"names":[],"mappings":";;AAkCA,kDAsCC;AAxED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,yDAA2D;AAC3D,6CAA4C;AAE5C;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CACvC,SAA8B,EAC9B,OAAY;IAEZ,gEAAgE;IAChE,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,KAAK,CAAA;IAErC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,GAAG,OAAO,EAAE,KAAK,IAAI,EAAE,CAAA;IAE7E;;;OAGG;IACH,MAAM,OAAO,GAAG,MAAM,IAAA,2BAAe,EACnC,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,EAC/C,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,oBAAoB,CACrB,CAAA;IAED,IAAI,OAAO;QAAE,OAAO,SAAS,CAAA;IAE7B;;;;;;OAMG;IACH,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,YAAM,CAAC,IAAI,CACT,4DAA4D;YAC1D,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,EAAE,gCAAgC,MAAM,EAAE,SAAS,IAAI,MAAM,EAAE,EAAE,EAAE,CAC9F,CAAA;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["/*\n * 이 대화가 보드를 고칠 수 있나 — **문을 지난 것과 별개로** 묻는다.\n *\n * ── 왜 나뉘어야 하나 (2026-09-10) ───────────────────────────────────────────\n * `boardAIChat` 의 문이 `board-ai:mutation` 이었다. 그래서 공장 현황을 묻는 화면이 보드 편집\n * 권한을 요구했고, 그것이 이 분리의 시작이었다.\n *\n * 문만 `ai-assistant:mutation` 으로 바꾸면 **거꾸로 넓어진다.** 코어 보드 편집 도구는 기본으로\n * 켜져 있으므로, 대화 권한만 가진 사람이 같은 문으로 보드를 고치게 된다. 권한을 좁히려던 변경이\n * 권한을 넓히는 변경이 되는 것이다.\n *\n * 그래서 둘로 본다.\n *\n * 묻는다 ai-assistant:mutation 문이 본다\n * 보드를 고친다 board-ai:mutation 이 파일이 본다\n *\n * ── 왜 게이트가 아니라 능력으로 두나 ────────────────────────────────────────\n * 권한이 없다고 요청을 거절하면, 보드 편집 권한이 없는 사람은 **보드가 열린 화면에서 아무것도\n * 물을 수 없게** 된다. 그건 과하다 — 읽고 묻는 것은 편집이 아니다. 그래서 거절하지 않고 **편집\n * 도구만 끈다.** 모델은 그 도구를 아예 못 보고, 고쳐 달라는 말에는 할 수 없다고 답한다.\n *\n * 도구를 숨기는 것만으로 충분하지 않다는 것은 이미 판정돼 있다 — 정의를 숨겨도 이름을 짐작하거나\n * 옛 대화가 재생되면 호출된다. 그래서 이 값은 `buildToolGate` 로 내려가 **정의 조립과 dispatch\n * 판정 양쪽**에 쓰인다(`assistant.ts`).\n */\nimport { checkPermission } from '@things-factory/auth-base'\nimport { logger } from '@things-factory/env'\n\n/**\n * 이 요청에 코어 보드 편집 도구를 켤 것인가.\n *\n * @param requested 대화면이 보낸 값. `false` 는 「끄고 싶다」는 뜻이고 그대로 존중한다.\n * `undefined` 는 「기본대로」이고, 기본은 켜짐이었다 — 이제 권한이 그것을 정한다.\n */\nexport async function effectiveBoardTools(\n requested: boolean | undefined,\n context: any\n): Promise<boolean | undefined> {\n /* 끄겠다고 한 것을 권한으로 되살리지 않는다. plant 처럼 애초에 보드가 없는 대화면이 이 값을 준다. */\n if (requested === false) return false\n\n const { domain, user, unsafeIP, prohibitedPrivileges } = context?.state ?? {}\n\n /*\n * 소유자·슈퍼유저 우회를 주지 않는다 — 옮기기 전 이 리졸버의 지시자에도 없었다. 여기서 새로\n * 주면 권한을 좁히는 변경이 우회를 하나 늘리는 변경이 된다.\n */\n const mayEdit = await checkPermission(\n { category: 'board-ai', privilege: 'mutation' },\n user,\n domain,\n unsafeIP,\n prohibitedPrivileges\n )\n\n if (mayEdit) return requested\n\n /*\n * **달라고 했는데 못 준 경우에만 남긴다.**\n *\n * 안 물어본 좁힘(=`undefined`)까지 매번 적으면, 보드가 없는 대화면이 질문을 던질 때마다 한 줄씩\n * 쌓여 로그가 소음이 된다. 소음이 되면 사람이 로그를 안 본다. 반대로 **명시로 켜 달라고 한 것을\n * 조용히 끄면** 대화면 쪽에서 원인을 찾을 길이 없다 — 그 경우만 적는다.\n */\n if (requested === true) {\n logger.warn(\n `[board-ai] board edit tools were requested but withheld — ` +\n `${user?.email || user?.id} has no board-ai:mutation in ${domain?.subdomain || domain?.id}`\n )\n }\n\n return false\n}\n"]}
@@ -1,15 +1,5 @@
1
- export interface GroundingSources {
2
- /** 시스템 프롬프트(타입 목록·규칙). */
3
- systemPrompt?: string;
4
- /** 보드 상태 요약 등 대화 앞에 붙인 문맥. */
5
- boardContext?: string;
6
- /** 사용자·어시스턴트 이력 텍스트. */
7
- messages?: string[];
8
- /** tool_result 로 회신된 원문(시간순). */
9
- toolResults?: string[];
10
- }
11
- /** 근거집합을 한 덩어리 문자열로. (id 토큰 대조만 하므로 구분자는 개행으로 충분하다.) */
12
- export declare function buildGroundedCorpus(sources: GroundingSources): string;
1
+ import { buildGroundedCorpus, type GroundingSources } from '@things-factory/ai-client-base';
2
+ export { buildGroundedCorpus, type GroundingSources };
13
3
  /**
14
4
  * 답이 근거 밖의 식별자를 지목했으면 그 목록. 비면 접지 정상.
15
5
  * 답을 고치거나 막지 않는다 — 사용자에게 표시할 경고만 낸다.
@@ -1,39 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildGroundedCorpus = buildGroundedCorpus;
3
+ exports.buildGroundedCorpus = void 0;
4
4
  exports.checkReplyGrounding = checkReplyGrounding;
5
5
  /*
6
6
  * 접지 검사 — 이 대화면의 답이 **모델이 실제로 받은 것** 안에서 말했는지.
7
7
  *
8
- * ── 왜 board-ai 에 필요한가 ─────────────────────────────────────────────────
9
- * 협의 도크(operato-twin)는 board-ai 위에 서 있고, 여기서 사람들이 운영 상태를 묻는다.
10
- * 헤더 경로(twin-ai)에는 접지 가드가 있었지만 도크 경로에는 없었다 — 같은 질문에 대해
11
- * 한쪽은 검증되고 한쪽은 검증되지 않는 상태였다. 판정 규칙은 공용 모듈이 소유하고
12
- * (`@things-factory/ai-client-base` 의 grounding), 이 파일은 **무엇을 근거로 볼 것인가**만 정한다.
8
+ * ── 이 파일에 무엇이 남았나 (2026-09-10) ────────────────────────────────────
9
+ * 판정 규칙은 처음부터 `@things-factory/ai-client-base` 에 있었고, **근거집합의 모양**은 여기
10
+ * 있었다. 그래서 보드가 없는 제품은 규칙만 있고 쓸 길이 없었다 — 접지 검사를 하려면 보드
11
+ * 편집기를 지나야 했다. 자리는 있고 길이 없는 쪽이다.
13
12
  *
14
- * ── 근거집합의 정의 ─────────────────────────────────────────────────────────
15
- * 모델이 이 턴에 받은 전부다. 하나라도 빼면 정상 답이 경고를 받는다(오탐):
16
- * · systemPrompt — 생성 가능한 컴포넌트 타입 목록 등. 여기 있는 이름을 말하는 건 정상이다.
17
- * · boardContext — 현재 보드 요약(컴포넌트 id·이름). 보드를 두고 말하는 근거.
18
- * · messages — 사용자·이력 텍스트. 사용자가 말한 식별자를 되받는 건 정상이다.
19
- * · toolResults — 도구가 돌려준 원문(압축 전). 운영 상태 답의 유일한 사실 근거.
13
+ * 그 모양(`GroundingSources` · `buildGroundedCorpus`)을 공용 패키지로 옮겼다. 칸 하나가
14
+ * 바뀌었다: `boardContext` → `hostContext`. 근거는 보드일 수도, 공장 현황일 수도, 저작 중인
15
+ * 자산일 수도 있고, 그것을 아는 것은 호스트뿐이다.
16
+ *
17
+ * **베끼지 않고 옮겼다** — 같은 글이 두 곳에 있으면 한쪽만 고쳐진다. 여기 남은 것은 이 패키지의
18
+ * 소비처(9개 앱이 이 패키지를 지난다)가 이름을 그대로 쓰게 하는 재수출 하나다.
20
19
  *
21
20
  * ── 사정거리 ────────────────────────────────────────────────────────────────
22
21
  * 식별자만 본다. "지게차들이 바쁘게 움직인다" 처럼 **식별자 없이 지어낸 서술**은 잡지 못한다 —
23
22
  * 그건 도구를 부르지 않고 답한 경우이고, 도구 호출을 강제하는 별도 레버의 몫이다.
23
+ *
24
+ * 그리고 식별자 관례가 **소문자**다(`ID_TOKEN`). 대문자 식별자를 쓰는 제품(plant 의 `WO-…`,
25
+ * 트윈의 `TAG-FUNC-…`)에는 이 가드가 닿지 않는다 — 공용 패키지 쪽 판단이 필요한 별 건이다.
24
26
  */
25
27
  const ai_client_base_1 = require("@things-factory/ai-client-base");
26
- /** 근거집합을 한 덩어리 문자열로. (id 토큰 대조만 하므로 구분자는 개행으로 충분하다.) */
27
- function buildGroundedCorpus(sources) {
28
- return [
29
- sources.systemPrompt ?? '',
30
- sources.boardContext ?? '',
31
- ...(sources.messages ?? []),
32
- ...(sources.toolResults ?? [])
33
- ]
34
- .filter(Boolean)
35
- .join('\n');
36
- }
28
+ Object.defineProperty(exports, "buildGroundedCorpus", { enumerable: true, get: function () { return ai_client_base_1.buildGroundedCorpus; } });
37
29
  /**
38
30
  * 답이 근거 밖의 식별자를 지목했으면 그 목록. 비면 접지 정상.
39
31
  * 답을 고치거나 막지 않는다 — 사용자에게 표시할 경고만 낸다.
@@ -41,6 +33,6 @@ function buildGroundedCorpus(sources) {
41
33
  function checkReplyGrounding(reply, sources) {
42
34
  if (!reply)
43
35
  return [];
44
- return (0, ai_client_base_1.groundingWarnings)(reply, buildGroundedCorpus(sources));
36
+ return (0, ai_client_base_1.groundingWarnings)(reply, (0, ai_client_base_1.buildGroundedCorpus)(sources));
45
37
  }
46
38
  //# sourceMappingURL=grounding.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"grounding.js","sourceRoot":"","sources":["../../server/service/grounding.ts"],"names":[],"mappings":";;AAkCA,kDASC;AAMD,kDAGC;AApDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,mEAAkE;AAalE,wDAAwD;AACxD,SAAgB,mBAAmB,CAAC,OAAyB;IAC3D,OAAO;QACL,OAAO,CAAC,YAAY,IAAI,EAAE;QAC1B,OAAO,CAAC,YAAY,IAAI,EAAE;QAC1B,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;QAC3B,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;KAC/B;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,KAAa,EAAE,OAAyB;IAC1E,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,OAAO,IAAA,kCAAiB,EAAC,KAAK,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAA;AAC/D,CAAC","sourcesContent":["/*\n * 접지 검사 — 이 대화면의 답이 **모델이 실제로 받은 것** 안에서 말했는지.\n *\n * ── 왜 board-ai 에 필요한가 ─────────────────────────────────────────────────\n * 협의 도크(operato-twin)는 board-ai 위에 서 있고, 여기서 사람들이 운영 상태를 묻는다.\n * 헤더 경로(twin-ai)에는 접지 가드가 있었지만 도크 경로에는 없었다 — 같은 질문에 대해\n * 한쪽은 검증되고 한쪽은 검증되지 않는 상태였다. 판정 규칙은 공용 모듈이 소유하고\n * (`@things-factory/ai-client-base` 의 grounding), 이 파일은 **무엇을 근거로 볼 것인가**만 정한다.\n *\n * ── 근거집합의 정의 ─────────────────────────────────────────────────────────\n * 모델이 이 턴에 받은 전부다. 하나라도 빼면 정상 답이 경고를 받는다(오탐):\n * · systemPrompt — 생성 가능한 컴포넌트 타입 목록 등. 여기 있는 이름을 말하는 건 정상이다.\n * · boardContext — 현재 보드 요약(컴포넌트 id·이름). 보드를 두고 말하는 근거.\n * · messages — 사용자·이력 텍스트. 사용자가 말한 식별자를 되받는 건 정상이다.\n * · toolResults — 도구가 돌려준 원문(압축 전). 운영 상태 답의 유일한 사실 근거.\n *\n * ── 사정거리 ────────────────────────────────────────────────────────────────\n * 식별자만 본다. \"지게차들이 바쁘게 움직인다\" 처럼 **식별자 없이 지어낸 서술**은 잡지 못한다 —\n * 그건 도구를 부르지 않고 답한 경우이고, 도구 호출을 강제하는 별도 레버의 몫이다.\n */\nimport { groundingWarnings } from '@things-factory/ai-client-base'\n\nexport interface GroundingSources {\n /** 시스템 프롬프트(타입 목록·규칙). */\n systemPrompt?: string\n /** 보드 상태 요약 등 대화 앞에 붙인 문맥. */\n boardContext?: string\n /** 사용자·어시스턴트 이력 텍스트. */\n messages?: string[]\n /** tool_result 로 회신된 원문(시간순). */\n toolResults?: string[]\n}\n\n/** 근거집합을 한 덩어리 문자열로. (id 토큰 대조만 하므로 구분자는 개행으로 충분하다.) */\nexport function buildGroundedCorpus(sources: GroundingSources): string {\n return [\n sources.systemPrompt ?? '',\n sources.boardContext ?? '',\n ...(sources.messages ?? []),\n ...(sources.toolResults ?? [])\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\n/**\n * 답이 근거 밖의 식별자를 지목했으면 그 목록. 비면 접지 정상.\n * 답을 고치거나 막지 않는다 — 사용자에게 표시할 경고만 낸다.\n */\nexport function checkReplyGrounding(reply: string, sources: GroundingSources): string[] {\n if (!reply) return []\n return groundingWarnings(reply, buildGroundedCorpus(sources))\n}\n"]}
1
+ {"version":3,"file":"grounding.js","sourceRoot":"","sources":["../../server/service/grounding.ts"],"names":[],"mappings":";;;AA8BA,kDAGC;AAjCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,mEAA8G;AAErG,oGAFA,oCAAmB,OAEA;AAE5B;;;GAGG;AACH,SAAgB,mBAAmB,CAAC,KAAa,EAAE,OAAyB;IAC1E,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,OAAO,IAAA,kCAAiB,EAAC,KAAK,EAAE,IAAA,oCAAmB,EAAC,OAAO,CAAC,CAAC,CAAA;AAC/D,CAAC","sourcesContent":["/*\n * 접지 검사 — 이 대화면의 답이 **모델이 실제로 받은 것** 안에서 말했는지.\n *\n * ── 이 파일에 무엇이 남았나 (2026-09-10) ────────────────────────────────────\n * 판정 규칙은 처음부터 `@things-factory/ai-client-base` 에 있었고, **근거집합의 모양**은 여기\n * 있었다. 그래서 보드가 없는 제품은 규칙만 있고 쓸 길이 없었다 — 접지 검사를 하려면 보드\n * 편집기를 지나야 했다. 자리는 있고 길이 없는 쪽이다.\n *\n * 그 모양(`GroundingSources` · `buildGroundedCorpus`)을 공용 패키지로 옮겼다. 칸 하나가\n * 바뀌었다: `boardContext` → `hostContext`. 근거는 보드일 수도, 공장 현황일 수도, 저작 중인\n * 자산일 수도 있고, 그것을 아는 것은 호스트뿐이다.\n *\n * **베끼지 않고 옮겼다** — 같은 글이 두 곳에 있으면 한쪽만 고쳐진다. 여기 남은 것은 이 패키지의\n * 소비처(9개 앱이 이 패키지를 지난다)가 이름을 그대로 쓰게 하는 재수출 하나다.\n *\n * ── 사정거리 ────────────────────────────────────────────────────────────────\n * 식별자만 본다. \"지게차들이 바쁘게 움직인다\" 처럼 **식별자 없이 지어낸 서술**은 잡지 못한다 —\n * 그건 도구를 부르지 않고 답한 경우이고, 도구 호출을 강제하는 별도 레버의 몫이다.\n *\n * 그리고 식별자 관례가 **소문자**다(`ID_TOKEN`). 대문자 식별자를 쓰는 제품(plant 의 `WO-…`,\n * 트윈의 `TAG-FUNC-…`)에는 이 가드가 닿지 않는다 — 공용 패키지 쪽 판단이 필요한 별 건이다.\n */\nimport { buildGroundedCorpus, groundingWarnings, type GroundingSources } from '@things-factory/ai-client-base'\n\nexport { buildGroundedCorpus, type GroundingSources }\n\n/**\n * 답이 근거 밖의 식별자를 지목했으면 그 목록. 비면 접지 정상.\n * 답을 고치거나 막지 않는다 — 사용자에게 표시할 경고만 낸다.\n */\nexport function checkReplyGrounding(reply: string, sources: GroundingSources): string[] {\n if (!reply) return []\n return groundingWarnings(reply, buildGroundedCorpus(sources))\n}\n"]}
@@ -1,8 +1,7 @@
1
+ export * from '@things-factory/ai-assistant';
1
2
  import { BoardAIChatResolver } from './board-ai-resolver.js';
2
- import { ChatSessionResolver } from './chat-session-resolver.js';
3
- import { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js';
3
+ import { BoardSessionResolver } from './board-session-resolver.js';
4
4
  import { BoardPatchSubscription } from './patch-entry/board-patch-subscription.js';
5
- import { BoardChatMessageSubscription } from './chat-message/chat-message-subscription.js';
6
5
  export * from './types.js';
7
6
  export * from './assistant.js';
8
7
  export * from './apply-patch.js';
@@ -10,13 +9,8 @@ export * from './prefab-registry.js';
10
9
  export * from './mention-resolver.js';
11
10
  export * from './catalog-resolver.js';
12
11
  export * from './board-ai-resolver.js';
13
- export * from './chat-session-resolver.js';
14
- export * from './chat-session/session-anchor.js';
15
- export * from './chat-session/index.js';
16
- export * from './chat-message/index.js';
17
- export * from './chat-session-participant/index.js';
18
12
  export * from './patch-entry/index.js';
19
- export declare const entities: (typeof import("./chat-message/chat-message.js").ChatMessage | typeof import("./chat-session/chat-session.js").ChatSession | typeof import("./patch-entry/patch-entry.js").PatchEntry | typeof import("./chat-session-participant/chat-session-participant.js").ChatSessionParticipant)[];
13
+ export declare const entities: typeof import("./patch-entry/patch-entry.js").PatchEntry[];
20
14
  export declare const schema: {
21
- resolverClasses: (typeof BoardPatchSubscription | typeof BoardAIChatResolver | typeof ChatSessionResolver | typeof MySessionActivitySubscription | typeof BoardChatMessageSubscription)[];
15
+ resolverClasses: (typeof BoardPatchSubscription | typeof BoardAIChatResolver | typeof BoardSessionResolver)[];
22
16
  };
@@ -2,15 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.schema = exports.entities = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ /*
6
+ * 대화 기계는 **`@things-factory/ai-assistant` 로 나갔다** (2026-09-10).
7
+ *
8
+ * 세션·메시지·참가자·인박스·구독은 보드가 없는 제품에도 뜻이 있다. 여기 남은 것은 보드 저작이다.
9
+ * 아래 재수출은 이 패키지를 지나는 소비처(아홉 앱)가 이름을 그대로 쓰게 한다 — 베낀 것이 아니라
10
+ * 가리키는 것이다.
11
+ *
12
+ * 자르는 선과 이유는 `things-factory/docs/design/ai-modules.md`.
13
+ */
14
+ tslib_1.__exportStar(require("@things-factory/ai-assistant"), exports);
5
15
  const board_ai_resolver_js_1 = require("./board-ai-resolver.js");
6
- const chat_session_resolver_js_1 = require("./chat-session-resolver.js");
7
- const session_activity_subscription_js_1 = require("./chat-session/session-activity-subscription.js");
16
+ const board_session_resolver_js_1 = require("./board-session-resolver.js");
8
17
  const board_patch_subscription_js_1 = require("./patch-entry/board-patch-subscription.js");
9
- const chat_message_subscription_js_1 = require("./chat-message/chat-message-subscription.js");
10
- const index_js_1 = require("./chat-session/index.js");
11
- const index_js_2 = require("./chat-message/index.js");
12
- const index_js_3 = require("./chat-session-participant/index.js");
13
- const index_js_4 = require("./patch-entry/index.js");
18
+ const index_js_1 = require("./patch-entry/index.js");
14
19
  tslib_1.__exportStar(require("./types.js"), exports);
15
20
  tslib_1.__exportStar(require("./assistant.js"), exports);
16
21
  tslib_1.__exportStar(require("./apply-patch.js"), exports);
@@ -18,27 +23,15 @@ tslib_1.__exportStar(require("./prefab-registry.js"), exports);
18
23
  tslib_1.__exportStar(require("./mention-resolver.js"), exports);
19
24
  tslib_1.__exportStar(require("./catalog-resolver.js"), exports);
20
25
  tslib_1.__exportStar(require("./board-ai-resolver.js"), exports);
21
- tslib_1.__exportStar(require("./chat-session-resolver.js"), exports);
22
- /* 세션 앵커 순수 로직 — 호스트가 앵커 종류를 참조할 수 있게 공개(리졸버는 데코레이터 때문에 무거움). */
23
- tslib_1.__exportStar(require("./chat-session/session-anchor.js"), exports);
24
- tslib_1.__exportStar(require("./chat-session/index.js"), exports);
25
- tslib_1.__exportStar(require("./chat-message/index.js"), exports);
26
- tslib_1.__exportStar(require("./chat-session-participant/index.js"), exports);
27
26
  tslib_1.__exportStar(require("./patch-entry/index.js"), exports);
28
- exports.entities = [
29
- ...index_js_1.entities,
30
- ...index_js_2.entities,
31
- ...index_js_3.entities,
32
- ...index_js_4.entities
33
- ];
27
+ /* 세션 계열 엔티티는 `ai-assistant` 가 낸다 — 프레임워크가 의존을 타고 재귀로 모은다. */
28
+ exports.entities = [...index_js_1.entities];
34
29
  exports.schema = {
35
30
  resolverClasses: [
36
31
  board_ai_resolver_js_1.BoardAIChatResolver,
37
- chat_session_resolver_js_1.ChatSessionResolver,
32
+ /* 보드에 매인 op 여섯 — 중립 세션 op 과 갈랐다(2026-09-10). */
33
+ board_session_resolver_js_1.BoardSessionResolver,
38
34
  board_patch_subscription_js_1.BoardPatchSubscription,
39
- chat_message_subscription_js_1.BoardChatMessageSubscription,
40
- /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */
41
- session_activity_subscription_js_1.MySessionActivitySubscription
42
35
  ]
43
36
  };
44
37
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,iEAA4D;AAC5D,yEAAgE;AAChE,sGAA+F;AAC/F,2FAAkF;AAClF,8FAA0F;AAC1F,sDAAyE;AACzE,sDAAyE;AACzE,kEAAgG;AAChG,qDAAuE;AAEvE,qDAA0B;AAC1B,yDAA8B;AAC9B,2DAAgC;AAChC,+DAAoC;AACpC,gEAAqC;AACrC,gEAAqC;AACrC,iEAAsC;AACtC,qEAA0C;AAC1C,gEAAgE;AAChE,2EAAgD;AAChD,kEAAuC;AACvC,kEAAuC;AACvC,8EAAmD;AACnD,iEAAsC;AAEzB,QAAA,QAAQ,GAAG;IACtB,GAAG,mBAAmB;IACtB,GAAG,mBAAmB;IACtB,GAAG,mBAA8B;IACjC,GAAG,mBAAkB;CACtB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,0CAAmB;QACnB,8CAAmB;QACnB,oDAAsB;QACtB,2DAA4B;QAC5B,qDAAqD;QACrD,gEAA6B;KAC9B;CACF,CAAA","sourcesContent":["import { BoardAIChatResolver } from './board-ai-resolver.js'\nimport { ChatSessionResolver } from './chat-session-resolver.js'\nimport { MySessionActivitySubscription } from './chat-session/session-activity-subscription.js'\nimport { BoardPatchSubscription } from './patch-entry/board-patch-subscription.js'\nimport { BoardChatMessageSubscription } from './chat-message/chat-message-subscription.js'\nimport { entities as ChatSessionEntities } from './chat-session/index.js'\nimport { entities as ChatMessageEntities } from './chat-message/index.js'\nimport { entities as ChatSessionParticipantEntities } from './chat-session-participant/index.js'\nimport { entities as PatchEntryEntities } from './patch-entry/index.js'\n\nexport * from './types.js'\nexport * from './assistant.js'\nexport * from './apply-patch.js'\nexport * from './prefab-registry.js'\nexport * from './mention-resolver.js'\nexport * from './catalog-resolver.js'\nexport * from './board-ai-resolver.js'\nexport * from './chat-session-resolver.js'\n/* 세션 앵커 순수 로직 — 호스트가 앵커 종류를 참조할 수 있게 공개(리졸버는 데코레이터 때문에 무거움). */\nexport * from './chat-session/session-anchor.js'\nexport * from './chat-session/index.js'\nexport * from './chat-message/index.js'\nexport * from './chat-session-participant/index.js'\nexport * from './patch-entry/index.js'\n\nexport const entities = [\n ...ChatSessionEntities,\n ...ChatMessageEntities,\n ...ChatSessionParticipantEntities,\n ...PatchEntryEntities\n]\n\nexport const schema = {\n resolverClasses: [\n BoardAIChatResolver,\n ChatSessionResolver,\n BoardPatchSubscription,\n BoardChatMessageSubscription,\n /* 내가 참여한 모든 세션의 활동 — 창을 닫아 둔 표면이 배지·목록을 갱신할 수 있게. */\n MySessionActivitySubscription\n ]\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;GAQG;AACH,uEAA4C;AAE5C,iEAA4D;AAC5D,2EAAkE;AAClE,2FAAkF;AAClF,qDAAuE;AAEvE,qDAA0B;AAC1B,yDAA8B;AAC9B,2DAAgC;AAChC,+DAAoC;AACpC,gEAAqC;AACrC,gEAAqC;AACrC,iEAAsC;AACtC,iEAAsC;AAEtC,6DAA6D;AAChD,QAAA,QAAQ,GAAG,CAAC,GAAG,mBAAkB,CAAC,CAAA;AAElC,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,0CAAmB;QACnB,gDAAgD;QAChD,gDAAoB;QACpB,oDAAsB;KACvB;CACF,CAAA","sourcesContent":["/*\n * 대화 기계는 **`@things-factory/ai-assistant` 로 나갔다** (2026-09-10).\n *\n * 세션·메시지·참가자·인박스·구독은 보드가 없는 제품에도 뜻이 있다. 여기 남은 것은 보드 저작이다.\n * 아래 재수출은 이 패키지를 지나는 소비처(아홉 앱)가 이름을 그대로 쓰게 한다 — 베낀 것이 아니라\n * 가리키는 것이다.\n *\n * 자르는 선과 이유는 `things-factory/docs/design/ai-modules.md`.\n */\nexport * from '@things-factory/ai-assistant'\n\nimport { BoardAIChatResolver } from './board-ai-resolver.js'\nimport { BoardSessionResolver } from './board-session-resolver.js'\nimport { BoardPatchSubscription } from './patch-entry/board-patch-subscription.js'\nimport { entities as PatchEntryEntities } from './patch-entry/index.js'\n\nexport * from './types.js'\nexport * from './assistant.js'\nexport * from './apply-patch.js'\nexport * from './prefab-registry.js'\nexport * from './mention-resolver.js'\nexport * from './catalog-resolver.js'\nexport * from './board-ai-resolver.js'\nexport * from './patch-entry/index.js'\n\n/* 세션 계열 엔티티는 `ai-assistant` 가 낸다 — 프레임워크가 의존을 타고 재귀로 모은다. */\nexport const entities = [...PatchEntryEntities]\n\nexport const schema = {\n resolverClasses: [\n BoardAIChatResolver,\n /* 보드에 매인 op 여섯 — 중립 세션 op 과 갈랐다(2026-09-10). */\n BoardSessionResolver,\n BoardPatchSubscription,\n ]\n}\n"]}
@@ -17,7 +17,7 @@ const graphql_yoga_1 = require("graphql-yoga");
17
17
  const shell_1 = require("@things-factory/shell");
18
18
  const patch_entry_js_1 = require("./patch-entry.js");
19
19
  const board_patch_broadcast_js_1 = require("./board-patch-broadcast.js");
20
- const subscribe_auth_js_1 = require("../subscribe-auth.js");
20
+ const ai_assistant_1 = require("@things-factory/ai-assistant");
21
21
  var board_patch_broadcast_js_2 = require("./board-patch-broadcast.js");
22
22
  Object.defineProperty(exports, "BOARD_PATCH_TOPIC", { enumerable: true, get: function () { return board_patch_broadcast_js_2.BOARD_PATCH_TOPIC; } });
23
23
  Object.defineProperty(exports, "boardPatchFilter", { enumerable: true, get: function () { return board_patch_broadcast_js_2.boardPatchFilter; } });
@@ -47,7 +47,7 @@ tslib_1.__decorate([
47
47
  subscribe: async ({ args, context }) => {
48
48
  const { domain, user } = context.state;
49
49
  const { boardId } = args;
50
- await (0, subscribe_auth_js_1.assertDomainSubscribeAllowed)(domain, user, (d, u) => process.superUserGranted(d, u));
50
+ await (0, ai_assistant_1.assertDomainSubscribeAllowed)(domain, user, (d, u) => process.superUserGranted(d, u));
51
51
  return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe(board_patch_broadcast_js_1.BOARD_PATCH_TOPIC), (0, graphql_yoga_1.filter)((0, board_patch_broadcast_js_1.boardPatchFilter)(domain.id, boardId)));
52
52
  }
53
53
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"board-patch-subscription.js","sourceRoot":"","sources":["../../../server/service/patch-entry/board-patch-subscription.ts"],"names":[],"mappings":";;;AAuBA,8CAOC;;AA9BD;;;;;;;;GAQG;AACH,+CAAgE;AAChE,+CAA2C;AAC3C,iDAA8C;AAC9C,qDAA6C;AAC7C,yEAAwG;AACxG,4DAAmE;AAEnE,uEAAgF;AAAvE,6HAAA,iBAAiB,OAAA;AAAE,4HAAA,gBAAgB,OAAA;AAG5C;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,OAA0B;IAC1D,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAM;IAC5B,IAAI,CAAC;QACH,cAAM,CAAC,OAAO,CAAC,4CAAiB,EAAE,OAAO,CAAC,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,uBAAuB;IACzB,CAAC;AACH,CAAC;AAGM,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAajC,kBAAkB,CAAS,OAA0B,EAAkB,OAAe;QACpF,OAAO,OAAO,CAAC,KAAK,CAAA;IACtB,CAAC;CACF,CAAA;AAhBY,wDAAsB;AAajC;IAZC,IAAA,2BAAY,EAAC;QACZ,WAAW,EACT,8FAA8F;QAChG,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;YAExB,MAAM,IAAA,gDAA4B,EAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAE1F,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,4CAAiB,CAAC,EAAE,IAAA,qBAAM,EAAC,IAAA,2CAAgB,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;QAChG,CAAC;KACF,CAAC;IACkB,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA8B,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;;;4CAAmB,2BAAU;gEAElG;iCAfU,sBAAsB;IADlC,IAAA,uBAAQ,EAAC,2BAAU,CAAC;GACR,sBAAsB,CAgBlC","sourcesContent":["/*\n * BoardPatchSubscription (Phase Board-AI C1 — op-브로드캐스트 spine).\n *\n * PatchEntry(AI 또는 user-direct 편집) 가 생성될 때마다 같은 board 구독자에게 방송 →\n * 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyBoardEditPatch 로 적용.\n *\n * 전송/패턴은 기존 board-service/board-subscription.ts 복제 (PubSub + graphql-ws, domain 격리).\n * 순수 로직(토픽/필터)은 board-patch-broadcast.ts (테스트 가능). 여기엔 pubsub 의존만.\n */\nimport { Resolver, Subscription, Root, Arg } from 'type-graphql'\nimport { filter, pipe } from 'graphql-yoga'\nimport { pubsub } from '@things-factory/shell'\nimport { PatchEntry } from './patch-entry.js'\nimport { BOARD_PATCH_TOPIC, boardPatchFilter, type BoardPatchPayload } from './board-patch-broadcast.js'\nimport { assertDomainSubscribeAllowed } from '../subscribe-auth.js'\n\nexport { BOARD_PATCH_TOPIC, boardPatchFilter } from './board-patch-broadcast.js'\nexport type { BoardPatchPayload } from './board-patch-broadcast.js'\n\n/**\n * PatchEntry 생성 직후 호출 — 같은 board 구독자에게 방송.\n * boardId 없으면(세션 미연결) skip. pubsub 미가용/실패해도 본 흐름(영속 완료)을 막지 않는다.\n */\nexport function publishBoardPatch(payload: BoardPatchPayload): void {\n if (!payload.boardId) return\n try {\n pubsub.publish(BOARD_PATCH_TOPIC, payload)\n } catch {\n /* 방송 생략 — 영속은 이미 끝남 */\n }\n}\n\n@Resolver(PatchEntry)\nexport class BoardPatchSubscription {\n @Subscription({\n description:\n 'Subscribes to board edit patches (AI or user-direct) for real-time collaboration on a board.',\n subscribe: async ({ args, context }) => {\n const { domain, user } = context.state\n const { boardId } = args\n\n await assertDomainSubscribeAllowed(domain, user, (d, u) => process.superUserGranted(d, u))\n\n return pipe(pubsub.subscribe(BOARD_PATCH_TOPIC), filter(boardPatchFilter(domain.id, boardId)))\n }\n })\n boardPatchActivity(@Root() payload: BoardPatchPayload, @Arg('boardId') boardId: string): PatchEntry {\n return payload.patch\n }\n}\n"]}
1
+ {"version":3,"file":"board-patch-subscription.js","sourceRoot":"","sources":["../../../server/service/patch-entry/board-patch-subscription.ts"],"names":[],"mappings":";;;AAuBA,8CAOC;;AA9BD;;;;;;;;GAQG;AACH,+CAAgE;AAChE,+CAA2C;AAC3C,iDAA8C;AAC9C,qDAA6C;AAC7C,yEAAwG;AACxG,+DAA2E;AAE3E,uEAAgF;AAAvE,6HAAA,iBAAiB,OAAA;AAAE,4HAAA,gBAAgB,OAAA;AAG5C;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,OAA0B;IAC1D,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAM;IAC5B,IAAI,CAAC;QACH,cAAM,CAAC,OAAO,CAAC,4CAAiB,EAAE,OAAO,CAAC,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,uBAAuB;IACzB,CAAC;AACH,CAAC;AAGM,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAajC,kBAAkB,CAAS,OAA0B,EAAkB,OAAe;QACpF,OAAO,OAAO,CAAC,KAAK,CAAA;IACtB,CAAC;CACF,CAAA;AAhBY,wDAAsB;AAajC;IAZC,IAAA,2BAAY,EAAC;QACZ,WAAW,EACT,8FAA8F;QAChG,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;YAExB,MAAM,IAAA,2CAA4B,EAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAE1F,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,4CAAiB,CAAC,EAAE,IAAA,qBAAM,EAAC,IAAA,2CAAgB,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;QAChG,CAAC;KACF,CAAC;IACkB,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA8B,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;;;4CAAmB,2BAAU;gEAElG;iCAfU,sBAAsB;IADlC,IAAA,uBAAQ,EAAC,2BAAU,CAAC;GACR,sBAAsB,CAgBlC","sourcesContent":["/*\n * BoardPatchSubscription (Phase Board-AI C1 — op-브로드캐스트 spine).\n *\n * PatchEntry(AI 또는 user-direct 편집) 가 생성될 때마다 같은 board 구독자에게 방송 →\n * 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyBoardEditPatch 로 적용.\n *\n * 전송/패턴은 기존 board-service/board-subscription.ts 복제 (PubSub + graphql-ws, domain 격리).\n * 순수 로직(토픽/필터)은 board-patch-broadcast.ts (테스트 가능). 여기엔 pubsub 의존만.\n */\nimport { Resolver, Subscription, Root, Arg } from 'type-graphql'\nimport { filter, pipe } from 'graphql-yoga'\nimport { pubsub } from '@things-factory/shell'\nimport { PatchEntry } from './patch-entry.js'\nimport { BOARD_PATCH_TOPIC, boardPatchFilter, type BoardPatchPayload } from './board-patch-broadcast.js'\nimport { assertDomainSubscribeAllowed } from '@things-factory/ai-assistant'\n\nexport { BOARD_PATCH_TOPIC, boardPatchFilter } from './board-patch-broadcast.js'\nexport type { BoardPatchPayload } from './board-patch-broadcast.js'\n\n/**\n * PatchEntry 생성 직후 호출 — 같은 board 구독자에게 방송.\n * boardId 없으면(세션 미연결) skip. pubsub 미가용/실패해도 본 흐름(영속 완료)을 막지 않는다.\n */\nexport function publishBoardPatch(payload: BoardPatchPayload): void {\n if (!payload.boardId) return\n try {\n pubsub.publish(BOARD_PATCH_TOPIC, payload)\n } catch {\n /* 방송 생략 — 영속은 이미 끝남 */\n }\n}\n\n@Resolver(PatchEntry)\nexport class BoardPatchSubscription {\n @Subscription({\n description:\n 'Subscribes to board edit patches (AI or user-direct) for real-time collaboration on a board.',\n subscribe: async ({ args, context }) => {\n const { domain, user } = context.state\n const { boardId } = args\n\n await assertDomainSubscribeAllowed(domain, user, (d, u) => process.superUserGranted(d, u))\n\n return pipe(pubsub.subscribe(BOARD_PATCH_TOPIC), filter(boardPatchFilter(domain.id, boardId)))\n }\n })\n boardPatchActivity(@Root() payload: BoardPatchPayload, @Arg('boardId') boardId: string): PatchEntry {\n return payload.patch\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { ChatSession } from '../chat-session/chat-session.js';
1
+ import { ChatSession } from '@things-factory/ai-assistant';
2
2
  export declare class PatchEntry {
3
3
  readonly id?: string;
4
4
  session?: ChatSession;
@@ -17,7 +17,7 @@ const typeorm_1 = require("typeorm");
17
17
  const type_graphql_1 = require("type-graphql");
18
18
  const graphql_scalars_1 = require("graphql-scalars");
19
19
  const env_1 = require("@things-factory/env");
20
- const chat_session_js_1 = require("../chat-session/chat-session.js");
20
+ const ai_assistant_1 = require("@things-factory/ai-assistant");
21
21
  const ORMCONFIG = env_1.config.get('ormconfig', {});
22
22
  const DATABASE_TYPE = ORMCONFIG.type;
23
23
  let PatchEntry = class PatchEntry {
@@ -38,8 +38,8 @@ tslib_1.__decorate([
38
38
  tslib_1.__metadata("design:type", String)
39
39
  ], PatchEntry.prototype, "id", void 0);
40
40
  tslib_1.__decorate([
41
- (0, typeorm_1.ManyToOne)(type => chat_session_js_1.ChatSession, session => session.patches, { onDelete: 'CASCADE' }),
42
- tslib_1.__metadata("design:type", chat_session_js_1.ChatSession)
41
+ (0, typeorm_1.ManyToOne)(type => ai_assistant_1.ChatSession, { onDelete: 'CASCADE' }),
42
+ tslib_1.__metadata("design:type", ai_assistant_1.ChatSession)
43
43
  ], PatchEntry.prototype, "session", void 0);
44
44
  tslib_1.__decorate([
45
45
  (0, typeorm_1.RelationId)((patch) => patch.session),