@things-factory/ai-assistant 10.1.46 → 10.1.50

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 (174) hide show
  1. package/client/components/assistant-chat.ts +100 -20
  2. package/client/components/assistant-dock.ts +617 -0
  3. package/client/components/assistant-mode.ts +29 -0
  4. package/client/components/assistant-session-manager.ts +607 -0
  5. package/client/components/chat-echo-dedup.ts +4 -0
  6. package/client/components/chat-input-builder.ts +12 -2
  7. package/client/components/panel-aria.test.ts +13 -8
  8. package/client/index.ts +10 -0
  9. package/client/setup-assistant-dock.ts +109 -0
  10. package/client/utils/assistant-activity.ts +122 -0
  11. package/client/utils/assistant-ask.ts +44 -0
  12. package/client/utils/assistant-capability-read.ts +24 -0
  13. package/client/utils/assistant-capability.test.ts +17 -0
  14. package/client/utils/assistant-capability.ts +19 -0
  15. package/client/utils/assistant-proposals.ts +50 -0
  16. package/client/utils/assistant-session-actions.ts +18 -0
  17. package/client/utils/assistant-session-admin.ts +167 -0
  18. package/client/utils/assistant-session-controller.ts +2 -0
  19. package/client/utils/assistant-session-transport.ts +6 -2
  20. package/client/utils/assistant-surface.ts +157 -0
  21. package/client/utils/attachment-policy.ts +71 -0
  22. package/dist-client/components/assistant-chat.d.ts +6 -0
  23. package/dist-client/components/assistant-chat.js +101 -21
  24. package/dist-client/components/assistant-chat.js.map +1 -1
  25. package/dist-client/components/assistant-dock.d.ts +74 -0
  26. package/dist-client/components/assistant-dock.js +641 -0
  27. package/dist-client/components/assistant-dock.js.map +1 -0
  28. package/dist-client/components/assistant-mode.d.ts +6 -0
  29. package/dist-client/components/assistant-mode.js +24 -0
  30. package/dist-client/components/assistant-mode.js.map +1 -1
  31. package/dist-client/components/assistant-session-manager.d.ts +77 -0
  32. package/dist-client/components/assistant-session-manager.js +652 -0
  33. package/dist-client/components/assistant-session-manager.js.map +1 -0
  34. package/dist-client/components/chat-echo-dedup.js +4 -0
  35. package/dist-client/components/chat-echo-dedup.js.map +1 -1
  36. package/dist-client/components/chat-input-builder.d.ts +11 -0
  37. package/dist-client/components/chat-input-builder.js +4 -2
  38. package/dist-client/components/chat-input-builder.js.map +1 -1
  39. package/dist-client/components/panel-aria.test.js +12 -8
  40. package/dist-client/components/panel-aria.test.js.map +1 -1
  41. package/dist-client/index.d.ts +10 -0
  42. package/dist-client/index.js +10 -0
  43. package/dist-client/index.js.map +1 -1
  44. package/dist-client/setup-assistant-dock.d.ts +15 -0
  45. package/dist-client/setup-assistant-dock.js +110 -0
  46. package/dist-client/setup-assistant-dock.js.map +1 -0
  47. package/dist-client/tsconfig.tsbuildinfo +1 -1
  48. package/dist-client/utils/assistant-activity.d.ts +22 -0
  49. package/dist-client/utils/assistant-activity.js +104 -0
  50. package/dist-client/utils/assistant-activity.js.map +1 -0
  51. package/dist-client/utils/assistant-ask.d.ts +23 -0
  52. package/dist-client/utils/assistant-ask.js +18 -0
  53. package/dist-client/utils/assistant-ask.js.map +1 -0
  54. package/dist-client/utils/assistant-capability-read.d.ts +6 -0
  55. package/dist-client/utils/assistant-capability-read.js +23 -0
  56. package/dist-client/utils/assistant-capability-read.js.map +1 -0
  57. package/dist-client/utils/assistant-capability.d.ts +4 -0
  58. package/dist-client/utils/assistant-capability.js +17 -0
  59. package/dist-client/utils/assistant-capability.js.map +1 -0
  60. package/dist-client/utils/assistant-capability.test.d.ts +1 -0
  61. package/dist-client/utils/assistant-capability.test.js +16 -0
  62. package/dist-client/utils/assistant-capability.test.js.map +1 -0
  63. package/dist-client/utils/assistant-proposals.d.ts +29 -0
  64. package/dist-client/utils/assistant-proposals.js +22 -0
  65. package/dist-client/utils/assistant-proposals.js.map +1 -0
  66. package/dist-client/utils/assistant-session-actions.d.ts +12 -0
  67. package/dist-client/utils/assistant-session-actions.js +19 -0
  68. package/dist-client/utils/assistant-session-actions.js.map +1 -0
  69. package/dist-client/utils/assistant-session-admin.d.ts +72 -0
  70. package/dist-client/utils/assistant-session-admin.js +81 -0
  71. package/dist-client/utils/assistant-session-admin.js.map +1 -0
  72. package/dist-client/utils/assistant-session-controller.d.ts +2 -0
  73. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  74. package/dist-client/utils/assistant-session-transport.js +6 -2
  75. package/dist-client/utils/assistant-session-transport.js.map +1 -1
  76. package/dist-client/utils/assistant-surface.d.ts +72 -0
  77. package/dist-client/utils/assistant-surface.js +99 -0
  78. package/dist-client/utils/assistant-surface.js.map +1 -0
  79. package/dist-client/utils/attachment-policy.d.ts +35 -0
  80. package/dist-client/utils/attachment-policy.js +56 -0
  81. package/dist-client/utils/attachment-policy.js.map +1 -0
  82. package/dist-server/service/assistant-chat-resolver.d.ts +1 -0
  83. package/dist-server/service/assistant-chat-resolver.js +72 -4
  84. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  85. package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +1 -0
  86. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +7 -0
  87. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  88. package/dist-server/service/assistant-mode/resolve.d.ts +3 -0
  89. package/dist-server/service/assistant-mode/resolve.js +6 -2
  90. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  91. package/dist-server/service/assistant-mode/types.d.ts +6 -0
  92. package/dist-server/service/assistant-mode/types.js +0 -9
  93. package/dist-server/service/assistant-mode/types.js.map +1 -1
  94. package/dist-server/service/chat-message/chat-message.d.ts +6 -0
  95. package/dist-server/service/chat-message/chat-message.js +19 -0
  96. package/dist-server/service/chat-message/chat-message.js.map +1 -1
  97. package/dist-server/service/chat-session/attachment-policy.d.ts +44 -0
  98. package/dist-server/service/chat-session/attachment-policy.js +56 -0
  99. package/dist-server/service/chat-session/attachment-policy.js.map +1 -0
  100. package/dist-server/service/chat-session/attachment-route.d.ts +3 -0
  101. package/dist-server/service/chat-session/attachment-route.js +44 -0
  102. package/dist-server/service/chat-session/attachment-route.js.map +1 -0
  103. package/dist-server/service/chat-session/attachment-tools.d.ts +3 -0
  104. package/dist-server/service/chat-session/attachment-tools.js +59 -0
  105. package/dist-server/service/chat-session/attachment-tools.js.map +1 -0
  106. package/dist-server/service/chat-session/session-attachments.d.ts +36 -0
  107. package/dist-server/service/chat-session/session-attachments.js +84 -0
  108. package/dist-server/service/chat-session/session-attachments.js.map +1 -0
  109. package/dist-server/service/chat-session/session-inbox.d.ts +29 -0
  110. package/dist-server/service/chat-session/session-inbox.js +32 -8
  111. package/dist-server/service/chat-session/session-inbox.js.map +1 -1
  112. package/dist-server/service/chat-session/session-purge.d.ts +14 -0
  113. package/dist-server/service/chat-session/session-purge.js +77 -0
  114. package/dist-server/service/chat-session/session-purge.js.map +1 -0
  115. package/dist-server/service/chat-session/session-retention.d.ts +6 -0
  116. package/dist-server/service/chat-session/session-retention.js +20 -0
  117. package/dist-server/service/chat-session/session-retention.js.map +1 -0
  118. package/dist-server/service/chat-session/session-visibility.d.ts +8 -0
  119. package/dist-server/service/chat-session/session-visibility.js +11 -0
  120. package/dist-server/service/chat-session/session-visibility.js.map +1 -1
  121. package/dist-server/service/chat-session/session-write-gate.d.ts +11 -0
  122. package/dist-server/service/chat-session/session-write-gate.js +24 -0
  123. package/dist-server/service/chat-session/session-write-gate.js.map +1 -0
  124. package/dist-server/service/chat-session-participant/invite-notification.d.ts +1 -1
  125. package/dist-server/service/chat-session-participant/invite-notification.js +21 -6
  126. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
  127. package/dist-server/service/chat-session-participant/participant-lookup-roles.d.ts +3 -0
  128. package/dist-server/service/chat-session-participant/participant-lookup-roles.js +10 -0
  129. package/dist-server/service/chat-session-participant/participant-lookup-roles.js.map +1 -0
  130. package/dist-server/service/chat-session-participant/participant-lookup.d.ts +2 -0
  131. package/dist-server/service/chat-session-participant/participant-lookup.js +26 -0
  132. package/dist-server/service/chat-session-participant/participant-lookup.js.map +1 -0
  133. package/dist-server/service/chat-session-resolver.d.ts +17 -2
  134. package/dist-server/service/chat-session-resolver.js +135 -15
  135. package/dist-server/service/chat-session-resolver.js.map +1 -1
  136. package/dist-server/service/index.d.ts +6 -0
  137. package/dist-server/service/index.js +8 -1
  138. package/dist-server/service/index.js.map +1 -1
  139. package/dist-server/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +11 -4
  141. package/server/service/assistant-chat-resolver.ts +72 -5
  142. package/server/service/assistant-mode/assistant-mode-resolver.ts +6 -0
  143. package/server/service/assistant-mode/resolve.ts +9 -2
  144. package/server/service/assistant-mode/types.ts +6 -0
  145. package/server/service/chat-message/chat-message.ts +17 -0
  146. package/server/service/chat-message/fold-session-history.test.ts +18 -0
  147. package/server/service/chat-session/attachment-policy.test.ts +42 -0
  148. package/server/service/chat-session/attachment-policy.ts +68 -0
  149. package/server/service/chat-session/attachment-route.ts +45 -0
  150. package/server/service/chat-session/attachment-tools.ts +63 -0
  151. package/server/service/chat-session/session-attachments.ts +89 -0
  152. package/server/service/chat-session/session-inbox.test.ts +71 -1
  153. package/server/service/chat-session/session-inbox.ts +61 -11
  154. package/server/service/chat-session/session-purge.test.ts +24 -0
  155. package/server/service/chat-session/session-purge.ts +81 -0
  156. package/server/service/chat-session/session-retention.ts +17 -0
  157. package/server/service/chat-session/session-visibility.ts +11 -0
  158. package/server/service/chat-session/session-write-gate.test.ts +53 -0
  159. package/server/service/chat-session/session-write-gate.ts +31 -0
  160. package/server/service/chat-session-participant/invite-notification.test.ts +15 -0
  161. package/server/service/chat-session-participant/invite-notification.ts +24 -6
  162. package/server/service/chat-session-participant/participant-lookup-roles.ts +6 -0
  163. package/server/service/chat-session-participant/participant-lookup.ts +22 -0
  164. package/server/service/chat-session-resolver.ts +130 -13
  165. package/server/service/index.ts +6 -0
  166. package/test/assistant-dock.test.ts +118 -0
  167. package/test/assistant-session-manager.test.ts +126 -0
  168. package/test/attachment-policy-agree.test.ts +32 -0
  169. package/test/open-doors.test.ts +5 -2
  170. package/translations/en.json +61 -1
  171. package/translations/ja.json +61 -1
  172. package/translations/ko.json +61 -1
  173. package/translations/ms.json +61 -1
  174. package/translations/zh.json +61 -1
@@ -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}\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}\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"]}
@@ -16,8 +16,12 @@ export const assistantSessionTransport = {
16
16
  },
17
17
  async list(target) {
18
18
  const result = await client.query({
19
- query: gql `query($type:String!,$id:String!){chatSessionInbox(anchorType:$type,anchorId:$id,mineOnly:true,limit:100){id name kind}}`,
20
- variables: { type: target.anchorType, id: target.anchorId }, fetchPolicy: 'no-cache'
19
+ /*
20
+ * Every readable conversation of this kind on the anchor, not only mine - the same scope the manager
21
+ * shows by default (design/ai/shared-dock.md §2-6). Without a kind: every kind, as before.
22
+ */
23
+ query: gql `query($type:String!,$id:String!,$kind:String){chatSessionInbox(anchorType:$type,anchorId:$id,kind:$kind,limit:100){id name kind}}`,
24
+ variables: { type: target.anchorType, id: target.anchorId, kind: target.kind || null }, fetchPolicy: 'no-cache'
21
25
  });
22
26
  if (result.errors?.length)
23
27
  throw new Error(result.errors[0].message);
@@ -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,KAAK,EAAE,GAAG,CAAA,yHAAyH;YACnI,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,WAAW,EAAE,UAAU;SACrF,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,gBAAgB,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uCAAuC,EAAE,EAAE,YAAY,EAAE,uCAAuC,EAAE,CAAC,CAAC,CAAA;QACjL,OAAO,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAA;IACrC,CAAC;IACD,KAAK,CAAC,QAAQ,CAAC,SAAS;QACtB,6DAA6D;QAC7D,MAAM,MAAM,CAAC,MAAM,CAAC;YAClB,QAAQ,EAAE,GAAG,CAAA,uDAAuD;YACpE,SAAS,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;SAC7B,CAAC,CAAA;IACJ,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,MAAM;QACjB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACjC,QAAQ,EAAE,GAAG,CAAA,mGAAmG;YAChH,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE;SAC5D,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,MAAM,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uCAAuC,EAAE,EAAE,YAAY,EAAE,iCAAiC,EAAE,CAAC,CAAC,CAAA;QAC/J,OAAO,MAAM,CAAC,IAAI,CAAC,eAAe,CAAA;IACpC,CAAC;CACF,CAAA","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 query: gql`query($type:String!,$id:String!){chatSessionInbox(anchorType:$type,anchorId:$id,mineOnly:true,limit:100){id name kind}}`,\n variables: { type: target.anchorType, id: target.anchorId }, 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}}`,\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"]}
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}}`,\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}}`,\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"]}
@@ -0,0 +1,72 @@
1
+ /** The row a conversation hangs on - its session anchor (ADR-0072 decision 6). */
2
+ export interface AssistantAnchor {
3
+ anchorType: string;
4
+ anchorId: string;
5
+ }
6
+ /** No anchor could stand - said to the person as a fact and what to do. */
7
+ export interface AssistantStuck {
8
+ stuck: string;
9
+ message: string;
10
+ }
11
+ export type AnchorResult = AssistantAnchor | AssistantStuck;
12
+ export declare function isAnchor(result: AnchorResult | undefined): result is AssistantAnchor;
13
+ /** For a surface that authors a board - the chat gets the board, the dock applies its patches. */
14
+ export interface BoardSurface {
15
+ currentBoard?: () => any;
16
+ boardProvider?: () => any;
17
+ selectedRefids?: () => number[];
18
+ knownTypes?: () => string[];
19
+ categories?: () => any[];
20
+ componentSchemas?: () => any;
21
+ searchProvider?: (query: string) => any[];
22
+ applyPatch(detail: any): Promise<{
23
+ applied: number;
24
+ missed: number;
25
+ note?: string;
26
+ }> | {
27
+ applied: number;
28
+ missed: number;
29
+ note?: string;
30
+ };
31
+ revertPatch?(patchId: string): Promise<void> | void;
32
+ applyAction?(detail: any): Promise<void> | void;
33
+ /** The chat door for board authoring, until board-ai joins the neutral door (ADR-0061 decision 0-1). */
34
+ chatEndpoint?: 'boardAIChat' | 'assistantChat';
35
+ }
36
+ export interface AssistantSurface {
37
+ /** Identity - a different key is a different conversation. */
38
+ key: string;
39
+ /** Matched against the server's mode `subjects` (assistantModes(subject)). */
40
+ subject: string;
41
+ /** The session anchor, or why none could stand. May ask the server (a row uuid, a domain id). */
42
+ anchor(): Promise<AnchorResult> | AnchorResult;
43
+ /** Words for the header - never a raw identifier. */
44
+ label(): string;
45
+ detail?(): string;
46
+ icon?: string;
47
+ /** Read at the moment of sending - never cached, so a draft cannot go stale. */
48
+ hostContext?(): Record<string, unknown>;
49
+ board?: BoardSurface;
50
+ /** The dock opened another conversation for this surface - e.g. the board modeller records its direct edits under it. */
51
+ sessionChanged?(sessionId: string): void;
52
+ }
53
+ type Listener = () => void;
54
+ /** An editor with a draft says it is on screen. The last one provided wins until it is cleared. */
55
+ export declare function provideAssistantSurface(owner: object, surface: AssistantSurface): void;
56
+ /** The editor is gone or inactive. A later provider that replaced it is left alone. */
57
+ export declare function clearAssistantSurface(owner: object): void;
58
+ /** The editor's draft or identity changed - the dock reads it again. */
59
+ export declare function notifyAssistantSurface(owner: object): void;
60
+ /** How a page's `context.subject` of this type becomes a surface - declared once by the app. */
61
+ export declare function registerSubjectSurface(type: string, make: (subject: {
62
+ type: string;
63
+ id?: string;
64
+ }) => AssistantSurface): void;
65
+ /** The app's own conversation when the screen shows nothing in particular - a factory, a site, a catalog. */
66
+ export declare function registerAssistantHome(surface: AssistantSurface): void;
67
+ export declare function subscribeAssistantSurface(listener: Listener): () => void;
68
+ /** What the dock talks about now: a provided editor, else the page's subject, else the app's home. */
69
+ export declare function currentAssistantSurface(): AssistantSurface | undefined;
70
+ /** The app's home surface - for the header's "back to the app's conversation". */
71
+ export declare function assistantHomeSurface(): AssistantSurface | undefined;
72
+ export {};
@@ -0,0 +1,99 @@
1
+ /*
2
+ * What the screen shows - the one way a screen tells the AI dock (design/ai/shared-dock.md §3).
3
+ *
4
+ * Before this there were five ways: `context.subject` (warehouse), `BoardEditHost` (twin), `StationAITarget`
5
+ * (plant), window events (figure) and the board modeller's own panel. The dock now reads one answer:
6
+ *
7
+ * 1. an editor that holds a draft provides a surface provideAssistantSurface(owner, surface)
8
+ * 2. a page that shows one thing says so in its context get context() { return { subject: { type, id } } }
9
+ * and the app says once how such a subject becomes a surface registerSubjectSurface(type, make)
10
+ * 3. otherwise the app's home registerAssistantHome(surface)
11
+ *
12
+ * The first that answers wins. The dock follows changes through `subscribeAssistantSurface`.
13
+ */
14
+ import { store } from '@operato/shell';
15
+ export function isAnchor(result) {
16
+ return !!result && 'anchorId' in result && !!result.anchorId;
17
+ }
18
+ const provided = new Map();
19
+ let lastOwner;
20
+ const subjectMakers = new Map();
21
+ let home;
22
+ const listeners = new Set();
23
+ function changed() {
24
+ listeners.forEach(listener => {
25
+ try {
26
+ listener();
27
+ }
28
+ catch {
29
+ /* one listener's error does not stop the others */
30
+ }
31
+ });
32
+ }
33
+ /** An editor with a draft says it is on screen. The last one provided wins until it is cleared. */
34
+ export function provideAssistantSurface(owner, surface) {
35
+ provided.set(owner, surface);
36
+ lastOwner = owner;
37
+ changed();
38
+ }
39
+ /** The editor is gone or inactive. A later provider that replaced it is left alone. */
40
+ export function clearAssistantSurface(owner) {
41
+ if (!provided.delete(owner))
42
+ return;
43
+ if (lastOwner === owner)
44
+ lastOwner = [...provided.keys()].pop();
45
+ changed();
46
+ }
47
+ /** The editor's draft or identity changed - the dock reads it again. */
48
+ export function notifyAssistantSurface(owner) {
49
+ if (provided.has(owner))
50
+ changed();
51
+ }
52
+ /** How a page's `context.subject` of this type becomes a surface - declared once by the app. */
53
+ export function registerSubjectSurface(type, make) {
54
+ subjectMakers.set(type, make);
55
+ changed();
56
+ }
57
+ /** The app's own conversation when the screen shows nothing in particular - a factory, a site, a catalog. */
58
+ export function registerAssistantHome(surface) {
59
+ home = surface;
60
+ changed();
61
+ }
62
+ export function subscribeAssistantSurface(listener) {
63
+ listeners.add(listener);
64
+ return () => listeners.delete(listener);
65
+ }
66
+ let pageSubjectKey = '';
67
+ let pageSurface;
68
+ function surfaceFromPage() {
69
+ const subject = store.getState()?.route?.context?.subject;
70
+ const type = subject?.type;
71
+ const make = type ? subjectMakers.get(type) : undefined;
72
+ if (!make)
73
+ return undefined;
74
+ const key = JSON.stringify([type, subject?.id ?? '']);
75
+ if (key !== pageSubjectKey) {
76
+ pageSubjectKey = key;
77
+ pageSurface = make({ type, id: subject?.id });
78
+ }
79
+ return pageSurface;
80
+ }
81
+ /** What the dock talks about now: a provided editor, else the page's subject, else the app's home. */
82
+ export function currentAssistantSurface() {
83
+ return (lastOwner && provided.get(lastOwner)) || surfaceFromPage() || home;
84
+ }
85
+ /** The app's home surface - for the header's "back to the app's conversation". */
86
+ export function assistantHomeSurface() {
87
+ return home;
88
+ }
89
+ /* The page context lives in the store; a page change is a surface change. */
90
+ let lastRouteSubject = '';
91
+ store.subscribe(() => {
92
+ const subject = store.getState()?.route?.context?.subject;
93
+ const key = JSON.stringify([subject?.type ?? '', subject?.id ?? '']);
94
+ if (key !== lastRouteSubject) {
95
+ lastRouteSubject = key;
96
+ changed();
97
+ }
98
+ });
99
+ //# sourceMappingURL=assistant-surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-surface.js","sourceRoot":"","sources":["../../client/utils/assistant-surface.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the screen shows - the one way a screen tells the AI dock (design/ai/shared-dock.md §3).\n *\n * Before this there were five ways: `context.subject` (warehouse), `BoardEditHost` (twin), `StationAITarget`\n * (plant), window events (figure) and the board modeller's own panel. The dock now reads one answer:\n *\n * 1. an editor that holds a draft provides a surface provideAssistantSurface(owner, surface)\n * 2. a page that shows one thing says so in its context get context() { return { subject: { type, id } } }\n * and the app says once how such a subject becomes a surface registerSubjectSurface(type, make)\n * 3. otherwise the app's home registerAssistantHome(surface)\n *\n * The first that answers wins. The dock follows changes through `subscribeAssistantSurface`.\n */\nimport { store } from '@operato/shell'\n\n/** The row a conversation hangs on - its session anchor (ADR-0072 decision 6). */\nexport interface AssistantAnchor {\n anchorType: string\n anchorId: string\n}\n\n/** No anchor could stand - said to the person as a fact and what to do. */\nexport interface AssistantStuck {\n stuck: string\n message: string\n}\n\nexport type AnchorResult = AssistantAnchor | AssistantStuck\n\nexport function isAnchor(result: AnchorResult | undefined): result is AssistantAnchor {\n return !!result && 'anchorId' in result && !!result.anchorId\n}\n\n/** For a surface that authors a board - the chat gets the board, the dock applies its patches. */\nexport interface BoardSurface {\n currentBoard?: () => any\n boardProvider?: () => any\n selectedRefids?: () => number[]\n knownTypes?: () => string[]\n categories?: () => any[]\n componentSchemas?: () => any\n searchProvider?: (query: string) => any[]\n applyPatch(detail: any): Promise<{ applied: number; missed: number; note?: string }> | { applied: number; missed: number; note?: string }\n revertPatch?(patchId: string): Promise<void> | void\n applyAction?(detail: any): Promise<void> | void\n /** The chat door for board authoring, until board-ai joins the neutral door (ADR-0061 decision 0-1). */\n chatEndpoint?: 'boardAIChat' | 'assistantChat'\n}\n\nexport interface AssistantSurface {\n /** Identity - a different key is a different conversation. */\n key: string\n /** Matched against the server's mode `subjects` (assistantModes(subject)). */\n subject: string\n /** The session anchor, or why none could stand. May ask the server (a row uuid, a domain id). */\n anchor(): Promise<AnchorResult> | AnchorResult\n /** Words for the header - never a raw identifier. */\n label(): string\n detail?(): string\n icon?: string\n /** Read at the moment of sending - never cached, so a draft cannot go stale. */\n hostContext?(): Record<string, unknown>\n board?: BoardSurface\n /** The dock opened another conversation for this surface - e.g. the board modeller records its direct edits under it. */\n sessionChanged?(sessionId: string): void\n}\n\ntype Listener = () => void\n\nconst provided = new Map<object, AssistantSurface>()\nlet lastOwner: object | undefined\nconst subjectMakers = new Map<string, (subject: { type: string; id?: string }) => AssistantSurface>()\nlet home: AssistantSurface | undefined\nconst listeners = new Set<Listener>()\n\nfunction changed() {\n listeners.forEach(listener => {\n try {\n listener()\n } catch {\n /* one listener's error does not stop the others */\n }\n })\n}\n\n/** An editor with a draft says it is on screen. The last one provided wins until it is cleared. */\nexport function provideAssistantSurface(owner: object, surface: AssistantSurface): void {\n provided.set(owner, surface)\n lastOwner = owner\n changed()\n}\n\n/** The editor is gone or inactive. A later provider that replaced it is left alone. */\nexport function clearAssistantSurface(owner: object): void {\n if (!provided.delete(owner)) return\n if (lastOwner === owner) lastOwner = [...provided.keys()].pop()\n changed()\n}\n\n/** The editor's draft or identity changed - the dock reads it again. */\nexport function notifyAssistantSurface(owner: object): void {\n if (provided.has(owner)) changed()\n}\n\n/** How a page's `context.subject` of this type becomes a surface - declared once by the app. */\nexport function registerSubjectSurface(type: string, make: (subject: { type: string; id?: string }) => AssistantSurface): void {\n subjectMakers.set(type, make)\n changed()\n}\n\n/** The app's own conversation when the screen shows nothing in particular - a factory, a site, a catalog. */\nexport function registerAssistantHome(surface: AssistantSurface): void {\n home = surface\n changed()\n}\n\nexport function subscribeAssistantSurface(listener: Listener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n\nlet pageSubjectKey = ''\nlet pageSurface: AssistantSurface | undefined\n\nfunction surfaceFromPage(): AssistantSurface | undefined {\n const subject = (store.getState() as any)?.route?.context?.subject\n const type = subject?.type\n const make = type ? subjectMakers.get(type) : undefined\n if (!make) return undefined\n const key = JSON.stringify([type, subject?.id ?? ''])\n if (key !== pageSubjectKey) {\n pageSubjectKey = key\n pageSurface = make({ type, id: subject?.id })\n }\n return pageSurface\n}\n\n/** What the dock talks about now: a provided editor, else the page's subject, else the app's home. */\nexport function currentAssistantSurface(): AssistantSurface | undefined {\n return (lastOwner && provided.get(lastOwner)) || surfaceFromPage() || home\n}\n\n/** The app's home surface - for the header's \"back to the app's conversation\". */\nexport function assistantHomeSurface(): AssistantSurface | undefined {\n return home\n}\n\n/* The page context lives in the store; a page change is a surface change. */\nlet lastRouteSubject = ''\nstore.subscribe(() => {\n const subject = (store.getState() as any)?.route?.context?.subject\n const key = JSON.stringify([subject?.type ?? '', subject?.id ?? ''])\n if (key !== lastRouteSubject) {\n lastRouteSubject = key\n changed()\n }\n})\n"]}
@@ -0,0 +1,35 @@
1
+ export type AttachmentKind = 'image' | 'text';
2
+ export interface AttachmentPolicy {
3
+ kinds: AttachmentKind[];
4
+ maxBytes?: number;
5
+ maxFiles?: number;
6
+ }
7
+ export declare const DEFAULT_MAX_BYTES: number;
8
+ export declare const DEFAULT_MAX_FILES = 5;
9
+ export declare function attachmentKindOf(file: {
10
+ type?: string;
11
+ name?: string;
12
+ }): AttachmentKind | null;
13
+ export type AttachmentRefusal = {
14
+ code: 'attachments-not-accepted';
15
+ } | {
16
+ code: 'attachment-kind-not-accepted';
17
+ name: string;
18
+ kinds: AttachmentKind[];
19
+ } | {
20
+ code: 'attachment-too-large';
21
+ name: string;
22
+ maxBytes: number;
23
+ } | {
24
+ code: 'too-many-attachments';
25
+ maxFiles: number;
26
+ };
27
+ export declare function refuseAttachments(policy: AttachmentPolicy | null | undefined, files: {
28
+ name?: string;
29
+ type?: string;
30
+ size?: number;
31
+ }[]): AttachmentRefusal | null;
32
+ /** The file picker's `accept` for a policy. */
33
+ export declare function acceptOf(policy: AttachmentPolicy | null | undefined): string;
34
+ /** Where a conversation's file opens - the gated route, under the page's domain path. */
35
+ export declare function chatAttachmentUrl(sessionId: string, attachmentId: string): string;
@@ -0,0 +1,56 @@
1
+ /*
2
+ * What a conversation accepts as attachments - the screen's copy of the server rule
3
+ * (server/service/chat-session/attachment-policy.ts, design/ai/shared-dock.md §9-1).
4
+ *
5
+ * The screen checks first so the person hears "no" before anything is sent; the door checks again and is
6
+ * the authority. `test/attachment-policy-agree.test.ts` runs both copies over one table so they cannot drift.
7
+ */
8
+ export const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
9
+ export const DEFAULT_MAX_FILES = 5;
10
+ const TEXT_TYPES = ['text/plain', 'text/csv', 'text/markdown', 'application/json'];
11
+ const TEXT_EXTENSIONS = ['.txt', '.csv', '.md', '.json'];
12
+ export function attachmentKindOf(file) {
13
+ const type = (file.type || '').toLowerCase();
14
+ if (type.startsWith('image/'))
15
+ return 'image';
16
+ if (TEXT_TYPES.includes(type))
17
+ return 'text';
18
+ const name = (file.name || '').toLowerCase();
19
+ if (TEXT_EXTENSIONS.some(ext => name.endsWith(ext)))
20
+ return 'text';
21
+ return null;
22
+ }
23
+ export function refuseAttachments(policy, files) {
24
+ if (!files.length)
25
+ return null;
26
+ if (!policy || !policy.kinds?.length)
27
+ return { code: 'attachments-not-accepted' };
28
+ const maxFiles = policy.maxFiles ?? DEFAULT_MAX_FILES;
29
+ if (files.length > maxFiles)
30
+ return { code: 'too-many-attachments', maxFiles };
31
+ const maxBytes = policy.maxBytes ?? DEFAULT_MAX_BYTES;
32
+ for (const file of files) {
33
+ const kind = attachmentKindOf(file);
34
+ if (!kind || !policy.kinds.includes(kind)) {
35
+ return { code: 'attachment-kind-not-accepted', name: file.name || '', kinds: policy.kinds };
36
+ }
37
+ if ((file.size ?? 0) > maxBytes)
38
+ return { code: 'attachment-too-large', name: file.name || '', maxBytes };
39
+ }
40
+ return null;
41
+ }
42
+ /** The file picker's `accept` for a policy. */
43
+ export function acceptOf(policy) {
44
+ const accept = [];
45
+ if (policy?.kinds?.includes('image'))
46
+ accept.push('image/*');
47
+ if (policy?.kinds?.includes('text'))
48
+ accept.push(...TEXT_EXTENSIONS, ...TEXT_TYPES);
49
+ return accept.join(',');
50
+ }
51
+ /** Where a conversation's file opens - the gated route, under the page's domain path. */
52
+ export function chatAttachmentUrl(sessionId, attachmentId) {
53
+ const domainPath = /^\/domain\/[^/]+/.exec(location.pathname)?.[0] ?? '';
54
+ return `${domainPath}/chat-attachments/${encodeURIComponent(sessionId)}/${encodeURIComponent(attachmentId)}`;
55
+ }
56
+ //# sourceMappingURL=attachment-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-policy.js","sourceRoot":"","sources":["../../client/utils/attachment-policy.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What a conversation accepts as attachments - the screen's copy of the server rule\n * (server/service/chat-session/attachment-policy.ts, design/ai/shared-dock.md §9-1).\n *\n * The screen checks first so the person hears \"no\" before anything is sent; the door checks again and is\n * the authority. `test/attachment-policy-agree.test.ts` runs both copies over one table so they cannot drift.\n */\n\nexport type AttachmentKind = 'image' | 'text'\n\nexport interface AttachmentPolicy {\n kinds: AttachmentKind[]\n maxBytes?: number\n maxFiles?: number\n}\n\nexport const DEFAULT_MAX_BYTES = 5 * 1024 * 1024\nexport const DEFAULT_MAX_FILES = 5\n\nconst TEXT_TYPES = ['text/plain', 'text/csv', 'text/markdown', 'application/json']\nconst TEXT_EXTENSIONS = ['.txt', '.csv', '.md', '.json']\n\nexport function attachmentKindOf(file: { type?: string; name?: string }): AttachmentKind | null {\n const type = (file.type || '').toLowerCase()\n if (type.startsWith('image/')) return 'image'\n if (TEXT_TYPES.includes(type)) return 'text'\n const name = (file.name || '').toLowerCase()\n if (TEXT_EXTENSIONS.some(ext => name.endsWith(ext))) return 'text'\n return null\n}\n\nexport type AttachmentRefusal =\n | { code: 'attachments-not-accepted' }\n | { code: 'attachment-kind-not-accepted'; name: string; kinds: AttachmentKind[] }\n | { code: 'attachment-too-large'; name: string; maxBytes: number }\n | { code: 'too-many-attachments'; maxFiles: number }\n\nexport function refuseAttachments(\n policy: AttachmentPolicy | null | undefined,\n files: { name?: string; type?: string; size?: number }[]\n): AttachmentRefusal | null {\n if (!files.length) return null\n if (!policy || !policy.kinds?.length) return { code: 'attachments-not-accepted' }\n\n const maxFiles = policy.maxFiles ?? DEFAULT_MAX_FILES\n if (files.length > maxFiles) return { code: 'too-many-attachments', maxFiles }\n\n const maxBytes = policy.maxBytes ?? DEFAULT_MAX_BYTES\n for (const file of files) {\n const kind = attachmentKindOf(file)\n if (!kind || !policy.kinds.includes(kind)) {\n return { code: 'attachment-kind-not-accepted', name: file.name || '', kinds: policy.kinds }\n }\n if ((file.size ?? 0) > maxBytes) return { code: 'attachment-too-large', name: file.name || '', maxBytes }\n }\n return null\n}\n\n/** The file picker's `accept` for a policy. */\nexport function acceptOf(policy: AttachmentPolicy | null | undefined): string {\n const accept: string[] = []\n if (policy?.kinds?.includes('image')) accept.push('image/*')\n if (policy?.kinds?.includes('text')) accept.push(...TEXT_EXTENSIONS, ...TEXT_TYPES)\n return accept.join(',')\n}\n\n/** Where a conversation's file opens - the gated route, under the page's domain path. */\nexport function chatAttachmentUrl(sessionId: string, attachmentId: string): string {\n const domainPath = /^\\/domain\\/[^/]+/.exec(location.pathname)?.[0] ?? ''\n return `${domainPath}/chat-attachments/${encodeURIComponent(sessionId)}/${encodeURIComponent(attachmentId)}`\n}\n"]}
@@ -13,6 +13,7 @@ declare class AssistantChatInput {
13
13
  mode: AssistantModeInput;
14
14
  hostContext?: any;
15
15
  truncateAfterMessageId?: string;
16
+ attachmentIds?: string[];
16
17
  }
17
18
  declare class AssistantChatOutput {
18
19
  reply: string;
@@ -49,6 +49,11 @@ const session_kind_js_1 = require("./chat-session/session-kind.js");
49
49
  const reader_gate_js_1 = require("./chat-message/reader-gate.js");
50
50
  const mention_invite_js_1 = require("./chat-session-participant/mention-invite.js");
51
51
  const invite_notification_js_1 = require("./chat-session-participant/invite-notification.js");
52
+ const participant_lookup_js_1 = require("./chat-session-participant/participant-lookup.js");
53
+ const session_write_gate_js_1 = require("./chat-session/session-write-gate.js");
54
+ const attachment_policy_js_1 = require("./chat-session/attachment-policy.js");
55
+ const session_attachments_js_1 = require("./chat-session/session-attachments.js");
56
+ const registry_js_1 = require("./assistant-mode/registry.js");
52
57
  const resolve_js_1 = require("./assistant-mode/resolve.js");
53
58
  const request_translator_js_1 = require("./request-translator.js");
54
59
  /**
@@ -123,6 +128,13 @@ tslib_1.__decorate([
123
128
  }),
124
129
  tslib_1.__metadata("design:type", String)
125
130
  ], AssistantChatInput.prototype, "truncateAfterMessageId", void 0);
131
+ tslib_1.__decorate([
132
+ (0, type_graphql_1.Field)(() => [String], {
133
+ nullable: true,
134
+ description: 'Files stored with this conversation by attachToConversation, sent with this turn.'
135
+ }),
136
+ tslib_1.__metadata("design:type", Array)
137
+ ], AssistantChatInput.prototype, "attachmentIds", void 0);
126
138
  AssistantChatInput = tslib_1.__decorate([
127
139
  (0, type_graphql_1.InputType)({ description: 'One turn of a conversation that has no document of its own.' })
128
140
  ], AssistantChatInput);
@@ -222,6 +234,16 @@ let AssistantChatResolver = class AssistantChatResolver {
222
234
  const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } });
223
235
  if (!found)
224
236
  throw new Error(`ChatSession ${input.sessionId} not found`);
237
+ /*
238
+ * A private conversation is written by its people only - and here more than anywhere: the answer is
239
+ * built on this conversation's history, so an outsider's question would read it back to them.
240
+ */
241
+ await (0, session_write_gate_js_1.assertMayWriteSession)({
242
+ session: found,
243
+ userId: user?.id,
244
+ isParticipant: () => (0, participant_lookup_js_1.isSessionParticipant)(domain.id, found.id, user?.id),
245
+ t: (key, options) => t(key, options)
246
+ });
225
247
  if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {
226
248
  throw new Error(context.t('ai-assistant.text.other-figure-history', {
227
249
  defaultValue: 'this conversation belongs to another figure. open the conversation of the model you have open.'
@@ -253,6 +275,25 @@ let AssistantChatResolver = class AssistantChatResolver {
253
275
  (0, station_session_guard_js_1.assertStationSession)(session, input.hostContext, t);
254
276
  const lastUserMessage = input.messages[input.messages.length - 1];
255
277
  let userMessageId;
278
+ /*
279
+ * Files sent with this turn (design/ai/shared-dock.md §9). They were stored with this conversation by
280
+ * attachToConversation; one that is not this conversation's is refused, never read. The policy is the
281
+ * mode's - checked again here, since the door is the authority, not the screen.
282
+ */
283
+ const attachmentRows = [];
284
+ if (input.attachmentIds?.length) {
285
+ if (!session)
286
+ throw new Error(t('ai-assistant.text.attachments-need-session', { defaultValue: 'attachments need a saved conversation.' }));
287
+ for (const id of input.attachmentIds) {
288
+ const row = await (0, session_attachments_js_1.findChatAttachment)(domain.id, session.id, id);
289
+ if (!row)
290
+ throw new Error(t('ai-assistant.text.attachment-not-in-session', { defaultValue: 'an attachment does not belong to this conversation.' }));
291
+ attachmentRows.push(row);
292
+ }
293
+ const refusal = (0, attachment_policy_js_1.refuseAttachments)((0, registry_js_1.getAssistantMode)(input.mode.kind)?.attachments, attachmentRows.map(row => ({ name: row.name, type: row.mimetype, size: Number(row.size) })));
294
+ if (refusal)
295
+ throw new Error(t(`ai-assistant.text.${refusal.code}`, { ...refusal, defaultValue: refusal.code }));
296
+ }
256
297
  if (session && lastUserMessage?.role === 'user') {
257
298
  const previousLast = await messageRepo.findOne({
258
299
  where: { session: { id: session.id } },
@@ -268,6 +309,7 @@ let AssistantChatResolver = class AssistantChatResolver {
268
309
  * from the request — two sources for one fact can disagree.
269
310
  */
270
311
  addressee: 'assistant',
312
+ attachments: attachmentRows.length ? JSON.stringify(attachmentRows.map(session_attachments_js_1.describeAttachment)) : undefined,
271
313
  creator: user,
272
314
  updater: user,
273
315
  parentMessage: previousLast ? { id: previousLast.id } : undefined
@@ -300,7 +342,7 @@ let AssistantChatResolver = class AssistantChatResolver {
300
342
  visibility: session.visibility
301
343
  });
302
344
  if (invite.invited.length > 0) {
303
- const payload = (0, invite_notification_js_1.buildInviteNotification)(invite.invited, undefined, user);
345
+ const payload = (0, invite_notification_js_1.buildInviteNotification)(invite.invited, undefined, user, session?.name);
304
346
  try {
305
347
  if (payload)
306
348
  await context.state?.notify?.(payload);
@@ -356,7 +398,13 @@ let AssistantChatResolver = class AssistantChatResolver {
356
398
  storedRows.map(message => ({
357
399
  id: message.id,
358
400
  role: message.role,
359
- content: message.content,
401
+ /*
402
+ * A file sent in an earlier turn is not sent again - one line names it, and the model opens it
403
+ * with readChatAttachment when it needs to (cost is a usage axis, ADR-0061 decision 6).
404
+ */
405
+ content: message.id !== userMessageId && message.attachmentsJson?.length
406
+ ? [message.content, ...message.attachmentsJson.map(attachment_policy_js_1.attachmentHistoryLine)].join('\n')
407
+ : message.content,
360
408
  senderId: message.creator?.id,
361
409
  senderName: message.creator?.name,
362
410
  addressee: message.addressee
@@ -370,7 +418,27 @@ let AssistantChatResolver = class AssistantChatResolver {
370
418
  : undefined
371
419
  })
372
420
  : input.messages.map(message => ({ role: message.role, content: message.content }));
373
- if (input.hostContext?.image?.data && conversation.length > 0) {
421
+ if (attachmentRows.length && conversation.length > 0) {
422
+ const last = conversation[conversation.length - 1];
423
+ if (last.role === 'user' && typeof last.content === 'string') {
424
+ const parts = [{ type: 'text', text: last.content }];
425
+ for (const row of attachmentRows) {
426
+ const file = await (0, session_attachments_js_1.attachmentForModel)(row);
427
+ if (file.type === 'image') {
428
+ parts.push({ type: 'image', source: { kind: 'base64', data: file.data, mediaType: file.mediaType } });
429
+ }
430
+ else {
431
+ parts.push({
432
+ type: 'text',
433
+ text: `[attachment ${row.id}: ${file.name}${file.truncated ? ' - cut, the file is longer' : ''}]\n${file.text}`
434
+ });
435
+ }
436
+ }
437
+ last.content = parts;
438
+ }
439
+ }
440
+ else if (input.hostContext?.image?.data && conversation.length > 0) {
441
+ /* The screen-image path (figure's rendered view) - not a file of the conversation. */
374
442
  const last = conversation[conversation.length - 1];
375
443
  if (last.role === 'user' && typeof last.content === 'string') {
376
444
  const raw = input.hostContext.image;
@@ -464,7 +532,7 @@ let AssistantChatResolver = class AssistantChatResolver {
464
532
  domainId: domain.id,
465
533
  client,
466
534
  maxTurns: LLM_HISTORY_MAX_TURNS,
467
- locale: context.state?.locale
535
+ locale: context.state?.lng
468
536
  });
469
537
  }
470
538
  return {