@design.estate/dees-catalog 6.1.0 → 6.3.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 (50) hide show
  1. package/dist_bundle/bundle.js +3161 -1498
  2. package/dist_ts_web/00_commitinfo_data.js +1 -1
  3. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +5 -1
  4. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +484 -8
  5. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +33 -2
  6. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +6 -4
  7. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +7 -1
  8. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +67 -6
  9. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.d.ts +3 -1
  10. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +24 -4
  11. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +26 -0
  12. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +398 -94
  13. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.d.ts +32 -0
  14. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.demo.d.ts +1 -0
  15. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.demo.js +31 -0
  16. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.js +297 -0
  17. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +1 -9
  18. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.d.ts +10 -1
  19. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +181 -2
  20. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +17 -1
  21. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +476 -36
  22. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/renderers.js +23 -20
  23. package/dist_ts_web/elements/00group-harness/harness.helpers.d.ts +5 -1
  24. package/dist_ts_web/elements/00group-harness/harness.helpers.js +66 -1
  25. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  26. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  27. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +39 -2
  28. package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +4 -0
  29. package/dist_ts_web/elements/00group-harness/toolregistry.js +8 -1
  30. package/package.json +2 -2
  31. package/readme.hints.md +16 -1
  32. package/readme.md +48 -11
  33. package/readme.playbook.md +8 -4
  34. package/ts_web/00_commitinfo_data.ts +1 -1
  35. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +490 -7
  36. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +22 -0
  37. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +6 -0
  38. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +64 -5
  39. package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +24 -3
  40. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +397 -84
  41. package/ts_web/elements/00group-harness/dees-harness-overflow-text.demo.ts +31 -0
  42. package/ts_web/elements/00group-harness/dees-harness-overflow-text.ts +236 -0
  43. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +0 -8
  44. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +193 -1
  45. package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +480 -34
  46. package/ts_web/elements/00group-harness/dees-harness-tool-card/renderers.ts +24 -21
  47. package/ts_web/elements/00group-harness/harness.helpers.ts +68 -1
  48. package/ts_web/elements/00group-harness/index.ts +1 -0
  49. package/ts_web/elements/00group-harness/interfaces.ts +46 -2
  50. package/ts_web/elements/00group-harness/toolregistry.ts +12 -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 { IHarnessToolCall, THarnessContentBlock } from '../interfaces.js';
13
+ import type {
14
+ IHarnessToolCall,
15
+ THarnessContentBlock,
16
+ THarnessMessageStreamDelta,
17
+ } from '../interfaces.js';
14
18
  import {
15
19
  DeesHarnessToolRegistry,
16
20
  parseMcpName,
@@ -19,8 +23,16 @@ import {
19
23
  } from '../toolregistry.js';
20
24
  import { builtinBodyRenderers } from './renderers.js';
21
25
  import { harnessCardStyles, harnessPillStyles, harnessMonoStyles } from '../harness.styles.js';
22
- import { harnessFormatClockTime, harnessFormatDuration, harnessTruncate } from '../harness.helpers.js';
26
+ import {
27
+ harnessApplyMessageDelta,
28
+ harnessEffectiveToolStatus,
29
+ harnessFormatClockTime,
30
+ harnessFormatDuration,
31
+ harnessTruncate,
32
+ } from '../harness.helpers.js';
33
+ import { DeesHarnessMessageList } from '../dees-harness-message-list/dees-harness-message-list.js';
23
34
  import '../dees-harness-content-blocks/dees-harness-content-blocks.js';
35
+ import '../dees-harness-overflow-text.js';
24
36
  import '../../00group-dataview/dees-dataview-codebox/dees-dataview-codebox.js';
25
37
  import './dees-harness-tool-fullscreen.js';
26
38
  import '../../00group-feedback/dees-badge/dees-badge.js';
@@ -49,12 +61,19 @@ export class DeesHarnessToolCard extends DeesElement {
49
61
  @property({ attribute: false })
50
62
  accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
51
63
 
64
+ /** Nested lists disable this to keep inline subtask streams one level deep. */
65
+ @property({ type: Boolean })
66
+ accessor allowSubtaskStreams: boolean = true;
67
+
52
68
  @state()
53
69
  accessor expanded: boolean | undefined = undefined;
54
70
 
55
71
  @state()
56
72
  accessor outputExpanded: boolean = false;
57
73
 
74
+ @state()
75
+ accessor bodyMounted: boolean = false;
76
+
58
77
  /** Body height cap; taller content scrolls in place and offers fullscreen. */
59
78
  @property({ type: Number })
60
79
  accessor bodyCapPx: number = 360;
@@ -75,6 +94,9 @@ export class DeesHarnessToolCard extends DeesElement {
75
94
  private bodyResizeObserver: ResizeObserver | null = null;
76
95
  private observedBodyInner: Element | null = null;
77
96
  private fullscreenPortal: HTMLElementTagNameMap['dees-harness-tool-fullscreen'] | null = null;
97
+ private disclosureFrame: number | null = null;
98
+ private subtaskResultElement: HTMLElement | null = null;
99
+ private subtaskResultResizeObserver: ResizeObserver | null = null;
78
100
 
79
101
  public static styles = [
80
102
  themeDefaultStyles,
@@ -88,6 +110,56 @@ export class DeesHarnessToolCard extends DeesElement {
88
110
  font-family: var(--dees-font-family);
89
111
  }
90
112
 
113
+ .harness-card.subtaskPreviewCard {
114
+ max-height: var(--dees-harness-active-subtask-height, 320px);
115
+ }
116
+
117
+ .harness-card.subtaskPreviewOpen {
118
+ display: flex;
119
+ flex-direction: column;
120
+ height: var(--dees-harness-active-subtask-height, 320px);
121
+ box-sizing: border-box;
122
+ overflow: hidden;
123
+ }
124
+
125
+ .harness-card.subtaskPreviewOpen .bodyDisclosure.open,
126
+ .harness-card.subtaskPreviewOpen .bodyDisclosureInner,
127
+ .harness-card.subtaskPreviewOpen .bodyClipFrame {
128
+ flex: 1;
129
+ min-height: 0;
130
+ }
131
+
132
+ .harness-card.subtaskPreviewOpen .bodyDisclosure.open {
133
+ display: flex;
134
+ flex-direction: column;
135
+ }
136
+
137
+ .harness-card.subtaskPreviewOpen .bodyDisclosureInner {
138
+ display: flex;
139
+ flex-direction: column;
140
+ overflow: hidden;
141
+ }
142
+
143
+ .harness-card.subtaskPreviewOpen .bodyClipFrame {
144
+ max-height: none !important;
145
+ overflow: hidden;
146
+ }
147
+
148
+ .harness-card.subtaskPreviewOpen .body,
149
+ .harness-card.subtaskPreviewOpen .subtaskPreview {
150
+ min-height: 0;
151
+ height: 100%;
152
+ }
153
+
154
+ .harness-card.subtaskPreviewOpen .body {
155
+ grid-template-rows: auto minmax(0, 1fr) auto;
156
+ box-sizing: border-box;
157
+ }
158
+
159
+ .harness-card.subtaskPreviewOpen .body.liveSubtaskBody {
160
+ grid-template-rows: minmax(0, 1fr) auto;
161
+ }
162
+
91
163
  .header {
92
164
  display: flex;
93
165
  align-items: center;
@@ -119,6 +191,37 @@ export class DeesHarnessToolCard extends DeesElement {
119
191
  color: var(--dees-color-accent-primary);
120
192
  }
121
193
 
194
+ .harness-card.subtaskPreviewOpen.status-running::before,
195
+ .harness-card.subtaskPreviewOpen.status-pending::before {
196
+ animation: harness-card-open-chat-scan 10s linear infinite;
197
+ }
198
+
199
+ @keyframes harness-card-open-chat-scan {
200
+ 0% {
201
+ background-position: 100% 0;
202
+ opacity: 0;
203
+ }
204
+ 1% {
205
+ opacity: 0.35;
206
+ }
207
+ 20% {
208
+ background-position: 0 0;
209
+ opacity: 0.35;
210
+ }
211
+ 24%, 100% {
212
+ background-position: 0 0;
213
+ opacity: 0;
214
+ }
215
+ }
216
+
217
+ @media (prefers-reduced-motion: reduce) {
218
+ .harness-card.subtaskPreviewOpen.status-running::before,
219
+ .harness-card.subtaskPreviewOpen.status-pending::before {
220
+ animation: none;
221
+ opacity: 0;
222
+ }
223
+ }
224
+
122
225
  .titleBlock {
123
226
  min-width: 0;
124
227
  flex: 1;
@@ -147,12 +250,10 @@ export class DeesHarnessToolCard extends DeesElement {
147
250
  }
148
251
 
149
252
  .subtitle {
253
+ display: block;
150
254
  margin-top: 1px;
151
255
  color: var(--dees-color-text-secondary);
152
256
  font-size: var(--dees-font-caption1-size, 11px);
153
- overflow: hidden;
154
- text-overflow: ellipsis;
155
- white-space: nowrap;
156
257
  }
157
258
 
158
259
  .metaColumn {
@@ -176,7 +277,7 @@ export class DeesHarnessToolCard extends DeesElement {
176
277
  inner overflow-x scrollers engage instead. */
177
278
  grid-template-columns: minmax(0, 1fr);
178
279
  gap: var(--dees-spacing-sm);
179
- padding: 0 var(--dees-spacing-md) var(--dees-spacing-md);
280
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md) var(--dees-spacing-md);
180
281
  }
181
282
 
182
283
  .kvGrid {
@@ -211,6 +312,30 @@ export class DeesHarnessToolCard extends DeesElement {
211
312
  /* Three body modes: collapsed (no body), normal (content fits the
212
313
  cap), capped (content taller than the cap scrolls inside the frame
213
314
  and offers the fullscreen modal). */
315
+ .bodyDisclosure {
316
+ display: grid;
317
+ grid-template-rows: 0fr;
318
+ min-width: 0;
319
+ opacity: 0;
320
+ transform: translateY(-3px);
321
+ transition:
322
+ grid-template-rows var(--dees-transition-default) var(--dees-ease-standard),
323
+ opacity var(--dees-transition-fast) var(--dees-ease-standard),
324
+ transform var(--dees-transition-default) var(--dees-ease-standard);
325
+ }
326
+
327
+ .bodyDisclosure.open {
328
+ grid-template-rows: 1fr;
329
+ opacity: 1;
330
+ transform: translateY(0);
331
+ }
332
+
333
+ .bodyDisclosureInner {
334
+ min-width: 0;
335
+ min-height: 0;
336
+ overflow: hidden;
337
+ }
338
+
214
339
  .bodyClipFrame {
215
340
  position: relative;
216
341
  overflow-y: auto;
@@ -219,6 +344,129 @@ export class DeesHarnessToolCard extends DeesElement {
219
344
  scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
220
345
  }
221
346
 
347
+ .subtaskPreview {
348
+ display: flex;
349
+ flex-direction: column;
350
+ min-width: 0;
351
+ block-size: 260px;
352
+ margin-inline: calc(0px - var(--dees-spacing-md));
353
+ overflow: hidden;
354
+ }
355
+
356
+ .subtaskPreview dees-harness-message-list {
357
+ flex: 1;
358
+ min-height: 0;
359
+ }
360
+
361
+ .subtaskPreviewState {
362
+ flex: 1;
363
+ display: grid;
364
+ place-content: center;
365
+ gap: var(--dees-spacing-xs);
366
+ min-height: 0;
367
+ padding: var(--dees-spacing-md);
368
+ color: var(--dees-color-text-secondary);
369
+ font-size: var(--dees-font-control-size-sm, 12px);
370
+ text-align: center;
371
+ }
372
+
373
+ .subtaskNotice {
374
+ padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
375
+ border-bottom: 1px solid var(--dees-color-border-subtle);
376
+ color: var(--dees-color-text-secondary);
377
+ font-size: var(--dees-font-caption1-size, 11px);
378
+ }
379
+
380
+ .harness-subtask-detail {
381
+ display: grid;
382
+ align-content: start;
383
+ align-self: start;
384
+ gap: 3px;
385
+ min-width: 0;
386
+ }
387
+
388
+ .body:not(.liveSubtaskBody) > .harness-subtask-detail-brief {
389
+ grid-row: 1;
390
+ max-height: 35%;
391
+ }
392
+
393
+ .body:not(.liveSubtaskBody) > .harness-subtask-detail-brief,
394
+ .body:not(.liveSubtaskBody) > .harness-subtask-detail-result {
395
+ grid-template-rows: auto minmax(0, 1fr);
396
+ min-height: 0;
397
+ overflow: hidden;
398
+ }
399
+
400
+ .body:not(.liveSubtaskBody) > .harness-subtask-detail-result {
401
+ grid-row: 2;
402
+ align-self: stretch;
403
+ }
404
+
405
+ .body:not(.liveSubtaskBody) > .harness-subtask-open {
406
+ grid-row: 3;
407
+ align-self: end;
408
+ }
409
+
410
+ .harness-subtask-detail + .harness-subtask-detail {
411
+ padding-top: var(--dees-spacing-sm);
412
+ border-top: 1px solid var(--dees-color-border-subtle);
413
+ }
414
+
415
+ .harness-subtask-detail-label {
416
+ color: var(--dees-color-text-muted);
417
+ font-size: var(--dees-font-caption2-size, 10px);
418
+ font-weight: 700;
419
+ letter-spacing: 0.04em;
420
+ text-transform: uppercase;
421
+ }
422
+
423
+ .harness-subtask-detail-text {
424
+ color: var(--dees-color-text-primary);
425
+ font-size: var(--dees-font-control-size-sm, 12px);
426
+ line-height: 1.5;
427
+ overflow-wrap: anywhere;
428
+ white-space: pre-wrap;
429
+ }
430
+
431
+ .harness-subtask-detail-brief .harness-subtask-detail-text,
432
+ .harness-subtask-detail-result .harness-subtask-detail-text {
433
+ min-height: 0;
434
+ overflow-y: auto;
435
+ scrollbar-width: none;
436
+ }
437
+
438
+ .harness-subtask-detail-brief .harness-subtask-detail-text::-webkit-scrollbar,
439
+ .harness-subtask-detail-result .harness-subtask-detail-text::-webkit-scrollbar {
440
+ display: none;
441
+ }
442
+
443
+ .harness-subtask-detail-text.fadeTop {
444
+ -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 40px, black 100%);
445
+ mask-image: linear-gradient(to bottom, transparent 0, black 40px, black 100%);
446
+ }
447
+
448
+ .harness-subtask-detail-text.fadeBottom {
449
+ -webkit-mask-image: linear-gradient(to bottom, black 0, black calc(100% - 40px), transparent 100%);
450
+ mask-image: linear-gradient(to bottom, black 0, black calc(100% - 40px), transparent 100%);
451
+ }
452
+
453
+ .harness-subtask-detail-text.fadeTop.fadeBottom {
454
+ -webkit-mask-image: linear-gradient(
455
+ to bottom,
456
+ transparent 0,
457
+ black 40px,
458
+ black calc(100% - 40px),
459
+ transparent 100%
460
+ );
461
+ mask-image: linear-gradient(
462
+ to bottom,
463
+ transparent 0,
464
+ black 40px,
465
+ black calc(100% - 40px),
466
+ transparent 100%
467
+ );
468
+ }
469
+
222
470
  .maximizeBar {
223
471
  display: flex;
224
472
  align-items: center;
@@ -378,6 +626,10 @@ export class DeesHarnessToolCard extends DeesElement {
378
626
  }
379
627
 
380
628
  @media (prefers-reduced-motion: reduce) {
629
+ .bodyDisclosure {
630
+ transition: none;
631
+ }
632
+
381
633
  .harness-card.returnFlash {
382
634
  animation: none;
383
635
  }
@@ -404,20 +656,41 @@ export class DeesHarnessToolCard extends DeesElement {
404
656
  public get isOpen(): boolean {
405
657
  if (this.expanded !== undefined) return this.expanded;
406
658
  const descriptor = this.descriptor;
659
+ if (descriptor.kind === 'subtask' && this.call.subtask) {
660
+ return this.allowSubtaskStreams
661
+ && (this.call.subtask.status.type === 'busy' || this.call.subtask.status.type === 'error');
662
+ }
407
663
  if (this.call.status === 'error' || this.call.isError) return true;
408
664
  if (typeof descriptor.defaultExpanded === 'function') return descriptor.defaultExpanded(this.enrichedCall);
409
665
  return descriptor.defaultExpanded ?? false;
410
666
  }
411
667
 
668
+ private get hasSubtaskPreview(): boolean {
669
+ return this.allowSubtaskStreams
670
+ && this.descriptor.kind === 'subtask'
671
+ && this.call.subtask !== undefined;
672
+ }
673
+
674
+ private get hasLiveSubtaskPreview(): boolean {
675
+ return this.hasSubtaskPreview && this.call.subtask?.status.type === 'busy';
676
+ }
677
+
678
+ private get activeSubtaskPreview(): boolean {
679
+ return this.hasSubtaskPreview && this.call.subtask?.status.type === 'busy';
680
+ }
681
+
412
682
  public render(): TemplateResult {
413
683
  const call = this.enrichedCall;
414
684
  const descriptor = this.descriptor;
685
+ const open = this.isOpen;
686
+ const renderBody = this.bodyMounted || open;
415
687
  const label = typeof descriptor.label === 'function' ? descriptor.label(call) : descriptor.label;
416
688
  const subtitle = descriptor.subtitle?.(call) ?? '';
417
- const statusTone = call.isError ? 'error' : call.status === 'completed' ? 'success' : call.status;
689
+ const effectiveStatus = harnessEffectiveToolStatus(call);
690
+ const statusTone = effectiveStatus === 'completed' ? 'success' : effectiveStatus;
418
691
  const duration = harnessFormatDuration(call.startedAt, call.finishedAt);
419
692
  return html`
420
- <div class="harness-card status-${call.isError ? 'error' : call.status} ${descriptor.destructive ? 'destructive' : ''} ${this.returnFlash ? 'returnFlash' : ''} ${this.outcomeScanTone ? `outcomeScan outcomeScan-${this.outcomeScanTone}` : ''}"
693
+ <div class="harness-card status-${effectiveStatus} ${descriptor.destructive ? 'destructive' : ''} ${this.hasSubtaskPreview ? 'subtaskPreviewCard' : ''} ${this.hasSubtaskPreview && open ? 'subtaskPreviewOpen' : ''} ${this.returnFlash ? 'returnFlash' : ''} ${this.outcomeScanTone ? `outcomeScan outcomeScan-${this.outcomeScanTone}` : ''}"
421
694
  @animationend=${(event: AnimationEvent) => {
422
695
  if (event.animationName === 'harness-card-return-flash') {
423
696
  this.endReturnFlash();
@@ -429,10 +702,8 @@ export class DeesHarnessToolCard extends DeesElement {
429
702
  <button
430
703
  class="header"
431
704
  type="button"
432
- aria-expanded=${this.isOpen ? 'true' : 'false'}
433
- @click=${() => {
434
- this.expanded = !this.isOpen;
435
- }}
705
+ aria-expanded=${open ? 'true' : 'false'}
706
+ @click=${this.toggleDisclosure}
436
707
  >
437
708
  <span class="toolIcon"><dees-icon .icon=${descriptor.icon} iconSize="15"></dees-icon></span>
438
709
  <span class="titleBlock">
@@ -440,7 +711,9 @@ export class DeesHarnessToolCard extends DeesElement {
440
711
  <span class="toolLabel">${label}</span>
441
712
  ${this.renderMcpBadges(call)}
442
713
  </span>
443
- ${subtitle ? html`<span class="subtitle" title=${subtitle}>${subtitle}</span>` : ''}
714
+ ${subtitle
715
+ ? html`<dees-harness-overflow-text class="subtitle" title=${subtitle}>${subtitle}</dees-harness-overflow-text>`
716
+ : ''}
444
717
  </span>
445
718
  <span class="metaColumn">
446
719
  ${call.startedAt
@@ -448,34 +721,69 @@ export class DeesHarnessToolCard extends DeesElement {
448
721
  : ''}
449
722
  ${duration ? html`<span class="duration">${duration}</span>` : ''}
450
723
  <span class="harness-pill tone-${statusTone}">
451
- ${call.isError ? 'error' : call.status}
724
+ ${effectiveStatus}
452
725
  </span>
453
726
  </span>
454
727
  </button>
455
- ${this.isOpen
728
+ ${renderBody
456
729
  ? html`
457
730
  <div
458
- class="bodyClipFrame"
459
- style=${this.uncapped ? '' : `max-height: ${this.bodyCapPx}px;`}
731
+ class=${`bodyDisclosure ${open ? 'open' : ''}`}
732
+ aria-hidden=${String(!open)}
460
733
  >
461
- <div class="body bodyInner">${this.renderBody(call)}</div>
734
+ <div class="bodyDisclosureInner" ?inert=${!open}>
735
+ <div
736
+ class="bodyClipFrame"
737
+ style=${this.uncapped || this.activeSubtaskPreview ? '' : `max-height: ${this.bodyCapPx}px;`}
738
+ >
739
+ <div class=${`body bodyInner ${this.hasLiveSubtaskPreview ? 'liveSubtaskBody' : ''}`}>
740
+ ${this.renderBody(call)}
741
+ </div>
742
+ </div>
743
+ ${this.bodyOverflowing && !this.uncapped && !this.hasSubtaskPreview
744
+ ? html`
745
+ <button class="maximizeBar" type="button" @click=${this.openFullscreen}>
746
+ <dees-icon icon="lucide:Maximize2" iconSize="12"></dees-icon>
747
+ Show everything
748
+ </button>
749
+ `
750
+ : ''}
751
+ </div>
462
752
  </div>
463
- ${this.bodyOverflowing && !this.uncapped
464
- ? html`
465
- <button class="maximizeBar" type="button" @click=${this.openFullscreen}>
466
- <dees-icon icon="lucide:Maximize2" iconSize="12"></dees-icon>
467
- Show everything
468
- </button>
469
- `
470
- : ''}
471
753
  `
472
754
  : ''}
473
755
  </div>
474
756
  `;
475
757
  }
476
758
 
759
+ private readonly toggleDisclosure = (): void => {
760
+ if (this.isOpen) {
761
+ if (this.disclosureFrame !== null) cancelAnimationFrame(this.disclosureFrame);
762
+ this.disclosureFrame = null;
763
+ this.expanded = false;
764
+ return;
765
+ }
766
+ if (this.bodyMounted) {
767
+ if (this.disclosureFrame !== null) cancelAnimationFrame(this.disclosureFrame);
768
+ this.disclosureFrame = null;
769
+ this.expanded = true;
770
+ return;
771
+ }
772
+ this.bodyMounted = true;
773
+ this.expanded = false;
774
+ void this.updateComplete.then(() => {
775
+ if (!this.isConnected || this.expanded !== false) return;
776
+ this.disclosureFrame = requestAnimationFrame(() => {
777
+ this.disclosureFrame = null;
778
+ if (!this.isConnected || this.expanded !== false) return;
779
+ this.shadowRoot?.querySelector<HTMLElement>('.bodyDisclosure')?.getBoundingClientRect();
780
+ this.expanded = true;
781
+ });
782
+ });
783
+ };
784
+
477
785
  private readonly openFullscreen = (): void => {
478
- if (this.fullscreenPortal) return;
786
+ if (this.fullscreenPortal || this.hasSubtaskPreview) return;
479
787
  // Appended to document.body: content-visibility containment in the
480
788
  // transcript would trap a position: fixed layer inside this card.
481
789
  const portal = document.createElement('dees-harness-tool-fullscreen');
@@ -542,10 +850,12 @@ export class DeesHarnessToolCard extends DeesElement {
542
850
  super.willUpdate(changedProperties);
543
851
  const previousCall = changedProperties.get('call') as IHarnessToolCall | undefined;
544
852
  if (!previousCall || previousCall.id !== this.call.id) return;
545
- const wasActive = previousCall.status === 'pending' || previousCall.status === 'running';
546
- const outcomeTone = this.call.status === 'error' || this.call.isError
853
+ const previousStatus = harnessEffectiveToolStatus(previousCall);
854
+ const currentStatus = harnessEffectiveToolStatus(this.call);
855
+ const wasActive = previousStatus === 'pending' || previousStatus === 'running';
856
+ const outcomeTone = currentStatus === 'error'
547
857
  ? 'error'
548
- : this.call.status === 'completed'
858
+ : currentStatus === 'completed'
549
859
  ? 'success'
550
860
  : null;
551
861
  if (wasActive && outcomeTone) {
@@ -557,10 +867,18 @@ export class DeesHarnessToolCard extends DeesElement {
557
867
 
558
868
  public updated(changedProperties: Map<PropertyKey, unknown>): void {
559
869
  super.updated(changedProperties);
870
+ if (!this.isConnected) return;
871
+ if (this.isOpen && !this.bodyMounted) this.bodyMounted = true;
560
872
  if (changedProperties.has('call') && this.fullscreenPortal) {
561
873
  this.fullscreenPortal.call = this.enrichedCall;
562
874
  }
563
- if (this.uncapped) return;
875
+ this.connectSubtaskResultScroll();
876
+ if (!this.isOpen || this.uncapped || this.hasSubtaskPreview) {
877
+ this.bodyResizeObserver?.disconnect();
878
+ this.observedBodyInner = null;
879
+ this.bodyOverflowing = false;
880
+ return;
881
+ }
564
882
  const bodyInner = this.shadowRoot?.querySelector('.bodyInner') ?? null;
565
883
  if (bodyInner === this.observedBodyInner) return;
566
884
  this.bodyResizeObserver?.disconnect();
@@ -578,7 +896,45 @@ export class DeesHarnessToolCard extends DeesElement {
578
896
  this.bodyResizeObserver.observe(bodyInner);
579
897
  }
580
898
 
899
+ private readonly syncSubtaskResultScroll = (): void => {
900
+ const result = this.subtaskResultElement;
901
+ if (!result) return;
902
+ result.classList.toggle('fadeTop', result.scrollTop > 1);
903
+ result.classList.toggle(
904
+ 'fadeBottom',
905
+ result.scrollHeight - result.scrollTop - result.clientHeight > 1,
906
+ );
907
+ };
908
+
909
+ private connectSubtaskResultScroll(): void {
910
+ const nextResult = this.shadowRoot?.querySelector<HTMLElement>(
911
+ '.harness-subtask-detail-result .harness-subtask-detail-text',
912
+ ) ?? null;
913
+ if (nextResult === this.subtaskResultElement) {
914
+ this.syncSubtaskResultScroll();
915
+ return;
916
+ }
917
+ this.subtaskResultElement?.removeEventListener('scroll', this.syncSubtaskResultScroll);
918
+ this.subtaskResultResizeObserver?.disconnect();
919
+ this.subtaskResultElement = nextResult;
920
+ if (!nextResult) return;
921
+ nextResult.addEventListener('scroll', this.syncSubtaskResultScroll, { passive: true });
922
+ this.subtaskResultResizeObserver ??= new ResizeObserver(this.syncSubtaskResultScroll);
923
+ this.subtaskResultResizeObserver.observe(nextResult);
924
+ this.syncSubtaskResultScroll();
925
+ }
926
+
927
+ public async connectedCallback(): Promise<void> {
928
+ const connected = super.connectedCallback();
929
+ if (this.hasUpdated) this.requestUpdate();
930
+ await connected;
931
+ }
932
+
581
933
  public async disconnectedCallback(): Promise<void> {
934
+ if (this.disclosureFrame !== null) {
935
+ cancelAnimationFrame(this.disclosureFrame);
936
+ this.disclosureFrame = null;
937
+ }
582
938
  if (this.flashFallbackTimeout) {
583
939
  clearTimeout(this.flashFallbackTimeout);
584
940
  this.flashFallbackTimeout = null;
@@ -592,6 +948,10 @@ export class DeesHarnessToolCard extends DeesElement {
592
948
  this.bodyResizeObserver?.disconnect();
593
949
  this.bodyResizeObserver = null;
594
950
  this.observedBodyInner = null;
951
+ this.subtaskResultElement?.removeEventListener('scroll', this.syncSubtaskResultScroll);
952
+ this.subtaskResultElement = null;
953
+ this.subtaskResultResizeObserver?.disconnect();
954
+ this.subtaskResultResizeObserver = null;
595
955
  await super.disconnectedCallback();
596
956
  }
597
957
 
@@ -612,9 +972,95 @@ export class DeesHarnessToolCard extends DeesElement {
612
972
  private renderBody(call: IHarnessToolCall): TemplateResult {
613
973
  const descriptor = this.descriptor;
614
974
  const helpers = this.createHelpers();
615
- if (descriptor.renderBody) return descriptor.renderBody(call, helpers);
616
- const builtin = builtinBodyRenderers[descriptor.kind] ?? builtinBodyRenderers.unknown;
617
- return builtin(call, helpers);
975
+ const base = descriptor.renderBody
976
+ ? descriptor.renderBody(call, helpers)
977
+ : (builtinBodyRenderers[descriptor.kind] ?? builtinBodyRenderers.unknown)(
978
+ descriptor.kind === 'subtask' && this.hasLiveSubtaskPreview
979
+ ? { ...call, output: undefined, errorText: undefined }
980
+ : call,
981
+ helpers,
982
+ );
983
+ const subtaskAction = descriptor.kind === 'subtask'
984
+ ? this.renderSubtaskAction(call)
985
+ : html``;
986
+ if (!this.hasLiveSubtaskPreview || !call.subtask) {
987
+ return descriptor.kind === 'subtask' ? html`${base}${subtaskAction}` : base;
988
+ }
989
+ const subtask = call.subtask;
990
+ return html`
991
+ <section class="subtaskPreview" role="region" aria-label="Live subagent transcript">
992
+ ${subtask.previewState === 'ready' && (subtask.notice || subtask.truncated)
993
+ ? html`<div class="subtaskNotice">${subtask.notice ?? 'Earlier child messages were omitted.'}</div>`
994
+ : ''}
995
+ ${subtask.previewState === 'ready'
996
+ ? html`
997
+ <dees-harness-message-list
998
+ .messages=${subtask.messages}
999
+ .permissions=${subtask.permissions ?? []}
1000
+ .questions=${subtask.questions ?? []}
1001
+ .status=${subtask.status}
1002
+ .toolRegistry=${this.toolRegistry}
1003
+ .allowSubtaskStreams=${false}
1004
+ .autoFollow=${this.isOpen}
1005
+ .hideScrollbar=${true}
1006
+ .fadeScrollEdges=${true}
1007
+ .showRoles=${true}
1008
+ .showTimestamps=${false}
1009
+ emptyTitle="Waiting for subagent output"
1010
+ emptyText="The child session is active."
1011
+ >
1012
+ <div slot="leading" class="subtaskThreadBrief">${base}</div>
1013
+ </dees-harness-message-list>
1014
+ `
1015
+ : html`
1016
+ <div class="subtaskPreviewState">
1017
+ ${base}
1018
+ <dees-harness-status .status=${subtask.status}></dees-harness-status>
1019
+ <span>${subtask.notice ?? (subtask.previewState === 'loading'
1020
+ ? 'Loading live subagent transcript…'
1021
+ : 'Live preview unavailable. Open the subagent chat to follow it.')}</span>
1022
+ </div>
1023
+ `}
1024
+ </section>
1025
+ ${subtaskAction}
1026
+ `;
1027
+ }
1028
+
1029
+ private renderSubtaskAction(callArg: IHarnessToolCall): TemplateResult {
1030
+ const childSessionId = callArg.subtask?.sessionId ?? callArg.childSessionId;
1031
+ if (!childSessionId) return html``;
1032
+ return html`
1033
+ <button
1034
+ class="harness-subtask-open"
1035
+ type="button"
1036
+ @click=${() => {
1037
+ this.dispatchEvent(new CustomEvent('harness-subtask-open', {
1038
+ detail: { sessionId: childSessionId, call: callArg },
1039
+ bubbles: true,
1040
+ composed: true,
1041
+ }));
1042
+ }}
1043
+ >
1044
+ ${(callArg.subtask
1045
+ ? callArg.subtask.status.type !== 'busy'
1046
+ : callArg.status === 'completed' || callArg.status === 'error')
1047
+ ? 'Open subagent chat'
1048
+ : 'Follow subagent live'}
1049
+ </button>
1050
+ `;
1051
+ }
1052
+
1053
+ public applySubtaskDelta(delta: THarnessMessageStreamDelta): boolean {
1054
+ if (!this.allowSubtaskStreams || this.call.subtask?.previewState !== 'ready') return false;
1055
+ if (!this.call.subtask.messages.some((message) => message.id === delta.messageId)) return false;
1056
+ const list = this.shadowRoot?.querySelector('.subtaskPreview > dees-harness-message-list');
1057
+ if (list instanceof DeesHarnessMessageList) {
1058
+ list.applyDelta(delta);
1059
+ return true;
1060
+ }
1061
+ if (!harnessApplyMessageDelta(this.call.subtask.messages, delta)) return false;
1062
+ this.requestUpdate();
1063
+ return true;
618
1064
  }
619
1065
 
620
1066
  private createHelpers(): IHarnessToolRenderHelpers {