@things-factory/ai-assistant 10.1.8 → 10.1.13

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 (49) hide show
  1. package/client/components/assistant-session-toolbar.ts +70 -0
  2. package/client/components/board-ai-chat.ts +50 -41
  3. package/client/index.ts +4 -0
  4. package/client/utils/assistant-request-context.ts +21 -0
  5. package/client/utils/assistant-session-controller.ts +166 -0
  6. package/client/utils/assistant-session-transport.ts +34 -0
  7. package/dist-client/components/assistant-session-toolbar.d.ts +18 -0
  8. package/dist-client/components/assistant-session-toolbar.js +109 -0
  9. package/dist-client/components/assistant-session-toolbar.js.map +1 -0
  10. package/dist-client/components/board-ai-chat.d.ts +6 -0
  11. package/dist-client/components/board-ai-chat.js +62 -40
  12. package/dist-client/components/board-ai-chat.js.map +1 -1
  13. package/dist-client/index.d.ts +4 -0
  14. package/dist-client/index.js +4 -0
  15. package/dist-client/index.js.map +1 -1
  16. package/dist-client/tsconfig.tsbuildinfo +1 -1
  17. package/dist-client/utils/assistant-request-context.d.ts +10 -0
  18. package/dist-client/utils/assistant-request-context.js +20 -0
  19. package/dist-client/utils/assistant-request-context.js.map +1 -0
  20. package/dist-client/utils/assistant-session-controller.d.ts +49 -0
  21. package/dist-client/utils/assistant-session-controller.js +165 -0
  22. package/dist-client/utils/assistant-session-controller.js.map +1 -0
  23. package/dist-client/utils/assistant-session-transport.d.ts +3 -0
  24. package/dist-client/utils/assistant-session-transport.js +39 -0
  25. package/dist-client/utils/assistant-session-transport.js.map +1 -0
  26. package/dist-server/service/assistant-chat-resolver.js +8 -0
  27. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  28. package/dist-server/service/chat-session/chat-session.js +1 -1
  29. package/dist-server/service/chat-session/chat-session.js.map +1 -1
  30. package/dist-server/service/chat-session/session-anchor.d.ts +1 -1
  31. package/dist-server/service/chat-session/session-anchor.js +3 -1
  32. package/dist-server/service/chat-session/session-anchor.js.map +1 -1
  33. package/dist-server/service/chat-session/station-session-guard.d.ts +12 -0
  34. package/dist-server/service/chat-session/station-session-guard.js +20 -0
  35. package/dist-server/service/chat-session/station-session-guard.js.map +1 -0
  36. package/dist-server/tsconfig.tsbuildinfo +1 -1
  37. package/docs/session-controller.md +52 -0
  38. package/package.json +2 -2
  39. package/server/service/assistant-chat-resolver.ts +9 -0
  40. package/server/service/chat-session/chat-session.ts +1 -1
  41. package/server/service/chat-session/session-anchor.ts +3 -1
  42. package/server/service/chat-session/station-session-guard.ts +17 -0
  43. package/server/service/session-anchor.test.ts +11 -0
  44. package/server/service/station-session-guard.test.ts +24 -0
  45. package/test/assistant-request-context.test.ts +30 -0
  46. package/test/assistant-session-controller.test.ts +88 -0
  47. package/test/assistant-session-toolbar.test.ts +29 -0
  48. package/translations/en.json +6 -0
  49. package/translations/ko.json +6 -0
@@ -14,12 +14,16 @@
14
14
  * 모드 전환은 컨테이너 (워크스페이스) 의 책임. 이 컴포넌트는 자체로 풀 채팅 UX.
15
15
  */
16
16
  import '@material/web/icon/icon.js';
17
+ import './assistant-session-toolbar.js';
18
+ import { type AssistantRequestContext } from '../utils/assistant-request-context.js';
17
19
  import { LitElement } from 'lit';
18
20
  import './mention-popup.js';
19
21
  import { type FilterResult } from './mention-popup.js';
20
22
  export declare class OxBoardAIChat extends LitElement {
21
23
  /** 영속 세션 id. 없으면 ad-hoc (메시지 영속 안 됨). */
22
24
  sessionId?: string;
25
+ prepareSession?: (adopt: (id: string) => void) => Promise<string | undefined>;
26
+ private adoptingSession;
23
27
  /**
24
28
  * 보드의 모든 ChatSession 리스트 — 다중 세션 활성화 시 탭 UI 로 표시.
25
29
  *
@@ -138,6 +142,8 @@ export declare class OxBoardAIChat extends LitElement {
138
142
  * 값이 들어오면 그 문장을 입력으로 삼아 한 번 전송하고 비운다(같은 질문이 반복 전송되지 않게).
139
143
  */
140
144
  autoAsk?: string;
145
+ autoAskRequestId?: string;
146
+ contextProvider?: () => AssistantRequestContext;
141
147
  /**
142
148
  * 빈 화면의 제목·부제 — **호스트가 자기 말로 준다.**
143
149
  *
@@ -16,6 +16,8 @@ import { __decorate, __metadata } from "tslib";
16
16
  * 모드 전환은 컨테이너 (워크스페이스) 의 책임. 이 컴포넌트는 자체로 풀 채팅 UX.
17
17
  */
18
18
  import '@material/web/icon/icon.js';
19
+ import './assistant-session-toolbar.js';
20
+ import { assistantAutoAskKey, assistantModelFingerprint } from '../utils/assistant-request-context.js';
19
21
  import { LitElement, css, html, nothing } from 'lit';
20
22
  import { customElement, property, state } from 'lit/decorators.js';
21
23
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
@@ -155,6 +157,7 @@ function initials(name) {
155
157
  let OxBoardAIChat = class OxBoardAIChat extends LitElement {
156
158
  constructor() {
157
159
  super(...arguments);
160
+ this.adoptingSession = false;
158
161
  /**
159
162
  * 보드의 모든 ChatSession 리스트 — 다중 세션 활성화 시 탭 UI 로 표시.
160
163
  *
@@ -282,6 +285,8 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
282
285
  }
283
286
  };
284
287
  this._onSessionCreateClick = () => {
288
+ if (this.busy || this.lines.length === 0)
289
+ return;
285
290
  this.dispatchEvent(new CustomEvent('session-create', {
286
291
  bubbles: true,
287
292
  composed: true
@@ -1601,12 +1606,17 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
1601
1606
  * sessionId 가 붙기 전에 보내면 대화에 남지 않으므로 세션이 준비될 때까지 기다린다.
1602
1607
  * 같은 문장을 두 번 보내지 않도록 보낸 값을 기억한다 — 재렌더마다 updated 가 다시 돌기 때문이다.
1603
1608
  */
1604
- if (this.autoAsk && this.autoAsk !== this._autoAsked && this.sessionId && !this.busy) {
1605
- this._autoAsked = this.autoAsk;
1609
+ if (this.autoAsk && assistantAutoAskKey(this.autoAsk, this.autoAskRequestId) !== this._autoAsked && (this.sessionId || this.prepareSession) && !this.busy) {
1610
+ this._autoAsked = assistantAutoAskKey(this.autoAsk, this.autoAskRequestId);
1606
1611
  this.input = this.autoAsk;
1607
1612
  this.send();
1608
1613
  }
1609
- if (changed.has('sessionId')) {
1614
+ if (changed.has('sessionId') && this.adoptingSession) {
1615
+ this.adoptingSession = false;
1616
+ this._startPresence();
1617
+ this._startMessageSubscription();
1618
+ }
1619
+ else if (changed.has('sessionId')) {
1610
1620
  // sessionId 가 바뀌면 (set / unset / 다른 보드 전환) 이전 세션의 모든 클라이언트
1611
1621
  // 컨텍스트 정리 — lines / pickedMentions / errorMessage / input 모두 새지 않도록.
1612
1622
  // 보드 단위 격리 (ChatSession 1:1 boardId) 가 깨지는 회귀 방지.
@@ -2232,38 +2242,11 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2232
2242
  if (!this.sessions || this.sessions.length === 0)
2233
2243
  return nothing;
2234
2244
  return html `
2235
- <div class="session-tabs" role="tablist">
2236
- ${this.sessions.map((s, i) => {
2237
- const isActive = s.id === this.sessionId;
2238
- const fallback = i18next.t('ai-assistant.text.session-tab-label', {
2239
- n: i + 1,
2240
- defaultValue: `세션 ${i + 1}`
2241
- });
2242
- const label = s.name?.trim() || fallback;
2243
- const tooltip = s.createdAt
2244
- ? `${label} · ${new Date(s.createdAt).toLocaleString()}`
2245
- : label;
2246
- return html `
2247
- <button
2248
- class="session-tab ${isActive ? 'active' : ''}"
2249
- role="tab"
2250
- aria-selected=${isActive ? 'true' : 'false'}
2251
- title=${tooltip}
2252
- @click=${() => this._onSessionTabClick(s.id)}>
2253
- ${label}
2254
- </button>
2255
- `;
2256
- })}
2257
- <button
2258
- class="session-tab-new"
2259
- title=${i18next.t('ai-assistant.button.new-chat', {
2260
- defaultValue: '새 대화'
2261
- })}
2262
- @click=${this._onSessionCreateClick}>
2263
- <md-icon>add</md-icon>
2264
- <span>${i18next.t('ai-assistant.button.new-chat', { defaultValue: '새 대화' })}</span>
2265
- </button>
2266
- </div>
2245
+ <ox-assistant-session-toolbar style="padding:8px 12px" .sessions=${this.sessions}
2246
+ .sessionId=${this.sessionId || ''} .disabled=${this.busy}
2247
+ @assistant-session-select=${(event) => { event.stopPropagation(); this._onSessionTabClick(event.detail.sessionId); }}
2248
+ @assistant-session-reset=${(event) => { event.stopPropagation(); this._onSessionCreateClick(); }}
2249
+ ></ox-assistant-session-toolbar>
2267
2250
  `;
2268
2251
  }
2269
2252
  /** C4 roster — 세션 참여자 + online 표시. 참여자 2명 미만이면 숨김(혼자면 의미 없음). */
@@ -2796,6 +2779,33 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2796
2779
  const text = this.input.trim();
2797
2780
  if (!text || this.busy)
2798
2781
  return;
2782
+ if (!this.sessionId && this.prepareSession) {
2783
+ this.busy = true;
2784
+ try {
2785
+ await this.prepareSession(id => { this.adoptingSession = true; this.sessionId = id; });
2786
+ await this.updateComplete;
2787
+ }
2788
+ catch (error) {
2789
+ this.errorMessage = error instanceof Error ? error.message : String(error);
2790
+ this.busy = false;
2791
+ return;
2792
+ }
2793
+ this.busy = false;
2794
+ }
2795
+ let requestContext;
2796
+ let liveBoard;
2797
+ let requestBase;
2798
+ try {
2799
+ const provided = this.contextProvider?.() || {};
2800
+ requestContext = structuredClone({ systemPrompt: this.systemPrompt, hostContext: this.hostContext, ...provided });
2801
+ liveBoard = structuredClone(this.boardProvider ? this.boardProvider() : this.currentBoard);
2802
+ requestBase = { version: 1, sessionId: this.sessionId, hostContext: requestContext.hostContext,
2803
+ modelFingerprint: assistantModelFingerprint(liveBoard) };
2804
+ }
2805
+ catch (error) {
2806
+ this.errorMessage = error instanceof Error ? error.message : String(error);
2807
+ return;
2808
+ }
2799
2809
  // pickedMentions / pickedUserMentions 에서 현재 텍스트에 실제 #/@ token 으로
2800
2810
  // 등장하는 것만 추출 — 지운 토큰 제외. inline 마커로 content 에 주입.
2801
2811
  const mentions = [];
@@ -2830,7 +2840,6 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2830
2840
  .map(l => ({ role: l.role, content: l.content }));
2831
2841
  // 라이브 보드 우선 — 호스트의 캔버스가 사용자 수작업 편집을 들고 있을 수 있음.
2832
2842
  // boardProvider 가 있으면 send 시점에 그것을 pull, 없으면 정적 currentBoard.
2833
- const liveBoard = this.boardProvider ? this.boardProvider() : this.currentBoard;
2834
2843
  // mentions 는 위에서 이미 계산해 user line 에 thread — 그대로 mutation 입력으로 재사용.
2835
2844
  const neutral = this.chatEndpoint === 'assistantChat';
2836
2845
  const result = await client.mutate({
@@ -2840,9 +2849,9 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2840
2849
  ? buildAssistantChatInput({
2841
2850
  sessionId: this.sessionId,
2842
2851
  history,
2843
- systemPrompt: this.systemPrompt,
2852
+ systemPrompt: requestContext.systemPrompt,
2844
2853
  truncateAfterMessageId: this._truncateAfterId,
2845
- hostContext: this.hostContext,
2854
+ hostContext: requestContext.hostContext,
2846
2855
  toolCategories: this.toolCategories,
2847
2856
  requireGroundingTools: this.requireGroundingTools
2848
2857
  })
@@ -2852,7 +2861,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2852
2861
  liveBoard,
2853
2862
  scopes: this.scopes,
2854
2863
  truncateAfterMessageId: this._truncateAfterId,
2855
- hostContext: this.hostContext,
2864
+ hostContext: requestContext.hostContext,
2856
2865
  toolCategories: this.toolCategories,
2857
2866
  boardTools: this.boardTools,
2858
2867
  requireGroundingTools: this.requireGroundingTools,
@@ -2891,6 +2900,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2891
2900
  this.dispatchEvent(new CustomEvent('board-edit-patch', {
2892
2901
  detail: {
2893
2902
  patch: out.patch,
2903
+ requestBase,
2894
2904
  summary: out.patch.summary,
2895
2905
  confidence: out.patch.confidence,
2896
2906
  patchId: out.patchId,
@@ -2903,7 +2913,7 @@ let OxBoardAIChat = class OxBoardAIChat extends LitElement {
2903
2913
  // 호스트로 ephemeral scene 조작 actions 전파 (selection / view / mode)
2904
2914
  if (Array.isArray(out.actions) && out.actions.length > 0) {
2905
2915
  this.dispatchEvent(new CustomEvent('board-action-execute', {
2906
- detail: { actions: out.actions, sessionId: out.sessionId },
2916
+ detail: { actions: out.actions, sessionId: out.sessionId, requestBase },
2907
2917
  bubbles: true,
2908
2918
  composed: true
2909
2919
  }));
@@ -2931,6 +2941,10 @@ __decorate([
2931
2941
  property({ type: String, attribute: 'session-id' }),
2932
2942
  __metadata("design:type", String)
2933
2943
  ], OxBoardAIChat.prototype, "sessionId", void 0);
2944
+ __decorate([
2945
+ property({ attribute: false }),
2946
+ __metadata("design:type", Function)
2947
+ ], OxBoardAIChat.prototype, "prepareSession", void 0);
2934
2948
  __decorate([
2935
2949
  property({ type: Array, attribute: false }),
2936
2950
  __metadata("design:type", Array)
@@ -3011,6 +3025,14 @@ __decorate([
3011
3025
  property({ attribute: false }),
3012
3026
  __metadata("design:type", String)
3013
3027
  ], OxBoardAIChat.prototype, "autoAsk", void 0);
3028
+ __decorate([
3029
+ property(),
3030
+ __metadata("design:type", String)
3031
+ ], OxBoardAIChat.prototype, "autoAskRequestId", void 0);
3032
+ __decorate([
3033
+ property({ attribute: false }),
3034
+ __metadata("design:type", Function)
3035
+ ], OxBoardAIChat.prototype, "contextProvider", void 0);
3014
3036
  __decorate([
3015
3037
  property({ type: Object, attribute: false }),
3016
3038
  __metadata("design:type", Object)