@design.estate/dees-catalog 6.0.0 → 6.2.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 (45) hide show
  1. package/dist_bundle/bundle.js +3105 -1500
  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 +2 -0
  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 +10 -1
  6. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.d.ts +3 -1
  7. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +24 -4
  8. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +26 -0
  9. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +398 -94
  10. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.d.ts +32 -0
  11. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.demo.d.ts +1 -0
  12. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.demo.js +31 -0
  13. package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.js +297 -0
  14. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +1 -9
  15. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.d.ts +10 -1
  16. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +181 -2
  17. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +17 -1
  18. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +480 -36
  19. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/renderers.js +28 -24
  20. package/dist_ts_web/elements/00group-harness/harness.helpers.d.ts +5 -1
  21. package/dist_ts_web/elements/00group-harness/harness.helpers.js +66 -1
  22. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  23. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  24. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +33 -2
  25. package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +4 -0
  26. package/dist_ts_web/elements/00group-harness/toolregistry.js +8 -1
  27. package/package.json +2 -2
  28. package/readme.hints.md +16 -1
  29. package/readme.md +41 -9
  30. package/readme.playbook.md +8 -4
  31. package/ts_web/00_commitinfo_data.ts +1 -1
  32. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +490 -7
  33. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +9 -0
  34. package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +24 -3
  35. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +397 -84
  36. package/ts_web/elements/00group-harness/dees-harness-overflow-text.demo.ts +31 -0
  37. package/ts_web/elements/00group-harness/dees-harness-overflow-text.ts +236 -0
  38. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +0 -8
  39. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +193 -1
  40. package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +484 -34
  41. package/ts_web/elements/00group-harness/dees-harness-tool-card/renderers.ts +29 -25
  42. package/ts_web/elements/00group-harness/harness.helpers.ts +68 -1
  43. package/ts_web/elements/00group-harness/index.ts +1 -0
  44. package/ts_web/elements/00group-harness/interfaces.ts +38 -2
  45. package/ts_web/elements/00group-harness/toolregistry.ts +12 -0
@@ -0,0 +1,236 @@
1
+ import { demoFunc } from './dees-harness-overflow-text.demo.js';
2
+ import {
3
+ DeesElement,
4
+ html,
5
+ css,
6
+ customElement,
7
+ type TemplateResult,
8
+ property,
9
+ state,
10
+ cssManager,
11
+ } from '@design.estate/dees-element';
12
+ import { themeDefaultStyles } from '../00theme.js';
13
+
14
+ declare global {
15
+ interface HTMLElementTagNameMap {
16
+ 'dees-harness-overflow-text': DeesHarnessOverflowText;
17
+ }
18
+ }
19
+
20
+ @customElement('dees-harness-overflow-text')
21
+ export class DeesHarnessOverflowText extends DeesElement {
22
+ public static demo = demoFunc;
23
+ public static demoGroups = ['Agent Chat'];
24
+
25
+ @property({ type: Number })
26
+ accessor fadeWidthPx: number = 40;
27
+
28
+ @property({ type: Number })
29
+ accessor introDelayMs: number = 2000;
30
+
31
+ @state()
32
+ accessor overflowing: boolean = false;
33
+
34
+ @state()
35
+ accessor travelPx: number = 0;
36
+
37
+ private readonly createdAt = performance.now();
38
+ private resizeObserver: ResizeObserver | null = null;
39
+ private visibilityObserver: IntersectionObserver | null = null;
40
+ private measureFrame: number | null = null;
41
+ private introTimeout: ReturnType<typeof setTimeout> | null = null;
42
+ private introAnimation: Animation | null = null;
43
+ private introHandled = false;
44
+
45
+ public static styles = [
46
+ themeDefaultStyles,
47
+ cssManager.defaultStyles,
48
+ css`
49
+ :host {
50
+ display: block;
51
+ min-width: 0;
52
+ width: 100%;
53
+ overflow: hidden;
54
+ }
55
+
56
+ .viewport {
57
+ display: block;
58
+ box-sizing: border-box;
59
+ width: 100%;
60
+ min-width: 0;
61
+ overflow: hidden;
62
+ white-space: nowrap;
63
+ }
64
+
65
+ .viewport.overflowing {
66
+ -webkit-mask-image: linear-gradient(
67
+ to right,
68
+ black 0,
69
+ black calc(100% - var(--harness-overflow-fade-width)),
70
+ transparent 100%
71
+ );
72
+ mask-image: linear-gradient(
73
+ to right,
74
+ black 0,
75
+ black calc(100% - var(--harness-overflow-fade-width)),
76
+ transparent 100%
77
+ );
78
+ }
79
+
80
+ .text {
81
+ display: inline-block;
82
+ min-width: max-content;
83
+ transform: translateX(0);
84
+ }
85
+
86
+ .viewport.overflowing:hover .text {
87
+ animation: harness-overflow-reveal var(--harness-overflow-duration) var(--dees-ease-standard);
88
+ }
89
+
90
+ @keyframes harness-overflow-reveal {
91
+ 0%, 10%, 100% { transform: translateX(0); }
92
+ 72%, 88% { transform: translateX(calc(0px - var(--harness-overflow-travel))); }
93
+ }
94
+
95
+ @media (prefers-reduced-motion: reduce) {
96
+ .viewport.overflowing:hover .text {
97
+ animation: none;
98
+ }
99
+ }
100
+ `,
101
+ ];
102
+
103
+ public render(): TemplateResult {
104
+ const durationSeconds = Math.min(7, Math.max(3.2, this.travelPx / 80 + 2.4));
105
+ return html`
106
+ <span
107
+ class=${`viewport ${this.overflowing ? 'overflowing' : ''}`}
108
+ style=${`
109
+ --harness-overflow-fade-width: ${this.fadeWidthPx}px;
110
+ --harness-overflow-travel: ${this.travelPx}px;
111
+ --harness-overflow-duration: ${durationSeconds}s;
112
+ `}
113
+ >
114
+ <span class="text"><slot @slotchange=${this.scheduleMeasure}></slot></span>
115
+ </span>
116
+ `;
117
+ }
118
+
119
+ public firstUpdated(changedProperties: Map<PropertyKey, unknown>): void {
120
+ super.firstUpdated(changedProperties);
121
+ if (!this.isConnected) return;
122
+ this.connectResizeObserver();
123
+ this.scheduleMeasure();
124
+ this.scheduleIntro();
125
+ }
126
+
127
+ public updated(changedProperties: Map<PropertyKey, unknown>): void {
128
+ super.updated(changedProperties);
129
+ if (!this.isConnected) return;
130
+ if (changedProperties.has('fadeWidthPx')) this.scheduleMeasure();
131
+ if (changedProperties.has('introDelayMs') && !this.introHandled) this.scheduleIntro();
132
+ }
133
+
134
+ public async connectedCallback(): Promise<void> {
135
+ const connected = super.connectedCallback();
136
+ if (this.hasUpdated) {
137
+ this.connectResizeObserver();
138
+ this.scheduleMeasure();
139
+ this.scheduleIntro();
140
+ }
141
+ await connected;
142
+ }
143
+
144
+ private connectResizeObserver(): void {
145
+ if (!this.isConnected || this.resizeObserver) return;
146
+ const viewport = this.shadowRoot?.querySelector('.viewport');
147
+ const text = this.shadowRoot?.querySelector('.text');
148
+ if (!viewport || !text) return;
149
+ this.resizeObserver = new ResizeObserver(this.scheduleMeasure);
150
+ this.resizeObserver.observe(viewport);
151
+ this.resizeObserver.observe(text);
152
+ this.visibilityObserver = new IntersectionObserver((entries) => {
153
+ if (!entries.some((entry) => entry.isIntersecting)) return;
154
+ this.scheduleMeasure();
155
+ requestAnimationFrame(() => this.tryRunIntro());
156
+ });
157
+ this.visibilityObserver.observe(this);
158
+ }
159
+
160
+ private readonly scheduleMeasure = (): void => {
161
+ if (!this.isConnected || this.measureFrame !== null) return;
162
+ this.measureFrame = requestAnimationFrame(() => {
163
+ this.measureFrame = null;
164
+ if (!this.isConnected) return;
165
+ const viewport = this.shadowRoot?.querySelector<HTMLElement>('.viewport');
166
+ const text = this.shadowRoot?.querySelector<HTMLElement>('.text');
167
+ if (!viewport || !text) return;
168
+ this.measureOverflow(viewport, text);
169
+ if (
170
+ !this.introHandled
171
+ && performance.now() - this.createdAt >= this.introDelayMs
172
+ ) {
173
+ requestAnimationFrame(() => this.tryRunIntro());
174
+ }
175
+ });
176
+ };
177
+
178
+ private scheduleIntro(): void {
179
+ if (!this.isConnected || this.introHandled) return;
180
+ if (this.introTimeout) clearTimeout(this.introTimeout);
181
+ const remainingMs = Math.max(0, this.introDelayMs - (performance.now() - this.createdAt));
182
+ this.introTimeout = setTimeout(() => {
183
+ this.introTimeout = null;
184
+ this.scheduleMeasure();
185
+ requestAnimationFrame(() => this.tryRunIntro());
186
+ }, remainingMs);
187
+ }
188
+
189
+ private tryRunIntro(): void {
190
+ if (!this.isConnected || this.introHandled) return;
191
+ const viewport = this.shadowRoot?.querySelector<HTMLElement>('.viewport');
192
+ const text = this.shadowRoot?.querySelector<HTMLElement>('.text');
193
+ if (!viewport || !text || viewport.clientWidth <= 0) return;
194
+ this.measureOverflow(viewport, text);
195
+ this.introHandled = true;
196
+ if (!this.overflowing || this.travelPx <= 0) return;
197
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
198
+ const duration = Math.min(7000, Math.max(3200, this.travelPx / 80 * 1000 + 2400));
199
+ this.introAnimation = text.animate([
200
+ { transform: 'translateX(0)', offset: 0 },
201
+ { transform: 'translateX(0)', offset: 0.1 },
202
+ { transform: `translateX(-${this.travelPx}px)`, offset: 0.72 },
203
+ { transform: `translateX(-${this.travelPx}px)`, offset: 0.88 },
204
+ { transform: 'translateX(0)', offset: 1 },
205
+ ], {
206
+ duration,
207
+ easing: 'cubic-bezier(0.2, 0, 0, 1)',
208
+ });
209
+ this.introAnimation.onfinish = () => {
210
+ this.introAnimation = null;
211
+ };
212
+ this.introAnimation.oncancel = () => {
213
+ this.introAnimation = null;
214
+ };
215
+ }
216
+
217
+ private measureOverflow(viewportArg: HTMLElement, textArg: HTMLElement): void {
218
+ const overflowPx = Math.max(0, textArg.scrollWidth - viewportArg.clientWidth);
219
+ this.overflowing = overflowPx > 0.5;
220
+ this.travelPx = this.overflowing ? overflowPx + this.fadeWidthPx : 0;
221
+ }
222
+
223
+ public async disconnectedCallback(): Promise<void> {
224
+ if (this.measureFrame !== null) cancelAnimationFrame(this.measureFrame);
225
+ if (this.introTimeout) clearTimeout(this.introTimeout);
226
+ this.measureFrame = null;
227
+ this.introTimeout = null;
228
+ this.introAnimation?.cancel();
229
+ this.introAnimation = null;
230
+ this.resizeObserver?.disconnect();
231
+ this.resizeObserver = null;
232
+ this.visibilityObserver?.disconnect();
233
+ this.visibilityObserver = null;
234
+ await super.disconnectedCallback();
235
+ }
236
+ }
@@ -751,8 +751,6 @@ export class DeesHarnessSessionList extends DeesElement {
751
751
  this.finishPointerDrag(false);
752
752
  }
753
753
  if (!changedProperties.has('sessions')) return;
754
- const previousSessions = changedProperties.get('sessions') as IHarnessSessionMeta[] | undefined;
755
- const previousById = new Map((previousSessions ?? []).map((session) => [session.id, session]));
756
754
  const knownIds = new Set(this.sessions.map((session) => session.id));
757
755
  if (this.pointerDragGesture && !knownIds.has(this.pointerDragGesture.sessionId)) {
758
756
  this.finishPointerDrag(false);
@@ -760,12 +758,6 @@ export class DeesHarnessSessionList extends DeesElement {
760
758
  const nextExpanded = new Set(
761
759
  [...this.expandedSessionIds].filter((sessionId) => knownIds.has(sessionId)),
762
760
  );
763
- for (const session of this.sessions) {
764
- const previous = previousById.get(session.id);
765
- if (previous && previous.working !== true && session.working === true) {
766
- nextExpanded.add(session.id);
767
- }
768
- }
769
761
  if (
770
762
  nextExpanded.size !== this.expandedSessionIds.size
771
763
  || [...nextExpanded].some((sessionId) => !this.expandedSessionIds.has(sessionId))
@@ -13,20 +13,33 @@ import {
13
13
  import { themeDefaultStyles } from '../../00theme.js';
14
14
  import type {
15
15
  IHarnessIntelligenceExchange,
16
+ IHarnessMessage,
16
17
  IHarnessScratchpad,
17
18
  IHarnessScratchpadSaveDetail,
18
19
  IHarnessSessionIntelligenceAskDetail,
19
20
  IHarnessSessionMetrics,
20
21
  IHarnessTodoItem,
22
+ IHarnessToolCall,
23
+ THarnessToolStatus,
21
24
  } from '../interfaces.js';
25
+ import { harnessEffectiveToolStatus, harnessFormatRelativeTime } from '../harness.helpers.js';
26
+ import { DeesHarnessToolRegistry } from '../toolregistry.js';
22
27
  import '../dees-harness-todos/dees-harness-todos.js';
28
+ import '../dees-harness-overflow-text.js';
23
29
  import '../../00group-button/dees-button/dees-button.js';
24
30
  import '../../00group-utility/dees-icon/dees-icon.js';
25
31
 
26
32
  const { repeat } = directives;
27
33
 
28
34
  const formatTokenCount = (valueArg: number): string => valueArg.toLocaleString('en-US');
29
- type TSessionSidebarSection = 'tasks' | 'metrics' | 'scratchpad' | 'intelligence';
35
+ type TSessionSidebarSection = 'tasks' | 'activity' | 'metrics' | 'scratchpad' | 'intelligence';
36
+
37
+ interface IToolActivityEntry {
38
+ key: string;
39
+ call: IHarnessToolCall;
40
+ createdAt?: number;
41
+ status: THarnessToolStatus;
42
+ }
30
43
 
31
44
  declare global {
32
45
  interface HTMLElementTagNameMap {
@@ -49,6 +62,13 @@ export class DeesHarnessSessionSidebar extends DeesElement {
49
62
  @property({ type: Boolean })
50
63
  accessor showTasks: boolean = true;
51
64
 
65
+ /** Top-level tool messages from the host's currently retained transcript window. */
66
+ @property({ attribute: false })
67
+ accessor toolMessages: IHarnessMessage[] = [];
68
+
69
+ @property({ attribute: false })
70
+ accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
71
+
52
72
  @property({ attribute: false })
53
73
  accessor metrics: IHarnessSessionMetrics = {};
54
74
 
@@ -313,6 +333,103 @@ export class DeesHarnessSessionSidebar extends DeesElement {
313
333
  min-width: 0;
314
334
  }
315
335
 
336
+ .activityList,
337
+ .activityGroup {
338
+ display: grid;
339
+ gap: var(--dees-spacing-sm);
340
+ }
341
+
342
+ .activityGroup + .activityGroup {
343
+ margin-top: var(--dees-spacing-md);
344
+ }
345
+
346
+ .activityGroupTitle {
347
+ color: var(--dees-color-text-muted);
348
+ font-size: var(--dees-font-caption2-size, 10px);
349
+ font-weight: 650;
350
+ letter-spacing: 0.04em;
351
+ text-transform: uppercase;
352
+ }
353
+
354
+ .activityItem {
355
+ display: grid;
356
+ grid-template-columns: 22px minmax(0, 1fr);
357
+ gap: var(--dees-spacing-sm);
358
+ align-items: start;
359
+ min-width: 0;
360
+ padding: var(--dees-spacing-xs) 0;
361
+ }
362
+
363
+ .activityIcon {
364
+ display: grid;
365
+ place-items: center;
366
+ width: 22px;
367
+ height: 22px;
368
+ border-radius: var(--dees-radius-md);
369
+ background: var(--dees-color-fill-secondary);
370
+ color: var(--dees-color-text-secondary);
371
+ }
372
+
373
+ .activityItem.status-running .activityIcon,
374
+ .activityItem.status-pending .activityIcon {
375
+ color: var(--dees-color-accent-primary);
376
+ }
377
+
378
+ .activityItem.status-error .activityIcon {
379
+ color: var(--dees-color-accent-error);
380
+ }
381
+
382
+ .activityCopy {
383
+ min-width: 0;
384
+ }
385
+
386
+ .activityTitleRow,
387
+ .activityMeta {
388
+ display: flex;
389
+ align-items: center;
390
+ gap: var(--dees-spacing-xs);
391
+ min-width: 0;
392
+ }
393
+
394
+ .activityTitle {
395
+ min-width: 0;
396
+ flex: 1;
397
+ color: var(--dees-color-text-primary);
398
+ font-size: var(--dees-font-control-size-sm, 12px);
399
+ font-weight: 600;
400
+ }
401
+
402
+ .activityStatus {
403
+ flex: 0 0 auto;
404
+ color: var(--dees-color-text-muted);
405
+ font-size: var(--dees-font-caption2-size, 10px);
406
+ text-transform: capitalize;
407
+ }
408
+
409
+ .activityItem.status-running .activityStatus,
410
+ .activityItem.status-pending .activityStatus {
411
+ color: var(--dees-color-accent-primary);
412
+ }
413
+
414
+ .activityItem.status-error .activityStatus {
415
+ color: var(--dees-color-text-error);
416
+ }
417
+
418
+ .activitySubtitle {
419
+ display: block;
420
+ width: 100%;
421
+ min-width: 0;
422
+ margin-top: 1px;
423
+ color: var(--dees-color-text-secondary);
424
+ font-size: var(--dees-font-caption2-size, 10px);
425
+ }
426
+
427
+ .activityMeta {
428
+ margin-top: 2px;
429
+ color: var(--dees-color-text-muted);
430
+ font-size: var(--dees-font-caption2-size, 10px);
431
+ }
432
+
316
433
  .metricList {
317
434
  display: grid;
318
435
  gap: 6px;
@@ -505,6 +622,7 @@ export class DeesHarnessSessionSidebar extends DeesElement {
505
622
  return html`
506
623
  <div class="sidebar">
507
624
  ${this.showTasks ? this.renderTasks() : ''}
625
+ ${this.toolActivityEntries.length ? this.renderToolActivity() : ''}
508
626
  ${this.hasMetrics ? this.renderMetrics() : ''}
509
627
  ${this.scratchpad ? this.renderScratchpad() : ''}
510
628
  ${this.intelligenceEnabled ? this.renderIntelligence() : ''}
@@ -516,9 +634,22 @@ export class DeesHarnessSessionSidebar extends DeesElement {
516
634
  return Object.values(this.metrics).some((value) => value !== undefined);
517
635
  }
518
636
 
637
+ private get toolActivityEntries(): IToolActivityEntry[] {
638
+ return this.toolMessages
639
+ .filter((message): message is IHarnessMessage & { toolCall: IHarnessToolCall } => Boolean(message.toolCall))
640
+ .map((message) => ({
641
+ key: JSON.stringify([message.id, message.toolCall.id]),
642
+ call: message.toolCall,
643
+ createdAt: message.createdAt ?? message.toolCall.startedAt,
644
+ status: harnessEffectiveToolStatus(message.toolCall),
645
+ }))
646
+ .sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0));
647
+ }
648
+
519
649
  private get availableSections(): TSessionSidebarSection[] {
520
650
  return [
521
651
  ...(this.showTasks ? ['tasks' as const] : []),
652
+ ...(this.toolActivityEntries.length ? ['activity' as const] : []),
522
653
  ...(this.hasMetrics ? ['metrics' as const] : []),
523
654
  ...(this.scratchpad ? ['scratchpad' as const] : []),
524
655
  ...(this.intelligenceEnabled ? ['intelligence' as const] : []),
@@ -527,6 +658,7 @@ export class DeesHarnessSessionSidebar extends DeesElement {
527
658
 
528
659
  private get defaultExpandedSection(): TSessionSidebarSection | undefined {
529
660
  if (this.showTasks && this.todos.length > 0) return 'tasks';
661
+ if (this.toolActivityEntries.length) return 'activity';
530
662
  if (this.hasMetrics) return 'metrics';
531
663
  if (this.scratchpad) return 'scratchpad';
532
664
  if (this.intelligenceEnabled) return 'intelligence';
@@ -580,6 +712,66 @@ export class DeesHarnessSessionSidebar extends DeesElement {
580
712
  );
581
713
  }
582
714
 
715
+ private renderToolActivity(): TemplateResult {
716
+ const entries = this.toolActivityEntries;
717
+ const active = entries.filter((entry) => entry.status === 'pending' || entry.status === 'running');
718
+ const history = entries.filter((entry) => entry.status === 'completed' || entry.status === 'error');
719
+ const summary = active.length
720
+ ? `${active.length} active · ${history.length} past`
721
+ : `${history.length} past`;
722
+ return this.renderSection(
723
+ 'activity',
724
+ 'Tools & Subagents',
725
+ 'lucide:Wrench',
726
+ html`
727
+ <div class="activityList">
728
+ ${active.length ? this.renderActivityGroup('Active', active) : ''}
729
+ ${history.length ? this.renderActivityGroup('History', history) : ''}
730
+ </div>
731
+ `,
732
+ summary,
733
+ );
734
+ }
735
+
736
+ private renderActivityGroup(labelArg: string, entriesArg: IToolActivityEntry[]): TemplateResult {
737
+ return html`
738
+ <section class="activityGroup" aria-label=${labelArg}>
739
+ <div class="activityGroupTitle">${labelArg}</div>
740
+ ${repeat(
741
+ entriesArg,
742
+ (entry) => entry.key,
743
+ (entry) => this.renderActivityEntry(entry),
744
+ )}
745
+ </section>
746
+ `;
747
+ }
748
+
749
+ private renderActivityEntry(entryArg: IToolActivityEntry): TemplateResult {
750
+ const descriptor = this.toolRegistry.resolve(entryArg.call);
751
+ const label = typeof descriptor.label === 'function'
752
+ ? descriptor.label(entryArg.call)
753
+ : descriptor.label;
754
+ const subtitle = descriptor.subtitle?.(entryArg.call) ?? '';
755
+ const relativeTime = harnessFormatRelativeTime(entryArg.createdAt);
756
+ return html`
757
+ <article class=${`activityItem status-${entryArg.status}`} data-tool-call-id=${entryArg.call.id}>
758
+ <span class="activityIcon">
759
+ <dees-icon icon=${descriptor.icon} iconSize="13"></dees-icon>
760
+ </span>
761
+ <span class="activityCopy">
762
+ <span class="activityTitleRow">
763
+ <dees-harness-overflow-text class="activityTitle" title=${label}>${label}</dees-harness-overflow-text>
764
+ <span class="activityStatus">${entryArg.status}</span>
765
+ </span>
766
+ ${subtitle
767
+ ? html`<dees-harness-overflow-text class="activitySubtitle" title=${subtitle}>${subtitle}</dees-harness-overflow-text>`
768
+ : ''}
769
+ ${relativeTime ? html`<span class="activityMeta">${relativeTime}</span>` : ''}
770
+ </span>
771
+ </article>
772
+ `;
773
+ }
774
+
583
775
  private renderMetrics(): TemplateResult {
584
776
  const metrics = this.metrics;
585
777
  const percentage = this.contextPercentage;