@design.estate/dees-catalog 4.12.0 → 6.0.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 (37) hide show
  1. package/dist_bundle/bundle.js +2719 -1261
  2. package/dist_ts_web/00_commitinfo_data.js +2 -2
  3. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +23 -4
  4. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +290 -78
  5. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +419 -42
  6. package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +44 -3
  7. package/dist_ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.js +5 -7
  8. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +5 -2
  9. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.d.ts +54 -0
  10. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.demo.d.ts +1 -0
  11. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.demo.js +56 -0
  12. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +870 -0
  13. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/index.d.ts +1 -0
  14. package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/index.js +2 -0
  15. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +5 -0
  16. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +87 -3
  17. package/dist_ts_web/elements/00group-harness/harness.markdown.js +3 -1
  18. package/dist_ts_web/elements/00group-harness/harness.styles.js +48 -1
  19. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  20. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  21. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +41 -0
  22. package/package.json +1 -1
  23. package/readme.md +13 -5
  24. package/ts_web/00_commitinfo_data.ts +1 -1
  25. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +277 -79
  26. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +367 -36
  27. package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +43 -2
  28. package/ts_web/elements/00group-harness/dees-harness-reasoning/dees-harness-reasoning.ts +4 -6
  29. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +4 -1
  30. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.demo.ts +67 -0
  31. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +802 -0
  32. package/ts_web/elements/00group-harness/dees-harness-session-sidebar/index.ts +1 -0
  33. package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +79 -1
  34. package/ts_web/elements/00group-harness/harness.markdown.ts +2 -0
  35. package/ts_web/elements/00group-harness/harness.styles.ts +47 -0
  36. package/ts_web/elements/00group-harness/index.ts +1 -0
  37. package/ts_web/elements/00group-harness/interfaces.ts +48 -0
@@ -0,0 +1,802 @@
1
+ import { demoFunc } from './dees-harness-session-sidebar.demo.js';
2
+ import {
3
+ DeesElement,
4
+ html,
5
+ css,
6
+ customElement,
7
+ type TemplateResult,
8
+ property,
9
+ state,
10
+ cssManager,
11
+ directives,
12
+ } from '@design.estate/dees-element';
13
+ import { themeDefaultStyles } from '../../00theme.js';
14
+ import type {
15
+ IHarnessIntelligenceExchange,
16
+ IHarnessScratchpad,
17
+ IHarnessScratchpadSaveDetail,
18
+ IHarnessSessionIntelligenceAskDetail,
19
+ IHarnessSessionMetrics,
20
+ IHarnessTodoItem,
21
+ } from '../interfaces.js';
22
+ import '../dees-harness-todos/dees-harness-todos.js';
23
+ import '../../00group-button/dees-button/dees-button.js';
24
+ import '../../00group-utility/dees-icon/dees-icon.js';
25
+
26
+ const { repeat } = directives;
27
+
28
+ const formatTokenCount = (valueArg: number): string => valueArg.toLocaleString('en-US');
29
+ type TSessionSidebarSection = 'tasks' | 'metrics' | 'scratchpad' | 'intelligence';
30
+
31
+ declare global {
32
+ interface HTMLElementTagNameMap {
33
+ 'dees-harness-session-sidebar': DeesHarnessSessionSidebar;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Generic session context sidebar. It renders host-owned state and emits
39
+ * mutations, but never loads, persists, or executes session data itself.
40
+ */
41
+ @customElement('dees-harness-session-sidebar')
42
+ export class DeesHarnessSessionSidebar extends DeesElement {
43
+ public static demo = demoFunc;
44
+ public static demoGroups = ['Agent Chat'];
45
+
46
+ @property({ attribute: false })
47
+ accessor todos: IHarnessTodoItem[] = [];
48
+
49
+ @property({ type: Boolean })
50
+ accessor showTasks: boolean = true;
51
+
52
+ @property({ attribute: false })
53
+ accessor metrics: IHarnessSessionMetrics = {};
54
+
55
+ @property({ attribute: false })
56
+ accessor scratchpad: IHarnessScratchpad | undefined = undefined;
57
+
58
+ @property({ type: Boolean })
59
+ accessor scratchpadBusy: boolean = false;
60
+
61
+ @property()
62
+ accessor scratchpadError: string = '';
63
+
64
+ @property({ type: Boolean })
65
+ accessor intelligenceEnabled: boolean = false;
66
+
67
+ @property({ attribute: false })
68
+ accessor intelligenceExchanges: IHarnessIntelligenceExchange[] = [];
69
+
70
+ @property({ type: Boolean })
71
+ accessor intelligenceBusy: boolean = false;
72
+
73
+ @property()
74
+ accessor intelligenceError: string = '';
75
+
76
+ @property({ type: Boolean })
77
+ accessor disabled: boolean = false;
78
+
79
+ @state()
80
+ private accessor scratchpadDraft: string = '';
81
+
82
+ @state()
83
+ private accessor scratchpadDraftRevision: number = 0;
84
+
85
+ @state()
86
+ private accessor scratchpadDirty: boolean = false;
87
+
88
+ @state()
89
+ private accessor scratchpadStale: boolean = false;
90
+
91
+ @state()
92
+ private accessor intelligenceQuestion: string = '';
93
+
94
+ @state()
95
+ private accessor expandedSection: TSessionSidebarSection | undefined = undefined;
96
+
97
+ private sectionStateInitialized = false;
98
+
99
+ public static styles = [
100
+ themeDefaultStyles,
101
+ cssManager.defaultStyles,
102
+ css`
103
+ :host {
104
+ display: flex;
105
+ flex-direction: column;
106
+ min-width: 0;
107
+ min-height: 0;
108
+ height: 100%;
109
+ font-family: var(--dees-font-family);
110
+ color: var(--dees-color-text-primary);
111
+ }
112
+
113
+ .sidebar {
114
+ display: flex;
115
+ flex-direction: column;
116
+ min-height: 0;
117
+ height: 100%;
118
+ overflow: hidden;
119
+ }
120
+
121
+ .section {
122
+ display: flex;
123
+ flex: 0 0 auto;
124
+ flex-direction: column;
125
+ min-height: 0;
126
+ overflow: hidden;
127
+ border-bottom: 1px solid var(--dees-color-border-subtle);
128
+ }
129
+
130
+ .section.expanded {
131
+ flex: 1 1 0;
132
+ }
133
+
134
+ .section:last-child {
135
+ border-bottom: 0;
136
+ }
137
+
138
+ .sectionHeading {
139
+ display: flex;
140
+ flex: 0 0 auto;
141
+ align-items: center;
142
+ gap: var(--dees-spacing-xs);
143
+ width: 100%;
144
+ min-height: 30px;
145
+ margin: 0;
146
+ padding: 6px var(--dees-spacing-md) 6px var(--dees-spacing-xs);
147
+ border: 0;
148
+ background: var(--dees-color-bg-secondary);
149
+ color: var(--dees-color-text-primary);
150
+ font-family: inherit;
151
+ font-size: var(--dees-font-control-size-sm, 12px);
152
+ font-weight: 650;
153
+ text-align: left;
154
+ cursor: pointer;
155
+ }
156
+
157
+ .sectionHeading:hover {
158
+ background: var(--dees-color-badge-default-bg);
159
+ }
160
+
161
+ .sectionHeading:focus-visible {
162
+ position: relative;
163
+ z-index: 1;
164
+ outline: 2px solid var(--dees-color-accent-primary);
165
+ outline-offset: -2px;
166
+ }
167
+
168
+ .sectionHeading dees-icon {
169
+ flex: 0 0 auto;
170
+ color: var(--dees-color-text-secondary);
171
+ }
172
+
173
+ .sectionHeadingText {
174
+ min-width: 0;
175
+ overflow: hidden;
176
+ text-overflow: ellipsis;
177
+ white-space: nowrap;
178
+ }
179
+
180
+ .sectionSummary {
181
+ flex: 0 0 auto;
182
+ margin-left: auto;
183
+ color: var(--dees-color-text-muted);
184
+ font-size: var(--dees-font-caption2-size, 10px);
185
+ font-variant-numeric: tabular-nums;
186
+ font-weight: 500;
187
+ }
188
+
189
+ .sectionContent {
190
+ flex: 1;
191
+ min-height: 0;
192
+ overflow-x: hidden;
193
+ overflow-y: auto;
194
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
195
+ scrollbar-width: thin;
196
+ scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
197
+ }
198
+
199
+ .sectionContent[hidden] {
200
+ display: none;
201
+ }
202
+
203
+ .sectionContent dees-harness-todos {
204
+ min-width: 0;
205
+ }
206
+
207
+ .scratchpadSection .sectionContent:not([hidden]) {
208
+ display: flex;
209
+ flex-direction: column;
210
+ overflow: hidden;
211
+ padding: 0;
212
+ }
213
+
214
+ .scratchpadSection textarea {
215
+ flex: 1;
216
+ min-height: 0;
217
+ resize: none;
218
+ overflow-y: auto;
219
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
220
+ border: 0;
221
+ border-radius: 0;
222
+ background: color-mix(in srgb, var(--dees-color-bg-secondary) 92%, black);
223
+ color: var(--dees-color-text-secondary);
224
+ }
225
+
226
+ .scratchpadSection textarea:focus-visible {
227
+ border-color: transparent;
228
+ background: color-mix(in srgb, var(--dees-color-bg-secondary) 92%, black);
229
+ box-shadow: none;
230
+ }
231
+
232
+ .scratchpadSection .conflict,
233
+ .scratchpadSection .error {
234
+ margin-right: var(--dees-spacing-md);
235
+ margin-left: var(--dees-spacing-md);
236
+ }
237
+
238
+ .scratchpadFooter {
239
+ display: flex;
240
+ flex: 0 0 auto;
241
+ align-items: center;
242
+ justify-content: space-between;
243
+ gap: var(--dees-spacing-sm);
244
+ padding: var(--dees-spacing-xs) var(--dees-spacing-md) var(--dees-spacing-sm);
245
+ border-top: 0;
246
+ background: linear-gradient(
247
+ to bottom,
248
+ color-mix(in srgb, var(--dees-color-bg-secondary) 92%, black),
249
+ var(--dees-color-bg-secondary)
250
+ );
251
+ }
252
+
253
+ .scratchpadFooter .editorMeta {
254
+ margin-top: 0;
255
+ }
256
+
257
+ .intelligenceSection .sectionContent:not([hidden]) {
258
+ display: flex;
259
+ flex-direction: column;
260
+ overflow: hidden;
261
+ padding: 0;
262
+ }
263
+
264
+ .intelligenceHistory {
265
+ flex: 1;
266
+ min-height: 0;
267
+ overflow-x: hidden;
268
+ overflow-y: auto;
269
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
270
+ scrollbar-width: thin;
271
+ scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
272
+ }
273
+
274
+ .intelligenceComposer {
275
+ flex: 0 0 auto;
276
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md) var(--dees-spacing-md);
277
+ background: linear-gradient(
278
+ to bottom,
279
+ var(--dees-color-bg-primary),
280
+ var(--dees-color-bg-secondary)
281
+ );
282
+ box-shadow: var(--dees-shadow-up-sm);
283
+ }
284
+
285
+ .intelligenceComposer textarea {
286
+ min-height: 64px;
287
+ max-height: 160px;
288
+ resize: none;
289
+ padding: var(--dees-spacing-sm) 0;
290
+ border: 0;
291
+ border-radius: 0;
292
+ background: transparent;
293
+ color: var(--dees-color-text-primary);
294
+ font-family: inherit;
295
+ font-size: var(--dees-font-control-size, 13px);
296
+ }
297
+
298
+ .intelligenceComposer textarea:focus-visible {
299
+ border-color: transparent;
300
+ box-shadow: none;
301
+ outline: 2px solid color-mix(in srgb, var(--dees-color-accent-primary) 55%, transparent);
302
+ outline-offset: 2px;
303
+ }
304
+
305
+ .intelligenceControls {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: var(--dees-spacing-sm);
309
+ }
310
+
311
+ .intelligenceControls .hint {
312
+ flex: 1;
313
+ min-width: 0;
314
+ }
315
+
316
+ .metricList {
317
+ display: grid;
318
+ gap: 6px;
319
+ }
320
+
321
+ .metricRow {
322
+ display: flex;
323
+ align-items: baseline;
324
+ justify-content: space-between;
325
+ gap: var(--dees-spacing-md);
326
+ color: var(--dees-color-text-secondary);
327
+ font-size: var(--dees-font-caption1-size, 11px);
328
+ }
329
+
330
+ .metricValue {
331
+ color: var(--dees-color-text-primary);
332
+ font-family: var(--dees-font-family-mono);
333
+ font-variant-numeric: tabular-nums;
334
+ text-align: right;
335
+ }
336
+
337
+ .contextProgress {
338
+ height: 4px;
339
+ margin-top: 2px;
340
+ overflow: hidden;
341
+ border-radius: var(--dees-radius-full);
342
+ background: var(--dees-color-fill-secondary);
343
+ }
344
+
345
+ .contextProgressFill {
346
+ height: 100%;
347
+ border-radius: inherit;
348
+ background: var(--dees-color-accent-primary);
349
+ transition: width var(--dees-transition-default) var(--dees-ease-standard);
350
+ }
351
+
352
+ textarea {
353
+ box-sizing: border-box;
354
+ width: 100%;
355
+ min-height: 108px;
356
+ resize: vertical;
357
+ padding: var(--dees-spacing-sm);
358
+ border: 1px solid var(--dees-color-border-default);
359
+ border-radius: var(--dees-radius-md);
360
+ background: var(--dees-color-bg-primary);
361
+ color: var(--dees-color-text-primary);
362
+ font: var(--dees-font-caption1-size, 11px) / 1.5 var(--dees-font-family-mono);
363
+ outline: none;
364
+ }
365
+
366
+ textarea:focus-visible {
367
+ border-color: var(--dees-color-accent-primary);
368
+ box-shadow: 0 0 0 1px var(--dees-color-accent-primary);
369
+ }
370
+
371
+ textarea:disabled {
372
+ opacity: 0.62;
373
+ cursor: not-allowed;
374
+ }
375
+
376
+ .editorMeta,
377
+ .exchangeMeta,
378
+ .hint {
379
+ color: var(--dees-color-text-muted);
380
+ font-size: var(--dees-font-caption2-size, 10px);
381
+ line-height: 1.4;
382
+ }
383
+
384
+ .editorMeta {
385
+ margin-top: 5px;
386
+ }
387
+
388
+ .actions {
389
+ display: flex;
390
+ align-items: center;
391
+ justify-content: flex-end;
392
+ gap: var(--dees-spacing-xs);
393
+ margin-top: var(--dees-spacing-sm);
394
+ }
395
+
396
+ .conflict,
397
+ .error {
398
+ margin-top: var(--dees-spacing-sm);
399
+ padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
400
+ border-radius: var(--dees-radius-md);
401
+ font-size: var(--dees-font-caption1-size, 11px);
402
+ line-height: 1.4;
403
+ }
404
+
405
+ .conflict {
406
+ color: var(--dees-color-text-warning);
407
+ background: var(--dees-color-badge-warning-bg);
408
+ }
409
+
410
+ .error {
411
+ color: var(--dees-color-text-error);
412
+ background: var(--dees-color-badge-error-bg);
413
+ }
414
+
415
+ .exchangeList {
416
+ display: grid;
417
+ gap: var(--dees-spacing-sm);
418
+ margin-bottom: var(--dees-spacing-sm);
419
+ }
420
+
421
+ .exchange {
422
+ display: grid;
423
+ gap: 4px;
424
+ padding-left: var(--dees-spacing-sm);
425
+ border-left: 1px solid var(--dees-color-border-default);
426
+ }
427
+
428
+ .question,
429
+ .answer {
430
+ overflow-wrap: anywhere;
431
+ font-size: var(--dees-font-caption1-size, 11px);
432
+ line-height: 1.45;
433
+ white-space: pre-wrap;
434
+ }
435
+
436
+ .question {
437
+ color: var(--dees-color-text-secondary);
438
+ }
439
+
440
+ .answer {
441
+ color: var(--dees-color-text-primary);
442
+ }
443
+
444
+ .exchange.running .answer {
445
+ color: var(--dees-color-text-muted);
446
+ }
447
+
448
+ .exchange.error .answer {
449
+ color: var(--dees-color-text-error);
450
+ }
451
+
452
+ .empty {
453
+ color: var(--dees-color-text-muted);
454
+ font-size: var(--dees-font-caption1-size, 11px);
455
+ }
456
+
457
+ @media (prefers-reduced-motion: reduce) {
458
+ .contextProgressFill {
459
+ transition: none;
460
+ }
461
+ }
462
+ `,
463
+ ];
464
+
465
+ protected willUpdate(changedPropertiesArg: Map<PropertyKey, unknown>): void {
466
+ const availableSections = this.availableSections;
467
+ if (!this.sectionStateInitialized && availableSections.length > 0) {
468
+ this.expandedSection = this.defaultExpandedSection;
469
+ this.sectionStateInitialized = true;
470
+ } else if (
471
+ this.expandedSection !== undefined
472
+ && !availableSections.includes(this.expandedSection)
473
+ ) {
474
+ this.expandedSection = this.defaultExpandedSection;
475
+ }
476
+ if (!changedPropertiesArg.has('scratchpad')) return;
477
+ const next = this.scratchpad;
478
+ if (!next) {
479
+ this.scratchpadDraft = '';
480
+ this.scratchpadDraftRevision = 0;
481
+ this.scratchpadDirty = false;
482
+ this.scratchpadStale = false;
483
+ return;
484
+ }
485
+ const previous = changedPropertiesArg.get('scratchpad') as IHarnessScratchpad | undefined;
486
+ if (!previous || previous.id !== next.id) {
487
+ this.acceptScratchpad(next);
488
+ return;
489
+ }
490
+ if (!this.scratchpadDirty || next.text === this.scratchpadDraft) {
491
+ this.acceptScratchpad(next);
492
+ return;
493
+ }
494
+ if (next.revision > this.scratchpadDraftRevision) this.scratchpadStale = true;
495
+ }
496
+
497
+ public get contextPercentage(): number | undefined {
498
+ const current = this.metrics.currentContextTokens;
499
+ const maximum = this.metrics.maxContextTokens;
500
+ if (current === undefined || maximum === undefined || maximum <= 0) return undefined;
501
+ return Math.min(100, Math.max(0, (current / maximum) * 100));
502
+ }
503
+
504
+ public render(): TemplateResult {
505
+ return html`
506
+ <div class="sidebar">
507
+ ${this.showTasks ? this.renderTasks() : ''}
508
+ ${this.hasMetrics ? this.renderMetrics() : ''}
509
+ ${this.scratchpad ? this.renderScratchpad() : ''}
510
+ ${this.intelligenceEnabled ? this.renderIntelligence() : ''}
511
+ </div>
512
+ `;
513
+ }
514
+
515
+ private get hasMetrics(): boolean {
516
+ return Object.values(this.metrics).some((value) => value !== undefined);
517
+ }
518
+
519
+ private get availableSections(): TSessionSidebarSection[] {
520
+ return [
521
+ ...(this.showTasks ? ['tasks' as const] : []),
522
+ ...(this.hasMetrics ? ['metrics' as const] : []),
523
+ ...(this.scratchpad ? ['scratchpad' as const] : []),
524
+ ...(this.intelligenceEnabled ? ['intelligence' as const] : []),
525
+ ];
526
+ }
527
+
528
+ private get defaultExpandedSection(): TSessionSidebarSection | undefined {
529
+ if (this.showTasks && this.todos.length > 0) return 'tasks';
530
+ if (this.hasMetrics) return 'metrics';
531
+ if (this.scratchpad) return 'scratchpad';
532
+ if (this.intelligenceEnabled) return 'intelligence';
533
+ return this.showTasks ? 'tasks' : undefined;
534
+ }
535
+
536
+ private renderSection(
537
+ idArg: TSessionSidebarSection,
538
+ labelArg: string,
539
+ iconArg: string,
540
+ contentArg: TemplateResult,
541
+ summaryArg = '',
542
+ ): TemplateResult {
543
+ const expanded = this.expandedSection === idArg;
544
+ const contentId = `harness-session-section-${idArg}`;
545
+ return html`
546
+ <section class=${`section ${idArg}Section ${expanded ? 'expanded' : ''}`}>
547
+ <button
548
+ class="sectionHeading"
549
+ type="button"
550
+ aria-expanded=${String(expanded)}
551
+ aria-controls=${contentId}
552
+ @click=${() => {
553
+ this.sectionStateInitialized = true;
554
+ this.expandedSection = expanded ? undefined : idArg;
555
+ }}
556
+ >
557
+ <dees-icon
558
+ icon=${expanded ? 'lucide:ChevronDown' : 'lucide:ChevronRight'}
559
+ iconSize="13"
560
+ ></dees-icon>
561
+ <dees-icon icon=${iconArg} iconSize="13"></dees-icon>
562
+ <span class="sectionHeadingText">${labelArg}</span>
563
+ ${summaryArg ? html`<span class="sectionSummary">${summaryArg}</span>` : ''}
564
+ </button>
565
+ <div id=${contentId} class="sectionContent" ?hidden=${!expanded}>
566
+ ${contentArg}
567
+ </div>
568
+ </section>
569
+ `;
570
+ }
571
+
572
+ private renderTasks(): TemplateResult {
573
+ const completed = this.todos.filter((todo) => todo.status === 'completed').length;
574
+ return this.renderSection(
575
+ 'tasks',
576
+ 'Tasks',
577
+ 'lucide:ListChecks',
578
+ html`<dees-harness-todos .todos=${this.todos} .showProgress=${false}></dees-harness-todos>`,
579
+ this.todos.length > 0 ? `${completed}/${this.todos.length}` : '',
580
+ );
581
+ }
582
+
583
+ private renderMetrics(): TemplateResult {
584
+ const metrics = this.metrics;
585
+ const percentage = this.contextPercentage;
586
+ const rows: Array<[string, number | undefined]> = [
587
+ ['Lifetime used', metrics.lifetimeUsedTokens],
588
+ ['Compactions', metrics.compactionCount],
589
+ ['Before latest compaction', metrics.tokensBeforeLatestCompaction],
590
+ ['Context window', metrics.maxContextTokens],
591
+ ['Current context', metrics.currentContextTokens],
592
+ ];
593
+ return this.renderSection(
594
+ 'metrics',
595
+ 'Tokens',
596
+ 'lucide:Gauge',
597
+ html`
598
+ <div class="metricList">
599
+ ${rows.map(([label, value]) => value === undefined ? '' : html`
600
+ <div class="metricRow">
601
+ <span>${label}</span>
602
+ <span class="metricValue">
603
+ ${formatTokenCount(value)}${label === 'Current context' && percentage !== undefined
604
+ ? ` · ${Math.round(percentage)}%`
605
+ : ''}
606
+ </span>
607
+ </div>
608
+ `)}
609
+ ${percentage === undefined ? '' : html`
610
+ <div
611
+ class="contextProgress"
612
+ role="progressbar"
613
+ aria-label="Current context window usage"
614
+ aria-valuemin="0"
615
+ aria-valuemax="100"
616
+ aria-valuenow=${String(Math.round(percentage))}
617
+ >
618
+ <div class="contextProgressFill" style=${`width: ${percentage}%`}></div>
619
+ </div>
620
+ `}
621
+ </div>
622
+ `,
623
+ );
624
+ }
625
+
626
+ private renderScratchpad(): TemplateResult {
627
+ const scratchpad = this.scratchpad!;
628
+ return this.renderSection(
629
+ 'scratchpad',
630
+ 'Session Scratchpad',
631
+ 'lucide:NotebookPen',
632
+ html`
633
+ <textarea
634
+ aria-label="Session scratchpad"
635
+ maxlength="32768"
636
+ .value=${this.scratchpadDraft}
637
+ ?disabled=${this.disabled || this.scratchpadBusy}
638
+ @input=${(event: InputEvent) => {
639
+ this.scratchpadDraft = (event.currentTarget as HTMLTextAreaElement).value;
640
+ this.scratchpadDirty = this.scratchpadDraft !== scratchpad.text;
641
+ }}
642
+ @keydown=${(event: KeyboardEvent) => {
643
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
644
+ event.preventDefault();
645
+ this.saveScratchpad();
646
+ }
647
+ }}
648
+ ></textarea>
649
+ ${this.scratchpadStale ? html`
650
+ <div class="conflict" role="status">
651
+ A newer revision arrived. Keep this draft on the latest revision or discard it.
652
+ <div class="actions">
653
+ <dees-button
654
+ size="sm"
655
+ type="ghost"
656
+ text="Discard draft"
657
+ @clicked=${this.discardScratchpadDraft}
658
+ ></dees-button>
659
+ <dees-button
660
+ size="sm"
661
+ type="outline"
662
+ text="Keep draft on latest"
663
+ @clicked=${this.rebaseScratchpadDraft}
664
+ ></dees-button>
665
+ </div>
666
+ </div>
667
+ ` : ''}
668
+ ${this.scratchpadError ? html`<div class="error" role="alert">${this.scratchpadError}</div>` : ''}
669
+ <div class="scratchpadFooter">
670
+ <span class="editorMeta">rev. ${scratchpad.revision}</span>
671
+ <dees-button
672
+ size="sm"
673
+ type="accent"
674
+ text="Save"
675
+ .status=${this.scratchpadBusy ? 'pending' : 'normal'}
676
+ ?disabled=${this.disabled || this.scratchpadBusy || !this.scratchpadDirty || this.scratchpadStale}
677
+ @clicked=${this.saveScratchpad}
678
+ ></dees-button>
679
+ </div>
680
+ `,
681
+ );
682
+ }
683
+
684
+ private renderIntelligence(): TemplateResult {
685
+ return this.renderSection(
686
+ 'intelligence',
687
+ 'Session Intelligence',
688
+ 'lucide:Sparkles',
689
+ html`
690
+ <div class="intelligenceHistory" role="log" aria-live="polite">
691
+ ${this.intelligenceExchanges.length ? html`
692
+ <div class="exchangeList">
693
+ ${repeat(
694
+ this.intelligenceExchanges,
695
+ (exchange) => exchange.id,
696
+ (exchange) => this.renderExchange(exchange),
697
+ )}
698
+ </div>
699
+ ` : html`<div class="empty">Ask about this session without interrupting its main work.</div>`}
700
+ </div>
701
+ <div class="intelligenceComposer">
702
+ <textarea
703
+ class="intelligenceInput"
704
+ aria-label="Ask Session Intelligence"
705
+ placeholder="Ask a parallel question…"
706
+ maxlength="8192"
707
+ rows="2"
708
+ .value=${this.intelligenceQuestion}
709
+ ?disabled=${this.disabled || this.intelligenceBusy}
710
+ @input=${(event: InputEvent) => {
711
+ this.intelligenceQuestion = (event.currentTarget as HTMLTextAreaElement).value;
712
+ }}
713
+ @keydown=${(event: KeyboardEvent) => {
714
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
715
+ event.preventDefault();
716
+ this.askIntelligence();
717
+ }
718
+ }}
719
+ ></textarea>
720
+ <div class="intelligenceControls">
721
+ <span class="hint">Parallel session · Ctrl/⌘ + Enter</span>
722
+ <dees-button
723
+ size="sm"
724
+ type="accent"
725
+ text="Ask"
726
+ .status=${this.intelligenceBusy ? 'pending' : 'normal'}
727
+ ?disabled=${this.disabled || this.intelligenceBusy || !this.intelligenceQuestion.trim()}
728
+ @clicked=${this.askIntelligence}
729
+ ></dees-button>
730
+ </div>
731
+ ${this.intelligenceError ? html`<div class="error" role="alert">${this.intelligenceError}</div>` : ''}
732
+ </div>
733
+ `,
734
+ );
735
+ }
736
+
737
+ private renderExchange(exchangeArg: IHarnessIntelligenceExchange): TemplateResult {
738
+ const answer = exchangeArg.status === 'running'
739
+ ? 'Thinking in parallel…'
740
+ : exchangeArg.status === 'error'
741
+ ? exchangeArg.error || 'Session Intelligence failed.'
742
+ : exchangeArg.answer || '';
743
+ return html`
744
+ <article class="exchange ${exchangeArg.status}">
745
+ <div class="question">${exchangeArg.question}</div>
746
+ <div class="answer">${answer}</div>
747
+ ${exchangeArg.model
748
+ ? html`<div class="exchangeMeta">${exchangeArg.model}</div>`
749
+ : ''}
750
+ </article>
751
+ `;
752
+ }
753
+
754
+ private acceptScratchpad(scratchpadArg: IHarnessScratchpad): void {
755
+ this.scratchpadDraft = scratchpadArg.text;
756
+ this.scratchpadDraftRevision = scratchpadArg.revision;
757
+ this.scratchpadDirty = false;
758
+ this.scratchpadStale = false;
759
+ }
760
+
761
+ private readonly discardScratchpadDraft = (): void => {
762
+ if (this.scratchpad) this.acceptScratchpad(this.scratchpad);
763
+ };
764
+
765
+ private readonly rebaseScratchpadDraft = (): void => {
766
+ if (!this.scratchpad) return;
767
+ this.scratchpadDraftRevision = this.scratchpad.revision;
768
+ this.scratchpadStale = false;
769
+ this.scratchpadDirty = this.scratchpadDraft !== this.scratchpad.text;
770
+ };
771
+
772
+ private readonly saveScratchpad = (): void => {
773
+ if (
774
+ !this.scratchpad
775
+ || !this.scratchpadDirty
776
+ || this.scratchpadStale
777
+ || this.disabled
778
+ || this.scratchpadBusy
779
+ ) return;
780
+ const detail: IHarnessScratchpadSaveDetail = {
781
+ text: this.scratchpadDraft,
782
+ expectedRevision: this.scratchpadDraftRevision,
783
+ };
784
+ this.dispatchEvent(new CustomEvent('harness-scratchpad-save', {
785
+ detail,
786
+ bubbles: true,
787
+ composed: true,
788
+ }));
789
+ };
790
+
791
+ private readonly askIntelligence = (): void => {
792
+ const question = this.intelligenceQuestion.trim();
793
+ if (!question || this.disabled || this.intelligenceBusy) return;
794
+ const detail: IHarnessSessionIntelligenceAskDetail = { question };
795
+ this.dispatchEvent(new CustomEvent('harness-session-intelligence-ask', {
796
+ detail,
797
+ bubbles: true,
798
+ composed: true,
799
+ }));
800
+ this.intelligenceQuestion = '';
801
+ };
802
+ }