@things-factory/ai-assistant 10.1.47 → 10.1.52

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 (160) hide show
  1. package/client/components/assistant-chat.ts +97 -19
  2. package/client/components/assistant-dock.ts +617 -0
  3. package/client/components/assistant-mode.ts +2 -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 +4 -1
  7. package/client/components/panel-aria.test.ts +13 -8
  8. package/client/index.ts +8 -0
  9. package/client/setup-assistant-dock.ts +118 -0
  10. package/client/utils/assistant-activity.ts +122 -0
  11. package/client/utils/assistant-ask.ts +44 -0
  12. package/client/utils/assistant-proposals.ts +50 -0
  13. package/client/utils/assistant-session-actions.ts +18 -0
  14. package/client/utils/assistant-session-admin.ts +167 -0
  15. package/client/utils/assistant-session-controller.ts +2 -0
  16. package/client/utils/assistant-session-transport.ts +6 -2
  17. package/client/utils/assistant-surface.ts +157 -0
  18. package/client/utils/attachment-policy.ts +71 -0
  19. package/dist-client/components/assistant-chat.d.ts +6 -0
  20. package/dist-client/components/assistant-chat.js +98 -20
  21. package/dist-client/components/assistant-chat.js.map +1 -1
  22. package/dist-client/components/assistant-dock.d.ts +74 -0
  23. package/dist-client/components/assistant-dock.js +641 -0
  24. package/dist-client/components/assistant-dock.js.map +1 -0
  25. package/dist-client/components/assistant-mode.d.ts +6 -0
  26. package/dist-client/components/assistant-mode.js.map +1 -1
  27. package/dist-client/components/assistant-session-manager.d.ts +77 -0
  28. package/dist-client/components/assistant-session-manager.js +652 -0
  29. package/dist-client/components/assistant-session-manager.js.map +1 -0
  30. package/dist-client/components/chat-echo-dedup.js +4 -0
  31. package/dist-client/components/chat-echo-dedup.js.map +1 -1
  32. package/dist-client/components/chat-input-builder.d.ts +2 -0
  33. package/dist-client/components/chat-input-builder.js +2 -1
  34. package/dist-client/components/chat-input-builder.js.map +1 -1
  35. package/dist-client/components/panel-aria.test.js +12 -8
  36. package/dist-client/components/panel-aria.test.js.map +1 -1
  37. package/dist-client/index.d.ts +8 -0
  38. package/dist-client/index.js +8 -0
  39. package/dist-client/index.js.map +1 -1
  40. package/dist-client/setup-assistant-dock.d.ts +15 -0
  41. package/dist-client/setup-assistant-dock.js +119 -0
  42. package/dist-client/setup-assistant-dock.js.map +1 -0
  43. package/dist-client/tsconfig.tsbuildinfo +1 -1
  44. package/dist-client/utils/assistant-activity.d.ts +22 -0
  45. package/dist-client/utils/assistant-activity.js +104 -0
  46. package/dist-client/utils/assistant-activity.js.map +1 -0
  47. package/dist-client/utils/assistant-ask.d.ts +23 -0
  48. package/dist-client/utils/assistant-ask.js +18 -0
  49. package/dist-client/utils/assistant-ask.js.map +1 -0
  50. package/dist-client/utils/assistant-proposals.d.ts +29 -0
  51. package/dist-client/utils/assistant-proposals.js +22 -0
  52. package/dist-client/utils/assistant-proposals.js.map +1 -0
  53. package/dist-client/utils/assistant-session-actions.d.ts +12 -0
  54. package/dist-client/utils/assistant-session-actions.js +19 -0
  55. package/dist-client/utils/assistant-session-actions.js.map +1 -0
  56. package/dist-client/utils/assistant-session-admin.d.ts +72 -0
  57. package/dist-client/utils/assistant-session-admin.js +81 -0
  58. package/dist-client/utils/assistant-session-admin.js.map +1 -0
  59. package/dist-client/utils/assistant-session-controller.d.ts +2 -0
  60. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  61. package/dist-client/utils/assistant-session-transport.js +6 -2
  62. package/dist-client/utils/assistant-session-transport.js.map +1 -1
  63. package/dist-client/utils/assistant-surface.d.ts +72 -0
  64. package/dist-client/utils/assistant-surface.js +99 -0
  65. package/dist-client/utils/assistant-surface.js.map +1 -0
  66. package/dist-client/utils/attachment-policy.d.ts +35 -0
  67. package/dist-client/utils/attachment-policy.js +56 -0
  68. package/dist-client/utils/attachment-policy.js.map +1 -0
  69. package/dist-server/service/assistant-chat-resolver.d.ts +1 -0
  70. package/dist-server/service/assistant-chat-resolver.js +71 -3
  71. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  72. package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +1 -0
  73. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +7 -0
  74. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  75. package/dist-server/service/assistant-mode/resolve.d.ts +3 -0
  76. package/dist-server/service/assistant-mode/resolve.js +6 -2
  77. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  78. package/dist-server/service/assistant-mode/types.d.ts +6 -0
  79. package/dist-server/service/assistant-mode/types.js +0 -9
  80. package/dist-server/service/assistant-mode/types.js.map +1 -1
  81. package/dist-server/service/chat-message/chat-message.d.ts +6 -0
  82. package/dist-server/service/chat-message/chat-message.js +19 -0
  83. package/dist-server/service/chat-message/chat-message.js.map +1 -1
  84. package/dist-server/service/chat-session/attachment-policy.d.ts +44 -0
  85. package/dist-server/service/chat-session/attachment-policy.js +56 -0
  86. package/dist-server/service/chat-session/attachment-policy.js.map +1 -0
  87. package/dist-server/service/chat-session/attachment-route.d.ts +3 -0
  88. package/dist-server/service/chat-session/attachment-route.js +44 -0
  89. package/dist-server/service/chat-session/attachment-route.js.map +1 -0
  90. package/dist-server/service/chat-session/attachment-tools.d.ts +3 -0
  91. package/dist-server/service/chat-session/attachment-tools.js +59 -0
  92. package/dist-server/service/chat-session/attachment-tools.js.map +1 -0
  93. package/dist-server/service/chat-session/session-attachments.d.ts +36 -0
  94. package/dist-server/service/chat-session/session-attachments.js +84 -0
  95. package/dist-server/service/chat-session/session-attachments.js.map +1 -0
  96. package/dist-server/service/chat-session/session-inbox.d.ts +29 -0
  97. package/dist-server/service/chat-session/session-inbox.js +32 -8
  98. package/dist-server/service/chat-session/session-inbox.js.map +1 -1
  99. package/dist-server/service/chat-session/session-purge.d.ts +14 -0
  100. package/dist-server/service/chat-session/session-purge.js +77 -0
  101. package/dist-server/service/chat-session/session-purge.js.map +1 -0
  102. package/dist-server/service/chat-session/session-retention.d.ts +6 -0
  103. package/dist-server/service/chat-session/session-retention.js +20 -0
  104. package/dist-server/service/chat-session/session-retention.js.map +1 -0
  105. package/dist-server/service/chat-session/session-visibility.d.ts +8 -0
  106. package/dist-server/service/chat-session/session-visibility.js +11 -0
  107. package/dist-server/service/chat-session/session-visibility.js.map +1 -1
  108. package/dist-server/service/chat-session/session-write-gate.d.ts +11 -0
  109. package/dist-server/service/chat-session/session-write-gate.js +24 -0
  110. package/dist-server/service/chat-session/session-write-gate.js.map +1 -0
  111. package/dist-server/service/chat-session-participant/invite-notification.d.ts +1 -1
  112. package/dist-server/service/chat-session-participant/invite-notification.js +21 -6
  113. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
  114. package/dist-server/service/chat-session-participant/participant-lookup-roles.d.ts +3 -0
  115. package/dist-server/service/chat-session-participant/participant-lookup-roles.js +10 -0
  116. package/dist-server/service/chat-session-participant/participant-lookup-roles.js.map +1 -0
  117. package/dist-server/service/chat-session-participant/participant-lookup.d.ts +2 -0
  118. package/dist-server/service/chat-session-participant/participant-lookup.js +26 -0
  119. package/dist-server/service/chat-session-participant/participant-lookup.js.map +1 -0
  120. package/dist-server/service/chat-session-resolver.d.ts +17 -2
  121. package/dist-server/service/chat-session-resolver.js +135 -15
  122. package/dist-server/service/chat-session-resolver.js.map +1 -1
  123. package/dist-server/service/index.d.ts +5 -0
  124. package/dist-server/service/index.js +5 -0
  125. package/dist-server/service/index.js.map +1 -1
  126. package/dist-server/tsconfig.tsbuildinfo +1 -1
  127. package/package.json +9 -2
  128. package/server/service/assistant-chat-resolver.ts +71 -4
  129. package/server/service/assistant-mode/assistant-mode-resolver.ts +6 -0
  130. package/server/service/assistant-mode/resolve.ts +9 -2
  131. package/server/service/assistant-mode/types.ts +6 -0
  132. package/server/service/chat-message/chat-message.ts +17 -0
  133. package/server/service/chat-session/attachment-policy.test.ts +42 -0
  134. package/server/service/chat-session/attachment-policy.ts +68 -0
  135. package/server/service/chat-session/attachment-route.ts +45 -0
  136. package/server/service/chat-session/attachment-tools.ts +63 -0
  137. package/server/service/chat-session/session-attachments.ts +89 -0
  138. package/server/service/chat-session/session-inbox.test.ts +71 -1
  139. package/server/service/chat-session/session-inbox.ts +61 -11
  140. package/server/service/chat-session/session-purge.test.ts +24 -0
  141. package/server/service/chat-session/session-purge.ts +81 -0
  142. package/server/service/chat-session/session-retention.ts +17 -0
  143. package/server/service/chat-session/session-visibility.ts +11 -0
  144. package/server/service/chat-session/session-write-gate.test.ts +53 -0
  145. package/server/service/chat-session/session-write-gate.ts +31 -0
  146. package/server/service/chat-session-participant/invite-notification.test.ts +15 -0
  147. package/server/service/chat-session-participant/invite-notification.ts +24 -6
  148. package/server/service/chat-session-participant/participant-lookup-roles.ts +6 -0
  149. package/server/service/chat-session-participant/participant-lookup.ts +22 -0
  150. package/server/service/chat-session-resolver.ts +130 -13
  151. package/server/service/index.ts +5 -0
  152. package/test/assistant-dock.test.ts +124 -0
  153. package/test/assistant-session-manager.test.ts +126 -0
  154. package/test/attachment-policy-agree.test.ts +32 -0
  155. package/test/open-doors.test.ts +5 -2
  156. package/translations/en.json +61 -1
  157. package/translations/ja.json +61 -1
  158. package/translations/ko.json +61 -1
  159. package/translations/ms.json +61 -1
  160. package/translations/zh.json +61 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-session-manager.js","sourceRoot":"","sources":["../../client/components/assistant-session-manager.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAA4C,MAAM,KAAK,CAAA;AAC9F,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EACL,qBAAqB,EACrB,cAAc,EAIf,MAAM,qCAAqC,CAAA;AAE5C,MAAM,IAAI,GAAG,EAAE,CAAA;AAGf,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,YAAoB,EAAE,UAAmC,EAAE,EAAE,EAAE,CACrF,OAAO,CAAC,CAAC,CAAC,wBAAwB,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;AAExE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEI,IAAM,uBAAuB,GAA7B,MAAM,uBAAwB,SAAQ,UAAU;IAAhD;;QA0PL,qGAAqG;QACzE,eAAU,GAAG,EAAE,CAAA;QACf,aAAQ,GAAG,EAAE,CAAA;QACzC,+DAA+D;QACnC,cAAS,GAAG,EAAE,CAAA;QAC1C,mEAAmE;QACnC,UAAK,GAA0B,qBAAqB,CAAA;QACpF,uFAAuF;QAC3D,SAAI,GAAG,EAAE,CAAA;QACrC,4EAA4E;QAC5C,eAAU,GAA2B,EAAE,CAAA;QAEtD,YAAO,GAA4B,EAAE,CAAA;QACrC,UAAK,GAAG,CAAC,CAAA;QACT,gBAAW,GAAG,CAAC,CAAA;QACf,WAAM,GAAG,EAAE,CAAA;QACX,aAAQ,GAAG,KAAK,CAAA;QAChB,UAAK,GAAkD,KAAK,CAAA;QAC5D,eAAU,GAAuD,KAAK,CAAA;QACtE,kBAAa,GAAG,KAAK,CAAA;QACrB,eAAU,GAAG,KAAK,CAAA;QAClB,aAAQ,GAAG,IAAI,CAAA;QACf,YAAO,GAAG,KAAK,CAAA;QACf,UAAK,GAAG,EAAE,CAAA;QACV,eAAU,GAAG,EAAE,CAAA;QAExB,eAAU,GAAG,CAAC,CAAA;IA2RxB,CAAC;aA9iBQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuPlB,AAvPY,CAuPZ;IA+BD,2FAA2F;IAC3F,KAAK,CAAC,MAAM;QACV,MAAM,UAAU,GAAG,EAAE,IAAI,CAAC,UAAU,CAAA;QACpC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;YACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC,CAAA;YACjC,OAAM;QACR,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;QACf,IAAI,CAAC;YACH;;;eAGG;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAClC,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE;oBACP,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;oBACpC,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;oBACjC,UAAU,EAAE,IAAI,CAAC,UAAU;iBAC5B;gBACD,KAAK,EAAE,IAAI,CAAC,QAAQ;gBACpB,MAAM,EAAE,CAAC;aACV,CAAC,CAAA;YACF,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU;gBAAE,OAAM;YAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;YACvB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAA;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU;gBAAE,OAAM;YAC1C,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;YACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC,CAAA;YACjC,IAAI,CAAC,KAAK,GAAI,KAAe,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,EAAE,uCAAuC,CAAC,CAAA;QAC/F,CAAC;gBAAS,CAAC;YACT,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU;gBAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QAC1D,CAAC;IACH,CAAC;IAES,OAAO,CAAC,OAAuB;QACvC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;QAE3H;;;WAGG;QACH,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,cAAc,CAA4B,CAAA;YACtF,KAAK,EAAE,KAAK,EAAE,CAAA;YACd,KAAK,EAAE,MAAM,EAAE,CAAA;QACjB,CAAC;IACH,CAAC;IAED,oBAAoB;QAClB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC9B,KAAK,CAAC,oBAAoB,EAAE,CAAA;IAC9B,CAAC;IAEO,IAAI,CAAC,IAAY,EAAE,MAAgB;QACzC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACtF,CAAC;IAED,wFAAwF;IAEhF,QAAQ,CAAC,KAAa;QAC5B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC9B,sEAAsE;QACtE,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE;YACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;YACpB,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;QACpB,CAAC,EAAE,GAAG,CAAC,CAAA;IACT,CAAC;IAED,oEAAoE;IAC5D,QAAQ,CAAC,MAAkB;QACjC,MAAM,EAAE,CAAA;QACR,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;IACpB,CAAC;IAEO,QAAQ;QACd,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAA;QACrB,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;IACpB,CAAC;IAED,8GAA8G;IACtG,KAAK,CAAC,GAAG,CAAC,KAA4B,EAAE,MAAc,EAAE,GAA2B,EAAE,OAAe;QAC1G,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;QACpB,IAAI,CAAC;YACH,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;gBAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAA;;gBACnC,IAAI,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;QAC9E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2GAA2G;YAC3G,IAAI,CAAC,KAAK,GAAI,KAAe,CAAC,OAAO,CAAA;QACvC,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QACnB,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IAC9C,CAAC;IAEO,MAAM,CAAC,KAA4B,EAAE,IAAY;QACvD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;QACpB,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,KAAK,CAAC,IAAI;YAAE,OAAM;QAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,gBAAgB,EAAE,yCAAyC,CAAC,CAAC,CAAA;IAC7I,CAAC;IAEO,SAAS,CAAC,KAA4B,EAAE,MAAe;QAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,cAAc,EAAE,wCAAwC,CAAC,CAAC,CAAA;IAC5J,CAAC;IAEO,aAAa,CAAC,KAA4B,EAAE,UAA8B;QAChF,KAAK,IAAI,CAAC,GAAG,CACX,KAAK,EACL,YAAY,EACZ,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,EACpD,CAAC,CAAC,oBAAoB,EAAE,8DAA8D,CAAC,CACxF,CAAA;IACH,CAAC;IAEO,cAAc,CAAC,KAA4B;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,gBAAgB,EAAE,yCAAyC,CAAC,CAAC,CAAA;IACpI,CAAC;IAED,wFAAwF;IAExF,MAAM;QACJ,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAA;QAEjC,OAAO,IAAI,CAAA;;;mBAGI,IAAI,CAAC,MAAM;wBACN,CAAC,CAAC,QAAQ,EAAE,gCAAgC,CAAC;uBAC9C,CAAC,CAAC,QAAQ,EAAE,gCAAgC,CAAC;mBACjD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAE,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC;qBAC/D,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;;qCAE7C,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,kBAAkB,CAAC;;;UAGvG,IAAI,CAAC,IAAI;YACT,CAAC,CAAC,IAAI,CAAA,sBAAsB,CAAC,CAAC,aAAa,EAAE,YAAY,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;wBACrF,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAI,CAAC,CAAC,MAA4B,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;qCACvF,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,kBAAkB,EAAE,mBAAmB,CAAC;oCACzE,CAAC,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;sBACrD;YACZ,CAAC,CAAC,OAAO;6BACU,CAAC,CAAC,cAAc,EAAE,OAAO,CAAC,WAAW,IAAI,CAAC,KAAK;oBACxD,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,GAAI,CAAC,CAAC,MAA4B,CAAC,KAAY,CAAC,CAAC;gCAClF,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC;iCACrB,CAAC,CAAC,aAAa,EAAE,eAAe,CAAC;wCAC1B,CAAC,CAAC,oBAAoB,EAAE,QAAQ,CAAC;;6BAE5C,CAAC,CAAC,mBAAmB,EAAE,YAAY,CAAC,WAAW,IAAI,CAAC,UAAU;oBACvE,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,GAAI,CAAC,CAAC,MAA4B,CAAC,KAAY,CAAC,CAAC;gCACvF,CAAC,CAAC,uBAAuB,EAAE,oBAAoB,CAAC;iCAC/C,CAAC,CAAC,aAAa,EAAE,QAAQ,CAAC;oCACvB,CAAC,CAAC,gBAAgB,EAAE,SAAS,CAAC;;iDAEjB,IAAI,CAAC,UAAU;oBAC5C,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,GAAI,CAAC,CAAC,MAA2B,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,EAAE,QAAQ,CAAC;iDAClG,IAAI,CAAC,aAAa;oBAC/C,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,GAAI,CAAC,CAAC,MAA2B,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,EAAE,kBAAkB,CAAC;;;UAGtJ,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,kCAAkC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO;UAC/E,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO;YAC5B,CAAC,CAAC,IAAI,CAAA,sBAAsB,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,gCAAgC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,uBAAuB,CAAC,QAAQ;YAC1J,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;UAClD,KAAK,GAAG,IAAI,CAAC,KAAK;YAClB,CAAC,CAAC,IAAI,CAAA,+BAA+B,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,WAAW,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,WAAW;YAC3I,CAAC,CAAC,OAAO;;;QAGX,IAAI,CAAC,WAAW,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa;YAC3C,CAAC,CAAC,IAAI,CAAA,uCAAuC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;+CAC5D,CAAC,CAAC,cAAc,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;oBAC3F;YACZ,CAAC,CAAC,OAAO;KACZ,CAAA;IACH,CAAC;IAEO,SAAS,CAAC,KAA4B;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAA;QACjE,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;QACrC,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,KAAK,SAAS,CAAA;QAEhD,IAAI,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC;YACjC,OAAO,IAAI,CAAA;;;;mBAIE,KAAK,CAAC,IAAI,IAAI,EAAE;uBACZ,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC;qBACvB,CAAC,CAAgB,EAAE,EAAE;gBAC9B,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;oBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAG,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC,CAAA;gBAC/E,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;oBAAE,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;YAC9C,CAAC;kBACO,CAAC,CAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAG,CAAC,CAAC,MAA2B,CAAC,KAAK,CAAC;;aAE3E,CAAA;QACT,CAAC;QAED,OAAO,IAAI,CAAA;mBACI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;eAChH,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;;;uBAG9E,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,yCAAyC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,2CAA2C,CAAC;WACxI,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;;;uCAGD,CAAC,CAAQ,EAAE,EAAE,GAAG,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,EAAE,CAAA,CAAC,CAAC,IAAI,IAAI;4BACxG,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA,sBAAsB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,kBAAkB,IAAI,EAAE;;kCAElJ,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE;UACzD,OAAO,CAAC,MAAM;YACd,CAAC,CAAC,IAAI,CAAA,8BAA8B,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,EAAE,CAAC;;sBAE9H;YACZ,CAAC,CAAC,OAAO;UACT,OAAO,CAAC,aAAa;YACrB,CAAC,CAAC,SAAS;gBACT,CAAC,CAAC,IAAI,CAAA,8BAA8B,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC;yBAChG,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,qCAAqC;gBACjF,CAAC,CAAC,IAAI,CAAA,8BAA8B,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC,cAAc,EAAE,cAAc,CAAC;yBACxG,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,SAAS,CAAC,mCAAmC;YAC5F,CAAC,CAAC,OAAO;;;kBAGD,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,mBAAmB,CAAC;uBAC3E,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,mBAAmB,CAAC;mBACpF,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;;qBAExC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB;;UAE3D,OAAO,CAAC,MAAM;YACd,CAAC,CAAC,IAAI,CAAA,qCAAqC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC;uBACvF,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,qCAAqC;YACnF,CAAC,CAAC,OAAO;;WAER,CAAA;IACT,CAAC;IAED,0GAA0G;IAClG,KAAK,CAAC,GAAG,CAAC,KAAa,EAAE,IAAY,EAAE,WAAmB;QAChE,OAAO,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC;YAC5B,IAAI,EAAE,SAAS;YACf,KAAK;YACL,IAAI;YACJ,aAAa,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;YACpC,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE;SAC9C,CAAC,CAAC,CAAA;IACL,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,KAA4B,EAAE,IAAY;QAChE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CACvB,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,EACrB,CAAC,CAAC,gBAAgB,EAAE,kFAAkF,EAAE,EAAE,IAAI,EAAE,CAAC,EACjH,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CACtB,CAAA;QACD,IAAI,EAAE;YAAE,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,KAA4B,EAAE,IAAY;QAC9D,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CACvB,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,EAC7B,CAAC,CAAC,cAAc,EAAE,6FAA6F,EAAE,EAAE,IAAI,EAAE,CAAC,EAC1H,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAC9B,CAAA;QACD,IAAI,EAAE;YAAE,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC3C,CAAC;;AAnT2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;2DAAgB;AACf;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;yDAAc;AAEb;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0DAAe;AAEV;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;;sDAAqD;AAExD;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;qDAAU;AAEL;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;;2DAAwC;AAEtD;IAAhB,KAAK,EAAE;;wDAA8C;AACrC;IAAhB,KAAK,EAAE;;sDAAkB;AACT;IAAhB,KAAK,EAAE;;4DAAwB;AACf;IAAhB,KAAK,EAAE;;uDAAoB;AACX;IAAhB,KAAK,EAAE;;yDAAyB;AAChB;IAAhB,KAAK,EAAE;;sDAAqE;AAC5D;IAAhB,KAAK,EAAE;;2DAA+E;AACtE;IAAhB,KAAK,EAAE;;8DAA8B;AACrB;IAAhB,KAAK,EAAE;;2DAA2B;AAClB;IAAhB,KAAK,EAAE;;yDAAwB;AACf;IAAhB,KAAK,EAAE;;wDAAwB;AACf;IAAhB,KAAK,EAAE;;sDAAmB;AACV;IAAhB,KAAK,EAAE;;2DAAwB;AAlRrB,uBAAuB;IADnC,aAAa,CAAC,8BAA8B,CAAC;GACjC,uBAAuB,CA+iBnC","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { css, html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPrompt } from '@operato/popup'\n\nimport {\n assistantSessionAdmin,\n sessionActions,\n type AssistantSessionAdmin,\n type AssistantSessionEntry,\n type AssistantSessionFilters\n} from '../utils/assistant-session-admin.js'\n\nconst PAGE = 30\n\n\nconst t = (key: string, defaultValue: string, options: Record<string, unknown> = {}) =>\n i18next.t(`ai-assistant.session.${key}`, { defaultValue, ...options })\n\n/**\n * The conversations on one anchor, managed - `<ox-assistant-session-manager>`.\n *\n * Search, a filter (all / mine / including hidden), paging, and on each row: open, rename, hide or\n * unhide, private ⇄ public, delete. Taken from operato-twin's dock, where it was written first, so every\n * dock gets it by placing this element (typically behind the toolbar's \"All History\").\n *\n * ── What is the host's ──────────────────────────────────────────────────────────\n * Opening and starting a conversation belong to the dock's session controller, so this element asks\n * with the toolbar's own events and does not open anything itself:\n * assistant-session-select { sessionId } open that conversation\n * assistant-session-reset start a new one\n * assistant-session-changed { sessionId, change: 'renamed' | 'hidden' | 'unhidden' | 'removed' | 'visibility' }\n * a row changed - the host refreshes its tabs; if the open\n * conversation was removed or hidden, it moves off it\n *\n * ── What is the server's ────────────────────────────────────────────────────────\n * Who may do what (`sessionActions`): rename any participant, hide anyone for their own list, delete\n * the creator, visibility the owner. The buttons follow those hints; the doors decide again.\n *\n * ── Asking before what cannot be undone ─────────────────────────────────────────\n * Delete, and opening a private conversation to the domain, ask on the row itself - [Cancel][OK].\n * Making it private, hiding and renaming do not: each can be undone from the same row.\n */\n@customElement('ox-assistant-session-manager')\nexport class AssistantSessionManager extends LitElement {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n font-size: 12.5px;\n color: var(--md-sys-color-on-surface);\n }\n\n .bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n .bar input,\n .bar select {\n min-width: 0;\n height: 28px;\n box-sizing: border-box;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n padding: 0 8px;\n }\n\n .bar input {\n flex: 1;\n }\n\n .new {\n flex: none;\n border: 0;\n border-radius: 6px;\n padding: 4px 8px;\n background: transparent;\n color: var(--md-sys-color-primary);\n font: inherit;\n font-weight: 650;\n cursor: pointer;\n }\n\n .filters {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 10px;\n padding: 6px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11.5px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .filters select {\n height: 26px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n }\n\n .filters label {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n cursor: pointer;\n }\n\n .kind {\n color: var(--md-sys-color-primary);\n }\n\n .list {\n flex: 1;\n min-height: 0;\n overflow: auto;\n }\n\n .row {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 9px 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n cursor: pointer;\n }\n\n .row:hover,\n .row.current {\n background: var(--md-sys-color-surface-container);\n }\n\n .row.hidden {\n opacity: 0.6;\n }\n\n .dot {\n flex: none;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n }\n\n .row.unread .dot {\n background: var(--md-sys-color-primary);\n }\n\n .row md-icon {\n --md-icon-size: 16px;\n flex: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .body {\n flex: 1;\n min-width: 0;\n }\n\n .title,\n .prev {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .row.unread .title {\n font-weight: 700;\n }\n\n .prev {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 11px;\n }\n\n /* Row actions show on hover and on keyboard focus - the list's body is the names. */\n .acts {\n display: none;\n gap: 2px;\n }\n\n .row:hover .acts,\n .row:focus-within .acts {\n display: flex;\n }\n\n button.icon {\n display: inline-flex;\n padding: 3px;\n border: 0;\n border-radius: 6px;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n\n button.icon:hover {\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface);\n }\n\n button.icon.danger:hover {\n color: var(--md-sys-color-error);\n }\n\n .rename {\n flex: 1;\n min-width: 0;\n height: 26px;\n border: 1px solid var(--md-sys-color-primary);\n border-radius: 6px;\n padding: 0 6px;\n font: inherit;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n }\n\n .ask {\n display: grid;\n gap: 6px;\n padding: 10px;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n background: var(--md-sys-color-surface-container);\n }\n\n .ask .buttons {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n }\n\n .ask button {\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 6px;\n padding: 4px 10px;\n background: var(--md-sys-color-surface-container-lowest);\n color: inherit;\n font: inherit;\n cursor: pointer;\n }\n\n .ask button.ok {\n border-color: var(--md-sys-color-primary);\n background: var(--md-sys-color-primary);\n color: var(--md-sys-color-on-primary);\n }\n\n .ask button.ok.danger {\n border-color: var(--md-sys-color-error);\n background: var(--md-sys-color-error);\n color: var(--md-sys-color-on-error);\n }\n\n .note,\n .empty {\n padding: 14px 12px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n [role='alert'] {\n color: var(--md-sys-color-error);\n }\n\n .more,\n .hiddenfilter {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n font: inherit;\n cursor: pointer;\n }\n\n .hiddenfilter md-icon {\n --md-icon-size: 16px;\n }\n `\n\n /** The anchor whose conversations are listed - the same one the dock's session controller opened. */\n @property({ type: String }) anchorType = ''\n @property({ type: String }) anchorId = ''\n /** The conversation the dock has open - marked in the list. */\n @property({ type: String }) sessionId = ''\n /** The doors. Replaceable for tests; a host normally leaves it. */\n @property({ attribute: false }) admin: AssistantSessionAdmin = assistantSessionAdmin\n /** The conversation kind (mode) open in the dock - the list's default scope (§2-6). */\n @property({ type: String }) kind = ''\n /** kind → the words people know it by (the modes' labels), for each row. */\n @property({ attribute: false }) kindLabels: Record<string, string> = {}\n\n @state() private entries: AssistantSessionEntry[] = []\n @state() private total = 0\n @state() private hiddenCount = 0\n @state() private search = ''\n @state() private allKinds = false\n @state() private whose: NonNullable<AssistantSessionFilters['whose']> = 'all'\n @state() private visibility: NonNullable<AssistantSessionFilters['visibility']> = 'all'\n @state() private includeHidden = false\n @state() private unreadOnly = false\n @state() private pageSize = PAGE\n @state() private loading = false\n @state() private error = ''\n @state() private renamingId = ''\n\n private generation = 0\n private searchTimer?: ReturnType<typeof setTimeout>\n\n /** Read the list again - the host calls this after it opened or started a conversation. */\n async reload(): Promise<void> {\n const generation = ++this.generation\n if (!this.anchorType || !this.anchorId) {\n this.entries = []\n this.total = this.hiddenCount = 0\n return\n }\n\n this.loading = true\n this.error = ''\n try {\n /*\n * Paging reads again from the start rather than appending - live activity reorders the list, and\n * appending a later page to an earlier order duplicates and drops rows.\n */\n const page = await this.admin.inbox({\n anchorType: this.anchorType,\n anchorId: this.anchorId,\n search: this.search,\n filters: {\n kind: this.allKinds ? '' : this.kind,\n whose: this.whose,\n visibility: this.visibility,\n includeHidden: this.includeHidden,\n unreadOnly: this.unreadOnly\n },\n limit: this.pageSize,\n offset: 0\n })\n if (generation !== this.generation) return\n this.entries = page.entries\n this.total = page.total\n this.hiddenCount = page.hiddenCount\n } catch (error) {\n if (generation !== this.generation) return\n this.entries = []\n this.total = this.hiddenCount = 0\n this.error = (error as Error).message || t('failed', 'could not load the conversation list.')\n } finally {\n if (generation === this.generation) this.loading = false\n }\n }\n\n protected updated(changed: PropertyValues) {\n if (changed.has('anchorType') || changed.has('anchorId') || changed.has('admin') || changed.has('kind')) void this.reload()\n\n /*\n * The rename box takes the keys the moment it opens, with the name selected. `autofocus` works once\n * per document, so the second rename opened a box that ignored the typing (2026-09-24, operato-ecs).\n */\n if (changed.has('renamingId') && this.renamingId) {\n const input = this.renderRoot.querySelector('input.rename') as HTMLInputElement | null\n input?.focus()\n input?.select()\n }\n }\n\n disconnectedCallback() {\n this.generation++\n clearTimeout(this.searchTimer)\n super.disconnectedCallback()\n }\n\n private emit(name: string, detail?: unknown) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n\n /* ── what the person does ────────────────────────────────────────────────────────── */\n\n private onSearch(value: string) {\n this.search = value\n clearTimeout(this.searchTimer)\n /* Not on every keystroke. A new search starts from the first page. */\n this.searchTimer = setTimeout(() => {\n this.pageSize = PAGE\n void this.reload()\n }, 200)\n }\n\n /** A filter changed - the list starts from its first page again. */\n private refilter(change: () => void) {\n change()\n this.pageSize = PAGE\n void this.reload()\n }\n\n private loadMore() {\n this.pageSize += PAGE\n void this.reload()\n }\n\n /** Runs one door, then reads the list again - on success and on failure alike, so the row shows the truth. */\n private async act(entry: AssistantSessionEntry, change: string, run: () => Promise<boolean>, refused: string) {\n this.renamingId = ''\n try {\n if (!(await run())) this.error = refused\n else this.emit('assistant-session-changed', { sessionId: entry.id, change })\n } catch (error) {\n /* Never pass a failure silently - a person must not believe a conversation was deleted when it was not. */\n this.error = (error as Error).message\n }\n const error = this.error\n await this.reload()\n if (error && !this.error) this.error = error\n }\n\n private rename(entry: AssistantSessionEntry, name: string) {\n const trimmed = name.trim()\n this.renamingId = ''\n if (!trimmed || trimmed === entry.name) return\n void this.act(entry, 'renamed', () => this.admin.rename(entry.id, trimmed), t('rename-refused', 'this conversation could not be renamed.'))\n }\n\n private setHidden(entry: AssistantSessionEntry, hidden: boolean) {\n void this.act(entry, hidden ? 'hidden' : 'unhidden', () => this.admin.hide(entry.id, hidden), t('hide-refused', 'this conversation could not be hidden.'))\n }\n\n private setVisibility(entry: AssistantSessionEntry, visibility: 'open' | 'private') {\n void this.act(\n entry,\n 'visibility',\n () => this.admin.setVisibility(entry.id, visibility),\n t('visibility-refused', 'only the owner of this conversation can change who reads it.')\n )\n }\n\n private _removeSession(entry: AssistantSessionEntry) {\n void this.act(entry, 'removed', () => this.admin.remove(entry.id), t('remove-refused', 'this conversation could not be deleted.'))\n }\n\n /* ── drawing ─────────────────────────────────────────────────────────────────────── */\n\n render(): TemplateResult {\n const shown = this.entries.length\n\n return html`\n <div class=\"bar\">\n <input\n .value=${this.search}\n placeholder=${t('search', 'Search names and last messages')}\n aria-label=${t('search', 'Search names and last messages')}\n @input=${(e: Event) => this.onSearch((e.target as HTMLInputElement).value)}\n @keydown=${(e: KeyboardEvent) => e.key === 'Escape' && this.onSearch('')}\n />\n <button class=\"new\" @click=${() => this.emit('assistant-session-reset')}>+ ${t('new', 'New conversation')}</button>\n </div>\n <div class=\"filters\">\n ${this.kind\n ? html`<select aria-label=${t('filter-kind', 'Which kind')} .value=${this.allKinds ? 'all' : 'this'}\n @change=${(e: Event) => this.refilter(() => (this.allKinds = (e.target as HTMLSelectElement).value === 'all'))}>\n <option value=\"this\">${this.kindLabels[this.kind] || t('filter-this-kind', 'This conversation')}</option>\n <option value=\"all\">${t('filter-all-kinds', 'All kinds here')}</option>\n </select>`\n : nothing}\n <select aria-label=${t('filter-whose', 'Whose')} .value=${this.whose}\n @change=${(e: Event) => this.refilter(() => (this.whose = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-all', 'All')}</option>\n <option value=\"mine\">${t('filter-mine', 'Started by me')}</option>\n <option value=\"participant\">${t('filter-participant', \"I'm in\")}</option>\n </select>\n <select aria-label=${t('filter-visibility', 'Visibility')} .value=${this.visibility}\n @change=${(e: Event) => this.refilter(() => (this.visibility = (e.target as HTMLSelectElement).value as any))}>\n <option value=\"all\">${t('filter-any-visibility', 'Public and private')}</option>\n <option value=\"open\">${t('filter-open', 'Public')}</option>\n <option value=\"private\">${t('filter-private', 'Private')}</option>\n </select>\n <label><input type=\"checkbox\" .checked=${this.unreadOnly}\n @change=${(e: Event) => this.refilter(() => (this.unreadOnly = (e.target as HTMLInputElement).checked))} />${t('filter-unread', 'Unread')}</label>\n <label><input type=\"checkbox\" .checked=${this.includeHidden}\n @change=${(e: Event) => this.refilter(() => (this.includeHidden = (e.target as HTMLInputElement).checked))} />${t('filter-hidden', 'Including hidden')}</label>\n </div>\n <div class=\"list\">\n ${this.error ? html`<div class=\"note\" role=\"alert\">${this.error}</div>` : nothing}\n ${shown === 0 && !this.loading\n ? html`<div class=\"empty\">${this.search ? t('no-match', 'No conversation matches \"{q}\".', { q: this.search }) : t('none', 'No conversations yet.')}</div>`\n : this.entries.map(entry => this.renderRow(entry))}\n ${shown < this.total\n ? html`<button class=\"more\" @click=${() => this.loadMore()}>${t('load-more', 'Show more ({rest})', { rest: this.total - shown })}</button>`\n : nothing}\n </div>\n <!-- A hidden conversation must not be forgotten: say how many, and one press shows them. -->\n ${this.hiddenCount > 0 && !this.includeHidden\n ? html`<button class=\"hiddenfilter\" @click=${() => this.refilter(() => (this.includeHidden = true))}>\n <md-icon>visibility_off</md-icon>${t('hidden-count', '{count} hidden', { count: this.hiddenCount })}\n </button>`\n : nothing}\n `\n }\n\n private renderRow(entry: AssistantSessionEntry): TemplateResult {\n const name = entry.name || t('untitled', 'Untitled conversation')\n const actions = sessionActions(entry)\n const isPrivate = entry.visibility === 'private'\n\n if (this.renamingId === entry.id) {\n return html`<div class=\"row\">\n <span class=\"dot\"></span>\n <input\n class=\"rename\"\n .value=${entry.name || ''}\n aria-label=${t('rename', 'Rename')}\n @keydown=${(e: KeyboardEvent) => {\n if (e.key === 'Enter') this.rename(entry, (e.target as HTMLInputElement).value)\n if (e.key === 'Escape') this.renamingId = ''\n }}\n @blur=${(e: Event) => this.rename(entry, (e.target as HTMLInputElement).value)}\n />\n </div>`\n }\n\n return html`<div\n class=\"row ${entry.unread ? 'unread' : ''} ${entry.hidden ? 'hidden' : ''} ${entry.id === this.sessionId ? 'current' : ''}\"\n @click=${() => this.emit('assistant-session-select', { sessionId: entry.id, kind: entry.kind })}\n >\n <span class=\"dot\"></span>\n <md-icon title=${isPrivate ? t('private', 'Private - only its participants read it') : t('open', 'Public - everyone in this domain reads it')}\n >${isPrivate ? 'lock' : 'public'}</md-icon\n >\n <span class=\"body\">\n <div class=\"title\" @dblclick=${(e: Event) => { e.stopPropagation(); if (actions.rename) this.renamingId = entry.id }}>${name}</div>\n <div class=\"prev\">${this.allKinds && entry.kind ? html`<span class=\"kind\">${this.kindLabels[entry.kind] || entry.kind}</span> · ` : nothing}${entry.lastMessagePreview || ''}</div>\n </span>\n <span class=\"acts\" @click=${(e: Event) => e.stopPropagation()}>\n ${actions.rename\n ? html`<button class=\"icon\" title=${t('rename', 'Rename')} aria-label=${t('rename', 'Rename')} @click=${() => (this.renamingId = entry.id)}>\n <md-icon>edit</md-icon>\n </button>`\n : nothing}\n ${actions.setVisibility\n ? isPrivate\n ? html`<button class=\"icon\" title=${t('make-open', 'Make public')} aria-label=${t('make-open', 'Make public')}\n @click=${() => this.askOpen(entry, name)}><md-icon>public</md-icon></button>`\n : html`<button class=\"icon\" title=${t('make-private', 'Make private')} aria-label=${t('make-private', 'Make private')}\n @click=${() => this.setVisibility(entry, 'private')}><md-icon>lock</md-icon></button>`\n : nothing}\n <button\n class=\"icon\"\n title=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n aria-label=${actions.unhide ? t('unhide', 'Show in my list') : t('hide', 'Hide from my list')}\n @click=${() => this.setHidden(entry, !entry.hidden)}\n >\n <md-icon>${actions.unhide ? 'visibility' : 'visibility_off'}</md-icon>\n </button>\n ${actions.remove\n ? html`<button class=\"icon danger\" title=${t('remove', 'Delete')} aria-label=${t('remove', 'Delete')}\n @click=${() => this.askRemove(entry, name)}><md-icon>delete</md-icon></button>`\n : nothing}\n </span>\n </div>`\n }\n\n /** Before what cannot be taken back - the platform's confirm, [Cancel][OK] (decided 2026-09-25, §7-3). */\n private async ask(title: string, text: string, confirmText: string): Promise<boolean> {\n return !!(await OxPrompt.open({\n type: 'warning',\n title,\n text,\n confirmButton: { text: confirmText },\n cancelButton: { text: t('cancel', 'Cancel') }\n }))\n }\n\n private async askRemove(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('remove', 'Delete'),\n t('confirm-remove', 'Delete \"{name}\"? It disappears for every participant and cannot be brought back.', { name }),\n t('remove', 'Delete')\n )\n if (ok) this._removeSession(entry)\n }\n\n private async askOpen(entry: AssistantSessionEntry, name: string) {\n const ok = await this.ask(\n t('make-open', 'Make public'),\n t('confirm-open', 'Make \"{name}\" public? Everyone in this domain will be able to read what is already written.', { name }),\n t('make-open', 'Make public')\n )\n if (ok) this.setVisibility(entry, 'open')\n }\n}\n"]}
@@ -17,6 +17,10 @@ export function remoteMessageToLine(m) {
17
17
  patchId: m.relatedPatchId ?? undefined,
18
18
  toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,
19
19
  groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,
20
+ /* Files sent with the turn (shared dock §9) - opened through the conversation's own route when drawn. */
21
+ attachments: Array.isArray(m.attachmentsJson)
22
+ ? m.attachmentsJson.map((a) => ({ id: a.id, name: a.name, type: a.mimetype, size: a.size }))
23
+ : undefined,
20
24
  senderEmail: m.creator?.email ?? undefined,
21
25
  senderName: m.creator?.name ?? undefined,
22
26
  /*
@@ -1 +1 @@
1
- {"version":3,"file":"chat-echo-dedup.js","sourceRoot":"","sources":["../../client/components/chat-echo-dedup.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 그룹챗 메시지 에코 dedup — 순수 로직.\n *\n * 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다\n * 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을\n * **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).\n *\n * 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.\n * (C2 의 patch-apply-gate.ts 와 동일한 \"순수 게이트\" 컨벤션.)\n */\n\n/** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */\nexport interface DedupLine {\n id?: string\n _localId?: string\n role: string\n content: string\n senderEmail?: string\n senderName?: string\n [k: string]: any\n}\n\n/** 서버 방송 메시지(필요 필드만). */\nexport interface RemoteMessage {\n id?: string\n role: string\n content: string\n relatedPatchId?: string\n toolUsagesJson?: any\n /** 접지 경고(영속) — 답이 언급했으나 근거에 없던 식별자. 나중에 읽는 사람도 같은 주의를 봐야 한다. */\n groundingWarningsJson?: any\n /** 발신자 — 표시용 name + **자기 여부 판정용 email**(내부 관리키 id 는 쓰지 않는다). */\n creator?: { name?: string; email?: string } | null\n /** 이 사람이 이 답의 문을 못 지나 본문이 한 줄로 바뀌어 왔나(ADR-0073 판정 1). */\n redacted?: boolean\n}\n\n/** 방송 메시지 → 라인. */\nexport function remoteMessageToLine(m: RemoteMessage): DedupLine {\n return {\n id: m.id,\n role: m.role,\n content: m.content,\n patchId: m.relatedPatchId ?? undefined,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,\n groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,\n senderEmail: m.creator?.email ?? undefined,\n senderName: m.creator?.name ?? undefined,\n /*\n * 서버가 이 사람에게 값을 안 내준 줄 — ADR-0073 판정 1. `content` 에 온 것은 어시스턴트가 쓴\n * 문장이 아니라 「권한이 필요합니다」 한 줄이다. 그대로 말풍선에 넣으면 어시스턴트가 그렇게 말한\n * 것처럼 읽힌다.\n */\n redacted: m.redacted === true\n }\n}\n\n/**\n * 원격(방송) 메시지 수신 결과를 계산.\n * - id 없음 / 이미 seen → 변화 없음(null).\n * - **내 발신 에코**이고 아직 서버 id 를 못 받은 optimistic 라인이 있으면 → 그 라인에 id 를 입힌다.\n * - 그 밖 → seen 에 추가 + 라인 append.\n *\n * 왜 '입히기'인가: 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 에코를 새 줄로 붙이고\n * 중복 제거를 mutation 응답 시점(reconcileSentLine)에 했는데, 그 응답은 **LLM 이 끝난 뒤** 오므로\n * 사용자는 대기 시간 내내 자기 질문을 두 줄로 본다. 그래서 붙이지 않고 대기 중인 줄에 흡수시킨다.\n *\n * `_localId` 는 유지한다 — 이후 mutation 응답이 patch(요약·patchId 등)를 그 줄에 병합할 수 있어야 한다.\n */\nexport function receiveRemoteMessage(\n lines: DedupLine[],\n seen: Set<string>,\n m: RemoteMessage | undefined | null\n): { lines: DedupLine[]; seen: Set<string> } | null {\n if (!m || !m.id) return null\n if (seen.has(m.id)) return null\n const nextSeen = new Set(seen)\n nextSeen.add(m.id)\n\n /* 내 발신 에코 흡수 — `_localId` 가 있는 줄은 이 클라이언트가 만든 것뿐이다.\n * 발신자 판정: 양쪽 email 이 있으면 같아야 하고, 방송에 발신자 email 이 없으면(단일 사용자·구형)\n * 내 것으로 본다. 다른 사용자의 메시지가 내 대기 줄을 삼키지 않도록 email 이 다르면 흡수하지 않는다. */\n /* AI 답변 에코 흡수 — 사용자 발화와 **같은 이유**로 붙이지 않고 대기 줄에 흡수한다.\n *\n * 왜 필요한가(실제 사고): 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 assistant 방송을\n * 새 줄로 붙였고, 뒤이어 mutation 응답이 \"같은 서버 id 를 가진 다른 줄이 있다\" 며 **대기 줄을 지웠다**.\n * 그런데 대기 줄에만 있는 것이 있다 — **영속되지 않는 응답 데이터**(조치 제안 카드·접지 경고).\n * 그래서 화면에서 실행 버튼이 사라졌다(제안 도구는 분명히 호출됐는데도).\n *\n * 흡수하면 서버가 보낸 본문(멘션 마커 포함)을 그 줄에 입히면서 대기 표시를 풀 수 있어, 답이 더\n * 빨리 보이는 효과도 있다. */\n if (m.role === 'assistant') {\n const idx = lines.findIndex(l => l.role === 'assistant' && !l.id && !!l._localId)\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n /* 본문·영속 데이터는 서버 것으로 갱신하고, 이 줄이 들고 있던 값(제안 등)은 유지한다. */\n content: m.content,\n patchId: m.relatedPatchId ?? nextLines[idx].patchId,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : nextLines[idx].toolUsages,\n groundingWarnings: Array.isArray(m.groundingWarningsJson)\n ? m.groundingWarningsJson\n : nextLines[idx].groundingWarnings,\n pending: false\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n if (m.role === 'user') {\n const echoEmail = m.creator?.email\n const idx = lines.findIndex(\n l =>\n l.role === 'user' &&\n !l.id &&\n !!l._localId &&\n (!echoEmail || l.senderEmail == null || l.senderEmail === echoEmail)\n )\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n senderEmail: echoEmail ?? nextLines[idx].senderEmail,\n senderName: m.creator?.name ?? nextLines[idx].senderName\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen }\n}\n\n/**\n * 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.\n * - **다른 줄**이 이미 그 serverId 를 갖고 있으면(옛 append 경로) optimistic 중복 라인 제거.\n * - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.\n * - 해당 _localId 라인이 없으면 변화 없음.\n * 반환: 새 { lines, seen } (불변).\n */\nexport function reconcileSentLine(\n lines: DedupLine[],\n seen: Set<string>,\n localId: string,\n serverId: string | undefined,\n patch: Partial<DedupLine>\n): { lines: DedupLine[]; seen: Set<string> } {\n const idx = lines.findIndex(l => l._localId === localId)\n /* 같은 서버 id 를 든 **다른** 줄 — 에코가 흡수되지 못하고 새 줄로 붙은 경우다. */\n const echoed = serverId ? lines.findIndex((l, i) => i !== idx && l.id === serverId) : -1\n\n /*\n * 대기 줄이 없어졌다 — 보내는 동안 대화가 지워졌거나 이력으로 갈렸다. 그래도 **응답이 어느 메시지의\n * 것인지는 안다**(`assistantMessageId`). 그 줄에 합친다.\n *\n * 여기서 그냥 돌아가면, 저장되는 값(본문 · 도구 사용)은 에코가 들고 있으니 화면은 멀쩡해 보이고\n * **저장되지 않는 것만 사라진다** — 제안 카드다. 모델은 「카드를 눌러 적용하세요」라고 말하고 화면에는\n * 누를 것이 없다(2026-09-18 트윈에서 실제로 그랬다).\n */\n if (idx < 0) {\n if (echoed < 0) return { lines, seen }\n const merged = [...lines]\n merged[echoed] = { ...merged[echoed], ...patch, id: serverId ?? merged[echoed].id }\n return { lines: merged, seen: serverId ? new Set(seen).add(serverId) : seen }\n }\n\n const nextLines = [...lines]\n /* seen 만 보고 지우면, 에코를 이 줄에 **흡수**한 경우(id 가 이미 이 줄에 있다) 자기 줄을 지운다.\n * 실제 중복은 \"다른 줄이 같은 id 를 가진\" 상태일 때뿐이다. */\n if (echoed >= 0) {\n /* 남는 것은 에코 줄이다. 응답에만 있는 것(제안)을 그 줄에 옮기고 대기 줄을 지운다. */\n nextLines[echoed] = { ...nextLines[echoed], ...patch, id: serverId ?? nextLines[echoed].id }\n nextLines.splice(idx, 1)\n return { lines: nextLines, seen }\n }\n const nextSeen = serverId ? new Set(seen).add(serverId) : seen\n nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined }\n return { lines: nextLines, seen: nextSeen }\n}\n"]}
1
+ {"version":3,"file":"chat-echo-dedup.js","sourceRoot":"","sources":["../../client/components/chat-echo-dedup.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 그룹챗 메시지 에코 dedup — 순수 로직.\n *\n * 실시간 방송은 자기 발신 메시지도 되돌려준다(에코). 또 pubsub publish 가 mutation 응답보다\n * 먼저 도착할 수 있어(레이스) optimistic 라인과 방송 라인이 겹칠 수 있다. 이 모듈은 그 조정을\n * **id 기반**으로 결정한다 (서버가 멘션 마커로 content 를 enrich 하므로 content 매칭은 불가).\n *\n * 컴포넌트 상태(this.lines / this._seenMessageIds)에서 분리 → 단위테스트 가능.\n * (C2 의 patch-apply-gate.ts 와 동일한 \"순수 게이트\" 컨벤션.)\n */\n\n/** dedup 에 필요한 최소 라인 형태 (board-ai-chat 의 ChatLine 부분집합). */\nexport interface DedupLine {\n id?: string\n _localId?: string\n role: string\n content: string\n senderEmail?: string\n senderName?: string\n [k: string]: any\n}\n\n/** 서버 방송 메시지(필요 필드만). */\nexport interface RemoteMessage {\n id?: string\n role: string\n content: string\n relatedPatchId?: string\n toolUsagesJson?: any\n /** 접지 경고(영속) — 답이 언급했으나 근거에 없던 식별자. 나중에 읽는 사람도 같은 주의를 봐야 한다. */\n groundingWarningsJson?: any\n /** 발신자 — 표시용 name + **자기 여부 판정용 email**(내부 관리키 id 는 쓰지 않는다). */\n creator?: { name?: string; email?: string } | null\n /** 이 사람이 이 답의 문을 못 지나 본문이 한 줄로 바뀌어 왔나(ADR-0073 판정 1). */\n redacted?: boolean\n}\n\n/** 방송 메시지 → 라인. */\nexport function remoteMessageToLine(m: RemoteMessage): DedupLine {\n return {\n id: m.id,\n role: m.role,\n content: m.content,\n patchId: m.relatedPatchId ?? undefined,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : undefined,\n groundingWarnings: Array.isArray(m.groundingWarningsJson) ? m.groundingWarningsJson : undefined,\n /* Files sent with the turn (shared dock §9) - opened through the conversation's own route when drawn. */\n attachments: Array.isArray((m as any).attachmentsJson)\n ? (m as any).attachmentsJson.map((a: any) => ({ id: a.id, name: a.name, type: a.mimetype, size: a.size }))\n : undefined,\n senderEmail: m.creator?.email ?? undefined,\n senderName: m.creator?.name ?? undefined,\n /*\n * 서버가 이 사람에게 값을 안 내준 줄 — ADR-0073 판정 1. `content` 에 온 것은 어시스턴트가 쓴\n * 문장이 아니라 「권한이 필요합니다」 한 줄이다. 그대로 말풍선에 넣으면 어시스턴트가 그렇게 말한\n * 것처럼 읽힌다.\n */\n redacted: m.redacted === true\n }\n}\n\n/**\n * 원격(방송) 메시지 수신 결과를 계산.\n * - id 없음 / 이미 seen → 변화 없음(null).\n * - **내 발신 에코**이고 아직 서버 id 를 못 받은 optimistic 라인이 있으면 → 그 라인에 id 를 입힌다.\n * - 그 밖 → seen 에 추가 + 라인 append.\n *\n * 왜 '입히기'인가: 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 에코를 새 줄로 붙이고\n * 중복 제거를 mutation 응답 시점(reconcileSentLine)에 했는데, 그 응답은 **LLM 이 끝난 뒤** 오므로\n * 사용자는 대기 시간 내내 자기 질문을 두 줄로 본다. 그래서 붙이지 않고 대기 중인 줄에 흡수시킨다.\n *\n * `_localId` 는 유지한다 — 이후 mutation 응답이 patch(요약·patchId 등)를 그 줄에 병합할 수 있어야 한다.\n */\nexport function receiveRemoteMessage(\n lines: DedupLine[],\n seen: Set<string>,\n m: RemoteMessage | undefined | null\n): { lines: DedupLine[]; seen: Set<string> } | null {\n if (!m || !m.id) return null\n if (seen.has(m.id)) return null\n const nextSeen = new Set(seen)\n nextSeen.add(m.id)\n\n /* 내 발신 에코 흡수 — `_localId` 가 있는 줄은 이 클라이언트가 만든 것뿐이다.\n * 발신자 판정: 양쪽 email 이 있으면 같아야 하고, 방송에 발신자 email 이 없으면(단일 사용자·구형)\n * 내 것으로 본다. 다른 사용자의 메시지가 내 대기 줄을 삼키지 않도록 email 이 다르면 흡수하지 않는다. */\n /* AI 답변 에코 흡수 — 사용자 발화와 **같은 이유**로 붙이지 않고 대기 줄에 흡수한다.\n *\n * 왜 필요한가(실제 사고): 방송은 mutation 응답보다 먼저 도착할 수 있다. 예전에는 assistant 방송을\n * 새 줄로 붙였고, 뒤이어 mutation 응답이 \"같은 서버 id 를 가진 다른 줄이 있다\" 며 **대기 줄을 지웠다**.\n * 그런데 대기 줄에만 있는 것이 있다 — **영속되지 않는 응답 데이터**(조치 제안 카드·접지 경고).\n * 그래서 화면에서 실행 버튼이 사라졌다(제안 도구는 분명히 호출됐는데도).\n *\n * 흡수하면 서버가 보낸 본문(멘션 마커 포함)을 그 줄에 입히면서 대기 표시를 풀 수 있어, 답이 더\n * 빨리 보이는 효과도 있다. */\n if (m.role === 'assistant') {\n const idx = lines.findIndex(l => l.role === 'assistant' && !l.id && !!l._localId)\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n /* 본문·영속 데이터는 서버 것으로 갱신하고, 이 줄이 들고 있던 값(제안 등)은 유지한다. */\n content: m.content,\n patchId: m.relatedPatchId ?? nextLines[idx].patchId,\n toolUsages: Array.isArray(m.toolUsagesJson) ? m.toolUsagesJson : nextLines[idx].toolUsages,\n groundingWarnings: Array.isArray(m.groundingWarningsJson)\n ? m.groundingWarningsJson\n : nextLines[idx].groundingWarnings,\n pending: false\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n if (m.role === 'user') {\n const echoEmail = m.creator?.email\n const idx = lines.findIndex(\n l =>\n l.role === 'user' &&\n !l.id &&\n !!l._localId &&\n (!echoEmail || l.senderEmail == null || l.senderEmail === echoEmail)\n )\n if (idx >= 0) {\n const nextLines = [...lines]\n nextLines[idx] = {\n ...nextLines[idx],\n id: m.id,\n senderEmail: echoEmail ?? nextLines[idx].senderEmail,\n senderName: m.creator?.name ?? nextLines[idx].senderName\n }\n return { lines: nextLines, seen: nextSeen }\n }\n }\n\n return { lines: [...lines, remoteMessageToLine(m)], seen: nextSeen }\n}\n\n/**\n * 전송한 optimistic 라인(_localId)을 서버 id 로 reconcile.\n * - **다른 줄**이 이미 그 serverId 를 갖고 있으면(옛 append 경로) optimistic 중복 라인 제거.\n * - 아니면 serverId 부여(+ seen 등록) + patch 병합 + _localId 해제.\n * - 해당 _localId 라인이 없으면 변화 없음.\n * 반환: 새 { lines, seen } (불변).\n */\nexport function reconcileSentLine(\n lines: DedupLine[],\n seen: Set<string>,\n localId: string,\n serverId: string | undefined,\n patch: Partial<DedupLine>\n): { lines: DedupLine[]; seen: Set<string> } {\n const idx = lines.findIndex(l => l._localId === localId)\n /* 같은 서버 id 를 든 **다른** 줄 — 에코가 흡수되지 못하고 새 줄로 붙은 경우다. */\n const echoed = serverId ? lines.findIndex((l, i) => i !== idx && l.id === serverId) : -1\n\n /*\n * 대기 줄이 없어졌다 — 보내는 동안 대화가 지워졌거나 이력으로 갈렸다. 그래도 **응답이 어느 메시지의\n * 것인지는 안다**(`assistantMessageId`). 그 줄에 합친다.\n *\n * 여기서 그냥 돌아가면, 저장되는 값(본문 · 도구 사용)은 에코가 들고 있으니 화면은 멀쩡해 보이고\n * **저장되지 않는 것만 사라진다** — 제안 카드다. 모델은 「카드를 눌러 적용하세요」라고 말하고 화면에는\n * 누를 것이 없다(2026-09-18 트윈에서 실제로 그랬다).\n */\n if (idx < 0) {\n if (echoed < 0) return { lines, seen }\n const merged = [...lines]\n merged[echoed] = { ...merged[echoed], ...patch, id: serverId ?? merged[echoed].id }\n return { lines: merged, seen: serverId ? new Set(seen).add(serverId) : seen }\n }\n\n const nextLines = [...lines]\n /* seen 만 보고 지우면, 에코를 이 줄에 **흡수**한 경우(id 가 이미 이 줄에 있다) 자기 줄을 지운다.\n * 실제 중복은 \"다른 줄이 같은 id 를 가진\" 상태일 때뿐이다. */\n if (echoed >= 0) {\n /* 남는 것은 에코 줄이다. 응답에만 있는 것(제안)을 그 줄에 옮기고 대기 줄을 지운다. */\n nextLines[echoed] = { ...nextLines[echoed], ...patch, id: serverId ?? nextLines[echoed].id }\n nextLines.splice(idx, 1)\n return { lines: nextLines, seen }\n }\n const nextSeen = serverId ? new Set(seen).add(serverId) : seen\n nextLines[idx] = { ...nextLines[idx], ...patch, id: serverId ?? nextLines[idx].id, _localId: undefined }\n return { lines: nextLines, seen: nextSeen }\n}\n"]}
@@ -87,6 +87,8 @@ export interface AssistantChatInputArgs {
87
87
  };
88
88
  truncateAfterMessageId?: string;
89
89
  hostContext?: any;
90
+ /** Files stored with the conversation by attachToConversation, sent with this turn. */
91
+ attachmentIds?: string[];
90
92
  }
91
93
  /**
92
94
  * Build the neutral input.
@@ -47,7 +47,8 @@ export function buildAssistantChatInput(args) {
47
47
  messages: args.history,
48
48
  mode: { kind: args.mode.kind, ...(args.mode.target ? { target: args.mode.target } : {}) },
49
49
  ...(args.truncateAfterMessageId ? { truncateAfterMessageId: args.truncateAfterMessageId } : {}),
50
- ...(args.hostContext ? { hostContext: args.hostContext } : {})
50
+ ...(args.hostContext ? { hostContext: args.hostContext } : {}),
51
+ ...(args.attachmentIds?.length ? { attachmentIds: args.attachmentIds } : {})
51
52
  };
52
53
  }
53
54
  //# sourceMappingURL=chat-input-builder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-input-builder.js","sourceRoot":"","sources":["../../client/components/chat-input-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6CH;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAA2B;IAChE,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QACjC,QAAQ,EAAE,IAAI,CAAC,OAAO;QACtB,YAAY,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QACpC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,qDAAqD;QACrD,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7E,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1H,CAAA;AACH,CAAC;AAuBD;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAA4B;IAClE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,kGAAkG;YAChG,kEAAkE,CACrE,CAAA;IACH,CAAC;IAED,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QACjC,QAAQ,EAAE,IAAI,CAAC,OAAO;QACtB,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QACzF,GAAG,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAA;AACH,CAAC","sourcesContent":["/**\n * boardAIChat GraphQL mutation 의 input 객체 빌더.\n *\n * board-ai-chat.ts (Lit / Material Web 의존) 와 분리 — pure 함수만 두면 jest 의 node\n * 환경에서도 import 가능. 회귀 테스트 (selectedRefids 누락 등) 가 단위 테스트로 잠긴다.\n */\n\nexport interface ChatMutationInputArgs {\n sessionId?: string\n history: Array<{ role: string; content: string }>\n liveBoard: any\n scopes?: string[]\n /** 편집으로 접은 지점(마지막으로 남긴 서버 메시지 id) — 서버가 그 뒤 이력을 에이전트 문맥에서 뺀다. */\n truncateAfterMessageId?: string\n /** 이 대화면이 무엇을 보고 있는지(예: { spaceId }) — 도구가 대상을 알 수 있게. 없으면 모델이 식별자를 되묻는다. */\n hostContext?: any\n /**\n * 이 대화면이 쓸 수 있는 외부 도구 카테고리(registerToolCategory 이름).\n * 미지정 = 등록된 전부(현행 동작). 지정하면 정의와 **실행** 모두 그 범위로 제한된다.\n */\n toolCategories?: string[]\n /** 코어 보드 편집 도구(addComponent·스타일링·씬 조작) 사용 여부. 기본 true. */\n boardTools?: boolean\n /**\n * 첫 턴에 도구 호출을 강제할지 — 라이브 상태를 다루는 대화면에서 켠다.\n * 근거 없이 상태를 단언하는 답(도구를 아예 부르지 않은 답)을 원천에서 막는다.\n */\n requireGroundingTools?: boolean\n knownTypes?: string[]\n categories?: string[]\n componentSchemas?: any\n /**\n * 선택된 컴포넌트의 `refid` 배열 — things-scene universal numeric handle.\n * id (데이터 바인딩 이름) 와는 다른 개념.\n */\n selectedRefids: number[]\n /**\n * popup 에서 명시 선택한 #mention 매핑 — {token, refid}.\n * 사용자 textarea 에는 #token 만 보이고, 정확한 refid 는 이 별도 채널로 server 에 전달.\n * server 가 mention map 으로 LLM 에 정확한 refid 정보 첨부 (사용자 노출 0).\n */\n mentions?: Array<{ token: string; refid: number }>\n /**\n * The registered conversation this board chat is (ADR-0061 decision 7). When sent, the server\n * takes the persona, tool categories, model and limits from that mode and ignores the per-request\n * ones above. Omitted for a surface that declares no mode.\n */\n mode?: { kind: string; target?: string }\n}\n\n/**\n * GraphQL mutation 의 input 객체를 빌드.\n *\n * 핵심 보호 — `selectedRefids` 가 항상 포함되어야 한다. 누락이 사용자에게 보고된\n * \"AI 가 선택된 컴포넌트를 모른다\" 의 root cause 였다.\n */\nexport function buildChatMutationInput(args: ChatMutationInputArgs): Record<string, any> {\n return {\n sessionId: args.sessionId ?? null,\n messages: args.history,\n currentBoard: args.liveBoard ?? null,\n scopes: args.scopes,\n ...(args.truncateAfterMessageId ? { truncateAfterMessageId: args.truncateAfterMessageId } : {}),\n ...(args.hostContext ? { hostContext: args.hostContext } : {}),\n /* 미지정은 서버 기본(전부 허용)에 맡긴다 — null 을 보내 굳이 덮어쓰지 않는다. */\n ...(args.toolCategories ? { toolCategories: args.toolCategories } : {}),\n ...(args.boardTools === false ? { boardTools: false } : {}),\n ...(args.requireGroundingTools ? { requireGroundingTools: true } : {}),\n knownTypes: args.knownTypes,\n categories: args.categories,\n componentSchemas: args.componentSchemas,\n selectedRefids: args.selectedRefids,\n ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions }),\n ...(args.mode?.kind ? { mode: { kind: args.mode.kind, ...(args.mode.target ? { target: args.mode.target } : {}) } } : {})\n }\n}\n\n/**\n * Input for `assistantChat` — the door for a surface that has no document of its own.\n *\n * Deliberately a separate builder rather than a flag on the one above. The two mutations take\n * different inputs, and folding them together would mean a board field reaching a surface that\n * has no board, or a required field going missing with nothing to notice it. The one thing they\n * share is the conversation, and that is passed the same way.\n */\nexport interface AssistantChatInputArgs {\n sessionId?: string\n history: Array<{ role: string; content: string }>\n /**\n * Which conversation this is and what it is about. The persona, the tool categories, the model\n * and the limits belong to the mode the app server registered (ADR-0061 decision 7) — a caller\n * that could choose its own tool categories was a caller choosing its own authority.\n */\n mode: { kind: string; target?: string }\n truncateAfterMessageId?: string\n hostContext?: any\n}\n\n/**\n * Build the neutral input.\n *\n * Refuses a conversation with no mode instead of sending one. A turn with no mode has no persona\n * and no tools, and an answer given as nobody reads like a bad model rather than a missing\n * argument — the server refuses it too, and this says so where the surface is.\n */\nexport function buildAssistantChatInput(args: AssistantChatInputArgs): Record<string, any> {\n if (!args.mode?.kind?.trim()) {\n throw new Error(\n '[ox-assistant-chat] mode.kind is required when the chat talks to assistantChat — the app server ' +\n 'registers the mode and the screen names it (ADR-0061 decision 7)'\n )\n }\n\n return {\n sessionId: args.sessionId ?? null,\n messages: args.history,\n mode: { kind: args.mode.kind, ...(args.mode.target ? { target: args.mode.target } : {}) },\n ...(args.truncateAfterMessageId ? { truncateAfterMessageId: args.truncateAfterMessageId } : {}),\n ...(args.hostContext ? { hostContext: args.hostContext } : {})\n }\n}\n"]}
1
+ {"version":3,"file":"chat-input-builder.js","sourceRoot":"","sources":["../../client/components/chat-input-builder.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * boardAIChat GraphQL mutation 의 input 객체 빌더.\n *\n * board-ai-chat.ts (Lit / Material Web 의존) 와 분리 — pure 함수만 두면 jest 의 node\n * 환경에서도 import 가능. 회귀 테스트 (selectedRefids 누락 등) 가 단위 테스트로 잠긴다.\n */\n\nexport interface ChatMutationInputArgs {\n sessionId?: string\n history: Array<{ role: string; content: string }>\n liveBoard: any\n scopes?: string[]\n /** 편집으로 접은 지점(마지막으로 남긴 서버 메시지 id) — 서버가 그 뒤 이력을 에이전트 문맥에서 뺀다. */\n truncateAfterMessageId?: string\n /** 이 대화면이 무엇을 보고 있는지(예: { spaceId }) — 도구가 대상을 알 수 있게. 없으면 모델이 식별자를 되묻는다. */\n hostContext?: any\n /**\n * 이 대화면이 쓸 수 있는 외부 도구 카테고리(registerToolCategory 이름).\n * 미지정 = 등록된 전부(현행 동작). 지정하면 정의와 **실행** 모두 그 범위로 제한된다.\n */\n toolCategories?: string[]\n /** 코어 보드 편집 도구(addComponent·스타일링·씬 조작) 사용 여부. 기본 true. */\n boardTools?: boolean\n /**\n * 첫 턴에 도구 호출을 강제할지 — 라이브 상태를 다루는 대화면에서 켠다.\n * 근거 없이 상태를 단언하는 답(도구를 아예 부르지 않은 답)을 원천에서 막는다.\n */\n requireGroundingTools?: boolean\n knownTypes?: string[]\n categories?: string[]\n componentSchemas?: any\n /**\n * 선택된 컴포넌트의 `refid` 배열 — things-scene universal numeric handle.\n * id (데이터 바인딩 이름) 와는 다른 개념.\n */\n selectedRefids: number[]\n /**\n * popup 에서 명시 선택한 #mention 매핑 — {token, refid}.\n * 사용자 textarea 에는 #token 만 보이고, 정확한 refid 는 이 별도 채널로 server 에 전달.\n * server 가 mention map 으로 LLM 에 정확한 refid 정보 첨부 (사용자 노출 0).\n */\n mentions?: Array<{ token: string; refid: number }>\n /**\n * The registered conversation this board chat is (ADR-0061 decision 7). When sent, the server\n * takes the persona, tool categories, model and limits from that mode and ignores the per-request\n * ones above. Omitted for a surface that declares no mode.\n */\n mode?: { kind: string; target?: string }\n}\n\n/**\n * GraphQL mutation 의 input 객체를 빌드.\n *\n * 핵심 보호 — `selectedRefids` 가 항상 포함되어야 한다. 누락이 사용자에게 보고된\n * \"AI 가 선택된 컴포넌트를 모른다\" 의 root cause 였다.\n */\nexport function buildChatMutationInput(args: ChatMutationInputArgs): Record<string, any> {\n return {\n sessionId: args.sessionId ?? null,\n messages: args.history,\n currentBoard: args.liveBoard ?? null,\n scopes: args.scopes,\n ...(args.truncateAfterMessageId ? { truncateAfterMessageId: args.truncateAfterMessageId } : {}),\n ...(args.hostContext ? { hostContext: args.hostContext } : {}),\n /* 미지정은 서버 기본(전부 허용)에 맡긴다 — null 을 보내 굳이 덮어쓰지 않는다. */\n ...(args.toolCategories ? { toolCategories: args.toolCategories } : {}),\n ...(args.boardTools === false ? { boardTools: false } : {}),\n ...(args.requireGroundingTools ? { requireGroundingTools: true } : {}),\n knownTypes: args.knownTypes,\n categories: args.categories,\n componentSchemas: args.componentSchemas,\n selectedRefids: args.selectedRefids,\n ...(args.mentions && args.mentions.length > 0 && { mentions: args.mentions }),\n ...(args.mode?.kind ? { mode: { kind: args.mode.kind, ...(args.mode.target ? { target: args.mode.target } : {}) } } : {})\n }\n}\n\n/**\n * Input for `assistantChat` — the door for a surface that has no document of its own.\n *\n * Deliberately a separate builder rather than a flag on the one above. The two mutations take\n * different inputs, and folding them together would mean a board field reaching a surface that\n * has no board, or a required field going missing with nothing to notice it. The one thing they\n * share is the conversation, and that is passed the same way.\n */\nexport interface AssistantChatInputArgs {\n sessionId?: string\n history: Array<{ role: string; content: string }>\n /**\n * Which conversation this is and what it is about. The persona, the tool categories, the model\n * and the limits belong to the mode the app server registered (ADR-0061 decision 7) — a caller\n * that could choose its own tool categories was a caller choosing its own authority.\n */\n mode: { kind: string; target?: string }\n truncateAfterMessageId?: string\n hostContext?: any\n /** Files stored with the conversation by attachToConversation, sent with this turn. */\n attachmentIds?: string[]\n}\n\n/**\n * Build the neutral input.\n *\n * Refuses a conversation with no mode instead of sending one. A turn with no mode has no persona\n * and no tools, and an answer given as nobody reads like a bad model rather than a missing\n * argument — the server refuses it too, and this says so where the surface is.\n */\nexport function buildAssistantChatInput(args: AssistantChatInputArgs): Record<string, any> {\n if (!args.mode?.kind?.trim()) {\n throw new Error(\n '[ox-assistant-chat] mode.kind is required when the chat talks to assistantChat — the app server ' +\n 'registers the mode and the screen names it (ADR-0061 decision 7)'\n )\n }\n\n return {\n sessionId: args.sessionId ?? null,\n messages: args.history,\n mode: { kind: args.mode.kind, ...(args.mode.target ? { target: args.mode.target } : {}) },\n ...(args.truncateAfterMessageId ? { truncateAfterMessageId: args.truncateAfterMessageId } : {}),\n ...(args.hostContext ? { hostContext: args.hostContext } : {}),\n ...(args.attachmentIds?.length ? { attachmentIds: args.attachmentIds } : {})\n }\n}\n"]}
@@ -36,14 +36,18 @@ describe('the panel does not raise a second tab axis', () => {
36
36
  * 이미지는 `hostContext.image` 로 사람 턴에 붙는 길이 있다.
37
37
  */
38
38
  describe('the attach control offers only what has a path', () => {
39
- it('★ 이미지만 받는다 — 읽히지 않는 것을 화면이 권하지 않는다', () => {
40
- /*
41
- * 여기서는 주석을 안 벗긴다. `image/*` 자체가 `/*` 를 품고 있어서, 주석 벗기개가 그 자리를 주석의
42
- * 시작으로 읽고 다음 `*` + `/` 까지 지워 버린다 — 값이 사라지고 시험은 「없다」고 답한다.
43
- * 이 값은 주석에 적힐 일이 없으므로 원문에서 그대로 읽는다.
44
- */
45
- const accept = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf-8').match(/accept="([^"]*)"/);
46
- expect(accept?.[1]).toBe('image/*');
39
+ /*
40
+ * 2026-09-25 — 받을 것을 **모드가 선언한다**(design/ai/shared-dock.md §9-1). 이미지 · 글 파일 모두 이제 모델에
41
+ * 닿는 길이 있다(보낸 턴에는 원문, 그 뒤에는 readChatAttachment). 그래서 규칙이 「이미지만」에서 「모드가 선언한
42
+ * 것만, 선언이 없으면 아무것도」로 넓어졌다. 보드 문(boardAIChat)은 모드 정책을 모르므로 이미지 길 그대로다.
43
+ */
44
+ const source = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf-8');
45
+ it('★ 중립 문은 모드의 선언만 받는다 — 선언이 없으면 첨부 단추가 없다', () => {
46
+ expect(source).toMatch(/accept=\$\{this\.usesAttachmentPolicy \? acceptOf\(this\.modeView\?\.attachments\) : 'image\/\*'\}/);
47
+ expect(source).toMatch(/return !this\.usesAttachmentPolicy \|\| !!this\.modeView\?\.attachments\?\.kinds\?\.length/);
48
+ });
49
+ it('보드 문은 이미지 길 그대로다', () => {
50
+ expect(source).toMatch(/: 'image\/\*'\}/);
47
51
  });
48
52
  });
49
53
  //# sourceMappingURL=panel-aria.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"panel-aria.test.js","sourceRoot":"","sources":["../../client/components/panel-aria.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC;SAChD,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AAED,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAE7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC9D,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C;;;;WAIG;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAA;QAEpG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 패널 안에 고르는 축이 둘일 때 — 둘 다 「탭」이라고 말하지 않는다.\n *\n * ── 왜 이 시험이 있나 ──────────────────────────────────────────────────────\n * 대화 고르개를 칩 줄로 만들면서 `role=\"tablist\"` 를 달았다. 바로 아래가 세션 탭이고 그쪽도 tablist\n * 라서, 화면 낭독기로 들으면 「탭 목록, 대화」 다음에 「탭 목록, Conversation history」 가 이어졌다.\n * 고르는 축이 둘인데 둘 다 같은 이름으로 불린다. 눈으로 보면 멀쩡해서 화면에서는 안 잡힌다 —\n * UX 레인이 소스를 읽고 잡았다(2026-09-18).\n *\n * 탭 축은 세션 toolbar(`assistant-session-toolbar.ts`)가 갖는다. 이 패널은 자기 탭 축을 세우지 않는다.\n * ARIA 탭은 좌우 화살표 이동과 `tabpanel` 이 함께 있어야 성립하는데, 여기에는 둘 다 없다.\n *\n * 주석은 벗겨 낸다 — 위 문단에 적힌 `role=\"tablist\"` 가 검사를 통과시키면 그 검사는 자기 산문을 센다.\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nfunction bare(name: string): string {\n return readFileSync(join(__dirname, name), 'utf-8')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/^\\s*\\/\\/.*$/gm, '')\n}\n\ndescribe('the panel does not raise a second tab axis', () => {\n it('★ assistant-chat 는 tablist/tab 을 선언하지 않는다 — 그 축은 세션 toolbar의 것이다', () => {\n const found = [...bare('assistant-chat.ts').matchAll(/role=\"(tablist|tab)\"/g)].map(m => m[1])\n\n expect(found).toEqual([])\n })\n\n it('세션 toolbar는 그 축을 계속 갖는다 — 위 시험이 축을 없애라는 뜻으로 읽히지 않도록', () => {\n expect(bare('assistant-session-toolbar.ts')).toContain('role=\"tablist\"')\n })\n})\n\n/*\n * 첨부는 **되는 것만** 권한다 — ADR-0071 보탬(2026-09-18).\n *\n * `.json` · `.txt` · `.csv` 도 받고 있었는데, 그 파일들은 실려만 가고 모델에 붙는 코드가 없었다. 사람이\n * CSV 를 붙이고 물으면 파일 칩이 뜨고 답도 왔다 — 그 답은 파일을 본 적이 없고 오류도 안 났다.\n * 이미지는 `hostContext.image` 로 사람 턴에 붙는 길이 있다.\n */\ndescribe('the attach control offers only what has a path', () => {\n it('★ 이미지만 받는다 — 읽히지 않는 것을 화면이 권하지 않는다', () => {\n /*\n * 여기서는 주석을 안 벗긴다. `image/*` 자체가 `/*` 를 품고 있어서, 주석 벗기개가 그 자리를 주석의\n * 시작으로 읽고 다음 `*` + `/` 까지 지워 버린다 — 값이 사라지고 시험은 「없다」고 답한다.\n * 이 값은 주석에 적힐 일이 없으므로 원문에서 그대로 읽는다.\n */\n const accept = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf-8').match(/accept=\"([^\"]*)\"/)\n\n expect(accept?.[1]).toBe('image/*')\n })\n})\n"]}
1
+ {"version":3,"file":"panel-aria.test.js","sourceRoot":"","sources":["../../client/components/panel-aria.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC;SAChD,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;AACjC,CAAC;AAED,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAE7F,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC9D;;;;OAIG;IACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAA;IAE1E,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,oGAAoG,CAAC,CAAA;QAC5H,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,4FAA4F,CAAC,CAAA;IACtH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 패널 안에 고르는 축이 둘일 때 — 둘 다 「탭」이라고 말하지 않는다.\n *\n * ── 왜 이 시험이 있나 ──────────────────────────────────────────────────────\n * 대화 고르개를 칩 줄로 만들면서 `role=\"tablist\"` 를 달았다. 바로 아래가 세션 탭이고 그쪽도 tablist\n * 라서, 화면 낭독기로 들으면 「탭 목록, 대화」 다음에 「탭 목록, Conversation history」 가 이어졌다.\n * 고르는 축이 둘인데 둘 다 같은 이름으로 불린다. 눈으로 보면 멀쩡해서 화면에서는 안 잡힌다 —\n * UX 레인이 소스를 읽고 잡았다(2026-09-18).\n *\n * 탭 축은 세션 toolbar(`assistant-session-toolbar.ts`)가 갖는다. 이 패널은 자기 탭 축을 세우지 않는다.\n * ARIA 탭은 좌우 화살표 이동과 `tabpanel` 이 함께 있어야 성립하는데, 여기에는 둘 다 없다.\n *\n * 주석은 벗겨 낸다 — 위 문단에 적힌 `role=\"tablist\"` 가 검사를 통과시키면 그 검사는 자기 산문을 센다.\n */\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nfunction bare(name: string): string {\n return readFileSync(join(__dirname, name), 'utf-8')\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/^\\s*\\/\\/.*$/gm, '')\n}\n\ndescribe('the panel does not raise a second tab axis', () => {\n it('★ assistant-chat 는 tablist/tab 을 선언하지 않는다 — 그 축은 세션 toolbar의 것이다', () => {\n const found = [...bare('assistant-chat.ts').matchAll(/role=\"(tablist|tab)\"/g)].map(m => m[1])\n\n expect(found).toEqual([])\n })\n\n it('세션 toolbar는 그 축을 계속 갖는다 — 위 시험이 축을 없애라는 뜻으로 읽히지 않도록', () => {\n expect(bare('assistant-session-toolbar.ts')).toContain('role=\"tablist\"')\n })\n})\n\n/*\n * 첨부는 **되는 것만** 권한다 — ADR-0071 보탬(2026-09-18).\n *\n * `.json` · `.txt` · `.csv` 도 받고 있었는데, 그 파일들은 실려만 가고 모델에 붙는 코드가 없었다. 사람이\n * CSV 를 붙이고 물으면 파일 칩이 뜨고 답도 왔다 — 그 답은 파일을 본 적이 없고 오류도 안 났다.\n * 이미지는 `hostContext.image` 로 사람 턴에 붙는 길이 있다.\n */\ndescribe('the attach control offers only what has a path', () => {\n /*\n * 2026-09-25 — 받을 것을 **모드가 선언한다**(design/ai/shared-dock.md §9-1). 이미지 · 글 파일 모두 이제 모델에\n * 닿는 길이 있다(보낸 턴에는 원문, 그 뒤에는 readChatAttachment). 그래서 규칙이 「이미지만」에서 「모드가 선언한\n * 것만, 선언이 없으면 아무것도」로 넓어졌다. 보드 문(boardAIChat)은 모드 정책을 모르므로 이미지 길 그대로다.\n */\n const source = readFileSync(join(__dirname, 'assistant-chat.ts'), 'utf-8')\n\n it('★ 중립 문은 모드의 선언만 받는다 — 선언이 없으면 첨부 단추가 없다', () => {\n expect(source).toMatch(/accept=\\$\\{this\\.usesAttachmentPolicy \\? acceptOf\\(this\\.modeView\\?\\.attachments\\) : 'image\\/\\*'\\}/)\n expect(source).toMatch(/return !this\\.usesAttachmentPolicy \\|\\| !!this\\.modeView\\?\\.attachments\\?\\.kinds\\?\\.length/)\n })\n\n it('보드 문은 이미지 길 그대로다', () => {\n expect(source).toMatch(/: 'image\\/\\*'\\}/)\n })\n})\n"]}
@@ -14,6 +14,14 @@ export * from './components/mention-popup-helpers.js';
14
14
  export * from './utils/assistant-session-controller.js';
15
15
  export * from './utils/assistant-session-transport.js';
16
16
  export * from './components/assistant-session-toolbar.js';
17
+ export * from './utils/assistant-session-admin.js';
18
+ export * from './components/assistant-session-manager.js';
19
+ export * from './components/assistant-dock.js';
20
+ export * from './setup-assistant-dock.js';
21
+ export * from './utils/assistant-surface.js';
22
+ export * from './utils/assistant-proposals.js';
23
+ export * from './utils/assistant-ask.js';
24
+ export * from './utils/assistant-activity.js';
17
25
  export * from './utils/assistant-request-context.js';
18
26
  export * from './utils/assistant-capability.js';
19
27
  export * from './utils/assistant-capability-read.js';
@@ -14,6 +14,14 @@ export * from './components/mention-popup-helpers.js';
14
14
  export * from './utils/assistant-session-controller.js';
15
15
  export * from './utils/assistant-session-transport.js';
16
16
  export * from './components/assistant-session-toolbar.js';
17
+ export * from './utils/assistant-session-admin.js';
18
+ export * from './components/assistant-session-manager.js';
19
+ export * from './components/assistant-dock.js';
20
+ export * from './setup-assistant-dock.js';
21
+ export * from './utils/assistant-surface.js';
22
+ export * from './utils/assistant-proposals.js';
23
+ export * from './utils/assistant-ask.js';
24
+ export * from './utils/assistant-activity.js';
17
25
  export * from './utils/assistant-request-context.js';
18
26
  export * from './utils/assistant-capability.js';
19
27
  export * from './utils/assistant-capability-read.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oCAAoC,CAAA;AAClD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uCAAuC,CAAA;AACrD,cAAc,yCAAyC,CAAA;AACvD,cAAc,wCAAwC,CAAA;AACtD,cAAc,2CAA2C,CAAA;AACzD,cAAc,sCAAsC,CAAA;AACpD,cAAc,iCAAiC,CAAA;AAC/C,cAAc,sCAAsC,CAAA","sourcesContent":["/**\n * `@things-factory/ai-assistant` — 대화면.\n *\n * 표준 컴포넌트는 `<ox-assistant-chat>` 이다.\n */\nexport * from './components/assistant-chat.js'\nexport * from './components/chat-defaults.js'\nexport * from './components/assistant-mode.js'\nexport * from './components/markdown.js'\nexport * from './components/chat-echo-dedup.js'\nexport * from './components/chat-input-builder.js'\nexport * from './components/mention-popup.js'\nexport * from './components/mention-popup-helpers.js'\nexport * from './utils/assistant-session-controller.js'\nexport * from './utils/assistant-session-transport.js'\nexport * from './components/assistant-session-toolbar.js'\nexport * from './utils/assistant-request-context.js'\nexport * from './utils/assistant-capability.js'\nexport * from './utils/assistant-capability-read.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oCAAoC,CAAA;AAClD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uCAAuC,CAAA;AACrD,cAAc,yCAAyC,CAAA;AACvD,cAAc,wCAAwC,CAAA;AACtD,cAAc,2CAA2C,CAAA;AACzD,cAAc,oCAAoC,CAAA;AAClD,cAAc,2CAA2C,CAAA;AACzD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,sCAAsC,CAAA;AACpD,cAAc,iCAAiC,CAAA;AAC/C,cAAc,sCAAsC,CAAA","sourcesContent":["/**\n * `@things-factory/ai-assistant` — 대화면.\n *\n * 표준 컴포넌트는 `<ox-assistant-chat>` 이다.\n */\nexport * from './components/assistant-chat.js'\nexport * from './components/chat-defaults.js'\nexport * from './components/assistant-mode.js'\nexport * from './components/markdown.js'\nexport * from './components/chat-echo-dedup.js'\nexport * from './components/chat-input-builder.js'\nexport * from './components/mention-popup.js'\nexport * from './components/mention-popup-helpers.js'\nexport * from './utils/assistant-session-controller.js'\nexport * from './utils/assistant-session-transport.js'\nexport * from './components/assistant-session-toolbar.js'\nexport * from './utils/assistant-session-admin.js'\nexport * from './components/assistant-session-manager.js'\nexport * from './components/assistant-dock.js'\nexport * from './setup-assistant-dock.js'\nexport * from './utils/assistant-surface.js'\nexport * from './utils/assistant-proposals.js'\nexport * from './utils/assistant-ask.js'\nexport * from './utils/assistant-activity.js'\nexport * from './utils/assistant-request-context.js'\nexport * from './utils/assistant-capability.js'\nexport * from './utils/assistant-capability-read.js'\n"]}
@@ -0,0 +1,15 @@
1
+ import '@material/web/icon/icon.js';
2
+ import './components/assistant-dock.js';
3
+ import { LitElement } from 'lit';
4
+ export declare class AssistantDockButton extends LitElement {
5
+ static styles: import("lit").CSSResult;
6
+ viewpart: string;
7
+ private unread;
8
+ private unsubscribe?;
9
+ connectedCallback(): void;
10
+ disconnectedCallback(): void;
11
+ render(): import("lit-html").TemplateResult<1>;
12
+ }
13
+ export declare function setupAssistantDock(options?: {
14
+ viewpart?: string;
15
+ }): void;
@@ -0,0 +1,119 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ /*
3
+ * Standing the AI dock up in an application - one call in its bootstrap (design/ai/shared-dock.md §2-1).
4
+ *
5
+ * setupAssistantDock()
6
+ * registerAssistantHome({ ... }) // what the app's own conversation is about
7
+ * registerSubjectSurface('items', ...) // how a page's context.subject becomes a surface (if any)
8
+ *
9
+ * The dock sits in the aside bar above everything, closed at first; the header band's `forum` button opens
10
+ * it and carries how many conversations have something unread. Asks from other screens open it too.
11
+ */
12
+ import '@material/web/icon/icon.js';
13
+ import './components/assistant-dock.js';
14
+ import { LitElement, css, html, nothing } from 'lit';
15
+ import { customElement, property, state } from 'lit/decorators.js';
16
+ import { i18next } from '@operato/i18n';
17
+ import { appendViewpart, openOverlay, TOOL_POSITION, VIEWPART_LEVEL, VIEWPART_POSITION } from '@operato/layout';
18
+ import { store } from '@operato/shell';
19
+ import { APPEND_APP_TOOL } from '@things-factory/apptool-base/client/index.js';
20
+ import { ASSISTANT_DOCK_VIEWPART } from './components/assistant-dock.js';
21
+ import { startSessionActivityFeed, subscribeSessionActivity, unreadSessionCount } from './utils/assistant-activity.js';
22
+ let AssistantDockButton = class AssistantDockButton extends LitElement {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.viewpart = ASSISTANT_DOCK_VIEWPART;
26
+ this.unread = 0;
27
+ }
28
+ static { this.styles = css `
29
+ :host {
30
+ position: relative;
31
+ display: inline-flex;
32
+ cursor: pointer;
33
+ }
34
+
35
+ .badge {
36
+ position: absolute;
37
+ top: -4px;
38
+ right: -6px;
39
+ min-width: 14px;
40
+ height: 14px;
41
+ padding: 0 3px;
42
+ box-sizing: border-box;
43
+ border-radius: 7px;
44
+ background: var(--md-sys-color-error);
45
+ color: var(--md-sys-color-on-error);
46
+ font-size: 9px;
47
+ line-height: 14px;
48
+ text-align: center;
49
+ }
50
+ `; }
51
+ connectedCallback() {
52
+ super.connectedCallback();
53
+ this.unread = unreadSessionCount();
54
+ this.unsubscribe = subscribeSessionActivity(() => (this.unread = unreadSessionCount()));
55
+ }
56
+ disconnectedCallback() {
57
+ this.unsubscribe?.();
58
+ super.disconnectedCallback();
59
+ }
60
+ /*
61
+ * Opens, never toggles: after a reload the viewpart can be marked shown while nothing is drawn, and a toggle
62
+ * then closed it - the first press did nothing (found on :3700, 2026-09-25). The dock's own ✕ closes it.
63
+ */
64
+ render() {
65
+ return html `
66
+ <md-icon
67
+ title=${i18next.t('ai-assistant.dock.open', { defaultValue: 'Ask AI' })}
68
+ @click=${() => openOverlay(this.viewpart, { backdrop: false })}
69
+ >forum</md-icon
70
+ >
71
+ ${this.unread ? html `<span class="badge">${this.unread}</span>` : nothing}
72
+ `;
73
+ }
74
+ };
75
+ __decorate([
76
+ property({ type: String }),
77
+ __metadata("design:type", Object)
78
+ ], AssistantDockButton.prototype, "viewpart", void 0);
79
+ __decorate([
80
+ state(),
81
+ __metadata("design:type", Object)
82
+ ], AssistantDockButton.prototype, "unread", void 0);
83
+ AssistantDockButton = __decorate([
84
+ customElement('ox-assistant-dock-button')
85
+ ], AssistantDockButton);
86
+ export { AssistantDockButton };
87
+ let installed = false;
88
+ export function setupAssistantDock(options = {}) {
89
+ if (installed)
90
+ return;
91
+ installed = true;
92
+ const viewpart = options.viewpart ?? ASSISTANT_DOCK_VIEWPART;
93
+ startSessionActivityFeed();
94
+ appendViewpart({
95
+ name: viewpart,
96
+ /*
97
+ * `persistent` keeps the dock on editors that bleed wide (the board modeller hides the aside bar with
98
+ * `widebleed`) - those are the screens where the conversation authors, so the dock must still open there.
99
+ */
100
+ viewpart: {
101
+ show: false,
102
+ persistent: true,
103
+ level: VIEWPART_LEVEL.TOPMOST,
104
+ template: html `<ox-assistant-dock .viewpart=${viewpart}></ox-assistant-dock>`
105
+ },
106
+ position: VIEWPART_POSITION.ASIDEBAR
107
+ });
108
+ store.dispatch({
109
+ type: APPEND_APP_TOOL,
110
+ tool: {
111
+ name: 'assistant-dock-open',
112
+ position: TOOL_POSITION.REAR,
113
+ template: html `<ox-assistant-dock-button .viewpart=${viewpart}></ox-assistant-dock-button>`
114
+ }
115
+ });
116
+ /* askAssistant() from any screen opens the dock. */
117
+ window.addEventListener('assistant-dock-open', () => openOverlay(viewpart, { backdrop: false }));
118
+ }
119
+ //# sourceMappingURL=setup-assistant-dock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-assistant-dock.js","sourceRoot":"","sources":["../client/setup-assistant-dock.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Standing the AI dock up in an application - one call in its bootstrap (design/ai/shared-dock.md §2-1).\n *\n * setupAssistantDock()\n * registerAssistantHome({ ... }) // what the app's own conversation is about\n * registerSubjectSurface('items', ...) // how a page's context.subject becomes a surface (if any)\n *\n * The dock sits in the aside bar above everything, closed at first; the header band's `forum` button opens\n * it and carries how many conversations have something unread. Asks from other screens open it too.\n */\nimport '@material/web/icon/icon.js'\nimport './components/assistant-dock.js'\n\nimport { LitElement, css, html, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { appendViewpart, openOverlay, TOOL_POSITION, VIEWPART_LEVEL, VIEWPART_POSITION } from '@operato/layout'\nimport { store } from '@operato/shell'\nimport { APPEND_APP_TOOL } from '@things-factory/apptool-base/client/index.js'\n\nimport { ASSISTANT_DOCK_VIEWPART } from './components/assistant-dock.js'\nimport { startSessionActivityFeed, subscribeSessionActivity, unreadSessionCount } from './utils/assistant-activity.js'\n\n@customElement('ox-assistant-dock-button')\nexport class AssistantDockButton extends LitElement {\n static styles = css`\n :host {\n position: relative;\n display: inline-flex;\n cursor: pointer;\n }\n\n .badge {\n position: absolute;\n top: -4px;\n right: -6px;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n box-sizing: border-box;\n border-radius: 7px;\n background: var(--md-sys-color-error);\n color: var(--md-sys-color-on-error);\n font-size: 9px;\n line-height: 14px;\n text-align: center;\n }\n `\n\n @property({ type: String }) viewpart = ASSISTANT_DOCK_VIEWPART\n @state() private unread = 0\n\n private unsubscribe?: () => void\n\n connectedCallback() {\n super.connectedCallback()\n this.unread = unreadSessionCount()\n this.unsubscribe = subscribeSessionActivity(() => (this.unread = unreadSessionCount()))\n }\n\n disconnectedCallback() {\n this.unsubscribe?.()\n super.disconnectedCallback()\n }\n\n /*\n * Opens, never toggles: after a reload the viewpart can be marked shown while nothing is drawn, and a toggle\n * then closed it - the first press did nothing (found on :3700, 2026-09-25). The dock's own ✕ closes it.\n */\n render() {\n return html`\n <md-icon\n title=${i18next.t('ai-assistant.dock.open', { defaultValue: 'Ask AI' })}\n @click=${() => openOverlay(this.viewpart, { backdrop: false })}\n >forum</md-icon\n >\n ${this.unread ? html`<span class=\"badge\">${this.unread}</span>` : nothing}\n `\n }\n}\n\nlet installed = false\n\nexport function setupAssistantDock(options: { viewpart?: string } = {}): void {\n if (installed) return\n installed = true\n const viewpart = options.viewpart ?? ASSISTANT_DOCK_VIEWPART\n\n startSessionActivityFeed()\n\n appendViewpart({\n name: viewpart,\n /*\n * `persistent` keeps the dock on editors that bleed wide (the board modeller hides the aside bar with\n * `widebleed`) - those are the screens where the conversation authors, so the dock must still open there.\n */\n viewpart: {\n show: false,\n persistent: true,\n level: VIEWPART_LEVEL.TOPMOST,\n template: html`<ox-assistant-dock .viewpart=${viewpart}></ox-assistant-dock>`\n },\n position: VIEWPART_POSITION.ASIDEBAR\n })\n\n store.dispatch({\n type: APPEND_APP_TOOL,\n tool: {\n name: 'assistant-dock-open',\n position: TOOL_POSITION.REAR,\n template: html`<ox-assistant-dock-button .viewpart=${viewpart}></ox-assistant-dock-button>`\n }\n })\n\n /* askAssistant() from any screen opens the dock. */\n window.addEventListener('assistant-dock-open', () => openOverlay(viewpart, { backdrop: false }))\n}\n"]}