@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
@@ -32,6 +32,7 @@ import {
32
32
  injectMentionUserIds,
33
33
  stripMentionRefids
34
34
  } from './markdown.js'
35
+ import { acceptOf, chatAttachmentUrl, refuseAttachments } from '../utils/attachment-policy.js'
35
36
  import { buildAssistantChatInput, buildChatMutationInput } from './chat-input-builder.js'
36
37
  import { receiveRemoteMessage, reconcileSentLine, remoteMessageToLine } from './chat-echo-dedup.js'
37
38
  import './mention-popup.js'
@@ -116,6 +117,8 @@ interface ChatLine {
116
117
  */
117
118
  groundingWarnings?: string[]
118
119
  attachments?: Array<{
120
+ /** A stored file of the conversation (shared dock §9) - opened by its route, not carried inline. */
121
+ id?: string
119
122
  name: string
120
123
  size: number
121
124
  type: string
@@ -168,6 +171,18 @@ const BOARD_AI_CHAT_MUTATION = gql`
168
171
  * front of the model this turn, so a surface that granted a category and sees it missing knows
169
172
  * the registration never happened, instead of reading a vague answer as a refusal.
170
173
  */
174
+ /* Files go to the conversation first; the turn then names them (design/ai/shared-dock.md §9). */
175
+ const ATTACH_TO_CONVERSATION_MUTATION = gql`
176
+ mutation AttachToConversation($sessionId: String!, $files: [Upload!]!, $kind: String) {
177
+ attachToConversation(sessionId: $sessionId, files: $files, kind: $kind) {
178
+ id
179
+ name
180
+ mimetype
181
+ size
182
+ }
183
+ }
184
+ `
185
+
171
186
  const ASSISTANT_CHAT_MUTATION = gql`
172
187
  mutation AssistantChat($input: AssistantChatInput!) {
173
188
  assistantChat(input: $input) {
@@ -223,6 +238,7 @@ const ASSISTANT_MODE_QUERY = gql`
223
238
  examples
224
239
  footerNotice
225
240
  stagedNotice
241
+ attachments
226
242
  }
227
243
  }
228
244
  `
@@ -265,6 +281,7 @@ const CHAT_MESSAGES_QUERY = gql`
265
281
  relatedPatchId
266
282
  toolUsagesJson
267
283
  groundingWarningsJson
284
+ attachmentsJson
268
285
  createdAt
269
286
  redacted
270
287
  creator {
@@ -286,6 +303,7 @@ const CHAT_MESSAGE_SUBSCRIPTION = gql`
286
303
  relatedPatchId
287
304
  toolUsagesJson
288
305
  groundingWarningsJson
306
+ attachmentsJson
289
307
  createdAt
290
308
  redacted
291
309
  creator {
@@ -595,9 +613,36 @@ export class OxAssistantChat extends LitElement {
595
613
  * `attachments` 짐 자체는 그대로 둔다: figure 의 도구가 거기서 이미지를 찾는다
596
614
  * (`figure-tools.ts` — `ctx.host.image` 가 없을 때의 갈래).
597
615
  */
616
+ /** This conversation talks to the neutral door - its attachments follow the mode's policy (§9). */
617
+ private get usesAttachmentPolicy(): boolean {
618
+ return this.conversation.chatEndpoint === 'assistantChat'
619
+ }
620
+
621
+ /** Whether the attach button stands: a mode that declares nothing takes nothing (the board door keeps its images). */
622
+ private get acceptsAttachments(): boolean {
623
+ return !this.usesAttachmentPolicy || !!this.modeView?.attachments?.kinds?.length
624
+ }
625
+
626
+ /** A refusal in the reader's words - the same keys the door uses. */
627
+ private attachmentRefusalText(refusal: NonNullable<ReturnType<typeof refuseAttachments>>): string {
628
+ const params: any = { ...refusal }
629
+ if (refusal.code === 'attachment-too-large') params.max = Math.round(refusal.maxBytes / 1024 / 1024)
630
+ if (refusal.code === 'too-many-attachments') params.max = refusal.maxFiles
631
+ if (refusal.code === 'attachment-kind-not-accepted') params.kinds = refusal.kinds.join(', ')
632
+ return String(i18next.t(`ai-assistant.text.${refusal.code}`, { ...params, defaultValue: refusal.code }))
633
+ }
634
+
598
635
  private async onAttachFiles(files: FileList | File[] | null | undefined) {
599
636
  if (!files || files.length === 0) return
600
637
  const list = Array.from(files)
638
+ if (this.usesAttachmentPolicy) {
639
+ /* Said before anything is sent - never cut silently (§9-1). */
640
+ const refusal = refuseAttachments(this.modeView?.attachments, [...this.attachments, ...list].map(f => ({ name: f.name, type: f.type, size: f.size })))
641
+ if (refusal) {
642
+ this.errorMessage = this.attachmentRefusalText(refusal)
643
+ return
644
+ }
645
+ }
601
646
  const newAttachments: ChatAttachment[] = []
602
647
 
603
648
  for (const file of list) {
@@ -2889,21 +2934,24 @@ export class OxAssistantChat extends LitElement {
2889
2934
  ${line.attachments && line.attachments.length > 0
2890
2935
  ? html`
2891
2936
  <div class="msg-attachments">
2892
- ${line.attachments.map(att =>
2893
- att.type?.startsWith('image/') || att.url?.startsWith('data:image/') || att.base64
2937
+ ${line.attachments.map(att => {
2938
+ /* A stored file opens through the conversation's own route (§9-3); a fresh one shows its local copy. */
2939
+ const href = att.url || att.base64 || (att.id && this.sessionId ? chatAttachmentUrl(this.sessionId, att.id) : '')
2940
+ /* By type - a text file carries base64 too, and drew as a broken image (found on :3700). */
2941
+ return att.type?.startsWith('image/') || att.url?.startsWith('data:image/') || att.base64?.startsWith('data:image/')
2894
2942
  ? html`
2895
- <div class="msg-attachment-img-wrap" title=${att.name}>
2896
- <img src=${att.url || att.base64} alt=${att.name} />
2943
+ <a class="msg-attachment-img-wrap" title=${att.name} href=${href} target="_blank" rel="noopener">
2944
+ <img src=${href} alt=${att.name} />
2897
2945
  <span class="msg-attachment-label">${att.name}</span>
2898
- </div>
2946
+ </a>
2899
2947
  `
2900
2948
  : html`
2901
- <div class="msg-attachment-file-chip" title=${att.name}>
2949
+ <a class="msg-attachment-file-chip" title=${att.name} href=${href || nothing} target="_blank" rel="noopener">
2902
2950
  <md-icon>description</md-icon>
2903
2951
  <span>${att.name}</span>
2904
- </div>
2952
+ </a>
2905
2953
  `
2906
- )}
2954
+ })}
2907
2955
  </div>
2908
2956
  `
2909
2957
  : nothing}
@@ -2976,7 +3024,7 @@ export class OxAssistantChat extends LitElement {
2976
3024
  id="chat-file-input"
2977
3025
  type="file"
2978
3026
  multiple
2979
- accept="image/*"
3027
+ accept=${this.usesAttachmentPolicy ? acceptOf(this.modeView?.attachments) : 'image/*'}
2980
3028
  style="display: none"
2981
3029
  @change=${(e: Event) => {
2982
3030
  const input = e.target as HTMLInputElement
@@ -2986,14 +3034,16 @@ export class OxAssistantChat extends LitElement {
2986
3034
  }
2987
3035
  }}
2988
3036
  />
2989
- <button
2990
- type="button"
2991
- class="action"
2992
- @click=${() => this.renderRoot.querySelector<HTMLInputElement>('#chat-file-input')?.click()}
2993
- title=${i18next.t('ai-assistant.tooltip.attach-file', { defaultValue: 'attach an image (drag & drop or paste supported)' })}>
2994
- <md-icon>attach_file</md-icon>
2995
- ${i18next.t('ai-assistant.button.attach', { defaultValue: 'Attach' })}
2996
- </button>
3037
+ ${this.acceptsAttachments
3038
+ ? html`<button
3039
+ type="button"
3040
+ class="action"
3041
+ @click=${() => this.renderRoot.querySelector<HTMLInputElement>('#chat-file-input')?.click()}
3042
+ title=${i18next.t('ai-assistant.tooltip.attach-file', { defaultValue: 'attach an image (drag & drop or paste supported)' })}>
3043
+ <md-icon>attach_file</md-icon>
3044
+ ${i18next.t('ai-assistant.button.attach', { defaultValue: 'Attach' })}
3045
+ </button>`
3046
+ : nothing}
2997
3047
  ${hasMessages
2998
3048
  ? html`
2999
3049
  <button
@@ -4420,6 +4470,33 @@ export class OxAssistantChat extends LitElement {
4420
4470
  this.busy = false
4421
4471
  }
4422
4472
 
4473
+ /* The neutral door takes stored files by id: upload them to the conversation first (§9-2). */
4474
+ let attachmentIds: string[] | undefined
4475
+ if (attachmentsToSend.length > 0 && this.usesAttachmentPolicy) {
4476
+ if (!this.sessionId) {
4477
+ this.errorMessage = i18next.t('ai-assistant.text.attachments-need-session', { defaultValue: 'attachments need a saved conversation.' })
4478
+ this.attachments = attachmentsToSend
4479
+ return
4480
+ }
4481
+ this.busy = true
4482
+ try {
4483
+ const uploaded = await client.mutate({
4484
+ mutation: ATTACH_TO_CONVERSATION_MUTATION,
4485
+ variables: { sessionId: this.sessionId, files: attachmentsToSend.map(a => a.file), kind: this.conversation.kind },
4486
+ /* The shell's client sends multipart only when asked - without it a File travels as {} and is refused. */
4487
+ context: { hasUpload: true }
4488
+ })
4489
+ if (uploaded.errors?.length) throw new Error(uploaded.errors.map(error => error.message).join('\n'))
4490
+ attachmentIds = (uploaded.data?.attachToConversation ?? []).map((file: any) => file.id)
4491
+ } catch (error) {
4492
+ this.errorMessage = error instanceof Error ? error.message : String(error)
4493
+ this.attachments = attachmentsToSend
4494
+ this.busy = false
4495
+ return
4496
+ }
4497
+ this.busy = false
4498
+ }
4499
+
4423
4500
  let requestContext: AssistantRequestContext
4424
4501
  let liveBoard: any
4425
4502
  let requestBase: any
@@ -4475,7 +4552,7 @@ export class OxAssistantChat extends LitElement {
4475
4552
  this.lines = [...this.lines, { role: 'assistant', content: '', pending: true, _localId: asstLocalId }]
4476
4553
 
4477
4554
  try {
4478
- if (attachmentsToSend.length > 0) {
4555
+ if (attachmentsToSend.length > 0 && !attachmentIds) {
4479
4556
  requestContext.hostContext = {
4480
4557
  ...requestContext.hostContext,
4481
4558
  attachments: attachmentsToSend.map(a => ({
@@ -4512,7 +4589,8 @@ export class OxAssistantChat extends LitElement {
4512
4589
  history,
4513
4590
  mode: { kind: this.conversation.kind!, target: this.conversation.target },
4514
4591
  truncateAfterMessageId: this._truncateAfterId,
4515
- hostContext: requestContext.hostContext
4592
+ hostContext: requestContext.hostContext,
4593
+ attachmentIds
4516
4594
  })
4517
4595
  : buildChatMutationInput({
4518
4596
  sessionId: this.sessionId,
@@ -4529,7 +4607,9 @@ export class OxAssistantChat extends LitElement {
4529
4607
  categories: this.categories,
4530
4608
  componentSchemas: this.componentSchemas,
4531
4609
  selectedRefids: this.selectedRefids ?? [],
4532
- mentions
4610
+ mentions,
4611
+ /* With a declared mode the server owns persona and tools; the board door still carries the board. */
4612
+ mode: this.conversation.kind ? { kind: this.conversation.kind, target: this.conversation.target } : undefined
4533
4613
  })
4534
4614
  }
4535
4615
  })