@things-factory/ai-assistant 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 (164) hide show
  1. package/client/components/board-ai-chat.test.ts +149 -0
  2. package/client/components/board-ai-chat.ts +3228 -0
  3. package/client/components/chat-defaults.ts +27 -0
  4. package/client/components/chat-echo-dedup.test.ts +230 -0
  5. package/client/components/chat-echo-dedup.ts +155 -0
  6. package/client/components/chat-input-builder.ts +69 -0
  7. package/client/components/markdown.test.ts +518 -0
  8. package/client/components/markdown.ts +508 -0
  9. package/client/components/mention-popup-helpers.ts +433 -0
  10. package/client/components/mention-popup.test.ts +588 -0
  11. package/client/components/mention-popup.ts +238 -0
  12. package/client/index.ts +14 -0
  13. package/client/tsconfig.json +13 -0
  14. package/client/utils/board-edit-patch.ts +374 -0
  15. package/dist-client/components/board-ai-chat.d.ts +423 -0
  16. package/dist-client/components/board-ai-chat.js +3034 -0
  17. package/dist-client/components/board-ai-chat.js.map +1 -0
  18. package/dist-client/components/board-ai-chat.test.d.ts +1 -0
  19. package/dist-client/components/board-ai-chat.test.js +138 -0
  20. package/dist-client/components/board-ai-chat.test.js.map +1 -0
  21. package/dist-client/components/chat-defaults.d.ts +8 -0
  22. package/dist-client/components/chat-defaults.js +22 -0
  23. package/dist-client/components/chat-defaults.js.map +1 -0
  24. package/dist-client/components/chat-echo-dedup.d.ts +54 -0
  25. package/dist-client/components/chat-echo-dedup.js +115 -0
  26. package/dist-client/components/chat-echo-dedup.js.map +1 -0
  27. package/dist-client/components/chat-echo-dedup.test.d.ts +1 -0
  28. package/dist-client/components/chat-echo-dedup.test.js +203 -0
  29. package/dist-client/components/chat-echo-dedup.test.js.map +1 -0
  30. package/dist-client/components/chat-input-builder.d.ts +55 -0
  31. package/dist-client/components/chat-input-builder.js +32 -0
  32. package/dist-client/components/chat-input-builder.js.map +1 -0
  33. package/dist-client/components/markdown.d.ts +60 -0
  34. package/dist-client/components/markdown.js +422 -0
  35. package/dist-client/components/markdown.js.map +1 -0
  36. package/dist-client/components/markdown.test.d.ts +1 -0
  37. package/dist-client/components/markdown.test.js +418 -0
  38. package/dist-client/components/markdown.test.js.map +1 -0
  39. package/dist-client/components/mention-popup-helpers.d.ts +165 -0
  40. package/dist-client/components/mention-popup-helpers.js +330 -0
  41. package/dist-client/components/mention-popup-helpers.js.map +1 -0
  42. package/dist-client/components/mention-popup.d.ts +35 -0
  43. package/dist-client/components/mention-popup.js +223 -0
  44. package/dist-client/components/mention-popup.js.map +1 -0
  45. package/dist-client/components/mention-popup.test.d.ts +1 -0
  46. package/dist-client/components/mention-popup.test.js +474 -0
  47. package/dist-client/components/mention-popup.test.js.map +1 -0
  48. package/dist-client/index.d.ts +14 -0
  49. package/dist-client/index.js +15 -0
  50. package/dist-client/index.js.map +1 -0
  51. package/dist-client/tsconfig.tsbuildinfo +1 -0
  52. package/dist-client/utils/board-edit-patch.d.ts +157 -0
  53. package/dist-client/utils/board-edit-patch.js +284 -0
  54. package/dist-client/utils/board-edit-patch.js.map +1 -0
  55. package/dist-server/index.d.ts +28 -0
  56. package/dist-server/index.js +32 -0
  57. package/dist-server/index.js.map +1 -0
  58. package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
  59. package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
  60. package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
  61. package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
  62. package/dist-server/service/chat-message/chat-message-publish.js +27 -0
  63. package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
  64. package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
  65. package/dist-server/service/chat-message/chat-message-subscription.js +57 -0
  66. package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
  67. package/dist-server/service/chat-message/chat-message.d.ts +68 -0
  68. package/dist-server/service/chat-message/chat-message.js +172 -0
  69. package/dist-server/service/chat-message/chat-message.js.map +1 -0
  70. package/dist-server/service/chat-message/fold-history.d.ts +43 -0
  71. package/dist-server/service/chat-message/fold-history.js +33 -0
  72. package/dist-server/service/chat-message/fold-history.js.map +1 -0
  73. package/dist-server/service/chat-message/history-summary.d.ts +43 -0
  74. package/dist-server/service/chat-message/history-summary.js +77 -0
  75. package/dist-server/service/chat-message/history-summary.js.map +1 -0
  76. package/dist-server/service/chat-message/index.d.ts +5 -0
  77. package/dist-server/service/chat-message/index.js +12 -0
  78. package/dist-server/service/chat-message/index.js.map +1 -0
  79. package/dist-server/service/chat-message/llm-history.d.ts +46 -0
  80. package/dist-server/service/chat-message/llm-history.js +87 -0
  81. package/dist-server/service/chat-message/llm-history.js.map +1 -0
  82. package/dist-server/service/chat-session/chat-session.d.ts +60 -0
  83. package/dist-server/service/chat-session/chat-session.js +159 -0
  84. package/dist-server/service/chat-session/chat-session.js.map +1 -0
  85. package/dist-server/service/chat-session/index.d.ts +3 -0
  86. package/dist-server/service/chat-session/index.js +7 -0
  87. package/dist-server/service/chat-session/index.js.map +1 -0
  88. package/dist-server/service/chat-session/session-activity-broadcast.d.ts +30 -0
  89. package/dist-server/service/chat-session/session-activity-broadcast.js +34 -0
  90. package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -0
  91. package/dist-server/service/chat-session/session-activity-publish.d.ts +12 -0
  92. package/dist-server/service/chat-session/session-activity-publish.js +37 -0
  93. package/dist-server/service/chat-session/session-activity-publish.js.map +1 -0
  94. package/dist-server/service/chat-session/session-activity-subscription.d.ts +14 -0
  95. package/dist-server/service/chat-session/session-activity-subscription.js +87 -0
  96. package/dist-server/service/chat-session/session-activity-subscription.js.map +1 -0
  97. package/dist-server/service/chat-session/session-anchor.d.ts +26 -0
  98. package/dist-server/service/chat-session/session-anchor.js +100 -0
  99. package/dist-server/service/chat-session/session-anchor.js.map +1 -0
  100. package/dist-server/service/chat-session/session-inbox.d.ts +80 -0
  101. package/dist-server/service/chat-session/session-inbox.js +133 -0
  102. package/dist-server/service/chat-session/session-inbox.js.map +1 -0
  103. package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +49 -0
  104. package/dist-server/service/chat-session-participant/chat-session-participant.js +141 -0
  105. package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
  106. package/dist-server/service/chat-session-participant/index.d.ts +4 -0
  107. package/dist-server/service/chat-session-participant/index.js +10 -0
  108. package/dist-server/service/chat-session-participant/index.js.map +1 -0
  109. package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
  110. package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
  111. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
  112. package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
  113. package/dist-server/service/chat-session-participant/presence.js +22 -0
  114. package/dist-server/service/chat-session-participant/presence.js.map +1 -0
  115. package/dist-server/service/chat-session-resolver.d.ts +93 -0
  116. package/dist-server/service/chat-session-resolver.js +706 -0
  117. package/dist-server/service/chat-session-resolver.js.map +1 -0
  118. package/dist-server/service/index.d.ts +24 -0
  119. package/dist-server/service/index.js +39 -0
  120. package/dist-server/service/index.js.map +1 -0
  121. package/dist-server/service/subscribe-auth.d.ts +14 -0
  122. package/dist-server/service/subscribe-auth.js +33 -0
  123. package/dist-server/service/subscribe-auth.js.map +1 -0
  124. package/dist-server/service/types.d.ts +5 -0
  125. package/dist-server/service/types.js +12 -0
  126. package/dist-server/service/types.js.map +1 -0
  127. package/dist-server/tsconfig.tsbuildinfo +1 -0
  128. package/package.json +48 -0
  129. package/server/index.ts +28 -0
  130. package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
  131. package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
  132. package/server/service/chat-message/chat-message-broadcast.ts +27 -0
  133. package/server/service/chat-message/chat-message-publish.ts +22 -0
  134. package/server/service/chat-message/chat-message-subscription.ts +45 -0
  135. package/server/service/chat-message/chat-message.ts +195 -0
  136. package/server/service/chat-message/fold-history.test.ts +98 -0
  137. package/server/service/chat-message/fold-history.ts +77 -0
  138. package/server/service/chat-message/history-summary.test.ts +127 -0
  139. package/server/service/chat-message/history-summary.ts +100 -0
  140. package/server/service/chat-message/index.ts +12 -0
  141. package/server/service/chat-message/llm-history.test.ts +194 -0
  142. package/server/service/chat-message/llm-history.ts +163 -0
  143. package/server/service/chat-session/chat-session.ts +193 -0
  144. package/server/service/chat-session/index.ts +5 -0
  145. package/server/service/chat-session/session-activity-broadcast.ts +57 -0
  146. package/server/service/chat-session/session-activity-publish.ts +44 -0
  147. package/server/service/chat-session/session-activity-subscription.ts +67 -0
  148. package/server/service/chat-session/session-anchor.ts +106 -0
  149. package/server/service/chat-session/session-inbox.test.ts +185 -0
  150. package/server/service/chat-session/session-inbox.ts +161 -0
  151. package/server/service/chat-session-participant/chat-session-participant.ts +135 -0
  152. package/server/service/chat-session-participant/index.ts +6 -0
  153. package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
  154. package/server/service/chat-session-participant/invite-notification.ts +48 -0
  155. package/server/service/chat-session-participant/presence.test.ts +39 -0
  156. package/server/service/chat-session-participant/presence.ts +21 -0
  157. package/server/service/chat-session-resolver.ts +644 -0
  158. package/server/service/index.ts +37 -0
  159. package/server/service/session-anchor.test.ts +133 -0
  160. package/server/service/subscribe-auth.test.ts +57 -0
  161. package/server/service/subscribe-auth.ts +34 -0
  162. package/server/service/types.ts +15 -0
  163. package/things-factory.config.js +1 -0
  164. package/tsconfig.json +9 -0
@@ -0,0 +1,133 @@
1
+ /*
2
+ * 세션 앵커 검증 — 대화가 무엇에 매달리는가.
3
+ *
4
+ * 대화는 보드에만 매달리지 않는다. 저작 대화는 보드에, 운영 대화는 공간(사이트와 그 공간에서
5
+ * 도는 트윈들)에 매달린다. 앵커가 대화의 성격과 **이력 경계**를 정한다.
6
+ *
7
+ * 이 테스트가 지키는 것:
8
+ * ① **하위호환** — 앵커 컬럼이 빈 옛 행(boardId 만 있는 행)도 board 앵커 조회에 잡혀야 한다.
9
+ * 백필 마이그레이션 없이 컬럼 추가만으로 끝내는 것이 이 설계의 전제다.
10
+ * ② **dual-write** — board 앵커 세션은 anchorId 와 boardId 를 함께 써야 한다. 그래야 기존
11
+ * 조회·인덱스·소비자(operato-board 등)가 그대로 동작한다.
12
+ * ③ 공간·인스턴스 앵커는 boardId 를 오염시키지 않는다(보드 조회에 섞이면 이력 경계가 무너진다).
13
+ */
14
+ import { anchorColumns, anchorWhere, isSessionAnchorType, SESSION_ANCHOR_TYPES } from './chat-session/session-anchor'
15
+
16
+ describe('앵커 저장 값 (anchorColumns)', () => {
17
+ it('board 앵커는 anchorId 와 boardId 에 같은 값을 쓴다 — 기존 조회 하위호환', () => {
18
+ expect(anchorColumns('board', 'B1')).toEqual({ anchorType: 'board', anchorId: 'B1', boardId: 'B1' })
19
+ })
20
+
21
+ it('space 앵커는 boardId 를 쓰지 않는다 — 보드 조회에 섞이면 이력 경계가 무너진다', () => {
22
+ expect(anchorColumns('space', 'busan')).toEqual({ anchorType: 'space', anchorId: 'busan', boardId: undefined })
23
+ })
24
+
25
+ it('instance 앵커도 boardId 를 쓰지 않는다', () => {
26
+ expect(anchorColumns('instance', 'busan-wms')).toEqual({
27
+ anchorType: 'instance',
28
+ anchorId: 'busan-wms',
29
+ boardId: undefined
30
+ })
31
+ })
32
+
33
+ it('앵커 종류 미지정은 none — 임의로 board 로 승격하지 않는다(잘못된 소속 방지)', () => {
34
+ expect(anchorColumns(undefined, 'X')).toEqual({ anchorType: 'none', anchorId: 'X', boardId: undefined })
35
+ })
36
+
37
+ /*
38
+ * This case used to assert that an empty id was normalised to undefined, and it was green while
39
+ * encoding the defect. Storing ('space', NULL) is not harmless: `anchorWhere` then searches for
40
+ * the empty string it was handed, misses the row it just wrote, and the caller creates another
41
+ * session on every call — with no error anywhere. An anchor is a pointer, so no id is a caller
42
+ * error and the only correct answer is a refusal.
43
+ */
44
+ it('an anchor with no id is refused — normalising it would hide a lookup that misses its own row', () => {
45
+ for (const type of ['board', 'space', 'instance', 'domain']) {
46
+ expect(() => anchorColumns(type, '')).toThrow(/was given no id/)
47
+ expect(() => anchorColumns(type, undefined)).toThrow(/was given no id/)
48
+ }
49
+ })
50
+
51
+ it("'none' is the one anchor that may have no id — it means not addressable by anchor", () => {
52
+ expect(anchorColumns('none', '')).toEqual({ anchorType: 'none', anchorId: undefined, boardId: undefined })
53
+ expect(anchorColumns(undefined, undefined)).toEqual({
54
+ anchorType: 'none',
55
+ anchorId: undefined,
56
+ boardId: undefined
57
+ })
58
+ })
59
+
60
+ it("'domain' anchors a conversation whose subject is the whole tenant", () => {
61
+ /* A factory-wide question has nothing narrower to point at, but it still needs a history
62
+ * boundary and a bucket in the inbox — which is what 'none' cannot give it. */
63
+ expect(anchorColumns('domain', 'dom-1')).toEqual({
64
+ anchorType: 'domain',
65
+ anchorId: 'dom-1',
66
+ boardId: undefined
67
+ })
68
+ })
69
+ })
70
+
71
+ describe('anchor types are checked, not cast', () => {
72
+ /*
73
+ * `anchorColumns` used to do `(anchorType || 'none') as SessionAnchorType`. The type existed and
74
+ * nothing enforced it, so any string reached a nullable varchar and was stored. What makes that
75
+ * expensive is not one bad row: the anchor decides which conversations are listed together, so a
76
+ * typo writes a session into a bucket nothing queries — the conversation is simply absent from
77
+ * the list and no error is raised on either side.
78
+ */
79
+ it('an unknown anchor type is refused on the write path', () => {
80
+ expect(() => anchorColumns('facility', 'F1')).toThrow(/is not an anchor type/)
81
+ })
82
+
83
+ it('an unknown anchor type is refused on the read path too — otherwise it reads as 0 rows', () => {
84
+ expect(() => anchorWhere('dom-1', 'facility', 'F1')).toThrow(/is not an anchor type/)
85
+ })
86
+
87
+ it('the refusal names what is allowed — a lane should not have to read this file', () => {
88
+ let message = ''
89
+ try {
90
+ anchorColumns('plant', 'P1')
91
+ } catch (e: any) {
92
+ message = String(e?.message)
93
+ }
94
+ for (const type of SESSION_ANCHOR_TYPES) expect(message).toContain(type)
95
+ expect(message).toMatch(/raise it/)
96
+ })
97
+
98
+ it('defaulting to none is not the fallback — a typo must not become "about nothing"', () => {
99
+ expect(() => anchorColumns('spaces', 'busan')).toThrow()
100
+ })
101
+
102
+ it('isSessionAnchorType accepts exactly the declared set', () => {
103
+ for (const type of SESSION_ANCHOR_TYPES) expect(isSessionAnchorType(type)).toBe(true)
104
+ for (const bad of ['', 'Board', 'facility', 'plant', null, undefined, 0, {}]) {
105
+ expect(isSessionAnchorType(bad)).toBe(false)
106
+ }
107
+ })
108
+ })
109
+
110
+ describe('앵커 조회 조건 (anchorWhere)', () => {
111
+ it('board 앵커는 boardId 로 찾는다 — 앵커 컬럼이 빈 옛 행도 잡힌다', () => {
112
+ /* 신규 행은 dual-write 로 boardId 도 채워지므로 같은 조건에 함께 걸린다. */
113
+ expect(anchorWhere('dom-1', 'board', 'B1')).toEqual({ boardId: 'B1', domain: { id: 'dom-1' } })
114
+ })
115
+
116
+ it('space 앵커는 앵커 컬럼으로 찾는다', () => {
117
+ expect(anchorWhere('dom-1', 'space', 'busan')).toEqual({
118
+ anchorType: 'space',
119
+ anchorId: 'busan',
120
+ domain: { id: 'dom-1' }
121
+ })
122
+ })
123
+
124
+ it('모든 조회에 domain 조건이 포함된다 — 빠지면 남의 테넌트 대화가 보인다', () => {
125
+ for (const type of SESSION_ANCHOR_TYPES) {
126
+ expect(anchorWhere('dom-9', type, 'X').domain).toEqual({ id: 'dom-9' })
127
+ }
128
+ })
129
+
130
+ it('공간 앵커 조회 조건에는 boardId 가 없다 — 보드 세션을 끌어오지 않는다', () => {
131
+ expect(anchorWhere('dom-1', 'space', 'busan').boardId).toBeUndefined()
132
+ })
133
+ })
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 구독 도메인 인증 게이트 — 단위 테스트 (보안 핵심: 도메인 격리).
3
+ * board-patch / chat-message 양쪽 spine 이 공유하는 게이트.
4
+ *
5
+ * 회귀 방지: process.superUserGranted 는 async 다. 동기 `!process.superUserGranted(...)` 로
6
+ * 평가하면 `!Promise` = 항상 false → membership throw 가 죽는다(fail-open). 게이트는 반드시
7
+ * await 로 평가해야 한다. 아래 superUser 분기 테스트가 그 회귀를 박는다.
8
+ */
9
+ import { assertDomainSubscribeAllowed } from './subscribe-auth'
10
+
11
+ const never = async () => false // superUser 아님 (async — 실제 process.superUserGranted 와 동일)
12
+ const always = async () => true // superUser (async)
13
+
14
+ const domain = { id: 'd1', subdomain: 'acme' }
15
+ const userInDomain = { email: 'a@x.io', domains: [{ subdomain: 'acme' }, { subdomain: 'other' }] }
16
+ const userOutDomain = { email: 'b@x.io', domains: [{ subdomain: 'other' }] }
17
+
18
+ describe('assertDomainSubscribeAllowed (도메인 격리 게이트)', () => {
19
+ it('domain 없으면 → reject (domain required)', async () => {
20
+ await expect(assertDomainSubscribeAllowed(undefined, userInDomain, never)).rejects.toThrow('domain required')
21
+ await expect(assertDomainSubscribeAllowed(null, userInDomain, never)).rejects.toThrow('domain required')
22
+ })
23
+
24
+ it('user 가 해당 subdomain 소속 → 통과 (superUser 검사 불필요)', async () => {
25
+ await expect(assertDomainSubscribeAllowed(domain, userInDomain, never)).resolves.toBeUndefined()
26
+ })
27
+
28
+ it('user 가 미소속 + superUser 아님 → reject (격리 — 핵심)', async () => {
29
+ await expect(assertDomainSubscribeAllowed(domain, userOutDomain, never)).rejects.toThrow(
30
+ 'domain(acme) is not working for user(b@x.io).'
31
+ )
32
+ })
33
+
34
+ it('user 가 미소속이어도 superUser(async resolve true) 면 → 통과 (await 회귀 방지)', async () => {
35
+ await expect(assertDomainSubscribeAllowed(domain, userOutDomain, always)).resolves.toBeUndefined()
36
+ })
37
+
38
+ it('user.domains 없음 + superUser 아님 → reject', async () => {
39
+ await expect(assertDomainSubscribeAllowed(domain, { email: 'c@x.io' }, never)).rejects.toThrow(
40
+ 'domain(acme) is not working for user(c@x.io).'
41
+ )
42
+ })
43
+
44
+ it('user.domains 없음 + superUser → 통과', async () => {
45
+ await expect(assertDomainSubscribeAllowed(domain, { email: 'c@x.io' }, always)).resolves.toBeUndefined()
46
+ })
47
+
48
+ it('isSuperUser 는 domain+user 를 받아 호출되고 await 된다', async () => {
49
+ const calls: any[] = []
50
+ const spy = async (d: any, u: any) => {
51
+ calls.push([d, u])
52
+ return true
53
+ }
54
+ await assertDomainSubscribeAllowed(domain, userOutDomain, spy)
55
+ expect(calls).toEqual([[domain, userOutDomain]])
56
+ })
57
+ })
@@ -0,0 +1,34 @@
1
+ /*
2
+ * 구독 도메인 인증 게이트 — 순수 로직 (데코레이터·pubsub 없음 → 테스트 가능).
3
+ *
4
+ * 실시간 구독(@Subscription)은 같은 도메인 구성원만 허용해야 한다(도메인 격리 = 보안 핵심).
5
+ * 이 게이트는 board-patch / chat-message 양쪽 spine 의 subscribe 클로저가 공유한다
6
+ * (reuse — fork 금지). superUserGranted 는 process 전역 의존이라 주입받아 순수성 유지.
7
+ */
8
+
9
+ /**
10
+ * 구독 허용 여부 단언. 불가 시 throw (graphql-ws 가 구독 거부).
11
+ * - domain 없음 → 거부.
12
+ * - user 가 해당 subdomain 의 도메인 소속이 아니고 superUser 도 아니면 → 거부.
13
+ *
14
+ * 주의(버그 수정): process.superUserGranted 는 async(Promise<boolean>) 다. 기존 양쪽 spine 은
15
+ * `!process.superUserGranted(...)` 로 동기 평가해 `!Promise` = 항상 false → membership throw 가
16
+ * 발동하지 않는 fail-open 상태였다. 여기서는 await 로 제대로 평가한다 → subscribe 는 async.
17
+ *
18
+ * @param domain context.state.domain
19
+ * @param user context.state.user (domains[] / email 사용)
20
+ * @param isSuperUser (domain, user) → boolean | Promise<boolean> (process.superUserGranted 주입)
21
+ */
22
+ export async function assertDomainSubscribeAllowed(
23
+ domain: any,
24
+ user: any,
25
+ isSuperUser: (domain: any, user: any) => boolean | Promise<boolean>
26
+ ): Promise<void> {
27
+ if (!domain) {
28
+ throw new Error('domain required')
29
+ }
30
+ const subdomain = domain?.subdomain
31
+ if (!user?.domains?.find((d: any) => d.subdomain === subdomain) && !(await isSuperUser(domain, user))) {
32
+ throw new Error(`domain(${subdomain}) is not working for user(${user?.email}).`)
33
+ }
34
+ }
@@ -0,0 +1,15 @@
1
+ /*
2
+ * 대화 한 줄 — 도메인을 모르는 가장 작은 모양.
3
+ *
4
+ * `board-ai/types.ts` 에 있었고, 그래서 대화 이력을 다루는 순수 함수(`llm-history`)가 보드 편집기의
5
+ * 타입 파일을 봐야 했다. 그 파일은 `BoardComponent`·`BoardModel` 을 들고 있다 — 이력을 접는 일과
6
+ * 아무 상관이 없다.
7
+ *
8
+ * 옮겨도 소비처는 그대로다: `board-ai` 가 이 이름을 재수출한다.
9
+ */
10
+
11
+ /** 모델과 주고받는 한 줄. `system` 은 여기 없다 — 그것은 프롬프트이지 대화가 아니다. */
12
+ export interface LLMMessage {
13
+ role: 'user' | 'assistant'
14
+ content: string
15
+ }
@@ -0,0 +1 @@
1
+ export default {}
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist-server",
5
+ "baseUrl": "./"
6
+ },
7
+ "include": ["./server/**/*"],
8
+ "exclude": ["**/*.spec.ts", "**/*.test.ts"]
9
+ }