@things-factory/ai-assistant 10.1.46 → 10.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/client/components/assistant-chat.ts +100 -20
  2. package/client/components/assistant-dock.ts +617 -0
  3. package/client/components/assistant-mode.ts +29 -0
  4. package/client/components/assistant-session-manager.ts +607 -0
  5. package/client/components/chat-echo-dedup.ts +4 -0
  6. package/client/components/chat-input-builder.ts +12 -2
  7. package/client/components/panel-aria.test.ts +13 -8
  8. package/client/index.ts +10 -0
  9. package/client/setup-assistant-dock.ts +109 -0
  10. package/client/utils/assistant-activity.ts +122 -0
  11. package/client/utils/assistant-ask.ts +44 -0
  12. package/client/utils/assistant-capability-read.ts +24 -0
  13. package/client/utils/assistant-capability.test.ts +17 -0
  14. package/client/utils/assistant-capability.ts +19 -0
  15. package/client/utils/assistant-proposals.ts +50 -0
  16. package/client/utils/assistant-session-actions.ts +18 -0
  17. package/client/utils/assistant-session-admin.ts +167 -0
  18. package/client/utils/assistant-session-controller.ts +2 -0
  19. package/client/utils/assistant-session-transport.ts +6 -2
  20. package/client/utils/assistant-surface.ts +157 -0
  21. package/client/utils/attachment-policy.ts +71 -0
  22. package/dist-client/components/assistant-chat.d.ts +6 -0
  23. package/dist-client/components/assistant-chat.js +101 -21
  24. package/dist-client/components/assistant-chat.js.map +1 -1
  25. package/dist-client/components/assistant-dock.d.ts +74 -0
  26. package/dist-client/components/assistant-dock.js +641 -0
  27. package/dist-client/components/assistant-dock.js.map +1 -0
  28. package/dist-client/components/assistant-mode.d.ts +6 -0
  29. package/dist-client/components/assistant-mode.js +24 -0
  30. package/dist-client/components/assistant-mode.js.map +1 -1
  31. package/dist-client/components/assistant-session-manager.d.ts +77 -0
  32. package/dist-client/components/assistant-session-manager.js +652 -0
  33. package/dist-client/components/assistant-session-manager.js.map +1 -0
  34. package/dist-client/components/chat-echo-dedup.js +4 -0
  35. package/dist-client/components/chat-echo-dedup.js.map +1 -1
  36. package/dist-client/components/chat-input-builder.d.ts +11 -0
  37. package/dist-client/components/chat-input-builder.js +4 -2
  38. package/dist-client/components/chat-input-builder.js.map +1 -1
  39. package/dist-client/components/panel-aria.test.js +12 -8
  40. package/dist-client/components/panel-aria.test.js.map +1 -1
  41. package/dist-client/index.d.ts +10 -0
  42. package/dist-client/index.js +10 -0
  43. package/dist-client/index.js.map +1 -1
  44. package/dist-client/setup-assistant-dock.d.ts +15 -0
  45. package/dist-client/setup-assistant-dock.js +110 -0
  46. package/dist-client/setup-assistant-dock.js.map +1 -0
  47. package/dist-client/tsconfig.tsbuildinfo +1 -1
  48. package/dist-client/utils/assistant-activity.d.ts +22 -0
  49. package/dist-client/utils/assistant-activity.js +104 -0
  50. package/dist-client/utils/assistant-activity.js.map +1 -0
  51. package/dist-client/utils/assistant-ask.d.ts +23 -0
  52. package/dist-client/utils/assistant-ask.js +18 -0
  53. package/dist-client/utils/assistant-ask.js.map +1 -0
  54. package/dist-client/utils/assistant-capability-read.d.ts +6 -0
  55. package/dist-client/utils/assistant-capability-read.js +23 -0
  56. package/dist-client/utils/assistant-capability-read.js.map +1 -0
  57. package/dist-client/utils/assistant-capability.d.ts +4 -0
  58. package/dist-client/utils/assistant-capability.js +17 -0
  59. package/dist-client/utils/assistant-capability.js.map +1 -0
  60. package/dist-client/utils/assistant-capability.test.d.ts +1 -0
  61. package/dist-client/utils/assistant-capability.test.js +16 -0
  62. package/dist-client/utils/assistant-capability.test.js.map +1 -0
  63. package/dist-client/utils/assistant-proposals.d.ts +29 -0
  64. package/dist-client/utils/assistant-proposals.js +22 -0
  65. package/dist-client/utils/assistant-proposals.js.map +1 -0
  66. package/dist-client/utils/assistant-session-actions.d.ts +12 -0
  67. package/dist-client/utils/assistant-session-actions.js +19 -0
  68. package/dist-client/utils/assistant-session-actions.js.map +1 -0
  69. package/dist-client/utils/assistant-session-admin.d.ts +72 -0
  70. package/dist-client/utils/assistant-session-admin.js +81 -0
  71. package/dist-client/utils/assistant-session-admin.js.map +1 -0
  72. package/dist-client/utils/assistant-session-controller.d.ts +2 -0
  73. package/dist-client/utils/assistant-session-controller.js.map +1 -1
  74. package/dist-client/utils/assistant-session-transport.js +6 -2
  75. package/dist-client/utils/assistant-session-transport.js.map +1 -1
  76. package/dist-client/utils/assistant-surface.d.ts +72 -0
  77. package/dist-client/utils/assistant-surface.js +99 -0
  78. package/dist-client/utils/assistant-surface.js.map +1 -0
  79. package/dist-client/utils/attachment-policy.d.ts +35 -0
  80. package/dist-client/utils/attachment-policy.js +56 -0
  81. package/dist-client/utils/attachment-policy.js.map +1 -0
  82. package/dist-server/service/assistant-chat-resolver.d.ts +1 -0
  83. package/dist-server/service/assistant-chat-resolver.js +72 -4
  84. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  85. package/dist-server/service/assistant-mode/assistant-mode-resolver.d.ts +1 -0
  86. package/dist-server/service/assistant-mode/assistant-mode-resolver.js +7 -0
  87. package/dist-server/service/assistant-mode/assistant-mode-resolver.js.map +1 -1
  88. package/dist-server/service/assistant-mode/resolve.d.ts +3 -0
  89. package/dist-server/service/assistant-mode/resolve.js +6 -2
  90. package/dist-server/service/assistant-mode/resolve.js.map +1 -1
  91. package/dist-server/service/assistant-mode/types.d.ts +6 -0
  92. package/dist-server/service/assistant-mode/types.js +0 -9
  93. package/dist-server/service/assistant-mode/types.js.map +1 -1
  94. package/dist-server/service/chat-message/chat-message.d.ts +6 -0
  95. package/dist-server/service/chat-message/chat-message.js +19 -0
  96. package/dist-server/service/chat-message/chat-message.js.map +1 -1
  97. package/dist-server/service/chat-session/attachment-policy.d.ts +44 -0
  98. package/dist-server/service/chat-session/attachment-policy.js +56 -0
  99. package/dist-server/service/chat-session/attachment-policy.js.map +1 -0
  100. package/dist-server/service/chat-session/attachment-route.d.ts +3 -0
  101. package/dist-server/service/chat-session/attachment-route.js +44 -0
  102. package/dist-server/service/chat-session/attachment-route.js.map +1 -0
  103. package/dist-server/service/chat-session/attachment-tools.d.ts +3 -0
  104. package/dist-server/service/chat-session/attachment-tools.js +59 -0
  105. package/dist-server/service/chat-session/attachment-tools.js.map +1 -0
  106. package/dist-server/service/chat-session/session-attachments.d.ts +36 -0
  107. package/dist-server/service/chat-session/session-attachments.js +84 -0
  108. package/dist-server/service/chat-session/session-attachments.js.map +1 -0
  109. package/dist-server/service/chat-session/session-inbox.d.ts +29 -0
  110. package/dist-server/service/chat-session/session-inbox.js +32 -8
  111. package/dist-server/service/chat-session/session-inbox.js.map +1 -1
  112. package/dist-server/service/chat-session/session-purge.d.ts +14 -0
  113. package/dist-server/service/chat-session/session-purge.js +77 -0
  114. package/dist-server/service/chat-session/session-purge.js.map +1 -0
  115. package/dist-server/service/chat-session/session-retention.d.ts +6 -0
  116. package/dist-server/service/chat-session/session-retention.js +20 -0
  117. package/dist-server/service/chat-session/session-retention.js.map +1 -0
  118. package/dist-server/service/chat-session/session-visibility.d.ts +8 -0
  119. package/dist-server/service/chat-session/session-visibility.js +11 -0
  120. package/dist-server/service/chat-session/session-visibility.js.map +1 -1
  121. package/dist-server/service/chat-session/session-write-gate.d.ts +11 -0
  122. package/dist-server/service/chat-session/session-write-gate.js +24 -0
  123. package/dist-server/service/chat-session/session-write-gate.js.map +1 -0
  124. package/dist-server/service/chat-session-participant/invite-notification.d.ts +1 -1
  125. package/dist-server/service/chat-session-participant/invite-notification.js +21 -6
  126. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
  127. package/dist-server/service/chat-session-participant/participant-lookup-roles.d.ts +3 -0
  128. package/dist-server/service/chat-session-participant/participant-lookup-roles.js +10 -0
  129. package/dist-server/service/chat-session-participant/participant-lookup-roles.js.map +1 -0
  130. package/dist-server/service/chat-session-participant/participant-lookup.d.ts +2 -0
  131. package/dist-server/service/chat-session-participant/participant-lookup.js +26 -0
  132. package/dist-server/service/chat-session-participant/participant-lookup.js.map +1 -0
  133. package/dist-server/service/chat-session-resolver.d.ts +17 -2
  134. package/dist-server/service/chat-session-resolver.js +135 -15
  135. package/dist-server/service/chat-session-resolver.js.map +1 -1
  136. package/dist-server/service/index.d.ts +6 -0
  137. package/dist-server/service/index.js +8 -1
  138. package/dist-server/service/index.js.map +1 -1
  139. package/dist-server/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +11 -4
  141. package/server/service/assistant-chat-resolver.ts +72 -5
  142. package/server/service/assistant-mode/assistant-mode-resolver.ts +6 -0
  143. package/server/service/assistant-mode/resolve.ts +9 -2
  144. package/server/service/assistant-mode/types.ts +6 -0
  145. package/server/service/chat-message/chat-message.ts +17 -0
  146. package/server/service/chat-message/fold-session-history.test.ts +18 -0
  147. package/server/service/chat-session/attachment-policy.test.ts +42 -0
  148. package/server/service/chat-session/attachment-policy.ts +68 -0
  149. package/server/service/chat-session/attachment-route.ts +45 -0
  150. package/server/service/chat-session/attachment-tools.ts +63 -0
  151. package/server/service/chat-session/session-attachments.ts +89 -0
  152. package/server/service/chat-session/session-inbox.test.ts +71 -1
  153. package/server/service/chat-session/session-inbox.ts +61 -11
  154. package/server/service/chat-session/session-purge.test.ts +24 -0
  155. package/server/service/chat-session/session-purge.ts +81 -0
  156. package/server/service/chat-session/session-retention.ts +17 -0
  157. package/server/service/chat-session/session-visibility.ts +11 -0
  158. package/server/service/chat-session/session-write-gate.test.ts +53 -0
  159. package/server/service/chat-session/session-write-gate.ts +31 -0
  160. package/server/service/chat-session-participant/invite-notification.test.ts +15 -0
  161. package/server/service/chat-session-participant/invite-notification.ts +24 -6
  162. package/server/service/chat-session-participant/participant-lookup-roles.ts +6 -0
  163. package/server/service/chat-session-participant/participant-lookup.ts +22 -0
  164. package/server/service/chat-session-resolver.ts +130 -13
  165. package/server/service/index.ts +6 -0
  166. package/test/assistant-dock.test.ts +118 -0
  167. package/test/assistant-session-manager.test.ts +126 -0
  168. package/test/attachment-policy-agree.test.ts +32 -0
  169. package/test/open-doors.test.ts +5 -2
  170. package/translations/en.json +61 -1
  171. package/translations/ja.json +61 -1
  172. package/translations/ko.json +61 -1
  173. package/translations/ms.json +61 -1
  174. package/translations/zh.json +61 -1
@@ -0,0 +1,22 @@
1
+ export interface SessionActivity {
2
+ sessionId: string;
3
+ kind: 'message' | 'meta';
4
+ anchorType?: string;
5
+ anchorId?: string;
6
+ at: string;
7
+ preview?: string;
8
+ actorId?: string;
9
+ }
10
+ type Listener = (activity: SessionActivity) => void;
11
+ export declare function unreadSessionCount(): number;
12
+ export declare function isSessionUnread(sessionId: string): boolean;
13
+ /** 그 세션을 읽었다 — 도크가 세션을 열 때 호출(서버 markSessionRead 와 짝). */
14
+ export declare function clearSessionUnread(sessionId: string): void;
15
+ export declare function subscribeSessionActivity(fn: Listener): () => void;
16
+ /**
17
+ * 구독 시작 — 부팅에서 1회. 재호출은 무시(중복 구독 방지).
18
+ * 실패해도 앱을 막지 않는다 — 배지가 없을 뿐이고, 도크를 열면 목록을 직접 읽는다.
19
+ */
20
+ export declare function startSessionActivityFeed(): void;
21
+ export declare function setOpenSession(sessionId: string): void;
22
+ export {};
@@ -0,0 +1,104 @@
1
+ /*
2
+ * AI 세션 활동 구독 — **앱 전역 1회**. 도크가 닫혀 있어도(마운트되지 않아도) 새 소식을 안다.
3
+ *
4
+ * operato-twin 의 `twin-ai-activity.ts` 에서 올려 왔다(2026-09-25, design/ai/shared-dock.md §2-8) — 안 읽음은
5
+ * 모든 앱에서 같은 기능이다. `setupAssistantDock()` 이 부팅에서 한 번 연다.
6
+ *
7
+ * ── 왜 도크가 아니라 여기인가 ───────────────────────────────────────────────
8
+ * 도크는 asidebar viewpart 이고 닫혀 있으면 **렌더되지 않는다**(템플릿이 아예 안 붙는다). 그래서
9
+ * 도크 안에서 구독하면 닫아 둔 동안 아무 소식을 받지 못한다 — 배지를 띄울 수 없다.
10
+ * 구독은 부팅 시 한 번 열고, 도크·컨텍스트 밴드는 이 스토어를 본다.
11
+ *
12
+ * ── 종류에 따라 다르게 다룬다 ───────────────────────────────────────────────
13
+ * message — 새 메시지. 안 읽은 세션으로 표시(배지 올림). **내가 보낸 것은 올리지 않는다.**
14
+ * meta — 이름 변경 등. 목록 갱신만 알리고 배지는 올리지 않는다(소음 방지).
15
+ *
16
+ * 정확한 미읽음 **개수**는 세지 않는다 — 세션 수만 센다(서버 목록 정책과 같다).
17
+ */
18
+ import gql from 'graphql-tag';
19
+ import { client, gqlContext } from '@operato/graphql';
20
+ const listeners = new Set();
21
+ /** 안 읽은 세션 id — 개수가 아니라 세션 단위. 도크에서 그 세션을 열면 지운다. */
22
+ const unreadSessions = new Set();
23
+ let subscription = null;
24
+ const ACTIVITY_SUBSCRIPTION = gql `
25
+ subscription MySessionActivity {
26
+ mySessionActivity {
27
+ sessionId
28
+ kind
29
+ anchorType
30
+ anchorId
31
+ at
32
+ preview
33
+ actorId
34
+ }
35
+ }
36
+ `;
37
+ export function unreadSessionCount() {
38
+ return unreadSessions.size;
39
+ }
40
+ export function isSessionUnread(sessionId) {
41
+ return unreadSessions.has(sessionId);
42
+ }
43
+ /** 그 세션을 읽었다 — 도크가 세션을 열 때 호출(서버 markSessionRead 와 짝). */
44
+ export function clearSessionUnread(sessionId) {
45
+ if (unreadSessions.delete(sessionId))
46
+ notify({ sessionId, kind: 'meta', at: new Date().toISOString() });
47
+ }
48
+ export function subscribeSessionActivity(fn) {
49
+ listeners.add(fn);
50
+ return () => listeners.delete(fn);
51
+ }
52
+ function notify(activity) {
53
+ listeners.forEach(fn => {
54
+ try {
55
+ fn(activity);
56
+ }
57
+ catch {
58
+ /* 구독자 오류가 다른 구독자를 막지 않는다 */
59
+ }
60
+ });
61
+ }
62
+ /**
63
+ * 구독 시작 — 부팅에서 1회. 재호출은 무시(중복 구독 방지).
64
+ * 실패해도 앱을 막지 않는다 — 배지가 없을 뿐이고, 도크를 열면 목록을 직접 읽는다.
65
+ */
66
+ export function startSessionActivityFeed() {
67
+ if (subscription)
68
+ return;
69
+ try {
70
+ subscription = client
71
+ .subscribe({ query: ACTIVITY_SUBSCRIPTION, context: gqlContext() })
72
+ .subscribe({
73
+ next: ({ data }) => {
74
+ const activity = data?.mySessionActivity;
75
+ if (!activity?.sessionId)
76
+ return;
77
+ /* 자기 발신은 배지를 올리지 않는다 — 방금 내가 쓴 것이 안 읽음으로 뜨면 어색하다.
78
+ * 서버는 actorId(사용자 id)를 싣지만 클라는 내부 관리키를 판정에 쓰지 않는다 →
79
+ * 자기 발신 판정은 도크가 열려 있는 세션인지로 대신한다(그 세션은 이미 읽고 있다). */
80
+ if (activity.kind === 'message' && !isOpenSession(activity.sessionId)) {
81
+ unreadSessions.add(activity.sessionId);
82
+ }
83
+ notify(activity);
84
+ },
85
+ error: () => {
86
+ subscription = null; // 재연결은 다음 호출에 맡긴다(자동 재시도 루프를 두지 않는다)
87
+ }
88
+ });
89
+ }
90
+ catch {
91
+ subscription = null;
92
+ }
93
+ }
94
+ /* 도크가 지금 열어 둔 세션 — 그 세션의 활동은 배지를 올리지 않는다(보고 있으므로). */
95
+ let openSessionId = '';
96
+ export function setOpenSession(sessionId) {
97
+ openSessionId = sessionId || '';
98
+ if (openSessionId)
99
+ clearSessionUnread(openSessionId);
100
+ }
101
+ function isOpenSession(sessionId) {
102
+ return !!openSessionId && openSessionId === sessionId;
103
+ }
104
+ //# sourceMappingURL=assistant-activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-activity.js","sourceRoot":"","sources":["../../client/utils/assistant-activity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAerD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY,CAAA;AACrC,qDAAqD;AACrD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAA;AACxC,IAAI,YAAY,GAAmC,IAAI,CAAA;AAEvD,MAAM,qBAAqB,GAAG,GAAG,CAAA;;;;;;;;;;;;CAYhC,CAAA;AAGD,MAAM,UAAU,kBAAkB;IAChC,OAAO,cAAc,CAAC,IAAI,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AACtC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,IAAI,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC;QAAE,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AACzG,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,EAAY;IACnD,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACjB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,MAAM,CAAC,QAAyB;IACvC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;QACrB,IAAI,CAAC;YACH,EAAE,CAAC,QAAQ,CAAC,CAAA;QACd,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;QAC9B,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,IAAI,YAAY;QAAE,OAAM;IACxB,IAAI,CAAC;QACH,YAAY,GAAG,MAAM;aAClB,SAAS,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC;aAClE,SAAS,CAAC;YACT,IAAI,EAAE,CAAC,EAAE,IAAI,EAAO,EAAE,EAAE;gBACtB,MAAM,QAAQ,GAAgC,IAAI,EAAE,iBAAiB,CAAA;gBACrE,IAAI,CAAC,QAAQ,EAAE,SAAS;oBAAE,OAAM;gBAChC;;qEAEqD;gBACrD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;oBACtE,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;gBACxC,CAAC;gBACD,MAAM,CAAC,QAAQ,CAAC,CAAA;YAClB,CAAC;YACD,KAAK,EAAE,GAAG,EAAE;gBACV,YAAY,GAAG,IAAI,CAAA,CAAC,qCAAqC;YAC3D,CAAC;SACF,CAAC,CAAA;IACN,CAAC;IAAC,MAAM,CAAC;QACP,YAAY,GAAG,IAAI,CAAA;IACrB,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,IAAI,aAAa,GAAG,EAAE,CAAA;AACtB,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,aAAa,GAAG,SAAS,IAAI,EAAE,CAAA;IAC/B,IAAI,aAAa;QAAE,kBAAkB,CAAC,aAAa,CAAC,CAAA;AACtD,CAAC;AACD,SAAS,aAAa,CAAC,SAAiB;IACtC,OAAO,CAAC,CAAC,aAAa,IAAI,aAAa,KAAK,SAAS,CAAA;AACvD,CAAC","sourcesContent":["/*\n * AI 세션 활동 구독 — **앱 전역 1회**. 도크가 닫혀 있어도(마운트되지 않아도) 새 소식을 안다.\n *\n * operato-twin 의 `twin-ai-activity.ts` 에서 올려 왔다(2026-09-25, design/ai/shared-dock.md §2-8) — 안 읽음은\n * 모든 앱에서 같은 기능이다. `setupAssistantDock()` 이 부팅에서 한 번 연다.\n *\n * ── 왜 도크가 아니라 여기인가 ───────────────────────────────────────────────\n * 도크는 asidebar viewpart 이고 닫혀 있으면 **렌더되지 않는다**(템플릿이 아예 안 붙는다). 그래서\n * 도크 안에서 구독하면 닫아 둔 동안 아무 소식을 받지 못한다 — 배지를 띄울 수 없다.\n * 구독은 부팅 시 한 번 열고, 도크·컨텍스트 밴드는 이 스토어를 본다.\n *\n * ── 종류에 따라 다르게 다룬다 ───────────────────────────────────────────────\n * message — 새 메시지. 안 읽은 세션으로 표시(배지 올림). **내가 보낸 것은 올리지 않는다.**\n * meta — 이름 변경 등. 목록 갱신만 알리고 배지는 올리지 않는다(소음 방지).\n *\n * 정확한 미읽음 **개수**는 세지 않는다 — 세션 수만 센다(서버 목록 정책과 같다).\n */\nimport gql from 'graphql-tag'\n\nimport { client, gqlContext } from '@operato/graphql'\n\n\nexport interface SessionActivity {\n sessionId: string\n kind: 'message' | 'meta'\n anchorType?: string\n anchorId?: string\n at: string\n preview?: string\n actorId?: string\n}\n\ntype Listener = (activity: SessionActivity) => void\n\nconst listeners = new Set<Listener>()\n/** 안 읽은 세션 id — 개수가 아니라 세션 단위. 도크에서 그 세션을 열면 지운다. */\nconst unreadSessions = new Set<string>()\nlet subscription: { unsubscribe(): void } | null = null\n\nconst ACTIVITY_SUBSCRIPTION = gql`\n subscription MySessionActivity {\n mySessionActivity {\n sessionId\n kind\n anchorType\n anchorId\n at\n preview\n actorId\n }\n }\n`\n\n\nexport function unreadSessionCount(): number {\n return unreadSessions.size\n}\n\nexport function isSessionUnread(sessionId: string): boolean {\n return unreadSessions.has(sessionId)\n}\n\n/** 그 세션을 읽었다 — 도크가 세션을 열 때 호출(서버 markSessionRead 와 짝). */\nexport function clearSessionUnread(sessionId: string): void {\n if (unreadSessions.delete(sessionId)) notify({ sessionId, kind: 'meta', at: new Date().toISOString() })\n}\n\nexport function subscribeSessionActivity(fn: Listener): () => void {\n listeners.add(fn)\n return () => listeners.delete(fn)\n}\n\nfunction notify(activity: SessionActivity) {\n listeners.forEach(fn => {\n try {\n fn(activity)\n } catch {\n /* 구독자 오류가 다른 구독자를 막지 않는다 */\n }\n })\n}\n\n/**\n * 구독 시작 — 부팅에서 1회. 재호출은 무시(중복 구독 방지).\n * 실패해도 앱을 막지 않는다 — 배지가 없을 뿐이고, 도크를 열면 목록을 직접 읽는다.\n */\nexport function startSessionActivityFeed(): void {\n if (subscription) return\n try {\n subscription = client\n .subscribe({ query: ACTIVITY_SUBSCRIPTION, context: gqlContext() })\n .subscribe({\n next: ({ data }: any) => {\n const activity: SessionActivity | undefined = data?.mySessionActivity\n if (!activity?.sessionId) return\n /* 자기 발신은 배지를 올리지 않는다 — 방금 내가 쓴 것이 안 읽음으로 뜨면 어색하다.\n * 서버는 actorId(사용자 id)를 싣지만 클라는 내부 관리키를 판정에 쓰지 않는다 →\n * 자기 발신 판정은 도크가 열려 있는 세션인지로 대신한다(그 세션은 이미 읽고 있다). */\n if (activity.kind === 'message' && !isOpenSession(activity.sessionId)) {\n unreadSessions.add(activity.sessionId)\n }\n notify(activity)\n },\n error: () => {\n subscription = null // 재연결은 다음 호출에 맡긴다(자동 재시도 루프를 두지 않는다)\n }\n })\n } catch {\n subscription = null\n }\n}\n\n/* 도크가 지금 열어 둔 세션 — 그 세션의 활동은 배지를 올리지 않는다(보고 있으므로). */\nlet openSessionId = ''\nexport function setOpenSession(sessionId: string): void {\n openSessionId = sessionId || ''\n if (openSessionId) clearSessionUnread(openSessionId)\n}\nfunction isOpenSession(sessionId: string): boolean {\n return !!openSessionId && openSessionId === sessionId\n}\n\n"]}
@@ -0,0 +1,23 @@
1
+ export interface AssistantAsk {
2
+ /** Sent as the next question. */
3
+ ask?: string;
4
+ /** Put in the input, not sent. */
5
+ draft?: string;
6
+ /** Ask in the open conversation instead of a new one. */
7
+ continueCurrent?: boolean;
8
+ /** A new conversation's name. */
9
+ name?: string;
10
+ /** Turns carried into the new conversation before the question. */
11
+ transcript?: {
12
+ role: 'user' | 'ai';
13
+ content: string;
14
+ }[];
15
+ /** A note stored first: what the screen was showing when the person asked. */
16
+ context?: string;
17
+ }
18
+ type Listener = () => void;
19
+ export declare function askAssistant(ask: AssistantAsk): void;
20
+ /** The dock takes the request once. */
21
+ export declare function takeAssistantAsk(): AssistantAsk | undefined;
22
+ export declare function subscribeAssistantAsk(listener: Listener): () => void;
23
+ export {};
@@ -0,0 +1,18 @@
1
+ let pending;
2
+ const listeners = new Set();
3
+ export function askAssistant(ask) {
4
+ pending = ask;
5
+ listeners.forEach(listener => listener());
6
+ window.dispatchEvent(new CustomEvent('assistant-dock-open'));
7
+ }
8
+ /** The dock takes the request once. */
9
+ export function takeAssistantAsk() {
10
+ const ask = pending;
11
+ pending = undefined;
12
+ return ask;
13
+ }
14
+ export function subscribeAssistantAsk(listener) {
15
+ listeners.add(listener);
16
+ return () => listeners.delete(listener);
17
+ }
18
+ //# sourceMappingURL=assistant-ask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-ask.js","sourceRoot":"","sources":["../../client/utils/assistant-ask.ts"],"names":[],"mappings":"AAwBA,IAAI,OAAiC,CAAA;AACrC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY,CAAA;AAErC,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,OAAO,GAAG,GAAG,CAAA;IACb,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAA;IACzC,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,qBAAqB,CAAC,CAAC,CAAA;AAC9D,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,OAAO,CAAA;IACnB,OAAO,GAAG,SAAS,CAAA;IACnB,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAkB;IACtD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACvB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzC,CAAC","sourcesContent":["/*\n * Asking the assistant from another screen - `askAssistant()` (design/ai/shared-dock.md §2-11).\n *\n * Generalised from operato-twin's promotion channel and operato-plant's ask box: a toast's \"ask AI\", a\n * header ask box, a performance page carrying its context. The request waits here until the dock takes it,\n * so a dock that is closed (not mounted) still gets it when it opens.\n */\nexport interface AssistantAsk {\n /** Sent as the next question. */\n ask?: string\n /** Put in the input, not sent. */\n draft?: string\n /** Ask in the open conversation instead of a new one. */\n continueCurrent?: boolean\n /** A new conversation's name. */\n name?: string\n /** Turns carried into the new conversation before the question. */\n transcript?: { role: 'user' | 'ai'; content: string }[]\n /** A note stored first: what the screen was showing when the person asked. */\n context?: string\n}\n\ntype Listener = () => void\n\nlet pending: AssistantAsk | undefined\nconst listeners = new Set<Listener>()\n\nexport function askAssistant(ask: AssistantAsk): void {\n pending = ask\n listeners.forEach(listener => listener())\n window.dispatchEvent(new CustomEvent('assistant-dock-open'))\n}\n\n/** The dock takes the request once. */\nexport function takeAssistantAsk(): AssistantAsk | undefined {\n const ask = pending\n pending = undefined\n return ask\n}\n\nexport function subscribeAssistantAsk(listener: Listener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { type AiCapability } from './assistant-capability.js';
2
+ /**
3
+ * Asks `aiCapability` (standard A7-a). A GraphQL error is thrown for the caller to show; the caller keeps
4
+ * `unknown` meanwhile and asks again — it does not remember a failed read as an answer.
5
+ */
6
+ export declare function readAiCapability(): Promise<AiCapability>;
@@ -0,0 +1,23 @@
1
+ import { client } from '@operato/graphql';
2
+ import gql from 'graphql-tag';
3
+ import { capabilityOf } from './assistant-capability.js';
4
+ /**
5
+ * Asks `aiCapability` (standard A7-a). A GraphQL error is thrown for the caller to show; the caller keeps
6
+ * `unknown` meanwhile and asks again — it does not remember a failed read as an answer.
7
+ */
8
+ export async function readAiCapability() {
9
+ const result = await client.query({
10
+ query: gql `
11
+ query {
12
+ aiCapability {
13
+ state
14
+ }
15
+ }
16
+ `,
17
+ fetchPolicy: 'no-cache'
18
+ });
19
+ if (result.errors?.length)
20
+ throw new Error(result.errors[0].message);
21
+ return capabilityOf(result.data?.aiCapability);
22
+ }
23
+ //# sourceMappingURL=assistant-capability-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-capability-read.js","sourceRoot":"","sources":["../../client/utils/assistant-capability-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,YAAY,EAAqB,MAAM,2BAA2B,CAAA;AAE3E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAChC,KAAK,EAAE,GAAG,CAAA;;;;;;KAMT;QACD,WAAW,EAAE,UAAU;KACxB,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IACpE,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;AAChD,CAAC","sourcesContent":["import { client } from '@operato/graphql'\nimport gql from 'graphql-tag'\n\nimport { capabilityOf, type AiCapability } from './assistant-capability.js'\n\n/**\n * Asks `aiCapability` (standard A7-a). A GraphQL error is thrown for the caller to show; the caller keeps\n * `unknown` meanwhile and asks again — it does not remember a failed read as an answer.\n */\nexport async function readAiCapability(): Promise<AiCapability> {\n const result = await client.query({\n query: gql`\n query {\n aiCapability {\n state\n }\n }\n `,\n fetchPolicy: 'no-cache'\n })\n\n if (result.errors?.length) throw new Error(result.errors[0].message)\n return capabilityOf(result.data?.aiCapability)\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export type AiCapability = 'configured' | 'not-configured' | 'unknown';
2
+ export declare function capabilityOf(answer: {
3
+ state?: string | null;
4
+ } | null | undefined): AiCapability;
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Whether this deployment has an AI to answer — the rule every dock reads before offering a conversation.
3
+ *
4
+ * AI standard A7-a: a screen does not find out by failing. Without this a dock takes the question, sends
5
+ * it, and only then shows the provider's error. `aiCapability` answers first.
6
+ *
7
+ * Three states, and the third is the screen's own: not asked yet, or an answer that could not be read.
8
+ * It is never folded into `configured` — that is where the question started (a button that was always on).
9
+ *
10
+ * Pure, so it is tested without GraphQL. The read is `readAiCapability` (`assistant-capability-read.ts`).
11
+ * Warehouse and twin each held a copy of this until it moved here (2026-09-24).
12
+ */
13
+ export function capabilityOf(answer) {
14
+ const state = answer?.state;
15
+ return state === 'configured' || state === 'not-configured' ? state : 'unknown';
16
+ }
17
+ //# sourceMappingURL=assistant-capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-capability.js","sourceRoot":"","sources":["../../client/utils/assistant-capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,UAAU,YAAY,CAAC,MAAoD;IAC/E,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,CAAA;IAC3B,OAAO,KAAK,KAAK,YAAY,IAAI,KAAK,KAAK,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACjF,CAAC","sourcesContent":["/*\n * Whether this deployment has an AI to answer — the rule every dock reads before offering a conversation.\n *\n * AI standard A7-a: a screen does not find out by failing. Without this a dock takes the question, sends\n * it, and only then shows the provider's error. `aiCapability` answers first.\n *\n * Three states, and the third is the screen's own: not asked yet, or an answer that could not be read.\n * It is never folded into `configured` — that is where the question started (a button that was always on).\n *\n * Pure, so it is tested without GraphQL. The read is `readAiCapability` (`assistant-capability-read.ts`).\n * Warehouse and twin each held a copy of this until it moved here (2026-09-24).\n */\n\nexport type AiCapability = 'configured' | 'not-configured' | 'unknown'\n\nexport function capabilityOf(answer: { state?: string | null } | null | undefined): AiCapability {\n const state = answer?.state\n return state === 'configured' || state === 'not-configured' ? state : 'unknown'\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ /*
2
+ * A7-a — the third state is never read as the first.
3
+ */
4
+ import { capabilityOf } from './assistant-capability';
5
+ describe('capabilityOf', () => {
6
+ it('passes the two answers the door gives', () => {
7
+ expect(capabilityOf({ state: 'configured' })).toBe('configured');
8
+ expect(capabilityOf({ state: 'not-configured' })).toBe('not-configured');
9
+ });
10
+ it('★ not asked yet, and an answer it cannot read, are unknown — never configured', () => {
11
+ for (const answer of [undefined, null, {}, { state: null }, { state: 'yes' }, { state: 'CONFIGURED' }]) {
12
+ expect(capabilityOf(answer)).toBe('unknown');
13
+ }
14
+ });
15
+ });
16
+ //# sourceMappingURL=assistant-capability.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-capability.test.js","sourceRoot":"","sources":["../../client/utils/assistant-capability.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAErD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAChE,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC1E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,KAAK,MAAM,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;YACvG,MAAM,CAAC,YAAY,CAAC,MAAa,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrD,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/*\n * A7-a — the third state is never read as the first.\n */\nimport { capabilityOf } from './assistant-capability'\n\ndescribe('capabilityOf', () => {\n it('passes the two answers the door gives', () => {\n expect(capabilityOf({ state: 'configured' })).toBe('configured')\n expect(capabilityOf({ state: 'not-configured' })).toBe('not-configured')\n })\n\n it('★ not asked yet, and an answer it cannot read, are unknown — never configured', () => {\n for (const answer of [undefined, null, {}, { state: null }, { state: 'yes' }, { state: 'CONFIGURED' }]) {\n expect(capabilityOf(answer as any)).toBe('unknown')\n }\n })\n})\n"]}
@@ -0,0 +1,29 @@
1
+ import type { AssistantSurface } from './assistant-surface.js';
2
+ export type ProposalVerdict = 'accepted' | 'refused' | 'unavailable' | 'declined';
3
+ export interface ProposalOutcome {
4
+ verdict: ProposalVerdict;
5
+ errorCode?: string;
6
+ applied?: number;
7
+ missed?: number;
8
+ /** A line for the conversation, besides the outcome itself (e.g. what was created). */
9
+ note?: string;
10
+ }
11
+ export interface ProposalContext {
12
+ sessionId?: string;
13
+ surface?: AssistantSurface;
14
+ /** Asks the person before anything irreversible. `false` → the handler returns `declined`. */
15
+ confirm(options: {
16
+ title: string;
17
+ text: string;
18
+ confirmText?: string;
19
+ cancelText?: string;
20
+ type?: 'warning' | 'info';
21
+ }): Promise<boolean>;
22
+ notify(level: 'info' | 'error', message: string): void;
23
+ }
24
+ export type ProposalHandler = (proposal: any, context: ProposalContext) => Promise<ProposalOutcome>;
25
+ /** Registered by the package that declares the tool - `proposeAction` by twin, a figure candidate by figure-ui. */
26
+ export declare function registerProposalHandler(tool: string, handler: ProposalHandler): void;
27
+ export declare function proposalHandlerOf(tool: string | undefined): ProposalHandler | undefined;
28
+ /** Runs the proposal - never throws; a failure is a `refused` outcome with its message as the code. */
29
+ export declare function runProposal(proposal: any, context: ProposalContext): Promise<ProposalOutcome>;
@@ -0,0 +1,22 @@
1
+ const handlers = new Map();
2
+ /** Registered by the package that declares the tool - `proposeAction` by twin, a figure candidate by figure-ui. */
3
+ export function registerProposalHandler(tool, handler) {
4
+ handlers.set(tool, handler);
5
+ }
6
+ export function proposalHandlerOf(tool) {
7
+ return tool ? handlers.get(tool) : undefined;
8
+ }
9
+ /** Runs the proposal - never throws; a failure is a `refused` outcome with its message as the code. */
10
+ export async function runProposal(proposal, context) {
11
+ const handler = proposalHandlerOf(proposal?.tool);
12
+ if (!handler)
13
+ return { verdict: 'unavailable', errorCode: 'unknown-proposal-kind' };
14
+ try {
15
+ const outcome = await handler(proposal, context);
16
+ return outcome?.verdict ? outcome : { verdict: 'unavailable', errorCode: 'no-outcome' };
17
+ }
18
+ catch (error) {
19
+ return { verdict: 'refused', errorCode: error?.message || 'failed' };
20
+ }
21
+ }
22
+ //# sourceMappingURL=assistant-proposals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-proposals.js","sourceRoot":"","sources":["../../client/utils/assistant-proposals.ts"],"names":[],"mappings":"AA4BA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAA;AAEnD,mHAAmH;AACnH,MAAM,UAAU,uBAAuB,CAAC,IAAY,EAAE,OAAwB;IAC5E,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IACxD,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9C,CAAC;AAED,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAa,EAAE,OAAwB;IACvE,MAAM,OAAO,GAAG,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,uBAAuB,EAAE,CAAA;IACnF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAChD,OAAO,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,CAAA;IACzF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAG,KAAe,EAAE,OAAO,IAAI,QAAQ,EAAE,CAAA;IACjF,CAAC;AACH,CAAC","sourcesContent":["/*\n * Proposal handlers - each proposal kind is applied by the package that declared its tool\n * (design/ai/shared-dock.md §4). The dock has no kind-specific code: it finds the handler, runs it,\n * and reports the outcome on every branch (ADR-0070). A kind nobody handles is `unavailable`.\n */\nimport type { AssistantSurface } from './assistant-surface.js'\n\nexport type ProposalVerdict = 'accepted' | 'refused' | 'unavailable' | 'declined'\n\nexport interface ProposalOutcome {\n verdict: ProposalVerdict\n errorCode?: string\n applied?: number\n missed?: number\n /** A line for the conversation, besides the outcome itself (e.g. what was created). */\n note?: string\n}\n\nexport interface ProposalContext {\n sessionId?: string\n surface?: AssistantSurface\n /** Asks the person before anything irreversible. `false` → the handler returns `declined`. */\n confirm(options: { title: string; text: string; confirmText?: string; cancelText?: string; type?: 'warning' | 'info' }): Promise<boolean>\n notify(level: 'info' | 'error', message: string): void\n}\n\nexport type ProposalHandler = (proposal: any, context: ProposalContext) => Promise<ProposalOutcome>\n\nconst handlers = new Map<string, ProposalHandler>()\n\n/** Registered by the package that declares the tool - `proposeAction` by twin, a figure candidate by figure-ui. */\nexport function registerProposalHandler(tool: string, handler: ProposalHandler): void {\n handlers.set(tool, handler)\n}\n\nexport function proposalHandlerOf(tool: string | undefined): ProposalHandler | undefined {\n return tool ? handlers.get(tool) : undefined\n}\n\n/** Runs the proposal - never throws; a failure is a `refused` outcome with its message as the code. */\nexport async function runProposal(proposal: any, context: ProposalContext): Promise<ProposalOutcome> {\n const handler = proposalHandlerOf(proposal?.tool)\n if (!handler) return { verdict: 'unavailable', errorCode: 'unknown-proposal-kind' }\n try {\n const outcome = await handler(proposal, context)\n return outcome?.verdict ? outcome : { verdict: 'unavailable', errorCode: 'no-outcome' }\n } catch (error) {\n return { verdict: 'refused', errorCode: (error as Error)?.message || 'failed' }\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ export declare function sessionActions(entry: {
2
+ participant?: boolean;
3
+ hidden?: boolean;
4
+ mine?: boolean;
5
+ owner?: boolean;
6
+ }): {
7
+ rename: boolean;
8
+ hide: boolean;
9
+ unhide: boolean;
10
+ remove: boolean;
11
+ setVisibility: boolean;
12
+ };
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Which actions a row of the conversation list offers this viewer - pure, so the rule is tested alone.
3
+ *
4
+ * The same hints the server gives on each inbox entry; the doors decide again:
5
+ * rename any participant (the name is the topic label; reversible)
6
+ * hide/unhide anyone, for their own list only (reversible)
7
+ * delete the creator (`mine`) (not reversible from a screen)
8
+ * visibility the owner (`owner`) (opening shows what is already written)
9
+ */
10
+ export function sessionActions(entry) {
11
+ return {
12
+ rename: entry.participant !== false,
13
+ hide: !entry.hidden,
14
+ unhide: !!entry.hidden,
15
+ remove: !!entry.mine,
16
+ setVisibility: !!entry.owner
17
+ };
18
+ }
19
+ //# sourceMappingURL=assistant-session-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-session-actions.js","sourceRoot":"","sources":["../../client/utils/assistant-session-actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAmF;IAChH,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,WAAW,KAAK,KAAK;QACnC,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM;QACnB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM;QACtB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI;QACpB,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK;KAC7B,CAAA;AACH,CAAC","sourcesContent":["/*\n * Which actions a row of the conversation list offers this viewer - pure, so the rule is tested alone.\n *\n * The same hints the server gives on each inbox entry; the doors decide again:\n * rename any participant (the name is the topic label; reversible)\n * hide/unhide anyone, for their own list only (reversible)\n * delete the creator (`mine`) (not reversible from a screen)\n * visibility the owner (`owner`) (opening shows what is already written)\n */\nexport function sessionActions(entry: { participant?: boolean; hidden?: boolean; mine?: boolean; owner?: boolean }) {\n return {\n rename: entry.participant !== false,\n hide: !entry.hidden,\n unhide: !!entry.hidden,\n remove: !!entry.mine,\n setVisibility: !!entry.owner\n }\n}\n"]}
@@ -0,0 +1,72 @@
1
+ export { sessionActions } from './assistant-session-actions.js';
2
+ /**
3
+ * Managing the conversations on one anchor - list, rename, hide, delete, and who may read them.
4
+ *
5
+ * These were written once, inside operato-twin's dock (things-factory `b3d1081f4c`, `b44c986c07`,
6
+ * 2026-07-30), and every other dock (plant, figure, warehouse, ecs) had only the tabs. They are the
7
+ * framework's doors (`chatSessionInbox` · `renameAISession` · `hideAISession` · `deleteAISession` ·
8
+ * `setSessionVisibility`), so the screen for them belongs here too - one list every dock opens.
9
+ *
10
+ * Who may do what is the server's to decide; the entry carries hints only:
11
+ * rename any participant (the name is the topic label; reversible)
12
+ * hide/unhide anyone, for their own list only (reversible)
13
+ * delete the creator (`mine`) (not reversible from a screen)
14
+ * visibility the owner (`owner`) (opening shows what is already written)
15
+ */
16
+ export type SessionVisibility = 'open' | 'private';
17
+ /** One row of `chatSessionInbox`, as the list draws it. */
18
+ export interface AssistantSessionEntry {
19
+ id: string;
20
+ name?: string;
21
+ kind?: string;
22
+ lastMessageAt?: string;
23
+ lastMessagePreview?: string;
24
+ previewRedacted?: boolean;
25
+ unread?: boolean;
26
+ participant?: boolean;
27
+ /** I created it - only then may I delete it. */
28
+ mine?: boolean;
29
+ /** I own it - only then may I change who reads it. */
30
+ owner?: boolean;
31
+ /** Hidden from my own list (returned only with the hidden filter on). */
32
+ hidden?: boolean;
33
+ visibility?: SessionVisibility | string;
34
+ }
35
+ /** Legacy single filter - kept for callers written before the filter set. */
36
+ export type AssistantSessionFilter = 'all' | 'mine' | 'hidden';
37
+ /** What the manager narrows by (design/ai/shared-dock.md §2-6). Defaults: this kind, everyone's, any visibility, hidden out. */
38
+ export interface AssistantSessionFilters {
39
+ /** A kind (mode) to list; empty lists every kind on the anchor. */
40
+ kind?: string;
41
+ whose?: 'all' | 'mine' | 'participant';
42
+ visibility?: 'all' | 'open' | 'private';
43
+ includeHidden?: boolean;
44
+ unreadOnly?: boolean;
45
+ }
46
+ export interface AssistantSessionQuery {
47
+ anchorType: string;
48
+ anchorId: string;
49
+ search?: string;
50
+ filter?: AssistantSessionFilter;
51
+ filters?: AssistantSessionFilters;
52
+ limit?: number;
53
+ offset?: number;
54
+ }
55
+ export interface AssistantSessionPage {
56
+ entries: AssistantSessionEntry[];
57
+ /** How many pass the filter - so "load more" knows whether the end of the list is the end of the data. */
58
+ total: number;
59
+ /** How many I hid - so a hidden conversation is never forgotten. */
60
+ hiddenCount: number;
61
+ }
62
+ export interface AssistantSessionAdmin {
63
+ inbox(query: AssistantSessionQuery): Promise<AssistantSessionPage>;
64
+ rename(sessionId: string, name: string): Promise<boolean>;
65
+ hide(sessionId: string, hidden: boolean): Promise<boolean>;
66
+ remove(sessionId: string): Promise<boolean>;
67
+ setVisibility(sessionId: string, visibility: SessionVisibility): Promise<boolean>;
68
+ }
69
+ /** The page size the server allows at most (`chatSessionInbox` clamps to 200). */
70
+ export declare const SESSION_PAGE_MAX = 200;
71
+ /** The framework's session doors, as they are. */
72
+ export declare const assistantSessionAdmin: AssistantSessionAdmin;
@@ -0,0 +1,81 @@
1
+ import { client } from '@operato/graphql';
2
+ import { i18next } from '@operato/i18n';
3
+ import gql from 'graphql-tag';
4
+ export { sessionActions } from './assistant-session-actions.js';
5
+ /** The page size the server allows at most (`chatSessionInbox` clamps to 200). */
6
+ export const SESSION_PAGE_MAX = 200;
7
+ async function mutate(mutation, variables, field) {
8
+ const result = await client.mutate({ mutation, variables });
9
+ if (result.errors?.length)
10
+ throw new Error(result.errors[0].message);
11
+ return result.data?.[field] === true;
12
+ }
13
+ /** The framework's session doors, as they are. */
14
+ export const assistantSessionAdmin = {
15
+ async inbox({ anchorType, anchorId, search, filter = 'all', filters = {}, limit = 50, offset = 0 }) {
16
+ const hidden = filters.includeHidden ?? filter === 'hidden';
17
+ const mine = filters.whose ? filters.whose === 'mine' : filter === 'mine';
18
+ const result = await client.query({
19
+ query: gql `
20
+ query ($t: String!, $i: String!, $h: Boolean, $q: String, $m: Boolean, $l: Int, $o: Int, $k: String, $p: Boolean, $v: String, $u: Boolean) {
21
+ hiddenSessionCount(anchorType: $t, anchorId: $i)
22
+ chatSessionInboxTotal(anchorType: $t, anchorId: $i, showHidden: $h, search: $q, mineOnly: $m, kind: $k, participantOnly: $p, visibility: $v, unreadOnly: $u)
23
+ chatSessionInbox(anchorType: $t, anchorId: $i, showHidden: $h, search: $q, mineOnly: $m, kind: $k, participantOnly: $p, visibility: $v, unreadOnly: $u, limit: $l, offset: $o) {
24
+ id
25
+ name
26
+ kind
27
+ lastMessageAt
28
+ lastMessagePreview
29
+ previewRedacted
30
+ unread
31
+ participant
32
+ mine
33
+ owner
34
+ hidden
35
+ visibility
36
+ }
37
+ }
38
+ `,
39
+ variables: {
40
+ t: anchorType,
41
+ i: anchorId,
42
+ /* Hidden ones only when asked - elsewhere they stay out of the list. */
43
+ h: hidden,
44
+ q: search?.trim() || null,
45
+ m: mine,
46
+ k: filters.kind || null,
47
+ p: filters.whose === 'participant' || null,
48
+ v: filters.visibility && filters.visibility !== 'all' ? filters.visibility : null,
49
+ u: filters.unreadOnly || null,
50
+ l: Math.min(SESSION_PAGE_MAX, Math.max(1, limit)),
51
+ o: Math.max(0, offset)
52
+ },
53
+ fetchPolicy: 'no-cache'
54
+ });
55
+ if (result.errors?.length)
56
+ throw new Error(result.errors[0].message);
57
+ const entries = result.data?.chatSessionInbox;
58
+ if (!Array.isArray(entries)) {
59
+ throw new Error(i18next.t('ai-assistant.text.sessions-unreadable', { defaultValue: 'could not load the conversation list.' }));
60
+ }
61
+ return {
62
+ /* With the hidden filter the server returns hidden ones alongside the rest, marked - the total counts both. */
63
+ entries,
64
+ total: result.data?.chatSessionInboxTotal ?? entries.length,
65
+ hiddenCount: result.data?.hiddenSessionCount ?? 0
66
+ };
67
+ },
68
+ rename(sessionId, name) {
69
+ return mutate(gql `mutation ($id: String!, $n: String!) { renameAISession(sessionId: $id, name: $n) }`, { id: sessionId, n: name }, 'renameAISession');
70
+ },
71
+ hide(sessionId, hidden) {
72
+ return mutate(gql `mutation ($id: String!, $h: Boolean) { hideAISession(sessionId: $id, hidden: $h) }`, { id: sessionId, h: hidden }, 'hideAISession');
73
+ },
74
+ remove(sessionId) {
75
+ return mutate(gql `mutation ($id: String!) { deleteAISession(sessionId: $id) }`, { id: sessionId }, 'deleteAISession');
76
+ },
77
+ setVisibility(sessionId, visibility) {
78
+ return mutate(gql `mutation ($id: String!, $v: String!) { setSessionVisibility(sessionId: $id, visibility: $v) }`, { id: sessionId, v: visibility }, 'setSessionVisibility');
79
+ }
80
+ };
81
+ //# sourceMappingURL=assistant-session-admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-session-admin.js","sourceRoot":"","sources":["../../client/utils/assistant-session-admin.ts"],"names":[],"mappings":"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","sourcesContent":["import { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport gql from 'graphql-tag'\n\nexport { sessionActions } from './assistant-session-actions.js'\n\n/**\n * Managing the conversations on one anchor - list, rename, hide, delete, and who may read them.\n *\n * These were written once, inside operato-twin's dock (things-factory `b3d1081f4c`, `b44c986c07`,\n * 2026-07-30), and every other dock (plant, figure, warehouse, ecs) had only the tabs. They are the\n * framework's doors (`chatSessionInbox` · `renameAISession` · `hideAISession` · `deleteAISession` ·\n * `setSessionVisibility`), so the screen for them belongs here too - one list every dock opens.\n *\n * Who may do what is the server's to decide; the entry carries hints only:\n * rename any participant (the name is the topic label; reversible)\n * hide/unhide anyone, for their own list only (reversible)\n * delete the creator (`mine`) (not reversible from a screen)\n * visibility the owner (`owner`) (opening shows what is already written)\n */\n\nexport type SessionVisibility = 'open' | 'private'\n\n/** One row of `chatSessionInbox`, as the list draws it. */\nexport interface AssistantSessionEntry {\n id: string\n name?: string\n kind?: string\n lastMessageAt?: string\n lastMessagePreview?: string\n previewRedacted?: boolean\n unread?: boolean\n participant?: boolean\n /** I created it - only then may I delete it. */\n mine?: boolean\n /** I own it - only then may I change who reads it. */\n owner?: boolean\n /** Hidden from my own list (returned only with the hidden filter on). */\n hidden?: boolean\n visibility?: SessionVisibility | string\n}\n\n/** Legacy single filter - kept for callers written before the filter set. */\nexport type AssistantSessionFilter = 'all' | 'mine' | 'hidden'\n\n/** What the manager narrows by (design/ai/shared-dock.md §2-6). Defaults: this kind, everyone's, any visibility, hidden out. */\nexport interface AssistantSessionFilters {\n /** A kind (mode) to list; empty lists every kind on the anchor. */\n kind?: string\n whose?: 'all' | 'mine' | 'participant'\n visibility?: 'all' | 'open' | 'private'\n includeHidden?: boolean\n unreadOnly?: boolean\n}\n\nexport interface AssistantSessionQuery {\n anchorType: string\n anchorId: string\n search?: string\n filter?: AssistantSessionFilter\n filters?: AssistantSessionFilters\n limit?: number\n offset?: number\n}\n\nexport interface AssistantSessionPage {\n entries: AssistantSessionEntry[]\n /** How many pass the filter - so \"load more\" knows whether the end of the list is the end of the data. */\n total: number\n /** How many I hid - so a hidden conversation is never forgotten. */\n hiddenCount: number\n}\n\nexport interface AssistantSessionAdmin {\n inbox(query: AssistantSessionQuery): Promise<AssistantSessionPage>\n rename(sessionId: string, name: string): Promise<boolean>\n hide(sessionId: string, hidden: boolean): Promise<boolean>\n remove(sessionId: string): Promise<boolean>\n setVisibility(sessionId: string, visibility: SessionVisibility): Promise<boolean>\n}\n\n/** The page size the server allows at most (`chatSessionInbox` clamps to 200). */\nexport const SESSION_PAGE_MAX = 200\n\nasync function mutate(mutation: any, variables: Record<string, unknown>, field: string): Promise<boolean> {\n const result = await client.mutate({ mutation, variables })\n if (result.errors?.length) throw new Error(result.errors[0].message)\n return result.data?.[field] === true\n}\n\n/** The framework's session doors, as they are. */\nexport const assistantSessionAdmin: AssistantSessionAdmin = {\n async inbox({ anchorType, anchorId, search, filter = 'all', filters = {}, limit = 50, offset = 0 }) {\n const hidden = filters.includeHidden ?? filter === 'hidden'\n const mine = filters.whose ? filters.whose === 'mine' : filter === 'mine'\n const result = await client.query({\n query: gql`\n query ($t: String!, $i: String!, $h: Boolean, $q: String, $m: Boolean, $l: Int, $o: Int, $k: String, $p: Boolean, $v: String, $u: Boolean) {\n hiddenSessionCount(anchorType: $t, anchorId: $i)\n chatSessionInboxTotal(anchorType: $t, anchorId: $i, showHidden: $h, search: $q, mineOnly: $m, kind: $k, participantOnly: $p, visibility: $v, unreadOnly: $u)\n chatSessionInbox(anchorType: $t, anchorId: $i, showHidden: $h, search: $q, mineOnly: $m, kind: $k, participantOnly: $p, visibility: $v, unreadOnly: $u, limit: $l, offset: $o) {\n id\n name\n kind\n lastMessageAt\n lastMessagePreview\n previewRedacted\n unread\n participant\n mine\n owner\n hidden\n visibility\n }\n }\n `,\n variables: {\n t: anchorType,\n i: anchorId,\n /* Hidden ones only when asked - elsewhere they stay out of the list. */\n h: hidden,\n q: search?.trim() || null,\n m: mine,\n k: filters.kind || null,\n p: filters.whose === 'participant' || null,\n v: filters.visibility && filters.visibility !== 'all' ? filters.visibility : null,\n u: filters.unreadOnly || null,\n l: Math.min(SESSION_PAGE_MAX, Math.max(1, limit)),\n o: Math.max(0, offset)\n },\n fetchPolicy: 'no-cache'\n })\n\n if (result.errors?.length) throw new Error(result.errors[0].message)\n const entries = result.data?.chatSessionInbox\n if (!Array.isArray(entries)) {\n throw new Error(i18next.t('ai-assistant.text.sessions-unreadable', { defaultValue: 'could not load the conversation list.' }))\n }\n\n return {\n /* With the hidden filter the server returns hidden ones alongside the rest, marked - the total counts both. */\n entries,\n total: result.data?.chatSessionInboxTotal ?? entries.length,\n hiddenCount: result.data?.hiddenSessionCount ?? 0\n }\n },\n\n rename(sessionId, name) {\n return mutate(gql`mutation ($id: String!, $n: String!) { renameAISession(sessionId: $id, name: $n) }`, { id: sessionId, n: name }, 'renameAISession')\n },\n\n hide(sessionId, hidden) {\n return mutate(gql`mutation ($id: String!, $h: Boolean) { hideAISession(sessionId: $id, hidden: $h) }`, { id: sessionId, h: hidden }, 'hideAISession')\n },\n\n remove(sessionId) {\n return mutate(gql`mutation ($id: String!) { deleteAISession(sessionId: $id) }`, { id: sessionId }, 'deleteAISession')\n },\n\n setVisibility(sessionId, visibility) {\n return mutate(\n gql`mutation ($id: String!, $v: String!) { setSessionVisibility(sessionId: $id, visibility: $v) }`,\n { id: sessionId, v: visibility },\n 'setSessionVisibility'\n )\n }\n}\n"]}
@@ -17,6 +17,8 @@ export interface AssistantSessionTarget {
17
17
  scope: string;
18
18
  anchorType: string;
19
19
  anchorId: string;
20
+ /** The conversation kind (mode) the tabs list - all readable conversations of it, not only mine (shared dock §2-6). */
21
+ kind?: string;
20
22
  }
21
23
  export interface AssistantSessionTransport {
22
24
  list(target: AssistantSessionTarget): Promise<AssistantSession[]>;