@things-factory/ai-assistant 10.1.79 → 10.1.81

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 (74) hide show
  1. package/client/components/assistant-chat.ts +102 -13
  2. package/client/components/assistant-dock.ts +160 -18
  3. package/client/components/assistant-mode.test.ts +16 -2
  4. package/client/components/assistant-session-manager.ts +52 -25
  5. package/client/components/assistant-session-toolbar.ts +4 -2
  6. package/client/components/chat-echo-dedup.ts +4 -0
  7. package/client/components/proposal-outcome-line.ts +36 -0
  8. package/client/components/proposal-request.test.ts +52 -0
  9. package/client/utils/assistant-navigation.test.ts +38 -0
  10. package/client/utils/assistant-navigation.ts +6 -0
  11. package/client/utils/assistant-surface.ts +21 -0
  12. package/dist-client/components/assistant-chat.d.ts +15 -0
  13. package/dist-client/components/assistant-chat.js +106 -13
  14. package/dist-client/components/assistant-chat.js.map +1 -1
  15. package/dist-client/components/assistant-dock.d.ts +13 -0
  16. package/dist-client/components/assistant-dock.js +175 -20
  17. package/dist-client/components/assistant-dock.js.map +1 -1
  18. package/dist-client/components/assistant-mode.test.js +14 -2
  19. package/dist-client/components/assistant-mode.test.js.map +1 -1
  20. package/dist-client/components/assistant-session-manager.d.ts +4 -0
  21. package/dist-client/components/assistant-session-manager.js +68 -26
  22. package/dist-client/components/assistant-session-manager.js.map +1 -1
  23. package/dist-client/components/assistant-session-toolbar.d.ts +1 -0
  24. package/dist-client/components/assistant-session-toolbar.js +8 -2
  25. package/dist-client/components/assistant-session-toolbar.js.map +1 -1
  26. package/dist-client/components/chat-echo-dedup.d.ts +2 -0
  27. package/dist-client/components/chat-echo-dedup.js +2 -0
  28. package/dist-client/components/chat-echo-dedup.js.map +1 -1
  29. package/dist-client/components/proposal-outcome-line.d.ts +29 -0
  30. package/dist-client/components/proposal-outcome-line.js +15 -0
  31. package/dist-client/components/proposal-outcome-line.js.map +1 -1
  32. package/dist-client/components/proposal-request.test.d.ts +1 -0
  33. package/dist-client/components/proposal-request.test.js +45 -0
  34. package/dist-client/components/proposal-request.test.js.map +1 -0
  35. package/dist-client/tsconfig.tsbuildinfo +1 -1
  36. package/dist-client/utils/assistant-navigation.d.ts +6 -0
  37. package/dist-client/utils/assistant-navigation.js +9 -0
  38. package/dist-client/utils/assistant-navigation.js.map +1 -0
  39. package/dist-client/utils/assistant-navigation.test.d.ts +1 -0
  40. package/dist-client/utils/assistant-navigation.test.js +37 -0
  41. package/dist-client/utils/assistant-navigation.test.js.map +1 -0
  42. package/dist-client/utils/assistant-surface.d.ts +19 -0
  43. package/dist-client/utils/assistant-surface.js.map +1 -1
  44. package/dist-server/service/assistant-chat-resolver.js +1 -0
  45. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  46. package/dist-server/service/chat-message/chat-message.d.ts +10 -0
  47. package/dist-server/service/chat-message/chat-message.js +29 -0
  48. package/dist-server/service/chat-message/chat-message.js.map +1 -1
  49. package/dist-server/service/chat-message/proposal-outcome.d.ts +8 -0
  50. package/dist-server/service/chat-message/proposal-outcome.js +35 -1
  51. package/dist-server/service/chat-message/proposal-outcome.js.map +1 -1
  52. package/dist-server/service/chat-session/session-inbox.d.ts +7 -0
  53. package/dist-server/service/chat-session/session-inbox.js +7 -0
  54. package/dist-server/service/chat-session/session-inbox.js.map +1 -1
  55. package/dist-server/service/chat-session-resolver.d.ts +1 -0
  56. package/dist-server/service/chat-session-resolver.js +63 -9
  57. package/dist-server/service/chat-session-resolver.js.map +1 -1
  58. package/dist-server/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +3 -3
  60. package/server/service/assistant-chat-resolver.ts +1 -0
  61. package/server/service/chat-message/chat-message.ts +32 -0
  62. package/server/service/chat-message/proposal-outcome.test.ts +53 -2
  63. package/server/service/chat-message/proposal-outcome.ts +33 -0
  64. package/server/service/chat-session/session-inbox.ts +7 -0
  65. package/server/service/chat-session-resolver.ts +71 -12
  66. package/test/assistant-dock.test.ts +8 -3
  67. package/test/assistant-session-toolbar.test.ts +7 -1
  68. package/test/open-doors.test.ts +2 -1
  69. package/test/session-discovery.test.ts +42 -0
  70. package/translations/en.json +26 -1
  71. package/translations/ja.json +26 -1
  72. package/translations/ko.json +26 -1
  73. package/translations/ms.json +26 -1
  74. package/translations/zh.json +26 -1
@@ -45,7 +45,7 @@ import { buildMentionCandidates, buildCatalogCandidates, buildSlashCandidates, b
45
45
  */
46
46
  import { chatDefaults } from './chat-defaults.js';
47
47
  import { isDifferentConversation, resolveConversation, pickInitialMode } from './assistant-mode.js';
48
- import { parseProposalOutcomeLine } from './proposal-outcome-line.js';
48
+ import { parseProposalOutcomeLine, parseProposalRequestLine, proposalCardState } from './proposal-outcome-line.js';
49
49
  import { availableTriggers } from './chat-triggers.js';
50
50
  function formatFileSize(bytes) {
51
51
  if (!bytes || bytes < 1024)
@@ -184,6 +184,7 @@ const CHAT_MESSAGES_QUERY = gql `
184
184
  content
185
185
  relatedPatchId
186
186
  toolUsagesJson
187
+ proposalsJson
187
188
  groundingWarningsJson
188
189
  attachmentsJson
189
190
  createdAt
@@ -205,6 +206,7 @@ const CHAT_MESSAGE_SUBSCRIPTION = gql `
205
206
  content
206
207
  relatedPatchId
207
208
  toolUsagesJson
209
+ proposalsJson
208
210
  groundingWarningsJson
209
211
  attachmentsJson
210
212
  createdAt
@@ -298,6 +300,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
298
300
  /** 안내를 닫았는가 — 한 번 닫으면 이 세션에서는 다시 띄우지 않는다(잔소리 금지). */
299
301
  this.foldNoticeDismissed = false;
300
302
  this.busy = false;
303
+ /** Host navigation may retain a conversation for reading while its subject is off screen. */
304
+ this.sendBlocked = '';
301
305
  /** C4 presence/roster — 세션 참여자 목록. lastSeenAt 으로 online 판정. */
302
306
  this.participants = [];
303
307
  /** roster online 재평가 트리거 — heartbeat tick 마다 갱신(시간 경과 반영). */
@@ -559,6 +563,10 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
559
563
  }
560
564
  /** Once per page: the miss is a wiring mistake, and repeating it per candidate buries it. */
561
565
  static { this.warnedMissingStagedNotice = false; }
566
+ /** The host may inspect navigation risk without reading or copying the person's input. */
567
+ get navigationState() {
568
+ return { busy: this.busy, draft: !!this.input.trim() || this.attachments.length > 0 };
569
+ }
562
570
  /** 현재 로그인 사용자 email — 그룹챗에서 내 메시지 vs 타인 메시지 구분.
563
571
  * email 을 쓰는 이유: /auth/profile 이 항상 내려주는 로그인 식별자이고(비어 있지 않다),
564
572
  * 내부 관리키(User.id)를 클라이언트 판정에 의존하지 않기 위함. */
@@ -2555,6 +2563,9 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
2555
2563
  const prevSameSender = !!prev && prev.role === 'user' && prev.senderEmail === line.senderEmail;
2556
2564
  const showSenderLabel = isOther && !prevSameSender;
2557
2565
  const content = (line.content ?? '').trim();
2566
+ /* A "requested" row is the card's state, shown on the card - as a line it would only be the machine's key. */
2567
+ if (line.role === 'system' && parseProposalRequestLine(content))
2568
+ return nothing;
2558
2569
  /*
2559
2570
  * 가려진 줄은 어시스턴트의 말이 아니다 — 마크다운으로 그리지 않고, 복사·재실행 같은 손잡이도
2560
2571
  * 달지 않는다(ADR-0073 판정 1). 그 손잡이들은 있지도 않은 답을 다룬다.
@@ -2797,9 +2808,9 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
2797
2808
  `
2798
2809
  : nothing}
2799
2810
  <button
2800
- ?disabled=${this.busy || (!this.input.trim() && this.attachments.length === 0)}
2811
+ ?disabled=${this.busy || !!this.sendBlocked || (!this.input.trim() && this.attachments.length === 0)}
2801
2812
  @click=${this.send}
2802
- title=${i18next.t('ai-assistant.text.send-tooltip')}>
2813
+ title=${this.sendBlocked || i18next.t('ai-assistant.text.send-tooltip')}>
2803
2814
  ${this.busy
2804
2815
  ? html `<span class="spinner"></span>`
2805
2816
  : html `<md-icon>arrow_upward</md-icon>`}
@@ -3486,11 +3497,15 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
3486
3497
  const items = line.proposals ?? [];
3487
3498
  if (items.length === 0)
3488
3499
  return nothing;
3500
+ /* The card's state is the conversation's rows - read once for every card of this line. */
3501
+ const outcomes = this.proposalOutcomes;
3502
+ const requested = this.proposalRequests;
3489
3503
  return html `
3490
3504
  <div class="proposals">
3491
3505
  ${items.map(p => {
3492
3506
  const key = this.proposalKey(p);
3493
- const sent = this.sentProposals.has(key);
3507
+ const card = proposalCardState(key, { outcomes, requested, sentHere: this.sentProposals.has(key) });
3508
+ const sent = card.state !== 'ready';
3494
3509
  /*
3495
3510
  Follow-up chips are the host's words, never this component's. The fallback that used
3496
3511
  to live here named a 3D modeller's operations in hardcoded Korean, and it reached the
@@ -3511,14 +3526,23 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
3511
3526
  the one action the card exists for became unreachable while the model kept
3512
3527
  telling people to press it.
3513
3528
  */''}
3514
- <button
3515
- class="run"
3516
- ?disabled=${sent}
3517
- @click=${() => this.executeProposal(p, key)}>
3518
- ${sent
3519
- ? this.proposalStateText(key)
3520
- : i18next.t('ai-assistant.button.run-proposal', { defaultValue: 'run' })}
3521
- </button>
3529
+ ${card.state === 'unconfirmed'
3530
+ ? html `
3531
+ <span class="preason">${i18next.t('ai-assistant.text.proposal-unconfirmed', { defaultValue: 'sent earlier - no answer received' })}</span>
3532
+ <button class="run" @click=${() => this.checkProposal(p, key)}>
3533
+ ${i18next.t('ai-assistant.button.check-proposal', { defaultValue: 'check result' })}
3534
+ </button>
3535
+ `
3536
+ : html `
3537
+ <button
3538
+ class="run"
3539
+ ?disabled=${sent}
3540
+ @click=${() => this.executeProposal(p, key)}>
3541
+ ${sent
3542
+ ? this.proposalStateText(key)
3543
+ : i18next.t('ai-assistant.button.run-proposal', { defaultValue: 'run' })}
3544
+ </button>
3545
+ `}
3522
3546
  ${ /*
3523
3547
  Drawn only where the host has said it staged this candidate, in the host's own
3524
3548
  words. Not knowing is not a claim: with no word from the host, nothing is drawn.
@@ -3646,10 +3670,34 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
3646
3670
  return `${p?.tool ?? ''}|${p?.command ?? ''}|${p?.instanceId ?? ''}`;
3647
3671
  }
3648
3672
  }
3649
- executeProposal(p, key) {
3673
+ async executeProposal(p, key) {
3674
+ if (this.sentProposals.has(key))
3675
+ return;
3650
3676
  this.sentProposals = new Set(this.sentProposals).add(key);
3651
3677
  this.proposalSentAt.set(key, Date.now());
3652
3678
  this._startElapsedTicker();
3679
+ /*
3680
+ * The server records the request first and answers whether this conversation has taken the proposal
3681
+ * already (ADR-0097 Q-D4 ④). Only a granted request reaches the host. A refused one - taken in another
3682
+ * window, or before a refresh - leaves the card to the rows; a failed one leaves it as it was, and the
3683
+ * error is said by the client's own error line. Either way the host is not asked.
3684
+ */
3685
+ if (this.sessionId) {
3686
+ let granted = false;
3687
+ try {
3688
+ granted = await this.requestProposal(key);
3689
+ }
3690
+ catch {
3691
+ granted = false;
3692
+ }
3693
+ if (!granted) {
3694
+ const left = new Set(this.sentProposals);
3695
+ left.delete(key);
3696
+ this.sentProposals = left;
3697
+ this.proposalSentAt.delete(key);
3698
+ return;
3699
+ }
3700
+ }
3653
3701
  this.dispatchEvent(new CustomEvent('assistant-proposal-execute', {
3654
3702
  /*
3655
3703
  * `proposalKey` 를 함께 준다 — 호스트가 `reportProposalOutcome` 으로 답할 때 쓰는 이름이다.
@@ -3660,6 +3708,42 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
3660
3708
  composed: true
3661
3709
  }));
3662
3710
  }
3711
+ async requestProposal(key) {
3712
+ const result = await client.mutate({
3713
+ mutation: gql `
3714
+ mutation ($id: String!, $key: String!) {
3715
+ requestProposal(sessionId: $id, proposalKey: $key)
3716
+ }
3717
+ `,
3718
+ variables: { id: this.sessionId, key }
3719
+ });
3720
+ if (result.errors?.length)
3721
+ throw new Error(result.errors[0].message);
3722
+ return result.data?.requestProposal === true;
3723
+ }
3724
+ /**
3725
+ * "Check result" on a card that was requested and never answered: the host is asked what happened
3726
+ * (`AssistantSurface.proposalOutcome`), never to run it again. The dock reports what it learns.
3727
+ */
3728
+ checkProposal(p, key) {
3729
+ this.dispatchEvent(new CustomEvent('assistant-proposal-check', {
3730
+ detail: { proposal: p, sessionId: this.sessionId, proposalKey: key },
3731
+ bubbles: true,
3732
+ composed: true
3733
+ }));
3734
+ }
3735
+ /** Keys whose run was requested - from the conversation's rows, like the outcomes. */
3736
+ get proposalRequests() {
3737
+ const requested = new Set();
3738
+ for (const line of this.lines) {
3739
+ if (line.role !== 'system')
3740
+ continue;
3741
+ const key = parseProposalRequestLine(line.content ?? '');
3742
+ if (key)
3743
+ requested.add(key);
3744
+ }
3745
+ return requested;
3746
+ }
3663
3747
  /**
3664
3748
  * 호스트가 무엇을 했는지 — 대화에 남은 `system` 행에서 읽는다(ADR-0070 A12-a 결정 ①).
3665
3749
  *
@@ -3959,6 +4043,10 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
3959
4043
  }));
3960
4044
  }
3961
4045
  async send() {
4046
+ if (this.sendBlocked) {
4047
+ this.errorMessage = this.sendBlocked;
4048
+ return;
4049
+ }
3962
4050
  const text = this.input.trim();
3963
4051
  const attachmentsToSend = [...this.attachments];
3964
4052
  if ((!text && attachmentsToSend.length === 0) || this.busy)
@@ -4022,6 +4110,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
4022
4110
  }
4023
4111
  catch (error) {
4024
4112
  this.errorMessage = error instanceof Error ? error.message : String(error);
4113
+ this.attachments = attachmentsToSend;
4025
4114
  return;
4026
4115
  }
4027
4116
  // pickedMentions / pickedUserMentions 에서 현재 텍스트에 실제 #/@ token 으로
@@ -4375,6 +4464,10 @@ __decorate([
4375
4464
  state(),
4376
4465
  __metadata("design:type", Object)
4377
4466
  ], OxAssistantChat.prototype, "busy", void 0);
4467
+ __decorate([
4468
+ property(),
4469
+ __metadata("design:type", Object)
4470
+ ], OxAssistantChat.prototype, "sendBlocked", void 0);
4378
4471
  __decorate([
4379
4472
  state(),
4380
4473
  __metadata("design:type", String)