@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
@@ -35,6 +35,9 @@ export declare class AssistantDock extends AssistantDock_base {
35
35
  private showingHistory;
36
36
  private conversationView;
37
37
  private asking?;
38
+ private screenChanged;
39
+ private navigationNotice;
40
+ private openedIdentity;
38
41
  private app;
39
42
  /** The kind the person picked; kept while the surface stays the same. */
40
43
  private chosenKind;
@@ -47,6 +50,10 @@ export declare class AssistantDock extends AssistantDock_base {
47
50
  connectedCallback(): void;
48
51
  disconnectedCallback(): void;
49
52
  private scopeKey;
53
+ private identityKey;
54
+ private get navigationState();
55
+ private mayLeaveConversation;
56
+ private requestSurface;
50
57
  /** The screen changed - a different surface or domain is a different conversation. */
51
58
  private follow;
52
59
  private open;
@@ -62,8 +69,14 @@ export declare class AssistantDock extends AssistantDock_base {
62
69
  private report;
63
70
  /** Every proposal ends in exactly one reported outcome - a kind nobody handles is `unavailable`. */
64
71
  private onProposal;
72
+ /**
73
+ * "Check result" - asks the surface what became of a requested proposal and reports only what it learns.
74
+ * Nothing is run: a lost answer never turns into a second application (ADR-0097 Q-D4 ④).
75
+ */
76
+ private onProposalCheck;
65
77
  private appendNote;
66
78
  private onScenePatch;
79
+ private mayApplyOnScreen;
67
80
  private onBoardRevert;
68
81
  private onSceneAction;
69
82
  private stuckLine;
@@ -21,6 +21,7 @@ import { setOpenSession, subscribeSessionActivity } from '../utils/assistant-act
21
21
  import { assistantHomeSurface, currentAssistantSurface, isAnchor, subscribeAssistantSurface } from '../utils/assistant-surface.js';
22
22
  import { runProposal } from '../utils/assistant-proposals.js';
23
23
  import { holdAssistantAsk, subscribeAssistantAsk, takeAssistantAsk } from '../utils/assistant-ask.js';
24
+ import { surfaceTransition } from '../utils/assistant-navigation.js';
24
25
  export const ASSISTANT_DOCK_VIEWPART = 'assistant-dock';
25
26
  const t = (key, defaultValue, options = {}) => i18next.t(`ai-assistant.dock.${key}`, { defaultValue, ...options });
26
27
  const MODES_QUERY = gql `
@@ -66,6 +67,9 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
66
67
  this.failure = '';
67
68
  this.showingHistory = false;
68
69
  this.conversationView = 0;
70
+ this.screenChanged = false;
71
+ this.navigationNotice = '';
72
+ this.openedIdentity = '';
69
73
  this.app = new AppController(this);
70
74
  /** The kind the person picked; kept while the surface stays the same. */
71
75
  this.chosenKind = '';
@@ -84,17 +88,21 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
84
88
  this.failure = next.error;
85
89
  }, { getItem: key => sessionStorage.getItem(key), setItem: (key, value) => sessionStorage.setItem(key, value) }, { lazy: true });
86
90
  this.onStartNewSession = () => void this.newConversation();
87
- this.onModeChange = (event) => {
91
+ this.onModeChange = async (event) => {
88
92
  const kind = event.detail?.kind;
89
93
  if (!kind || kind === this.kind)
90
94
  return;
95
+ if (!(await this.mayLeaveConversation()))
96
+ return;
91
97
  this.chosenKind = kind;
92
98
  void this.open();
93
99
  };
94
100
  /** Every proposal ends in exactly one reported outcome - a kind nobody handles is `unavailable`. */
95
101
  this.onProposal = async (event) => {
96
102
  const { proposal, sessionId, proposalKey } = event.detail ?? {};
97
- const outcome = await runProposal(proposal, this.proposalContext(sessionId));
103
+ const outcome = this.openedFor !== this.scopeKey(currentAssistantSurface()) || this.openedIdentity !== this.identityKey()
104
+ ? { verdict: 'unavailable', errorCode: 'conversation-screen-changed', note: t('return-to-subject', 'Return to the conversation subject before applying this proposal.') }
105
+ : await runProposal(proposal, this.proposalContext(sessionId));
98
106
  if (outcome.verdict === 'unavailable' && outcome.errorCode === 'unknown-proposal-kind') {
99
107
  notify({ level: 'error', message: t('proposal-not-runnable', 'this proposal cannot be run on this screen.') });
100
108
  }
@@ -103,8 +111,36 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
103
111
  if (outcome.note && sessionId)
104
112
  await this.appendNote(sessionId, outcome.note);
105
113
  };
114
+ /**
115
+ * "Check result" - asks the surface what became of a requested proposal and reports only what it learns.
116
+ * Nothing is run: a lost answer never turns into a second application (ADR-0097 Q-D4 ④).
117
+ */
118
+ this.onProposalCheck = async (event) => {
119
+ const { proposal, sessionId, proposalKey } = event.detail ?? {};
120
+ const ask = this.surface?.proposalOutcome;
121
+ if (!ask) {
122
+ notify({ level: 'info', message: t('proposal-result-uncheckable', 'this screen cannot check that result. open the screen where it was applied.') });
123
+ return;
124
+ }
125
+ let outcome;
126
+ try {
127
+ outcome = await ask.call(this.surface, proposalKey, proposal);
128
+ }
129
+ catch {
130
+ outcome = undefined;
131
+ }
132
+ if (!outcome?.verdict) {
133
+ notify({ level: 'info', message: t('proposal-result-unknown', 'the result is still unknown. it was not applied again.') });
134
+ return;
135
+ }
136
+ await this.report(sessionId, proposalKey, outcome);
137
+ if (outcome.note && sessionId)
138
+ await this.appendNote(sessionId, outcome.note);
139
+ };
106
140
  /* ── board surfaces: the chat's scene edits land on the open board ───────────────── */
107
141
  this.onScenePatch = async (event) => {
142
+ if (!this.mayApplyOnScreen())
143
+ return;
108
144
  const board = this.surface?.board;
109
145
  if (!board)
110
146
  return;
@@ -112,8 +148,10 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
112
148
  if (result?.missed)
113
149
  notify({ level: 'info', message: t('patch-missed', '{missed} of the changes could not be applied.', { missed: result.missed }) });
114
150
  };
115
- this.onBoardRevert = (event) => void this.surface?.board?.revertPatch?.(event.detail?.patchId);
116
- this.onSceneAction = (event) => void this.surface?.board?.applyAction?.(event.detail);
151
+ this.onBoardRevert = (event) => { if (this.mayApplyOnScreen())
152
+ void this.surface?.board?.revertPatch?.(event.detail?.patchId); };
153
+ this.onSceneAction = (event) => { if (this.mayApplyOnScreen())
154
+ void this.surface?.board?.applyAction?.(event.detail); };
117
155
  }
118
156
  static { this.styles = [
119
157
  ScrollbarStyles,
@@ -239,12 +277,42 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
239
277
  gap: 4px;
240
278
  font: inherit;
241
279
  }
280
+
281
+ .next-step {
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: space-between;
285
+ gap: 10px;
286
+ padding: 8px 12px;
287
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
288
+ background: var(--md-sys-color-surface-container-low);
289
+ color: var(--md-sys-color-on-surface-variant);
290
+ font-size: 12px;
291
+ line-height: 1.4;
292
+ }
293
+
294
+ .next-step a {
295
+ display: inline-flex;
296
+ align-items: center;
297
+ gap: 3px;
298
+ flex: none;
299
+ color: var(--md-sys-color-primary);
300
+ font-weight: 650;
301
+ text-decoration: none;
302
+ }
303
+
304
+ .next-step a:hover { text-decoration: underline; }
305
+ .next-step md-icon { --md-icon-size: 16px; }
306
+ ox-assistant-chat[hidden] { display: none; }
307
+ .transition { padding: 10px 12px; font-size: 13px; line-height: 1.5; border-bottom: 1px solid var(--md-sys-color-outline-variant); background: var(--md-sys-color-surface-container-low); }
308
+ .transition button { display: block; margin-top: 6px; font: inherit; }
242
309
  `
243
310
  ]; }
244
311
  connectedCallback() {
245
312
  super.connectedCallback();
246
313
  this.addEventListener('assistant-start-new-session', this.onStartNewSession);
247
- this.unsubscribers.push(subscribeAssistantSurface(() => this.follow()), subscribeAssistantAsk(() => void this.takeAsk()), subscribeSessionActivity(activity => {
314
+ this.unsubscribers.push(subscribeAssistantSurface(() => this.follow()), store.subscribe(() => { if (this.openedIdentity !== this.identityKey())
315
+ this.follow(); }), subscribeAssistantAsk(() => void this.takeAsk()), subscribeSessionActivity(activity => {
248
316
  /* A rename or a new conversation elsewhere on this anchor - the tabs read again. */
249
317
  if (activity.kind === 'meta' && isAnchor(this.anchor) && activity.anchorId === this.anchor.anchorId)
250
318
  void this.reloadSessions();
@@ -259,23 +327,73 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
259
327
  this.controller.clear();
260
328
  super.disconnectedCallback();
261
329
  }
262
- scopeKey(surface = this.surface) {
330
+ scopeKey(surface) {
263
331
  return JSON.stringify([this.app.contextPath, surface?.key ?? '']);
264
332
  }
333
+ identityKey() {
334
+ const user = store.getState().auth?.user;
335
+ return JSON.stringify([this.app.contextPath, user?.id || user?.email || '']);
336
+ }
337
+ get navigationState() {
338
+ return this.renderRoot.querySelector('ox-assistant-chat')?.navigationState ?? { busy: false, draft: false };
339
+ }
340
+ async mayLeaveConversation() {
341
+ if (this.navigationState.busy) {
342
+ notify({ level: 'info', message: t('wait-before-switch', 'Wait for the current response before changing conversations.') });
343
+ return false;
344
+ }
345
+ if (!this.navigationState.draft)
346
+ return true;
347
+ const identity = this.identityKey();
348
+ const accepted = await OxPrompt.open({
349
+ type: 'warning', title: t('switch-title', 'Change conversation?'),
350
+ text: t('switch-draft', 'Your unsent text and attachments will not move to the other conversation.'),
351
+ confirmButton: { text: t('switch-confirm', 'Discard draft and switch') },
352
+ cancelButton: { text: t('stay', 'Keep writing') }
353
+ });
354
+ return !!accepted && identity === this.identityKey();
355
+ }
356
+ async requestSurface(surface) {
357
+ if (!(await this.mayLeaveConversation()))
358
+ return;
359
+ this.chosenKind = '';
360
+ this.surface = surface;
361
+ this.navigationNotice = surface ? t('now-about', 'Conversation subject: {name}', { name: surface.label() }) : '';
362
+ await this.open();
363
+ }
265
364
  /** The screen changed - a different surface or domain is a different conversation. */
266
365
  follow(force = false) {
267
366
  const next = currentAssistantSurface();
268
- if (!force && next === this.surface && this.scopeKey(next) === this.openedFor) {
269
- /* The same conversation, told that its words changed - a name loaded, a board renamed on save. */
367
+ const transition = surfaceTransition({
368
+ identityChanged: this.openedIdentity !== this.identityKey(),
369
+ sameSubject: this.scopeKey(next) === this.openedFor,
370
+ active: isAnchor(this.anchor) && (!!this.sessionId || this.navigationState.draft || this.navigationState.busy)
371
+ });
372
+ if (!force && transition === 'refresh') {
373
+ this.surface = next;
374
+ this.screenChanged = false;
270
375
  this.requestUpdate();
271
376
  return;
272
377
  }
378
+ if (!force && transition === 'offer') {
379
+ this.screenChanged = true;
380
+ return;
381
+ }
382
+ if (transition === 'reset') {
383
+ this.asking = undefined;
384
+ this.capability = 'unknown';
385
+ if (this.openedIdentity)
386
+ takeAssistantAsk();
387
+ }
388
+ this.navigationNotice = transition === 'follow' && next ? t('now-about', 'Conversation subject: {name}', { name: next.label() }) : '';
273
389
  if (next?.key !== this.surface?.key)
274
390
  this.chosenKind = '';
275
391
  this.surface = next;
276
392
  void this.open();
277
393
  }
278
394
  async open() {
395
+ this.openedIdentity = this.identityKey();
396
+ this.screenChanged = false;
279
397
  const ticket = ++this.revision;
280
398
  this.conversationView++;
281
399
  this.showingHistory = false;
@@ -283,7 +401,7 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
283
401
  this.loading = true;
284
402
  this.failure = '';
285
403
  this.anchor = undefined;
286
- this.openedFor = this.scopeKey();
404
+ this.openedFor = this.scopeKey(this.surface);
287
405
  try {
288
406
  if (this.capability !== 'configured') {
289
407
  this.capability = await readAiCapability();
@@ -342,13 +460,17 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
342
460
  async newConversation() {
343
461
  if (this.loading || !isAnchor(this.anchor))
344
462
  return;
463
+ if (!(await this.mayLeaveConversation()))
464
+ return;
345
465
  this.showingHistory = false;
346
466
  const revision = this.controller.state.revision;
347
467
  await this.controller.reset();
348
468
  if (revision !== this.controller.state.revision)
349
469
  this.conversationView++;
350
470
  }
351
- selectSession(id, pickedKind) {
471
+ async selectSession(id, pickedKind) {
472
+ if (id !== this.sessionId && !(await this.mayLeaveConversation()))
473
+ return;
352
474
  this.showingHistory = false;
353
475
  const follow = followPickedConversation(pickedKind, this.kind);
354
476
  if (follow) {
@@ -365,6 +487,12 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
365
487
  async takeAsk() {
366
488
  if (this.loading || !this.anchor)
367
489
  return;
490
+ if (this.screenChanged || this.navigationState.busy || this.navigationState.draft) {
491
+ const waiting = takeAssistantAsk();
492
+ if (waiting)
493
+ notify({ level: 'info', message: t('ask-kept-current', 'The current conversation was kept. Switch to the intended subject before asking again.') });
494
+ return;
495
+ }
368
496
  if (!isAnchor(this.anchor)) {
369
497
  /*
370
498
  A question is not sent later on its own, once some conversation happens to stand - by then the person is looking
@@ -438,6 +566,8 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
438
566
  }
439
567
  /* ── proposals ───────────────────────────────────────────────────────────────────── */
440
568
  proposalContext(sessionId) {
569
+ const identity = this.identityKey();
570
+ const scope = this.openedFor;
441
571
  return {
442
572
  sessionId,
443
573
  surface: this.surface,
@@ -447,7 +577,7 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
447
577
  text,
448
578
  confirmButton: { text: confirmText ?? t('confirm', 'OK') },
449
579
  cancelButton: { text: cancelText ?? t('cancel', 'Cancel') }
450
- })),
580
+ })) && identity === this.identityKey() && scope === this.scopeKey(currentAssistantSurface()),
451
581
  notify: (level, message) => notify({ level, message })
452
582
  };
453
583
  }
@@ -483,6 +613,12 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
483
613
  /* the outcome is already reported; a lost note is not a lost outcome */
484
614
  }
485
615
  }
616
+ mayApplyOnScreen() {
617
+ if (this.openedFor === this.scopeKey(currentAssistantSurface()) && this.openedIdentity === this.identityKey())
618
+ return true;
619
+ notify({ level: 'info', message: t('return-to-subject', 'Return to the conversation subject before applying this proposal.') });
620
+ return false;
621
+ }
486
622
  /* ── drawing ─────────────────────────────────────────────────────────────────────── */
487
623
  stuckLine() {
488
624
  if (this.capability === 'not-configured')
@@ -512,16 +648,16 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
512
648
  <md-icon>home</md-icon>
513
649
  </button>`
514
650
  : nothing}
515
- ${ready
651
+ ${ready && this.showingHistory
516
652
  ? html `<button
517
653
  class="sbtn"
518
654
  title=${this.showingHistory ? t('back-to-conversation', 'back to the conversation') : t('conversations', 'conversations')}
519
655
  @click=${() => (this.showingHistory = !this.showingHistory)}
520
656
  >
521
- <md-icon>${this.showingHistory ? 'forum' : 'list'}</md-icon>
657
+ <md-icon>arrow_back</md-icon>${t('back-to-conversation', 'back to the conversation')}
522
658
  </button>`
523
659
  : nothing}
524
- <button class="sbtn" title=${t('rebind', 'follow this screen again')} @click=${() => this.follow(true)}>
660
+ <button class="sbtn" title=${t('rebind', 'follow this screen again')} @click=${() => this.requestSurface(currentAssistantSurface())}>
525
661
  <md-icon>refresh</md-icon>
526
662
  </button>
527
663
  <button class="sbtn" title=${t('close', 'close')} @click=${() => closeOverlay(this.viewpart)}>
@@ -532,9 +668,7 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
532
668
  }
533
669
  /** The home button: talk about the application instead of this screen, until the screen changes. */
534
670
  useSurface(surface) {
535
- this.surface = surface;
536
- this.chosenKind = '';
537
- void this.open();
671
+ void this.requestSurface(surface);
538
672
  }
539
673
  render() {
540
674
  const anchor = this.anchor;
@@ -546,6 +680,14 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
546
680
  const mode = ready ? { kind: this.kind, target: anchor.anchorId, hostContext: {} } : undefined;
547
681
  return html `
548
682
  ${this.header()}
683
+ ${surface?.nextStep ? html `<div class="next-step" role="status">
684
+ <span>${surface.nextStep.description?.() || ''}</span>
685
+ <a href=${surface.nextStep.href}>${surface.nextStep.label()}<md-icon>arrow_forward</md-icon></a>
686
+ </div>` : nothing}
687
+ ${this.screenChanged ? html `<div class="transition" role="status">
688
+ ${t('screen-and-conversation', 'Screen: {screen}. Conversation: {conversation}. Your conversation has been kept.', { screen: currentAssistantSurface()?.label() || t('no-subject', 'No subject'), conversation: surface?.label() || '' })}
689
+ <button class="sbtn" @click=${() => this.requestSurface(currentAssistantSurface())}>${t('switch-to-screen', 'Talk about this screen')}</button>
690
+ </div>` : this.navigationNotice ? html `<div class="transition" role="status">${this.navigationNotice}</div>` : nothing}
549
691
  ${ready && this.showingHistory
550
692
  ? html `<ox-assistant-session-manager
551
693
  .anchorType=${anchor.anchorType}
@@ -553,6 +695,7 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
553
695
  .sessionId=${this.sessionId}
554
696
  .kind=${this.kind}
555
697
  .kindLabels=${this.kindLabels}
698
+ .scopeLabel=${t('conversation-scope', 'Conversations · {name}', { name: surface?.label() || '' })}
556
699
  @assistant-session-select=${(event) => this.selectSession(event.detail.sessionId, event.detail.kind)}
557
700
  @assistant-session-reset=${() => this.newConversation()}
558
701
  @assistant-session-changed=${() => this.reloadSessions()}
@@ -565,20 +708,23 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
565
708
  .loading=${this.loading}
566
709
  .error=${this.failure}
567
710
  historyEnabled
711
+ singleConversation
568
712
  @assistant-session-select=${(event) => this.selectSession(event.detail.sessionId)}
569
713
  @assistant-session-reset=${() => this.newConversation()}
570
714
  @assistant-session-history=${() => (this.showingHistory = true)}
571
715
  @assistant-session-retry=${() => this.open()}
572
716
  ></ox-assistant-session-toolbar>`
573
717
  : nothing}
574
- ${ready && !this.showingHistory && !this.loading && !this.failure
718
+ ${ready && !this.loading && !this.failure
575
719
  ? keyed(this.conversationView, html `<ox-assistant-chat
720
+ ?hidden=${this.showingHistory}
721
+ .sendBlocked=${this.screenChanged ? t('return-to-subject', 'Return to the conversation subject before sending or applying changes.') : ''}
576
722
  .sessionId=${this.sessionId}
577
723
  .prepareSession=${(adopt) => this.controller.prepareSession(adopt)}
578
724
  .contextProvider=${() => {
579
725
  /* The screen moved on between writing and sending - do not answer about the old one. */
580
- if (openedFor !== this.scopeKey(currentAssistantSurface() ?? surface))
581
- throw new Error(t('screen-changed', 'the screen changed while you were writing. send it again.'));
726
+ if (openedFor !== this.scopeKey(currentAssistantSurface()) || this.openedIdentity !== this.identityKey())
727
+ throw new Error(t('return-to-subject', 'Return to the conversation subject before applying this proposal.'));
582
728
  return { hostContext: surface?.hostContext?.() ?? {} };
583
729
  }}
584
730
  .mode=${mode}
@@ -598,6 +744,7 @@ let AssistantDock = class AssistantDock extends localize(i18next)(LitElement) {
598
744
  .draftRequestId=${this.asking?.requestId}
599
745
  @assistant-mode-change=${this.onModeChange}
600
746
  @assistant-proposal-execute=${this.onProposal}
747
+ @assistant-proposal-check=${this.onProposalCheck}
601
748
  @scene-edit-patch=${this.onScenePatch}
602
749
  @board-edit-revert=${this.onBoardRevert}
603
750
  @scene-action-execute=${this.onSceneAction}
@@ -676,6 +823,14 @@ __decorate([
676
823
  state(),
677
824
  __metadata("design:type", Object)
678
825
  ], AssistantDock.prototype, "asking", void 0);
826
+ __decorate([
827
+ state(),
828
+ __metadata("design:type", Object)
829
+ ], AssistantDock.prototype, "screenChanged", void 0);
830
+ __decorate([
831
+ state(),
832
+ __metadata("design:type", Object)
833
+ ], AssistantDock.prototype, "navigationNotice", void 0);
679
834
  AssistantDock = __decorate([
680
835
  customElement('ox-assistant-dock')
681
836
  ], AssistantDock);