@design.estate/dees-catalog 6.7.0 → 6.8.0

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 (34) hide show
  1. package/dist_bundle/bundle.js +1153 -193
  2. package/dist_ts_web/00_commitinfo_data.js +1 -1
  3. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.d.ts +2 -1
  4. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +57 -17
  5. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +92 -6
  6. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +921 -49
  7. package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.d.ts +5 -1
  8. package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.js +42 -7
  9. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.d.ts +6 -1
  10. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.js +53 -14
  11. package/dist_ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.d.ts +2 -1
  12. package/dist_ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.js +13 -3
  13. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +31 -13
  14. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +9 -3
  15. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +6 -1
  16. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +135 -13
  17. package/dist_ts_web/elements/00group-harness/harness.styles.js +16 -13
  18. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +30 -0
  19. package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +2 -0
  20. package/dist_ts_web/elements/00group-harness/toolregistry.js +7 -1
  21. package/package.json +2 -2
  22. package/readme.md +4 -2
  23. package/ts_web/00_commitinfo_data.ts +1 -1
  24. package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +53 -15
  25. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +943 -51
  26. package/ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.ts +37 -8
  27. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.ts +40 -12
  28. package/ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.ts +6 -2
  29. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +30 -12
  30. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +8 -2
  31. package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +119 -11
  32. package/ts_web/elements/00group-harness/harness.styles.ts +15 -12
  33. package/ts_web/elements/00group-harness/interfaces.ts +34 -0
  34. package/ts_web/elements/00group-harness/toolregistry.ts +7 -0
@@ -10,7 +10,11 @@ import {
10
10
  cssManager,
11
11
  } from '@design.estate/dees-element';
12
12
  import { themeDefaultStyles } from '../../00theme.js';
13
- import type { IHarnessPermissionRequest, THarnessPermissionResponse } from '../interfaces.js';
13
+ import type {
14
+ IHarnessPermissionRequest,
15
+ IHarnessPermissionViewState,
16
+ THarnessPermissionResponse,
17
+ } from '../interfaces.js';
14
18
  import { harnessStringifyValue } from '../harness.helpers.js';
15
19
  import '../../00group-button/dees-button/dees-button.js';
16
20
  import '../../00group-feedback/dees-badge/dees-badge.js';
@@ -45,6 +49,12 @@ export class DeesHarnessPermissionCard extends DeesElement {
45
49
  @property({ type: Boolean })
46
50
  accessor busy: boolean = false;
47
51
 
52
+ @property({ attribute: false })
53
+ accessor viewState: IHarnessPermissionViewState | undefined = undefined;
54
+
55
+ @property({ type: Boolean })
56
+ accessor suppressEntry: boolean = false;
57
+
48
58
  @state()
49
59
  accessor remember: boolean = false;
50
60
 
@@ -53,7 +63,11 @@ export class DeesHarnessPermissionCard extends DeesElement {
53
63
  accessor answered: THarnessPermissionResponse | undefined = undefined;
54
64
 
55
65
  public get effectiveResponse(): THarnessPermissionResponse | undefined {
56
- return this.request.response ?? this.answered;
66
+ return this.request.response ?? this.answered ?? this.viewState?.answered;
67
+ }
68
+
69
+ private get effectiveRemember(): boolean {
70
+ return this.remember || this.viewState?.remember === true;
57
71
  }
58
72
 
59
73
  public static styles = [
@@ -82,6 +96,10 @@ export class DeesHarnessPermissionCard extends DeesElement {
82
96
  }
83
97
  }
84
98
 
99
+ :host(.no-entry) {
100
+ animation: none;
101
+ }
102
+
85
103
  @media (prefers-reduced-motion: reduce) {
86
104
  :host {
87
105
  animation: none;
@@ -183,7 +201,7 @@ export class DeesHarnessPermissionCard extends DeesElement {
183
201
  : ''}
184
202
  <div class="actions">
185
203
  ${this.effectiveResponse
186
- ? html`<span class="answeredNote">Answered: ${this.effectiveResponse}${this.remember ? ' (remembered)' : ''}</span>`
204
+ ? html`<span class="answeredNote">Answered: ${this.effectiveResponse}${this.effectiveRemember ? ' (remembered)' : ''}</span>`
187
205
  : html`
188
206
  <dees-button type="accent" size="sm" ?disabled=${disabled} @clicked=${() => this.respond('once')}>
189
207
  Allow Once
@@ -197,11 +215,12 @@ export class DeesHarnessPermissionCard extends DeesElement {
197
215
  <label class="rememberLabel">
198
216
  <input
199
217
  type="checkbox"
200
- .checked=${this.remember}
218
+ .checked=${this.effectiveRemember}
201
219
  ?disabled=${disabled}
202
- @change=${(event: Event) => {
203
- this.remember = (event.currentTarget as HTMLInputElement).checked;
204
- }}
220
+ @change=${(event: Event) => {
221
+ this.remember = (event.currentTarget as HTMLInputElement).checked;
222
+ if (this.viewState) this.viewState.remember = this.remember;
223
+ }}
205
224
  />
206
225
  remember for this session
207
226
  </label>
@@ -213,13 +232,18 @@ export class DeesHarnessPermissionCard extends DeesElement {
213
232
 
214
233
  private respond(response: THarnessPermissionResponse): void {
215
234
  if (this.busy || this.effectiveResponse !== undefined) return;
235
+ const remember = this.effectiveRemember || response === 'always';
216
236
  this.answered = response;
237
+ if (this.viewState) {
238
+ this.viewState.answered = response;
239
+ this.viewState.remember = remember;
240
+ }
217
241
  this.dispatchEvent(
218
242
  new CustomEvent('harness-permission-response', {
219
243
  detail: {
220
244
  requestId: this.request.id,
221
245
  response,
222
- remember: this.remember || response === 'always',
246
+ remember,
223
247
  request: this.request,
224
248
  },
225
249
  bubbles: true,
@@ -227,4 +251,9 @@ export class DeesHarnessPermissionCard extends DeesElement {
227
251
  }),
228
252
  );
229
253
  }
254
+
255
+ public firstUpdated(changedPropertiesArg: Map<string, unknown>): void {
256
+ super.firstUpdated(changedPropertiesArg);
257
+ if (this.suppressEntry) this.classList.add('no-entry');
258
+ }
230
259
  }
@@ -10,7 +10,7 @@ import {
10
10
  cssManager,
11
11
  } from '@design.estate/dees-element';
12
12
  import { themeDefaultStyles } from '../../00theme.js';
13
- import type { IHarnessQuestionRequest } from '../interfaces.js';
13
+ import type { IHarnessQuestionRequest, IHarnessQuestionViewState } from '../interfaces.js';
14
14
  import '../../00group-button/dees-button/dees-button.js';
15
15
  import '../../00group-utility/dees-icon/dees-icon.js';
16
16
 
@@ -38,6 +38,12 @@ export class DeesHarnessQuestionCard extends DeesElement {
38
38
  @property({ type: Boolean })
39
39
  accessor busy: boolean = false;
40
40
 
41
+ @property({ attribute: false })
42
+ accessor viewState: IHarnessQuestionViewState | undefined = undefined;
43
+
44
+ @property({ type: Boolean })
45
+ accessor suppressEntry: boolean = false;
46
+
41
47
  /** local optimistic answer; request.response (host echo) takes precedence */
42
48
  @state()
43
49
  accessor answered: string[] | undefined = undefined;
@@ -49,7 +55,17 @@ export class DeesHarnessQuestionCard extends DeesElement {
49
55
  accessor customText: string = '';
50
56
 
51
57
  public get effectiveResponse(): string[] | undefined {
52
- return this.request.response ?? this.answered;
58
+ return this.request.response ?? this.answered ?? this.viewState?.answered;
59
+ }
60
+
61
+ private get effectiveSelected(): ReadonlySet<string> {
62
+ return this.selected.size > 0
63
+ ? this.selected
64
+ : new Set(this.viewState?.selected ?? []);
65
+ }
66
+
67
+ private get effectiveCustomText(): string {
68
+ return this.customText || this.viewState?.customText || '';
53
69
  }
54
70
 
55
71
  public static styles = [
@@ -72,6 +88,10 @@ export class DeesHarnessQuestionCard extends DeesElement {
72
88
  }
73
89
  }
74
90
 
91
+ :host(.no-entry) {
92
+ animation: none;
93
+ }
94
+
75
95
  @media (prefers-reduced-motion: reduce) {
76
96
  :host {
77
97
  animation: none;
@@ -230,7 +250,7 @@ export class DeesHarnessQuestionCard extends DeesElement {
230
250
  <div class="options" role=${request.multiSelect ? 'group' : 'radiogroup'}>
231
251
  ${request.options.map((option) => html`
232
252
  <button
233
- class="option ${this.selected.has(option.label) ? 'selected' : ''}"
253
+ class="option ${this.effectiveSelected.has(option.label) ? 'selected' : ''}"
234
254
  type="button"
235
255
  ?disabled=${disabled}
236
256
  @click=${() => this.handleOptionClick(option.label)}
@@ -249,23 +269,24 @@ export class DeesHarnessQuestionCard extends DeesElement {
249
269
  <div class="customRow">
250
270
  <input
251
271
  placeholder="Answer in your own words…"
252
- .value=${this.customText}
272
+ .value=${this.effectiveCustomText}
253
273
  ?disabled=${disabled}
254
274
  @input=${(event: Event) => {
255
275
  this.customText = (event.currentTarget as HTMLInputElement).value;
276
+ if (this.viewState) this.viewState.customText = this.customText;
256
277
  }}
257
278
  @keydown=${(event: KeyboardEvent) => {
258
- if (event.key === 'Enter' && this.customText.trim()) {
259
- this.respond([...this.selected, this.customText.trim()]);
279
+ if (event.key === 'Enter' && this.effectiveCustomText.trim()) {
280
+ this.respond([...this.effectiveSelected, this.effectiveCustomText.trim()]);
260
281
  }
261
282
  }}
262
283
  />
263
284
  <dees-button
264
285
  size="sm"
265
- ?disabled=${disabled || (!this.customText.trim() && !this.selected.size)}
286
+ ?disabled=${disabled || (!this.effectiveCustomText.trim() && !this.effectiveSelected.size)}
266
287
  @clicked=${() => {
267
- const answers = [...this.selected];
268
- if (this.customText.trim()) answers.push(this.customText.trim());
288
+ const answers = [...this.effectiveSelected];
289
+ if (this.effectiveCustomText.trim()) answers.push(this.effectiveCustomText.trim());
269
290
  this.respond(answers);
270
291
  }}
271
292
  >Send Answer</dees-button>
@@ -278,8 +299,8 @@ export class DeesHarnessQuestionCard extends DeesElement {
278
299
  <dees-button
279
300
  type="accent"
280
301
  size="sm"
281
- ?disabled=${disabled || !this.selected.size}
282
- @clicked=${() => this.respond([...this.selected])}
302
+ ?disabled=${disabled || !this.effectiveSelected.size}
303
+ @clicked=${() => this.respond([...this.effectiveSelected])}
283
304
  >Confirm</dees-button>
284
305
  </div>
285
306
  `
@@ -292,10 +313,11 @@ export class DeesHarnessQuestionCard extends DeesElement {
292
313
  private handleOptionClick(label: string): void {
293
314
  if (this.busy || this.effectiveResponse !== undefined) return;
294
315
  if (this.request.multiSelect) {
295
- const next = new Set(this.selected);
316
+ const next = new Set(this.effectiveSelected);
296
317
  if (next.has(label)) next.delete(label);
297
318
  else next.add(label);
298
319
  this.selected = next;
320
+ if (this.viewState) this.viewState.selected = [...next];
299
321
  return;
300
322
  }
301
323
  this.respond([label]);
@@ -304,6 +326,7 @@ export class DeesHarnessQuestionCard extends DeesElement {
304
326
  private respond(answers: string[]): void {
305
327
  if (this.busy || this.effectiveResponse !== undefined || !answers.length) return;
306
328
  this.answered = answers;
329
+ if (this.viewState) this.viewState.answered = answers;
307
330
  this.dispatchEvent(
308
331
  new CustomEvent('harness-question-response', {
309
332
  detail: {
@@ -316,4 +339,9 @@ export class DeesHarnessQuestionCard extends DeesElement {
316
339
  }),
317
340
  );
318
341
  }
342
+
343
+ public firstUpdated(changedPropertiesArg: Map<string, unknown>): void {
344
+ super.firstUpdated(changedPropertiesArg);
345
+ if (this.suppressEntry) this.classList.add('no-entry');
346
+ }
319
347
  }
@@ -10,7 +10,7 @@ import {
10
10
  cssManager,
11
11
  } from '@design.estate/dees-element';
12
12
  import { themeDefaultStyles } from '../../00theme.js';
13
- import type { IHarnessReasoningPart } from '../interfaces.js';
13
+ import type { IHarnessMessageViewState, IHarnessReasoningPart } from '../interfaces.js';
14
14
  import { harnessFormatDuration } from '../harness.helpers.js';
15
15
  import '../../00group-utility/dees-icon/dees-icon.js';
16
16
 
@@ -37,6 +37,9 @@ export class DeesHarnessReasoning extends DeesElement {
37
37
  @property({ attribute: false })
38
38
  accessor reasoningParts: IHarnessReasoningPart[] = [];
39
39
 
40
+ @property({ attribute: false })
41
+ accessor viewState: IHarnessMessageViewState | undefined = undefined;
42
+
40
43
  @state()
41
44
  accessor expanded: boolean | undefined = undefined;
42
45
 
@@ -178,7 +181,7 @@ export class DeesHarnessReasoning extends DeesElement {
178
181
  }
179
182
 
180
183
  public get isOpen(): boolean {
181
- return this.expanded ?? this.isStreaming;
184
+ return this.expanded ?? this.viewState?.reasoningExpanded ?? this.isStreaming;
182
185
  }
183
186
 
184
187
  /** All parts joined into the one full trace the expanded body shows. */
@@ -224,6 +227,7 @@ export class DeesHarnessReasoning extends DeesElement {
224
227
  aria-expanded=${this.isOpen ? 'true' : 'false'}
225
228
  @click=${() => {
226
229
  this.expanded = !this.isOpen;
230
+ if (this.viewState) this.viewState.reasoningExpanded = this.expanded;
227
231
  }}
228
232
  >
229
233
  <span class="chevron ${this.isOpen ? 'open' : ''}">
@@ -367,6 +367,8 @@ export class DeesHarnessSessionList extends DeesElement {
367
367
  }
368
368
 
369
369
  .itemInner {
370
+ position: relative;
371
+ isolation: isolate;
370
372
  min-width: 0;
371
373
  overflow: hidden;
372
374
  border: 1px solid var(--card-inner-border);
@@ -399,24 +401,34 @@ export class DeesHarnessSessionList extends DeesElement {
399
401
 
400
402
  @keyframes workingBackgroundScan {
401
403
  from {
402
- background-position: 100% 0;
404
+ transform: translate3d(-60%, 0, 0);
403
405
  }
404
406
  to {
405
- background-position: 0 0;
407
+ transform: translate3d(0, 0, 0);
406
408
  }
407
409
  }
408
410
 
409
411
  .item.state-working .itemInner {
410
- background:
411
- linear-gradient(
412
- 105deg,
413
- transparent 40%,
414
- color-mix(in srgb, var(--session-working) 6%, transparent) 45%,
415
- color-mix(in srgb, var(--session-working) 20%, transparent) 50%,
416
- color-mix(in srgb, var(--session-working) 6%, transparent) 55%,
417
- transparent 60%
418
- ) 100% 0 / 250% 100% no-repeat,
419
- var(--dees-color-bg-primary);
412
+ background: var(--dees-color-bg-primary);
413
+ }
414
+
415
+ .item.state-working .itemInner::before {
416
+ content: '';
417
+ position: absolute;
418
+ z-index: -1;
419
+ inset: 0 auto 0 0;
420
+ width: 250%;
421
+ pointer-events: none;
422
+ background: linear-gradient(
423
+ 105deg,
424
+ transparent 40%,
425
+ color-mix(in srgb, var(--session-working) 6%, transparent) 45%,
426
+ color-mix(in srgb, var(--session-working) 20%, transparent) 50%,
427
+ color-mix(in srgb, var(--session-working) 6%, transparent) 55%,
428
+ transparent 60%
429
+ );
430
+ transform: translate3d(-60%, 0, 0);
431
+ will-change: transform;
420
432
  animation: workingBackgroundScan 2s linear infinite;
421
433
  }
422
434
 
@@ -923,12 +935,18 @@ export class DeesHarnessSessionList extends DeesElement {
923
935
  .item,
924
936
  .itemInner,
925
937
  .item.state-attention .itemInner,
938
+ .item.state-working .itemInner::before,
926
939
  .item.state-working .itemHeader .stateDot,
927
940
  .scrollShadow,
928
941
  .expandToggle dees-icon {
929
942
  animation: none;
930
943
  transition: none;
931
944
  }
945
+
946
+ .item.state-working .itemInner::before {
947
+ transform: translate3d(-60%, 0, 0);
948
+ will-change: auto;
949
+ }
932
950
  }
933
951
 
934
952
  @media (pointer: coarse) {
@@ -694,8 +694,14 @@ export class DeesHarnessSessionSidebar extends DeesElement {
694
694
  <span class="sectionHeadingText">${labelArg}</span>
695
695
  ${summaryArg ? html`<span class="sectionSummary">${summaryArg}</span>` : ''}
696
696
  </button>
697
- <div id=${contentId} class="sectionContent" ?hidden=${!expanded}>
698
- ${contentArg}
697
+ <div
698
+ id=${contentId}
699
+ class="sectionContent"
700
+ role="region"
701
+ aria-hidden=${String(!expanded)}
702
+ ?hidden=${!expanded}
703
+ >
704
+ ${expanded ? contentArg : ''}
699
705
  </div>
700
706
  </section>
701
707
  `;
@@ -11,6 +11,7 @@ import {
11
11
  } from '@design.estate/dees-element';
12
12
  import { themeDefaultStyles } from '../../00theme.js';
13
13
  import type {
14
+ IHarnessMessageViewState,
14
15
  IHarnessToolCall,
15
16
  THarnessContentBlock,
16
17
  THarnessMessageStreamDelta,
@@ -69,6 +70,9 @@ export class DeesHarnessToolCard extends DeesElement {
69
70
  @property({ type: Boolean })
70
71
  accessor allowSubtaskStreams: boolean = true;
71
72
 
73
+ @property({ attribute: false })
74
+ accessor viewState: IHarnessMessageViewState | undefined = undefined;
75
+
72
76
  @state()
73
77
  accessor expanded: boolean | undefined = undefined;
74
78
 
@@ -102,9 +106,12 @@ export class DeesHarnessToolCard extends DeesElement {
102
106
  private observedBodyInner: Element | null = null;
103
107
  private fullscreenPortal: HTMLElementTagNameMap['dees-harness-tool-fullscreen'] | null = null;
104
108
  private disclosureFrame: number | null = null;
109
+ private returnScrollFrame: number | null = null;
105
110
  private subtaskResultElement: HTMLElement | null = null;
106
111
  private subtaskResultResizeObserver: ResizeObserver | null = null;
107
112
  private terminalTailFollowing = true;
113
+ private terminalScrollHydrated = false;
114
+ private terminalScrollHydrationFrame: number | null = null;
108
115
  private terminalTailReturnTimeout: ReturnType<typeof setTimeout> | null = null;
109
116
  private terminalTailReturnDelayMs = 5000;
110
117
 
@@ -208,18 +215,18 @@ export class DeesHarnessToolCard extends DeesElement {
208
215
 
209
216
  @keyframes harness-card-open-chat-scan {
210
217
  0% {
211
- background-position: 100% 0;
218
+ transform: translate3d(-60%, 0, 0);
212
219
  opacity: 0;
213
220
  }
214
221
  1% {
215
222
  opacity: 0.35;
216
223
  }
217
224
  20% {
218
- background-position: 0 0;
225
+ transform: translate3d(0, 0, 0);
219
226
  opacity: 0.35;
220
227
  }
221
228
  24%, 100% {
222
- background-position: 0 0;
229
+ transform: translate3d(0, 0, 0);
223
230
  opacity: 0;
224
231
  }
225
232
  }
@@ -608,7 +615,8 @@ export class DeesHarnessToolCard extends DeesElement {
608
615
  .harness-card.outcomeScan::after {
609
616
  content: '';
610
617
  position: absolute;
611
- inset: 0;
618
+ inset: 0 auto 0 0;
619
+ width: 250%;
612
620
  pointer-events: none;
613
621
  background: linear-gradient(
614
622
  105deg,
@@ -617,7 +625,9 @@ export class DeesHarnessToolCard extends DeesElement {
617
625
  color-mix(in srgb, var(--harness-outcome-scan-color) 14%, transparent) 50%,
618
626
  color-mix(in srgb, var(--harness-outcome-scan-color) 3%, transparent) 55%,
619
627
  transparent 60%
620
- ) 100% 0 / 250% 100% no-repeat;
628
+ );
629
+ transform: translate3d(-60%, 0, 0);
630
+ will-change: transform;
621
631
  animation: harness-tool-outcome-scan 0.48s linear 0.08s both;
622
632
  }
623
633
 
@@ -631,8 +641,8 @@ export class DeesHarnessToolCard extends DeesElement {
631
641
  }
632
642
 
633
643
  @keyframes harness-tool-outcome-scan {
634
- from { background-position: 100% 0; }
635
- to { background-position: 0 0; }
644
+ from { transform: translate3d(-60%, 0, 0); }
645
+ to { transform: translate3d(0, 0, 0); }
636
646
  }
637
647
 
638
648
  @media (prefers-reduced-motion: reduce) {
@@ -647,6 +657,7 @@ export class DeesHarnessToolCard extends DeesElement {
647
657
  .harness-card.outcomeScan::after {
648
658
  animation: none;
649
659
  opacity: 0;
660
+ will-change: auto;
650
661
  }
651
662
  }
652
663
  `,
@@ -665,6 +676,7 @@ export class DeesHarnessToolCard extends DeesElement {
665
676
 
666
677
  public get isOpen(): boolean {
667
678
  if (this.expanded !== undefined) return this.expanded;
679
+ if (this.viewState?.toolExpanded !== undefined) return this.viewState.toolExpanded;
668
680
  const descriptor = this.descriptor;
669
681
  if (descriptor.kind === 'subtask' && this.call.subtask) {
670
682
  return this.allowSubtaskStreams
@@ -774,12 +786,14 @@ export class DeesHarnessToolCard extends DeesElement {
774
786
  if (this.disclosureFrame !== null) cancelAnimationFrame(this.disclosureFrame);
775
787
  this.disclosureFrame = null;
776
788
  this.expanded = false;
789
+ if (this.viewState) this.viewState.toolExpanded = false;
777
790
  return;
778
791
  }
779
792
  if (this.bodyMounted) {
780
793
  if (this.disclosureFrame !== null) cancelAnimationFrame(this.disclosureFrame);
781
794
  this.disclosureFrame = null;
782
795
  this.expanded = true;
796
+ if (this.viewState) this.viewState.toolExpanded = true;
783
797
  return;
784
798
  }
785
799
  this.bodyMounted = true;
@@ -791,12 +805,18 @@ export class DeesHarnessToolCard extends DeesElement {
791
805
  if (!this.isConnected || this.expanded !== false) return;
792
806
  this.shadowRoot?.querySelector<HTMLElement>('.bodyDisclosure')?.getBoundingClientRect();
793
807
  this.expanded = true;
808
+ if (this.viewState) this.viewState.toolExpanded = true;
794
809
  });
795
810
  });
796
811
  };
797
812
 
798
813
  private readonly openFullscreen = (): void => {
799
814
  if (this.fullscreenPortal || this.hasSubtaskPreview) return;
815
+ this.dispatchEvent(new CustomEvent('harness-card-retention', {
816
+ detail: { active: true },
817
+ bubbles: true,
818
+ composed: true,
819
+ }));
800
820
  // Appended to document.body: content-visibility containment in the
801
821
  // transcript would trap a position: fixed layer inside this card.
802
822
  const portal = document.createElement('dees-harness-tool-fullscreen');
@@ -826,7 +846,10 @@ export class DeesHarnessToolCard extends DeesElement {
826
846
  // reduced-motion skips the animation, so animationend never fires there
827
847
  if (this.flashFallbackTimeout) clearTimeout(this.flashFallbackTimeout);
828
848
  this.flashFallbackTimeout = setTimeout(() => this.endReturnFlash(), 900);
829
- requestAnimationFrame(() => {
849
+ if (this.returnScrollFrame !== null) cancelAnimationFrame(this.returnScrollFrame);
850
+ this.returnScrollFrame = requestAnimationFrame(() => {
851
+ this.returnScrollFrame = null;
852
+ if (!this.isConnected) return;
830
853
  this.scrollIntoView({ block: 'center', behavior: 'smooth' });
831
854
  });
832
855
  };
@@ -838,6 +861,11 @@ export class DeesHarnessToolCard extends DeesElement {
838
861
  }
839
862
  if (!this.returnFlash) return;
840
863
  this.returnFlash = false;
864
+ this.dispatchEvent(new CustomEvent('harness-card-retention', {
865
+ detail: { active: false },
866
+ bubbles: true,
867
+ composed: true,
868
+ }));
841
869
  this.dispatchEvent(new CustomEvent('harness-card-flash', {
842
870
  detail: { active: false },
843
871
  bubbles: true,
@@ -861,11 +889,44 @@ export class DeesHarnessToolCard extends DeesElement {
861
889
 
862
890
  public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
863
891
  super.willUpdate(changedProperties);
892
+ if (changedProperties.has('viewState') || changedProperties.has('call')) {
893
+ this.terminalScrollHydrated = false;
894
+ if (this.terminalScrollHydrationFrame !== null) {
895
+ cancelAnimationFrame(this.terminalScrollHydrationFrame);
896
+ this.terminalScrollHydrationFrame = null;
897
+ }
898
+ if (this.expanded === undefined && this.viewState?.toolExpanded !== undefined) {
899
+ this.expanded = this.viewState.toolExpanded;
900
+ }
901
+ if (!this.outputExpanded && this.viewState?.toolOutputExpanded === true) this.outputExpanded = true;
902
+ if (this.diffViewOverride === null && this.viewState?.toolDiffView !== undefined) {
903
+ this.diffViewOverride = this.viewState.toolDiffView;
904
+ }
905
+ if (this.viewState?.toolTerminalTailFollowing !== undefined) {
906
+ this.terminalTailFollowing = this.viewState.toolTerminalTailFollowing;
907
+ }
908
+ if (this.viewState) {
909
+ const subtask = this.call.subtask;
910
+ const ownsLiveTranscript = Boolean(
911
+ this.hasLiveSubtaskPreview && subtask?.previewState === 'ready',
912
+ );
913
+ if (!subtask || !ownsLiveTranscript) {
914
+ delete this.viewState.subtaskTranscript;
915
+ delete this.viewState.subtaskTranscriptSessionId;
916
+ } else if (
917
+ this.viewState.subtaskTranscriptSessionId !== subtask.sessionId
918
+ || !this.viewState.subtaskTranscript
919
+ ) {
920
+ this.viewState.subtaskTranscriptSessionId = subtask.sessionId;
921
+ this.viewState.subtaskTranscript = { messages: {}, permissions: {}, questions: {} };
922
+ }
923
+ }
924
+ }
864
925
  const previousCall = changedProperties.get('call') as IHarnessToolCall | undefined;
865
926
  if (!previousCall) return;
866
927
  if (previousCall.id !== this.call.id) {
867
- this.diffViewOverride = null;
868
- this.terminalTailFollowing = true;
928
+ this.diffViewOverride = this.viewState?.toolDiffView ?? null;
929
+ this.terminalTailFollowing = this.viewState?.toolTerminalTailFollowing ?? true;
869
930
  this.cancelTerminalTailReturn();
870
931
  return;
871
932
  }
@@ -887,6 +948,10 @@ export class DeesHarnessToolCard extends DeesElement {
887
948
  public updated(changedProperties: Map<PropertyKey, unknown>): void {
888
949
  super.updated(changedProperties);
889
950
  if (!this.isConnected) return;
951
+ this.terminalScrollHydrationFrame ??= requestAnimationFrame(() => {
952
+ this.terminalScrollHydrationFrame = null;
953
+ if (this.isConnected) this.terminalScrollHydrated = true;
954
+ });
890
955
  if (this.isOpen && !this.bodyMounted) this.bodyMounted = true;
891
956
  if (changedProperties.has('call') && this.fullscreenPortal) {
892
957
  this.fullscreenPortal.call = this.enrichedCall;
@@ -923,12 +988,21 @@ export class DeesHarnessToolCard extends DeesElement {
923
988
  });
924
989
  this.bodyResizeObserver.observe(bodyInner);
925
990
  if (this.terminalTailFollowing) this.scrollTerminalToTail();
991
+ else if (this.viewState?.toolTerminalScrollTop !== undefined) {
992
+ const frame = this.shadowRoot?.querySelector<HTMLElement>('.bodyClipFrame');
993
+ if (frame) this.setTerminalScrollTop(frame, this.viewState.toolTerminalScrollTop);
994
+ }
926
995
  }
927
996
 
928
997
  private readonly handleBodyScroll = (eventArg: Event): void => {
929
998
  if (this.descriptor.kind !== 'terminal' || this.uncapped) return;
999
+ if (!this.terminalScrollHydrated) return;
930
1000
  const frame = eventArg.currentTarget as HTMLElement;
931
1001
  this.terminalTailFollowing = frame.scrollHeight - frame.scrollTop - frame.clientHeight <= 2;
1002
+ if (this.viewState) {
1003
+ this.viewState.toolTerminalTailFollowing = this.terminalTailFollowing;
1004
+ this.viewState.toolTerminalScrollTop = frame.scrollTop;
1005
+ }
932
1006
  if (this.terminalTailFollowing) this.cancelTerminalTailReturn();
933
1007
  };
934
1008
 
@@ -936,7 +1010,16 @@ export class DeesHarnessToolCard extends DeesElement {
936
1010
  if (this.descriptor.kind !== 'terminal' || this.uncapped || !this.isOpen) return;
937
1011
  const frame = this.shadowRoot?.querySelector<HTMLElement>('.bodyClipFrame');
938
1012
  if (!frame) return;
939
- frame.scrollTop = frame.scrollHeight;
1013
+ if (this.viewState?.toolTerminalTailFollowing === false) {
1014
+ this.terminalTailFollowing = false;
1015
+ this.setTerminalScrollTop(frame, this.viewState.toolTerminalScrollTop ?? frame.scrollTop);
1016
+ return;
1017
+ }
1018
+ this.setTerminalScrollTop(frame, frame.scrollHeight);
1019
+ }
1020
+
1021
+ private setTerminalScrollTop(frameArg: HTMLElement, scrollTopArg: number): void {
1022
+ frameArg.scrollTop = scrollTopArg;
940
1023
  }
941
1024
 
942
1025
  private readonly scheduleTerminalTailReturn = (): void => {
@@ -945,7 +1028,12 @@ export class DeesHarnessToolCard extends DeesElement {
945
1028
  this.terminalTailReturnTimeout = setTimeout(() => {
946
1029
  this.terminalTailReturnTimeout = null;
947
1030
  this.terminalTailFollowing = true;
1031
+ if (this.viewState) this.viewState.toolTerminalTailFollowing = true;
948
1032
  this.scrollTerminalToTail();
1033
+ if (this.viewState) {
1034
+ const frame = this.shadowRoot?.querySelector<HTMLElement>('.bodyClipFrame');
1035
+ if (frame) this.viewState.toolTerminalScrollTop = frame.scrollTop;
1036
+ }
949
1037
  }, this.terminalTailReturnDelayMs);
950
1038
  };
951
1039
 
@@ -957,6 +1045,7 @@ export class DeesHarnessToolCard extends DeesElement {
957
1045
 
958
1046
  private readonly handleDiffViewChange = (eventArg: CustomEvent<IDiffViewChangeDetail>): void => {
959
1047
  this.diffViewOverride = eventArg.detail.diffView;
1048
+ if (this.viewState) this.viewState.toolDiffView = this.diffViewOverride;
960
1049
  };
961
1050
 
962
1051
  private readonly syncSubtaskResultScroll = (): void => {
@@ -998,6 +1087,15 @@ export class DeesHarnessToolCard extends DeesElement {
998
1087
  cancelAnimationFrame(this.disclosureFrame);
999
1088
  this.disclosureFrame = null;
1000
1089
  }
1090
+ if (this.returnScrollFrame !== null) {
1091
+ cancelAnimationFrame(this.returnScrollFrame);
1092
+ this.returnScrollFrame = null;
1093
+ }
1094
+ if (this.terminalScrollHydrationFrame !== null) {
1095
+ cancelAnimationFrame(this.terminalScrollHydrationFrame);
1096
+ this.terminalScrollHydrationFrame = null;
1097
+ }
1098
+ this.terminalScrollHydrated = false;
1001
1099
  if (this.flashFallbackTimeout) {
1002
1100
  clearTimeout(this.flashFallbackTimeout);
1003
1101
  this.flashFallbackTimeout = null;
@@ -1007,8 +1105,16 @@ export class DeesHarnessToolCard extends DeesElement {
1007
1105
  this.outcomeScanFallbackTimeout = null;
1008
1106
  }
1009
1107
  this.cancelTerminalTailReturn();
1108
+ if (this.fullscreenPortal || this.returnFlash) {
1109
+ this.dispatchEvent(new CustomEvent('harness-card-retention', {
1110
+ detail: { active: false },
1111
+ bubbles: true,
1112
+ composed: true,
1113
+ }));
1114
+ }
1010
1115
  this.fullscreenPortal?.remove();
1011
1116
  this.fullscreenPortal = null;
1117
+ this.returnFlash = false;
1012
1118
  this.bodyResizeObserver?.disconnect();
1013
1119
  this.bodyResizeObserver = null;
1014
1120
  this.observedBodyInner = null;
@@ -1064,6 +1170,7 @@ export class DeesHarnessToolCard extends DeesElement {
1064
1170
  .questions=${subtask.questions ?? []}
1065
1171
  .status=${subtask.status}
1066
1172
  .toolRegistry=${this.toolRegistry}
1173
+ .viewState=${this.viewState?.subtaskTranscript}
1067
1174
  .allowSubtaskStreams=${false}
1068
1175
  .autoFollow=${this.isOpen}
1069
1176
  .hideScrollbar=${true}
@@ -1160,6 +1267,7 @@ export class DeesHarnessToolCard extends DeesElement {
1160
1267
  type="button"
1161
1268
  @click=${() => {
1162
1269
  this.outputExpanded = true;
1270
+ if (this.viewState) this.viewState.toolOutputExpanded = true;
1163
1271
  }}
1164
1272
  >
1165
1273
  Show all