@things-factory/ai-assistant 10.1.81 → 10.1.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/components/assistant-dock.ts +57 -0
- package/client/components/assistant-session-manager.ts +9 -2
- package/client/utils/assistant-session-controller.ts +2 -0
- package/client/utils/assistant-session-transport.ts +2 -2
- package/dist-client/components/assistant-dock.d.ts +3 -0
- package/dist-client/components/assistant-dock.js +57 -0
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-session-manager.js +9 -2
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-controller.d.ts +2 -0
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-session-transport.js +2 -2
- package/dist-client/utils/assistant-session-transport.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +5 -5
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-visibility.d.ts +8 -5
- package/dist-server/service/chat-session/session-visibility.js +9 -15
- package/dist-server/service/chat-session/session-visibility.js.map +1 -1
- package/dist-server/service/chat-session-resolver.js +1 -1
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/chat-session/chat-session.ts +5 -5
- package/server/service/chat-session/session-visibility.test.ts +6 -6
- package/server/service/chat-session/session-visibility.ts +9 -15
- package/server/service/chat-session-resolver.ts +1 -1
- package/server/service/session-anchor.test.ts +2 -2
- package/translations/en.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +2 -1
- package/translations/ms.json +2 -1
- package/translations/zh.json +2 -1
|
@@ -11,6 +11,8 @@ export interface AssistantSession {
|
|
|
11
11
|
* 칸이 서기 전에 난 세션에는 값이 없다. 그때는 지금 대화로 두고, 서버가 다음 턴에 찍는다.
|
|
12
12
|
*/
|
|
13
13
|
kind?: string;
|
|
14
|
+
/** Who reads it: 'open' (everyone in the domain) or 'private' (participants). The dock draws an open one apart. */
|
|
15
|
+
visibility?: string;
|
|
14
16
|
}
|
|
15
17
|
export interface AssistantSessionTarget {
|
|
16
18
|
/** Account/domain boundary supplied by the authenticated host. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-session-controller.js","sourceRoot":"","sources":["../../client/utils/assistant-session-controller.ts"],"names":[],"mappings":"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","sourcesContent":["export interface AssistantSession {\n id: string\n name?: string\n /**\n * 어느 대화인가 — 등록된 모드의 kind (ADR-0072 결정 3).\n *\n * 목록은 한 대상의 대화를 **모두** 보여 준다. 그 대상 위에 대화가 둘 설 수 있으므로(A17), 고른 것이\n * 지금 열려 있는 대화와 다를 수 있다. 그때 화면은 그 대화로 따라가야 한다 — 안 그러면 문이 거절하고\n * (`session-kind-mismatch`), 목록은 눌리는데 안 열리는 목록이 된다.\n *\n * 칸이 서기 전에 난 세션에는 값이 없다. 그때는 지금 대화로 두고, 서버가 다음 턴에 찍는다.\n */\n kind?: string\n}\nexport interface AssistantSessionTarget {\n /** Account/domain boundary supplied by the authenticated host. */\n scope: string\n anchorType: string\n anchorId: string\n /** The conversation kind (mode) the tabs list - all readable conversations of it, not only mine (shared dock §2-6). */\n kind?: string\n}\nexport interface AssistantSessionTransport {\n list(target: AssistantSessionTarget): Promise<AssistantSession[]>\n create(target: AssistantSessionTarget): Promise<AssistantSession>\n isEmpty?(sessionId: string): Promise<boolean>\n /**\n * 이 대화를 여기까지 읽었다 — 열린 대화가 바뀔 때 한 번.\n *\n * 읽은 지점은 **다른 참여자에게 보이는 사실**이다(안 읽음 배지 · 목록의 굵은 글씨). 도크마다 손으로\n * 부르던 것을 여기로 올렸다 — twin 만 부르고 있었고, 멘션으로 사람이 들어올 수 있게 된 뒤에는\n * 세 화면이 같은 사실을 달리 말하게 된다.\n */\n markRead?(sessionId: string): Promise<void> | void\n}\nexport interface AssistantSessionState {\n target?: AssistantSessionTarget\n sessions: AssistantSession[]\n sessionId: string\n loading: boolean\n error: string\n /** Changes on reset, including temporary conversations without a server ID. */\n revision: number\n}\nconst keyOf = (target?: AssistantSessionTarget) => target\n ? JSON.stringify([target.scope, target.anchorType, target.anchorId]) : ''\n\n/** Domain-neutral session lifecycle. Never deletes history or edits host documents. */\nexport class AssistantSessionController {\n state: AssistantSessionState = { sessions: [], sessionId: '', loading: false, error: '', revision: 0 }\n private generation = 0\n private preferred = new Map<string, string>()\n private pending = new Map<string, Promise<AssistantSession[]>>()\n\n private transport: AssistantSessionTransport\n private changed: (state: AssistantSessionState) => void\n private storage?: Pick<Storage, 'getItem' | 'setItem'>\n private lazy: boolean\n private preparing?: Promise<string | undefined>\n constructor(transport: AssistantSessionTransport, changed: (state: AssistantSessionState) => void,\n storage?: Pick<Storage, 'getItem' | 'setItem'>, options: { lazy?: boolean } = {}) {\n this.transport = transport\n this.changed = changed\n this.storage = storage\n this.lazy = !!options.lazy\n }\n\n private remember(key: string, id: string) {\n this.preferred.set(key, id)\n try { this.storage?.setItem('assistant-session:' + key, id) } catch { /* Storage is optional. */ }\n }\n\n private preference(key: string): string | undefined {\n try { return this.preferred.get(key) || this.storage?.getItem('assistant-session:' + key) || undefined }\n catch { return this.preferred.get(key) }\n }\n\n private publish(patch: Partial<AssistantSessionState>) {\n const before = this.state.sessionId\n this.state = { ...this.state, ...patch }\n /*\n * 열린 대화가 바뀌면 읽음 지점을 옮긴다. 한 자리에서 하는 이유는 여는 길이 넷이기 때문이다\n * (열기 · 고르기 · 새로 시작 · 보내며 만들기) — 갈래마다 부르면 한 곳이 빠진다.\n */\n if (this.state.sessionId && this.state.sessionId !== before) void this.markRead(this.state.sessionId)\n this.changed(this.state)\n }\n\n /** 실패는 삼킨다 — 읽음 표시가 못 가는 것이 대화를 막을 이유는 아니다. */\n private async markRead(sessionId: string): Promise<void> {\n try {\n await this.transport.markRead?.(sessionId)\n } catch {\n /* noop */\n }\n }\n\n async open(target?: AssistantSessionTarget, force = false): Promise<void> {\n const key = keyOf(target)\n if (!force && key === keyOf(this.state.target) && (this.state.loading || this.state.sessionId || (this.lazy && this.state.target && !this.state.error))) return\n const generation = ++this.generation\n const snapshot = target ? { ...target } : undefined\n this.publish({ target: snapshot, sessions: [], sessionId: '', loading: !!target, error: '' })\n if (!snapshot) return\n try {\n let pending = this.pending.get(key)\n if (!pending) {\n pending = (async () => {\n const sessions = await this.transport.list(snapshot)\n // Failed/malformed reads must not create replacement sessions.\n if (!Array.isArray(sessions) || sessions.some(session => !session.id)) throw new Error('Invalid session list')\n if (sessions.length || this.lazy) return [...sessions]\n // Do not issue a mutation for a target abandoned while the inbox was loading.\n if (keyOf(this.state.target) !== key || this.pending.get(key) !== pending) throw new Error('Session target changed')\n const created = await this.transport.create(snapshot)\n if (!created?.id) throw new Error('Invalid new session')\n return [created]\n })()\n this.pending.set(key, pending)\n void pending.finally(() => {\n if (this.pending.get(key) === pending) this.pending.delete(key)\n }).catch(() => {})\n }\n const sessions = await pending\n if (generation !== this.generation) return\n const preference = this.preference(key)\n const sessionId = this.lazy && preference === '__draft__' ? '' : sessions.find(session => session.id === preference)?.id || sessions[0]?.id || ''\n this.remember(key, sessionId || '__draft__')\n this.publish({ sessions, sessionId, loading: false })\n } catch (error) {\n if (generation === this.generation) this.publish({ loading: false, error: error instanceof Error ? error.message : String(error) })\n }\n }\n\n select(id: string): boolean {\n if (this.state.loading || !this.state.sessions.some(session => session.id === id)) return false\n this.remember(keyOf(this.state.target), id)\n this.publish({ sessionId: id, error: '' })\n return true\n }\n\n async reset(): Promise<void> {\n if (this.state.loading || this.preparing) return\n if (this.lazy) {\n if (!this.state.sessionId) return\n ++this.generation\n this.remember(keyOf(this.state.target), '__draft__')\n this.publish({ sessionId: '', error: '', revision: this.state.revision + 1 })\n return\n }\n const target = this.state.target\n if (!target) {\n this.publish({ sessions: [], sessionId: '', error: '', revision: this.state.revision + 1 })\n return\n }\n const generation = ++this.generation\n const key = keyOf(target)\n this.publish({ loading: true, error: '' })\n try {\n if (this.state.sessionId && this.transport.isEmpty) {\n const empty = await this.transport.isEmpty(this.state.sessionId)\n if (generation !== this.generation) return\n if (empty) { this.publish({ loading: false }); return }\n }\n const session = await this.transport.create({ ...target })\n if (!session?.id) throw new Error('Invalid new session')\n this.remember(key, session.id)\n if (generation !== this.generation) return\n this.publish({ sessions: [session, ...this.state.sessions.filter(existing => existing.id !== session.id)], sessionId: session.id,\n loading: false, revision: this.state.revision + 1 })\n } catch (error) {\n if (generation === this.generation) this.publish({ loading: false, error: error instanceof Error ? error.message : String(error) })\n }\n }\n\n /** Called only for a real send. Adoption happens before hosts render the new ID. */\n prepareSession(adopt: (id: string) => void = () => {}): Promise<string | undefined> {\n if (this.state.sessionId) return Promise.resolve(this.state.sessionId)\n if (!this.state.target) return Promise.resolve(undefined)\n if (this.state.loading || this.state.error) return Promise.reject(new Error('Session history is not ready'))\n if (this.preparing) return this.preparing\n const target = { ...this.state.target }, generation = this.generation\n const pending = (async () => {\n const session = await this.transport.create(target)\n if (generation !== this.generation) throw new Error('Session target changed')\n if (!session?.id) throw new Error('Invalid new session')\n adopt(session.id)\n this.remember(keyOf(target), session.id)\n this.publish({ sessionId: session.id, sessions: [session, ...this.state.sessions] })\n return session.id\n })()\n this.preparing = pending\n void pending.finally(() => { if (this.preparing === pending) this.preparing = undefined }).catch(() => {})\n return pending\n }\n\n /** Invalidate pending UI updates at logout/disposal. Server authorization remains authoritative. */\n clear() {\n ++this.generation\n this.preferred.clear()\n this.pending.clear()\n this.publish({ target: undefined, sessions: [], sessionId: '', loading: false, error: '', revision: this.state.revision + 1 })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assistant-session-controller.js","sourceRoot":"","sources":["../../client/utils/assistant-session-controller.ts"],"names":[],"mappings":"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","sourcesContent":["export interface AssistantSession {\n id: string\n name?: string\n /**\n * 어느 대화인가 — 등록된 모드의 kind (ADR-0072 결정 3).\n *\n * 목록은 한 대상의 대화를 **모두** 보여 준다. 그 대상 위에 대화가 둘 설 수 있으므로(A17), 고른 것이\n * 지금 열려 있는 대화와 다를 수 있다. 그때 화면은 그 대화로 따라가야 한다 — 안 그러면 문이 거절하고\n * (`session-kind-mismatch`), 목록은 눌리는데 안 열리는 목록이 된다.\n *\n * 칸이 서기 전에 난 세션에는 값이 없다. 그때는 지금 대화로 두고, 서버가 다음 턴에 찍는다.\n */\n kind?: string\n /** Who reads it: 'open' (everyone in the domain) or 'private' (participants). The dock draws an open one apart. */\n visibility?: string\n}\nexport interface AssistantSessionTarget {\n /** Account/domain boundary supplied by the authenticated host. */\n scope: string\n anchorType: string\n anchorId: string\n /** The conversation kind (mode) the tabs list - all readable conversations of it, not only mine (shared dock §2-6). */\n kind?: string\n}\nexport interface AssistantSessionTransport {\n list(target: AssistantSessionTarget): Promise<AssistantSession[]>\n create(target: AssistantSessionTarget): Promise<AssistantSession>\n isEmpty?(sessionId: string): Promise<boolean>\n /**\n * 이 대화를 여기까지 읽었다 — 열린 대화가 바뀔 때 한 번.\n *\n * 읽은 지점은 **다른 참여자에게 보이는 사실**이다(안 읽음 배지 · 목록의 굵은 글씨). 도크마다 손으로\n * 부르던 것을 여기로 올렸다 — twin 만 부르고 있었고, 멘션으로 사람이 들어올 수 있게 된 뒤에는\n * 세 화면이 같은 사실을 달리 말하게 된다.\n */\n markRead?(sessionId: string): Promise<void> | void\n}\nexport interface AssistantSessionState {\n target?: AssistantSessionTarget\n sessions: AssistantSession[]\n sessionId: string\n loading: boolean\n error: string\n /** Changes on reset, including temporary conversations without a server ID. */\n revision: number\n}\nconst keyOf = (target?: AssistantSessionTarget) => target\n ? JSON.stringify([target.scope, target.anchorType, target.anchorId]) : ''\n\n/** Domain-neutral session lifecycle. Never deletes history or edits host documents. */\nexport class AssistantSessionController {\n state: AssistantSessionState = { sessions: [], sessionId: '', loading: false, error: '', revision: 0 }\n private generation = 0\n private preferred = new Map<string, string>()\n private pending = new Map<string, Promise<AssistantSession[]>>()\n\n private transport: AssistantSessionTransport\n private changed: (state: AssistantSessionState) => void\n private storage?: Pick<Storage, 'getItem' | 'setItem'>\n private lazy: boolean\n private preparing?: Promise<string | undefined>\n constructor(transport: AssistantSessionTransport, changed: (state: AssistantSessionState) => void,\n storage?: Pick<Storage, 'getItem' | 'setItem'>, options: { lazy?: boolean } = {}) {\n this.transport = transport\n this.changed = changed\n this.storage = storage\n this.lazy = !!options.lazy\n }\n\n private remember(key: string, id: string) {\n this.preferred.set(key, id)\n try { this.storage?.setItem('assistant-session:' + key, id) } catch { /* Storage is optional. */ }\n }\n\n private preference(key: string): string | undefined {\n try { return this.preferred.get(key) || this.storage?.getItem('assistant-session:' + key) || undefined }\n catch { return this.preferred.get(key) }\n }\n\n private publish(patch: Partial<AssistantSessionState>) {\n const before = this.state.sessionId\n this.state = { ...this.state, ...patch }\n /*\n * 열린 대화가 바뀌면 읽음 지점을 옮긴다. 한 자리에서 하는 이유는 여는 길이 넷이기 때문이다\n * (열기 · 고르기 · 새로 시작 · 보내며 만들기) — 갈래마다 부르면 한 곳이 빠진다.\n */\n if (this.state.sessionId && this.state.sessionId !== before) void this.markRead(this.state.sessionId)\n this.changed(this.state)\n }\n\n /** 실패는 삼킨다 — 읽음 표시가 못 가는 것이 대화를 막을 이유는 아니다. */\n private async markRead(sessionId: string): Promise<void> {\n try {\n await this.transport.markRead?.(sessionId)\n } catch {\n /* noop */\n }\n }\n\n async open(target?: AssistantSessionTarget, force = false): Promise<void> {\n const key = keyOf(target)\n if (!force && key === keyOf(this.state.target) && (this.state.loading || this.state.sessionId || (this.lazy && this.state.target && !this.state.error))) return\n const generation = ++this.generation\n const snapshot = target ? { ...target } : undefined\n this.publish({ target: snapshot, sessions: [], sessionId: '', loading: !!target, error: '' })\n if (!snapshot) return\n try {\n let pending = this.pending.get(key)\n if (!pending) {\n pending = (async () => {\n const sessions = await this.transport.list(snapshot)\n // Failed/malformed reads must not create replacement sessions.\n if (!Array.isArray(sessions) || sessions.some(session => !session.id)) throw new Error('Invalid session list')\n if (sessions.length || this.lazy) return [...sessions]\n // Do not issue a mutation for a target abandoned while the inbox was loading.\n if (keyOf(this.state.target) !== key || this.pending.get(key) !== pending) throw new Error('Session target changed')\n const created = await this.transport.create(snapshot)\n if (!created?.id) throw new Error('Invalid new session')\n return [created]\n })()\n this.pending.set(key, pending)\n void pending.finally(() => {\n if (this.pending.get(key) === pending) this.pending.delete(key)\n }).catch(() => {})\n }\n const sessions = await pending\n if (generation !== this.generation) return\n const preference = this.preference(key)\n const sessionId = this.lazy && preference === '__draft__' ? '' : sessions.find(session => session.id === preference)?.id || sessions[0]?.id || ''\n this.remember(key, sessionId || '__draft__')\n this.publish({ sessions, sessionId, loading: false })\n } catch (error) {\n if (generation === this.generation) this.publish({ loading: false, error: error instanceof Error ? error.message : String(error) })\n }\n }\n\n select(id: string): boolean {\n if (this.state.loading || !this.state.sessions.some(session => session.id === id)) return false\n this.remember(keyOf(this.state.target), id)\n this.publish({ sessionId: id, error: '' })\n return true\n }\n\n async reset(): Promise<void> {\n if (this.state.loading || this.preparing) return\n if (this.lazy) {\n if (!this.state.sessionId) return\n ++this.generation\n this.remember(keyOf(this.state.target), '__draft__')\n this.publish({ sessionId: '', error: '', revision: this.state.revision + 1 })\n return\n }\n const target = this.state.target\n if (!target) {\n this.publish({ sessions: [], sessionId: '', error: '', revision: this.state.revision + 1 })\n return\n }\n const generation = ++this.generation\n const key = keyOf(target)\n this.publish({ loading: true, error: '' })\n try {\n if (this.state.sessionId && this.transport.isEmpty) {\n const empty = await this.transport.isEmpty(this.state.sessionId)\n if (generation !== this.generation) return\n if (empty) { this.publish({ loading: false }); return }\n }\n const session = await this.transport.create({ ...target })\n if (!session?.id) throw new Error('Invalid new session')\n this.remember(key, session.id)\n if (generation !== this.generation) return\n this.publish({ sessions: [session, ...this.state.sessions.filter(existing => existing.id !== session.id)], sessionId: session.id,\n loading: false, revision: this.state.revision + 1 })\n } catch (error) {\n if (generation === this.generation) this.publish({ loading: false, error: error instanceof Error ? error.message : String(error) })\n }\n }\n\n /** Called only for a real send. Adoption happens before hosts render the new ID. */\n prepareSession(adopt: (id: string) => void = () => {}): Promise<string | undefined> {\n if (this.state.sessionId) return Promise.resolve(this.state.sessionId)\n if (!this.state.target) return Promise.resolve(undefined)\n if (this.state.loading || this.state.error) return Promise.reject(new Error('Session history is not ready'))\n if (this.preparing) return this.preparing\n const target = { ...this.state.target }, generation = this.generation\n const pending = (async () => {\n const session = await this.transport.create(target)\n if (generation !== this.generation) throw new Error('Session target changed')\n if (!session?.id) throw new Error('Invalid new session')\n adopt(session.id)\n this.remember(keyOf(target), session.id)\n this.publish({ sessionId: session.id, sessions: [session, ...this.state.sessions] })\n return session.id\n })()\n this.preparing = pending\n void pending.finally(() => { if (this.preparing === pending) this.preparing = undefined }).catch(() => {})\n return pending\n }\n\n /** Invalidate pending UI updates at logout/disposal. Server authorization remains authoritative. */\n clear() {\n ++this.generation\n this.preferred.clear()\n this.pending.clear()\n this.publish({ target: undefined, sessions: [], sessionId: '', loading: false, error: '', revision: this.state.revision + 1 })\n }\n}\n"]}
|
|
@@ -20,7 +20,7 @@ export const assistantSessionTransport = {
|
|
|
20
20
|
* Every readable conversation of this kind on the anchor, not only mine - the same scope the manager
|
|
21
21
|
* shows by default (design/ai/shared-dock.md §2-6). Without a kind: every kind, as before.
|
|
22
22
|
*/
|
|
23
|
-
query: gql `query($type:String!,$id:String!,$kind:String){chatSessionInbox(anchorType:$type,anchorId:$id,kind:$kind,limit:100){id name kind}}`,
|
|
23
|
+
query: gql `query($type:String!,$id:String!,$kind:String){chatSessionInbox(anchorType:$type,anchorId:$id,kind:$kind,limit:100){id name kind visibility}}`,
|
|
24
24
|
variables: { type: target.anchorType, id: target.anchorId, kind: target.kind || null }, fetchPolicy: 'no-cache'
|
|
25
25
|
});
|
|
26
26
|
if (result.errors?.length)
|
|
@@ -38,7 +38,7 @@ export const assistantSessionTransport = {
|
|
|
38
38
|
},
|
|
39
39
|
async create(target) {
|
|
40
40
|
const result = await client.mutate({
|
|
41
|
-
mutation: gql `mutation($type:String!,$id:String!){createAISession(anchorType:$type,anchorId:$id){id name kind}}`,
|
|
41
|
+
mutation: gql `mutation($type:String!,$id:String!){createAISession(anchorType:$type,anchorId:$id){id name kind visibility}}`,
|
|
42
42
|
variables: { type: target.anchorType, id: target.anchorId }
|
|
43
43
|
});
|
|
44
44
|
if (result.errors?.length)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-session-transport.js","sourceRoot":"","sources":["../../client/utils/assistant-session-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,GAAG,MAAM,aAAa,CAAA;AAG7B,6FAA6F;AAC7F,MAAM,CAAC,MAAM,yBAAyB,GAA8B;IAClE,KAAK,CAAC,OAAO,CAAC,SAAS;QACrB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAChC,KAAK,EAAE,GAAG,CAAA,6DAA6D;YACvE,SAAS,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,WAAW,EAAE,UAAU;SACtD,CAAC,CAAA;QACF,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACpE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uCAAuC,EAAE,EAAE,YAAY,EAAE,mCAAmC,EAAE,CAAC,CAAC,CAAA;QACzK,OAAO,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,CAAA;IAC9C,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,MAAM;QACf,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAChC;;;eAGG;YACH,KAAK,EAAE,GAAG,CAAA,
|
|
1
|
+
{"version":3,"file":"assistant-session-transport.js","sourceRoot":"","sources":["../../client/utils/assistant-session-transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport gql from 'graphql-tag'\nimport type { AssistantSessionTransport } from './assistant-session-controller'\n\n/** Existing authorized session API; does not introduce a product-specific storage format. */\nexport const assistantSessionTransport: AssistantSessionTransport = {\n async isEmpty(sessionId) {\n const result = await client.query({\n query: gql`query($id:String!){chatMessages(sessionId:$id,limit:1){id}}`,\n variables: { id: sessionId }, fetchPolicy: 'no-cache'\n })\n if (result.errors?.length) throw new Error(result.errors[0].message)\n if (!Array.isArray(result.data?.chatMessages)) throw new Error(i18next.t('ai-assistant.text.messages-unreadable', { defaultValue: 'could not read this conversation.' }))\n return result.data.chatMessages.length === 0\n },\n async list(target) {\n const result = await client.query({\n /*\n * Every readable conversation of this kind on the anchor, not only mine - the same scope the manager\n * shows by default (design/ai/shared-dock.md §2-6). Without a kind: every kind, as before.\n */\n query: gql`query($type:String!,$id:String!,$kind:String){chatSessionInbox(anchorType:$type,anchorId:$id,kind:$kind,limit:100){id name kind visibility}}`,\n variables: { type: target.anchorType, id: target.anchorId, kind: target.kind || null }, fetchPolicy: 'no-cache'\n })\n if (result.errors?.length) throw new Error(result.errors[0].message)\n if (!Array.isArray(result.data?.chatSessionInbox)) throw new Error(i18next.t('ai-assistant.text.sessions-unreadable', { defaultValue: 'could not load the conversation list.' }))\n return result.data.chatSessionInbox\n },\n async markRead(sessionId) {\n /* 참여자가 아닌 대화(내 읽음 지점이 없는 자리)면 서버가 false 를 돌려준다 — 그것도 답이다. */\n await client.mutate({\n mutation: gql`mutation($id:String!){markSessionRead(sessionId:$id)}`,\n variables: { id: sessionId }\n })\n },\n async create(target) {\n const result = await client.mutate({\n mutation: gql`mutation($type:String!,$id:String!){createAISession(anchorType:$type,anchorId:$id){id name kind visibility}}`,\n variables: { type: target.anchorType, id: target.anchorId }\n })\n if (result.errors?.length) throw new Error(result.errors[0].message)\n if (!result.data?.createAISession?.id) throw new Error(i18next.t('ai-assistant.text.session-not-created', { defaultValue: 'could not start a conversation.' }))\n return result.data.createAISession\n }\n}\n"]}
|
|
@@ -58,12 +58,12 @@ export declare class ChatSession {
|
|
|
58
58
|
* `open` 도메인 안이면 누구나 들어와 읽고 말한다. 초대 절차는 없다.
|
|
59
59
|
* `private` 참여자만 읽는다.
|
|
60
60
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* 사적으로 오간 원문이 소급해서 공개된다.
|
|
61
|
+
* 새 세션은 **모두** `private` 으로 시작한다(2026-09-30 — `initialVisibility`). 앵커는 무엇에 대한
|
|
62
|
+
* 이야기인지를 말할 뿐 누구에게 보이는지를 정하지 않는다. 칸의 기본값 `open` 은 칸이 서기 전의 옛 행을
|
|
63
|
+
* 그날까지의 규칙대로 읽기 위한 것이고, 새 행은 늘 값을 적는다.
|
|
65
64
|
*
|
|
66
|
-
* owner 가 양방향으로 바꿀 수 있다. 공개로 바꾸기 전에 화면이 한 줄을 먼저
|
|
65
|
+
* owner 가 양방향으로 바꿀 수 있다. 공개로 바꾸기 전에 화면이 한 줄을 먼저 보이고, 공개 대화는 도크가
|
|
66
|
+
* 다르게 그린다.
|
|
67
67
|
*
|
|
68
68
|
* open 이어도 답 행의 문 판정(ADR-0073 판정 1)은 그대로다 — 방에 들어온 것과 값을 읽는 것은 다르다.
|
|
69
69
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-session.js","sourceRoot":"","sources":["../../../server/service/chat-session/chat-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSession — Board 와 결합되는 AI 협력 세션.\n *\n * 한 보드에 여러 세션 가능 — DB 레벨 unique 제약 없음.\n *\n * 향후 폭증할 세션 분화 차원 (예: thread / 사용자별 / 컨텍스트별 design vs ops /\n * archived / forked from clone / agent별) 을 미리 차단하지 않기 위해 구조 자체는 1:N\n * 으로 열어둠. 단일 세션 정책 (현재 UX) 은 app 레벨 idempotent 로직 — `startBoardAISession`\n * 이 boardId 매칭 시 기존 세션 반환, `chatSessionByBoard` 가 첫 매칭 반환.\n *\n * 미래에 다중 세션 활성화 시 discriminator 컬럼 (kind / name / threadId / creatorId 등)\n * 을 그때 추가. 마이그레이션 부담 최소화 — 현재는 unique 만 풀어 자리 마련.\n *\n * board-service 의 Board 가 owning side 가 될 예정 (ChatSession FK).\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n OneToMany,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatMessage } from '../chat-message/chat-message.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n// 인덱스는 조회 성능용으로 유지 (chatSessionByBoard 가 [domain, boardId] 로 자주 쿼리).\n// unique 제약은 의도적으로 제거 — 한 보드에 여러 세션을 미래에 허용하기 위함.\n@Index('ix_chat_session_1', (session: ChatSession) => [session.domain, session.boardId])\n// 앵커 조회용 — chatSessionsByAnchor 가 [domain, anchorType, anchorId] 로 질의.\n@Index('ix_chat_session_2', (session: ChatSession) => [session.domain, session.anchorType, session.anchorId])\n@ObjectType({\n description: 'One AI conversation, anchored to what it is about. An anchor may have several — threads, one per person, one per context.'\n})\nexport class ChatSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ChatSession) => session.domain)\n domainId?: string\n\n /** Board.id 와 연결. Board 가 owning side 가 될 예정. 1:N — 한 보드에 여러 세션 가능.\n *\n * **앵커 일반화 이후에도 유지되는 하위호환 컬럼**이다(제거하지 말 것). 앵커가 board 인 세션은\n * anchorId 와 함께 이 컬럼에도 같은 값을 쓴다(dual-write) — 기존 조회/인덱스/소비자가 그대로 동작한다. */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'Connected Board id. Multiple sessions per board allowed (future: threads / per-user / contexts). Kept for backward compatibility — board-anchored sessions dual-write this alongside anchorId.'\n })\n boardId?: string\n\n /**\n * 대화가 매달린 대상의 **종류**. 세션은 보드에만 매달리지 않는다 —\n * 저작 대화는 보드에, 운영 대화는 공간(사이트)에 매달린다. 앵커가 대화의 성격과 이력 경계를 정한다.\n *\n * 'board' | 'space' | 'instance' | 'none'. 미설정(기존 데이터)은 boardId 가 있으면 board 로 읽는다 —\n * 백필 마이그레이션 없이 lazy 하위호환(컬럼 추가만).\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n \"What this conversation is anchored to: 'board' | 'figure' (authoring a Figure) | 'application' | 'space' | 'instance' | 'domain' | 'subject' (one subject in a 360, '<axis>:<itemId>') | 'none'. Absent on legacy rows — treated as 'board' when boardId is present.\"\n })\n anchorType?: string\n\n /** 앵커 대상의 id(보드 id · 공간 id · 인스턴스 id). anchorType 과 함께 의미를 갖는다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the anchored target (board id / space id / instance id).' })\n anchorId?: string\n\n /**\n * 세션의 사용자 정의 이름 (탭 레이블 / 세션 식별 표시용).\n *\n * nullable — 미설정 시 UI 가 fallback 라벨 (\"세션 N\" 또는 createdAt 기반) 사용. 사용자가\n * 다중 세션 운영 시 의미 부여 (예: \"초기 설계\", \"운영 시뮬레이션\", \"사용자A 의 사적 세션\").\n */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'User-given name of this session (tab label / identification).' })\n name?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this session.' })\n creator?: User\n\n @RelationId((session: ChatSession) => session.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who last updated this session.' })\n updater?: User\n\n @RelationId((session: ChatSession) => session.updater)\n updaterId?: string\n\n /** 토큰 절감용 — 오래된 메시지를 LLM 으로 압축한 요약. Phase 2 에서 작성. */\n /**\n * 마지막 메시지 시각 — **비정규화**. 목록 정렬(최근 활동순)과 안 읽음 판정의 기준.\n *\n * 왜 비정규화인가: 세션마다 메시지를 집계하면(MAX/COUNT + group by) 목록 한 번에 집계 쿼리가 붙고,\n * 이 프레임워크는 5개 DB 드라이버를 지원해야 한다. 세션 컬럼 하나로 두면 정렬·비교가 값싸게 끝나고\n * 드라이버 차이가 없다. 메시지를 저장할 때 함께 갱신한다(activityPatch).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last message in this session (denormalized for list ordering and unread comparison — avoids per-session aggregation across the supported DB drivers).' })\n lastMessageAt?: Date\n\n /**\n * 어느 대화인가 — `twin.board` · `figure.catalog` 같은 모드의 kind (ADR-0072 결정 3).\n *\n * **서버가 적는다.** 클라이언트가 보내는 값이 아니다 — 등록된 모드에서 나온 것만 적힌다(`addressee`\n * 와 같은 결). 한 대상 위에 대화가 둘 설 수 있게 되면서(A17), 세션이 자기가 어느 대화인지 알아야\n * 한다. 몰랐을 때 화면에서 이렇게 나왔다: 모드를 바꿔도 같은 세션이 그대로 남아, 「도형 찾기」로\n * 바꾼 뒤 보낸 말이 앞의 고치기 대화에 붙었다(2026-09-18, Figure 레인 :3300).\n *\n * 값이 없는 세션은 이 칸이 서기 전에 난 것이다. 다음 턴에 요청한 kind 로 한 번 찍고, 찍힌 뒤에는\n * 「요청한 모드와 다르면 거절」 하나만 남는다. 마이그레이션은 만들지 않는다.\n */\n @Column({ type: 'varchar', length: 64, nullable: true })\n @Field({ nullable: true, description: \"Which conversation this session is — the registered mode kind. Written by the server, never sent by the client.\" })\n kind?: string\n\n /**\n * 이 대화를 도메인 안 누구나 읽는가 — ADR-0073 판정 2.\n *\n * `open` 도메인 안이면 누구나 들어와 읽고 말한다. 초대 절차는 없다.\n * `private` 참여자만 읽는다.\n *\n * 앵커가 있는 세션(space · board · station)은 공유된 무엇을 놓고 하는 이야기이므로 `open` 으로\n * 시작한다. 앵커가 없는 세션과 **옮겨 적어 만든 세션**은 `private` 으로 시작한다 — 가벼운 질문을\n * 승격하면서 앞선 대화가 새 세션에 옮겨 적히는데(`appendSessionTurns`), 그것이 공개로 태어나면\n * 사적으로 오간 원문이 소급해서 공개된다.\n *\n * owner 가 양방향으로 바꿀 수 있다. 공개로 바꾸기 전에 화면이 한 줄을 먼저 보인다.\n *\n * open 이어도 답 행의 문 판정(ADR-0073 판정 1)은 그대로다 — 방에 들어온 것과 값을 읽는 것은 다르다.\n */\n @Column({ type: 'varchar', length: 16, default: 'open' })\n @Field({ description: \"Who may read this conversation: 'open' (anyone in the domain) or 'private' (participants only).\" })\n visibility!: string\n\n /** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'Short preview of the last message, stored so the session list does not need to query messages.' })\n lastMessagePreview?: string\n\n /**\n * 그 미리보기가 지나온 문 — ADR-0073 판정 1.\n *\n * 목록은 답을 **다시 읽는 두 번째 길**이다. 메시지 화면에서만 가리면 같은 문장이 목록에 그대로 뜬다.\n * 문이 둘이고 한쪽이 느슨하면 느슨한 쪽이 이긴다(ADR-0061 결정 1). 미리보기를 저장할 때 그 행의 문을\n * 함께 옮겨 적고, 목록도 같은 관문을 지난다.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], { nullable: true, description: 'Doors of the message the preview came from — the session list passes the same gate as the conversation.' })\n lastMessageDoors?: string[]\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'Compressed summary of older messages (for token saving).' })\n lastSummary?: string\n\n /**\n * `lastSummary` 가 **어디까지 덮는지** — 이 메시지까지 요약에 반영됐다.\n *\n * 이게 없으면 누적 요약을 할 수 없다: 매 턴 전체를 다시 요약하거나(비용) 이미 요약한 구간을\n * 또 이어 붙인다(중복). 이력에서 이 id 를 못 찾으면(삭제·편집) 덮은 범위를 알 수 없으므로\n * 전부 미반영으로 보고 다시 요약한다 — 빠뜨리는 쪽보다 안전하다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the last message folded into lastSummary — enables incremental summarization instead of re-reading the whole history.' })\n summaryUpToMessageId?: string\n\n /** 사용 모델 식별 — 'anthropic:claude-sonnet-4-6' 등 */\n @Column({ nullable: true })\n @Field({ nullable: true })\n aiClientId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n deletedAt?: Date\n\n // ── 관계 (TypeORM 만, GraphQL 노출은 별도 query 통해 페이징) ──\n @OneToMany(type => ChatMessage, message => message.session)\n messages?: ChatMessage[]\n\n /*\n * ── 보드 패치 관계를 끊었다 (2026-09-10) ──────────────────────────────────\n * 여기 `@OneToMany(PatchEntry)` 가 있었다. 그래서 **대화 세션이 보드 편집기를 알아야** 했고,\n * 세션 계열을 중립 패키지로 옮기려는 순간 `ai-assistant → board-ai → ai-assistant` 순환이\n * 된다.\n *\n * 끊어도 잃는 것이 없다 — 이 관계를 **자료로 읽는 자리가 하나도 없었다**(전 저장소에서\n * `session.patches` 를 읽는 코드 0 건). 유일한 쓰임이 `PatchEntry` 쪽의 역방향 선언 한 줄이었고,\n * TypeORM 은 역방향 없이도 `@ManyToOne` 을 만든다. 패치는 `sessionId` 로 찾는다\n * (`chatPatches` 가 이미 그렇게 한다).\n *\n * 방향은 남는다: **보드 패치가 세션을 안다. 세션은 보드 패치를 모른다.** 종속이 한쪽이면\n * 중립 쪽이 옮겨질 수 있다.\n */\n}\n"]}
|
|
1
|
+
{"version":3,"file":"chat-session.js","sourceRoot":"","sources":["../../../server/service/chat-session/chat-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ChatSession — Board 와 결합되는 AI 협력 세션.\n *\n * 한 보드에 여러 세션 가능 — DB 레벨 unique 제약 없음.\n *\n * 향후 폭증할 세션 분화 차원 (예: thread / 사용자별 / 컨텍스트별 design vs ops /\n * archived / forked from clone / agent별) 을 미리 차단하지 않기 위해 구조 자체는 1:N\n * 으로 열어둠. 단일 세션 정책 (현재 UX) 은 app 레벨 idempotent 로직 — `startBoardAISession`\n * 이 boardId 매칭 시 기존 세션 반환, `chatSessionByBoard` 가 첫 매칭 반환.\n *\n * 미래에 다중 세션 활성화 시 discriminator 컬럼 (kind / name / threadId / creatorId 등)\n * 을 그때 추가. 마이그레이션 부담 최소화 — 현재는 unique 만 풀어 자리 마련.\n *\n * board-service 의 Board 가 owning side 가 될 예정 (ChatSession FK).\n */\nimport {\n Column,\n CreateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n ManyToOne,\n OneToMany,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nimport { ChatMessage } from '../chat-message/chat-message.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n// 인덱스는 조회 성능용으로 유지 (chatSessionByBoard 가 [domain, boardId] 로 자주 쿼리).\n// unique 제약은 의도적으로 제거 — 한 보드에 여러 세션을 미래에 허용하기 위함.\n@Index('ix_chat_session_1', (session: ChatSession) => [session.domain, session.boardId])\n// 앵커 조회용 — chatSessionsByAnchor 가 [domain, anchorType, anchorId] 로 질의.\n@Index('ix_chat_session_2', (session: ChatSession) => [session.domain, session.anchorType, session.anchorId])\n@ObjectType({\n description: 'One AI conversation, anchored to what it is about. An anchor may have several — threads, one per person, one per context.'\n})\nexport class ChatSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ChatSession) => session.domain)\n domainId?: string\n\n /** Board.id 와 연결. Board 가 owning side 가 될 예정. 1:N — 한 보드에 여러 세션 가능.\n *\n * **앵커 일반화 이후에도 유지되는 하위호환 컬럼**이다(제거하지 말 것). 앵커가 board 인 세션은\n * anchorId 와 함께 이 컬럼에도 같은 값을 쓴다(dual-write) — 기존 조회/인덱스/소비자가 그대로 동작한다. */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n 'Connected Board id. Multiple sessions per board allowed (future: threads / per-user / contexts). Kept for backward compatibility — board-anchored sessions dual-write this alongside anchorId.'\n })\n boardId?: string\n\n /**\n * 대화가 매달린 대상의 **종류**. 세션은 보드에만 매달리지 않는다 —\n * 저작 대화는 보드에, 운영 대화는 공간(사이트)에 매달린다. 앵커가 대화의 성격과 이력 경계를 정한다.\n *\n * 'board' | 'space' | 'instance' | 'none'. 미설정(기존 데이터)은 boardId 가 있으면 board 로 읽는다 —\n * 백필 마이그레이션 없이 lazy 하위호환(컬럼 추가만).\n */\n @Column({ nullable: true })\n @Field({\n nullable: true,\n description:\n \"What this conversation is anchored to: 'board' | 'figure' (authoring a Figure) | 'application' | 'space' | 'instance' | 'domain' | 'subject' (one subject in a 360, '<axis>:<itemId>') | 'none'. Absent on legacy rows — treated as 'board' when boardId is present.\"\n })\n anchorType?: string\n\n /** 앵커 대상의 id(보드 id · 공간 id · 인스턴스 id). anchorType 과 함께 의미를 갖는다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the anchored target (board id / space id / instance id).' })\n anchorId?: string\n\n /**\n * 세션의 사용자 정의 이름 (탭 레이블 / 세션 식별 표시용).\n *\n * nullable — 미설정 시 UI 가 fallback 라벨 (\"세션 N\" 또는 createdAt 기반) 사용. 사용자가\n * 다중 세션 운영 시 의미 부여 (예: \"초기 설계\", \"운영 시뮬레이션\", \"사용자A 의 사적 세션\").\n */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'User-given name of this session (tab label / identification).' })\n name?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who created this session.' })\n creator?: User\n\n @RelationId((session: ChatSession) => session.creator)\n creatorId?: string\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true, description: 'User who last updated this session.' })\n updater?: User\n\n @RelationId((session: ChatSession) => session.updater)\n updaterId?: string\n\n /** 토큰 절감용 — 오래된 메시지를 LLM 으로 압축한 요약. Phase 2 에서 작성. */\n /**\n * 마지막 메시지 시각 — **비정규화**. 목록 정렬(최근 활동순)과 안 읽음 판정의 기준.\n *\n * 왜 비정규화인가: 세션마다 메시지를 집계하면(MAX/COUNT + group by) 목록 한 번에 집계 쿼리가 붙고,\n * 이 프레임워크는 5개 DB 드라이버를 지원해야 한다. 세션 컬럼 하나로 두면 정렬·비교가 값싸게 끝나고\n * 드라이버 차이가 없다. 메시지를 저장할 때 함께 갱신한다(activityPatch).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Timestamp of the last message in this session (denormalized for list ordering and unread comparison — avoids per-session aggregation across the supported DB drivers).' })\n lastMessageAt?: Date\n\n /**\n * 어느 대화인가 — `twin.board` · `figure.catalog` 같은 모드의 kind (ADR-0072 결정 3).\n *\n * **서버가 적는다.** 클라이언트가 보내는 값이 아니다 — 등록된 모드에서 나온 것만 적힌다(`addressee`\n * 와 같은 결). 한 대상 위에 대화가 둘 설 수 있게 되면서(A17), 세션이 자기가 어느 대화인지 알아야\n * 한다. 몰랐을 때 화면에서 이렇게 나왔다: 모드를 바꿔도 같은 세션이 그대로 남아, 「도형 찾기」로\n * 바꾼 뒤 보낸 말이 앞의 고치기 대화에 붙었다(2026-09-18, Figure 레인 :3300).\n *\n * 값이 없는 세션은 이 칸이 서기 전에 난 것이다. 다음 턴에 요청한 kind 로 한 번 찍고, 찍힌 뒤에는\n * 「요청한 모드와 다르면 거절」 하나만 남는다. 마이그레이션은 만들지 않는다.\n */\n @Column({ type: 'varchar', length: 64, nullable: true })\n @Field({ nullable: true, description: \"Which conversation this session is — the registered mode kind. Written by the server, never sent by the client.\" })\n kind?: string\n\n /**\n * 이 대화를 도메인 안 누구나 읽는가 — ADR-0073 판정 2.\n *\n * `open` 도메인 안이면 누구나 들어와 읽고 말한다. 초대 절차는 없다.\n * `private` 참여자만 읽는다.\n *\n * 새 세션은 **모두** `private` 으로 시작한다(2026-09-30 — `initialVisibility`). 앵커는 무엇에 대한\n * 이야기인지를 말할 뿐 누구에게 보이는지를 정하지 않는다. 칸의 기본값 `open` 은 칸이 서기 전의 옛 행을\n * 그날까지의 규칙대로 읽기 위한 것이고, 새 행은 늘 값을 적는다.\n *\n * owner 가 양방향으로 바꿀 수 있다. 공개로 바꾸기 전에 화면이 한 줄을 먼저 보이고, 공개 대화는 도크가\n * 다르게 그린다.\n *\n * open 이어도 답 행의 문 판정(ADR-0073 판정 1)은 그대로다 — 방에 들어온 것과 값을 읽는 것은 다르다.\n */\n @Column({ type: 'varchar', length: 16, default: 'open' })\n @Field({ description: \"Who may read this conversation: 'open' (anyone in the domain) or 'private' (participants only).\" })\n visibility!: string\n\n /** 목록에 보여줄 마지막 메시지 미리보기 — 목록에서 메시지 테이블을 다시 읽지 않기 위해 짧게 저장. */\n @Column({ nullable: true, length: 200 })\n @Field({ nullable: true, description: 'Short preview of the last message, stored so the session list does not need to query messages.' })\n lastMessagePreview?: string\n\n /**\n * 그 미리보기가 지나온 문 — ADR-0073 판정 1.\n *\n * 목록은 답을 **다시 읽는 두 번째 길**이다. 메시지 화면에서만 가리면 같은 문장이 목록에 그대로 뜬다.\n * 문이 둘이고 한쪽이 느슨하면 느슨한 쪽이 이긴다(ADR-0061 결정 1). 미리보기를 저장할 때 그 행의 문을\n * 함께 옮겨 적고, 목록도 같은 관문을 지난다.\n */\n @Column({ type: 'simple-array', nullable: true })\n @Field(() => [String], { nullable: true, description: 'Doors of the message the preview came from — the session list passes the same gate as the conversation.' })\n lastMessageDoors?: string[]\n\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n @Field({ nullable: true, description: 'Compressed summary of older messages (for token saving).' })\n lastSummary?: string\n\n /**\n * `lastSummary` 가 **어디까지 덮는지** — 이 메시지까지 요약에 반영됐다.\n *\n * 이게 없으면 누적 요약을 할 수 없다: 매 턴 전체를 다시 요약하거나(비용) 이미 요약한 구간을\n * 또 이어 붙인다(중복). 이력에서 이 id 를 못 찾으면(삭제·편집) 덮은 범위를 알 수 없으므로\n * 전부 미반영으로 보고 다시 요약한다 — 빠뜨리는 쪽보다 안전하다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Id of the last message folded into lastSummary — enables incremental summarization instead of re-reading the whole history.' })\n summaryUpToMessageId?: string\n\n /** 사용 모델 식별 — 'anthropic:claude-sonnet-4-6' 등 */\n @Column({ nullable: true })\n @Field({ nullable: true })\n aiClientId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n deletedAt?: Date\n\n // ── 관계 (TypeORM 만, GraphQL 노출은 별도 query 통해 페이징) ──\n @OneToMany(type => ChatMessage, message => message.session)\n messages?: ChatMessage[]\n\n /*\n * ── 보드 패치 관계를 끊었다 (2026-09-10) ──────────────────────────────────\n * 여기 `@OneToMany(PatchEntry)` 가 있었다. 그래서 **대화 세션이 보드 편집기를 알아야** 했고,\n * 세션 계열을 중립 패키지로 옮기려는 순간 `ai-assistant → board-ai → ai-assistant` 순환이\n * 된다.\n *\n * 끊어도 잃는 것이 없다 — 이 관계를 **자료로 읽는 자리가 하나도 없었다**(전 저장소에서\n * `session.patches` 를 읽는 코드 0 건). 유일한 쓰임이 `PatchEntry` 쪽의 역방향 선언 한 줄이었고,\n * TypeORM 은 역방향 없이도 `@ManyToOne` 을 만든다. 패치는 `sessionId` 로 찾는다\n * (`chatPatches` 가 이미 그렇게 한다).\n *\n * 방향은 남는다: **보드 패치가 세션을 안다. 세션은 보드 패치를 모른다.** 종속이 한쪽이면\n * 중립 쪽이 옮겨질 수 있다.\n */\n}\n"]}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
export type SessionVisibility = 'open' | 'private';
|
|
2
2
|
export declare const SESSION_VISIBILITIES: readonly SessionVisibility[];
|
|
3
3
|
/**
|
|
4
|
-
* 이렇게 만들어진 세션은 무엇으로
|
|
4
|
+
* 이렇게 만들어진 세션은 무엇으로 시작하나 — **언제나 비공개.**
|
|
5
5
|
*
|
|
6
|
-
* 앵커가 있으면
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* ── 앵커가 있으면 공개로 태어나던 것을 거둔 이유 (2026-09-30, 제품 오너) ──
|
|
7
|
+
* operato-office 의 대화는 앱 행(`application`)에 매달린다. 그래서 원장 · 급여를 묻는 경영자의 질문이 도메인
|
|
8
|
+
* 안 모든 직원에게 열린 방으로 태어났다. 목록은 그것을 「사업장 공개」 한 줄로만 말해서 공개와 비공개가 한눈에
|
|
9
|
+
* 갈리지 않았다. 앵커는 「무엇에 대한 이야기인가」이지 「누구에게 보이나」가 아니다.
|
|
10
|
+
*
|
|
11
|
+
* 공개는 owner 가 고른다(`setSessionVisibility`) — 화면이 여는 순간 되묻고, 공개 대화는 도크가 다르게 그린다.
|
|
9
12
|
*/
|
|
10
|
-
export declare function initialVisibility(
|
|
13
|
+
export declare function initialVisibility(_session?: {
|
|
11
14
|
anchorType?: string | null;
|
|
12
15
|
copyOf?: string | null;
|
|
13
16
|
}): SessionVisibility;
|
|
@@ -11,23 +11,17 @@ exports.mayReadSession = mayReadSession;
|
|
|
11
11
|
exports.mayWriteSession = mayWriteSession;
|
|
12
12
|
exports.SESSION_VISIBILITIES = ['open', 'private'];
|
|
13
13
|
/**
|
|
14
|
-
* 이렇게 만들어진 세션은 무엇으로
|
|
14
|
+
* 이렇게 만들어진 세션은 무엇으로 시작하나 — **언제나 비공개.**
|
|
15
15
|
*
|
|
16
|
-
* 앵커가 있으면
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* ── 앵커가 있으면 공개로 태어나던 것을 거둔 이유 (2026-09-30, 제품 오너) ──
|
|
17
|
+
* operato-office 의 대화는 앱 행(`application`)에 매달린다. 그래서 원장 · 급여를 묻는 경영자의 질문이 도메인
|
|
18
|
+
* 안 모든 직원에게 열린 방으로 태어났다. 목록은 그것을 「사업장 공개」 한 줄로만 말해서 공개와 비공개가 한눈에
|
|
19
|
+
* 갈리지 않았다. 앵커는 「무엇에 대한 이야기인가」이지 「누구에게 보이나」가 아니다.
|
|
20
|
+
*
|
|
21
|
+
* 공개는 owner 가 고른다(`setSessionVisibility`) — 화면이 여는 순간 되묻고, 공개 대화는 도크가 다르게 그린다.
|
|
19
22
|
*/
|
|
20
|
-
function initialVisibility(
|
|
21
|
-
|
|
22
|
-
return 'private';
|
|
23
|
-
/*
|
|
24
|
-
* `'none'` 은 **앵커가 아니다.** 저장되는 앵커 종류 목록에 그 글자가 들어 있어서(`session-anchor.ts`
|
|
25
|
-
* — 대상이 없다는 것도 행에 적어야 하므로), 「값이 있으면 앵커가 있다」로 읽으면 대상 없는 개인 질문이
|
|
26
|
-
* 공개 방으로 태어난다. ADR-0073 결정 2 가 그것을 `private` 로 못박았다 — 「앵커 없는 세션(`twin.none`
|
|
27
|
-
* 같은 개인 질문)」.
|
|
28
|
-
*/
|
|
29
|
-
const anchored = !!session.anchorType && session.anchorType !== 'none';
|
|
30
|
-
return anchored ? 'open' : 'private';
|
|
23
|
+
function initialVisibility(_session = {}) {
|
|
24
|
+
return 'private';
|
|
31
25
|
}
|
|
32
26
|
/** 이 사람이 이 대화를 읽을 수 있나. open 이면 도메인 안 누구나, private 이면 참여자만. */
|
|
33
27
|
function mayReadSession(visibility, isParticipant) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-visibility.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-visibility.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;
|
|
1
|
+
{"version":3,"file":"session-visibility.js","sourceRoot":"","sources":["../../../server/service/chat-session/session-visibility.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAgBH,8CAEC;AAGD,wCAEC;AASD,0CAEC;AA9BY,QAAA,oBAAoB,GAAiC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAErF;;;;;;;;;GASG;AACH,SAAgB,iBAAiB,CAAC,WAAmE,EAAE;IACrG,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,+DAA+D;AAC/D,SAAgB,cAAc,CAAC,UAAqC,EAAE,aAAsB;IAC1F,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC,KAAK,MAAM,IAAI,aAAa,CAAA;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,UAAqC,EAAE,aAAsB;IAC3F,OAAO,cAAc,CAAC,UAAU,EAAE,aAAa,CAAC,CAAA;AAClD,CAAC","sourcesContent":["/*\n * 새 대화가 공개로 태어나는가 — ADR-0073 판정 2.\n *\n * 판정을 세션을 만드는 자리마다 적으면 한 곳이 빠진 날 그 세션만 다른 규칙으로 산다. 한 줄로 모은다.\n */\n\nexport type SessionVisibility = 'open' | 'private'\n\nexport const SESSION_VISIBILITIES: readonly SessionVisibility[] = ['open', 'private']\n\n/**\n * 이렇게 만들어진 세션은 무엇으로 시작하나 — **언제나 비공개.**\n *\n * ── 앵커가 있으면 공개로 태어나던 것을 거둔 이유 (2026-09-30, 제품 오너) ──\n * operato-office 의 대화는 앱 행(`application`)에 매달린다. 그래서 원장 · 급여를 묻는 경영자의 질문이 도메인\n * 안 모든 직원에게 열린 방으로 태어났다. 목록은 그것을 「사업장 공개」 한 줄로만 말해서 공개와 비공개가 한눈에\n * 갈리지 않았다. 앵커는 「무엇에 대한 이야기인가」이지 「누구에게 보이나」가 아니다.\n *\n * 공개는 owner 가 고른다(`setSessionVisibility`) — 화면이 여는 순간 되묻고, 공개 대화는 도크가 다르게 그린다.\n */\nexport function initialVisibility(_session: { anchorType?: string | null; copyOf?: string | null } = {}): SessionVisibility {\n return 'private'\n}\n\n/** 이 사람이 이 대화를 읽을 수 있나. open 이면 도메인 안 누구나, private 이면 참여자만. */\nexport function mayReadSession(visibility: string | null | undefined, isParticipant: boolean): boolean {\n return (visibility ?? 'open') === 'open' || isParticipant\n}\n\n/**\n * 이 사람이 이 대화에 **쓸** 수 있나 — 읽을 수 있는 사람만 쓴다.\n *\n * 읽는 문(`chatMessages`)과 목록만 막고 쓰는 문을 열어 두면, 비공개 대화의 id 를 가진 사람이 그 안에 말을\n * 넣을 수 있었다. `assistantChat` 에서는 더 나쁘다 — 답이 그 대화의 이력 위에서 만들어지므로, 읽을 수 없는\n * 내용이 답으로 새어 나간다(2026-09-24, operato-ecs 두 사람 시험 7번). 쓰는 문도 같은 관문을 지난다.\n */\nexport function mayWriteSession(visibility: string | null | undefined, isParticipant: boolean): boolean {\n return mayReadSession(visibility, isParticipant)\n}\n"]}
|
|
@@ -511,7 +511,7 @@ let ChatSessionResolver = class ChatSessionResolver {
|
|
|
511
511
|
const session = await repo.save({
|
|
512
512
|
domain,
|
|
513
513
|
...(0, session_anchor_js_1.anchorColumns)(anchorType, anchorId),
|
|
514
|
-
/*
|
|
514
|
+
/* 새 대화는 비공개로 태어난다 — 공개는 owner 가 고른다(ADR-0073 결정 2, 2026-09-30 고침). */
|
|
515
515
|
visibility: (0, session_visibility_js_1.initialVisibility)({ anchorType }),
|
|
516
516
|
creator: user,
|
|
517
517
|
updater: user
|