@design.estate/dees-catalog 9.6.2 → 9.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.
- package/dist_bundle/bundle.js +3138 -2725
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/demos/inputs.d.ts +1 -1
- package/dist_ts_web/demos/inputs.js +5 -4
- package/dist_ts_web/elements/00group-appui/dees-appui/dees-appui.js +6 -1
- package/dist_ts_web/elements/00group-form/dees-form/dees-form.d.ts +5 -1
- package/dist_ts_web/elements/00group-form/dees-form/dees-form.js +27 -3
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +11 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +3 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +4 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +64 -16
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +4 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +52 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +4 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +93 -20
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/renderers.js +7 -21
- package/dist_ts_web/elements/00group-harness/harness.helpers.d.ts +20 -0
- package/dist_ts_web/elements/00group-harness/harness.helpers.js +58 -1
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +6 -0
- package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +8 -0
- package/dist_ts_web/elements/00group-harness/toolregistry.js +10 -2
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/component.d.ts +18 -3
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/component.js +181 -144
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/date-validation.d.ts +13 -0
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/date-validation.js +65 -0
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/datepicker-popup.js +44 -56
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/styles.js +6 -1
- package/dist_ts_web/elements/00group-input/dees-input-datepicker/template.js +11 -5
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.d.ts +8 -8
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.js +6 -53
- package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.d.ts +62 -0
- package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.demo.d.ts +1 -0
- package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.demo.js +3 -0
- package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.js +224 -0
- package/dist_ts_web/elements/00group-input/dees-input-textarea/index.d.ts +1 -0
- package/dist_ts_web/elements/00group-input/dees-input-textarea/index.js +2 -0
- package/dist_ts_web/elements/00group-input/index.d.ts +1 -0
- package/dist_ts_web/elements/00group-input/index.js +2 -1
- package/dist_ts_web/elements/00group-input/text-input-contextmenu.d.ts +29 -0
- package/dist_ts_web/elements/00group-input/text-input-contextmenu.js +29 -0
- package/package.json +2 -2
- package/readme.md +61 -6
- package/scripts/check-packed-consumer.mjs +8 -5
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/demos/inputs.ts +5 -4
- package/ts_web/elements/00group-appui/dees-appui/dees-appui.ts +4 -0
- package/ts_web/elements/00group-form/dees-form/dees-form.ts +25 -2
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +4 -0
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +3 -0
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +49 -14
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +50 -0
- package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +95 -23
- package/ts_web/elements/00group-harness/dees-harness-tool-card/renderers.ts +7 -20
- package/ts_web/elements/00group-harness/harness.helpers.ts +63 -0
- package/ts_web/elements/00group-harness/interfaces.ts +6 -0
- package/ts_web/elements/00group-harness/toolregistry.ts +22 -1
- package/ts_web/elements/00group-input/dees-input-datepicker/component.ts +149 -148
- package/ts_web/elements/00group-input/dees-input-datepicker/date-validation.ts +64 -0
- package/ts_web/elements/00group-input/dees-input-datepicker/datepicker-popup.ts +40 -40
- package/ts_web/elements/00group-input/dees-input-datepicker/styles.ts +5 -0
- package/ts_web/elements/00group-input/dees-input-datepicker/template.ts +10 -4
- package/ts_web/elements/00group-input/dees-input-text/dees-input-text.ts +5 -52
- package/ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.demo.ts +2 -0
- package/ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.ts +128 -0
- package/ts_web/elements/00group-input/dees-input-textarea/index.ts +1 -0
- package/ts_web/elements/00group-input/index.ts +1 -0
- package/ts_web/elements/00group-input/text-input-contextmenu.ts +31 -0
|
@@ -13,6 +13,7 @@ import { DeesInputCheckbox } from '../../00group-input/dees-input-checkbox/dees-
|
|
|
13
13
|
import { DeesInputCode } from '../../00group-input/dees-input-code/dees-input-code.js';
|
|
14
14
|
import { DeesInputDatepicker } from '../../00group-input/dees-input-datepicker/index.js';
|
|
15
15
|
import { DeesInputText } from '../../00group-input/dees-input-text/dees-input-text.js';
|
|
16
|
+
import { DeesInputTextarea } from '../../00group-input/dees-input-textarea/dees-input-textarea.js';
|
|
16
17
|
import { DeesInputQuantitySelector } from '../../00group-input/dees-input-quantityselector/dees-input-quantityselector.js';
|
|
17
18
|
import { DeesInputRadiogroup } from '../../00group-input/dees-input-radiogroup/dees-input-radiogroup.js';
|
|
18
19
|
import { DeesInputDropdown } from '../../00group-input/dees-input-dropdown/dees-input-dropdown.js';
|
|
@@ -44,6 +45,7 @@ const FORM_INPUT_TYPES = [
|
|
|
44
45
|
DeesInputQuantitySelector,
|
|
45
46
|
DeesInputRadiogroup,
|
|
46
47
|
DeesInputText,
|
|
48
|
+
DeesInputTextarea,
|
|
47
49
|
DeesInputToggle,
|
|
48
50
|
DeesInputTypelist,
|
|
49
51
|
DeesInputTags,
|
|
@@ -65,6 +67,9 @@ export interface IFormCompatibleElement extends HTMLElement {
|
|
|
65
67
|
};
|
|
66
68
|
getValue?: () => unknown;
|
|
67
69
|
setValue?: (value: unknown) => void;
|
|
70
|
+
checkValidity?: () => boolean;
|
|
71
|
+
reportValidity?: () => boolean;
|
|
72
|
+
reset?: () => void;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
export type TFormDataValue =
|
|
@@ -96,6 +101,7 @@ export type TFormInputElement =
|
|
|
96
101
|
| DeesInputQuantitySelector
|
|
97
102
|
| DeesInputRadiogroup
|
|
98
103
|
| DeesInputText
|
|
104
|
+
| DeesInputTextarea
|
|
99
105
|
| DeesInputToggle
|
|
100
106
|
| DeesInputTypelist
|
|
101
107
|
| DeesInputTags
|
|
@@ -281,7 +287,9 @@ export class DeesForm extends DeesElement {
|
|
|
281
287
|
public async updateRequiredStatus() {
|
|
282
288
|
let requiredOK = true;
|
|
283
289
|
for (const childArg of this.getFormElements()) {
|
|
284
|
-
if (childArg.
|
|
290
|
+
if (childArg.disabled) continue;
|
|
291
|
+
if ((childArg.required && !getFormElementValue(childArg))
|
|
292
|
+
|| (childArg as IFormCompatibleElement).checkValidity?.() === false) {
|
|
285
293
|
requiredOK = false;
|
|
286
294
|
}
|
|
287
295
|
}
|
|
@@ -312,6 +320,15 @@ export class DeesForm extends DeesElement {
|
|
|
312
320
|
}
|
|
313
321
|
|
|
314
322
|
public async gatherAndDispatch() {
|
|
323
|
+
for (const child of this.getFormElements()) {
|
|
324
|
+
if (child.disabled) continue;
|
|
325
|
+
const input = child as IFormCompatibleElement;
|
|
326
|
+
if (input.checkValidity?.() === false) {
|
|
327
|
+
input.reportValidity?.();
|
|
328
|
+
child.focus();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
315
332
|
const valueObject = await this.collectFormData();
|
|
316
333
|
const formDataEvent = new CustomEvent('formData', {
|
|
317
334
|
detail: {
|
|
@@ -371,12 +388,18 @@ export class DeesForm extends DeesElement {
|
|
|
371
388
|
const inputChildren = this.getFormElements();
|
|
372
389
|
|
|
373
390
|
for (const inputChild of inputChildren) {
|
|
374
|
-
|
|
391
|
+
const input = inputChild as IFormCompatibleElement;
|
|
392
|
+
if (input.reset) input.reset();
|
|
393
|
+
else setFormElementValue(inputChild, null);
|
|
375
394
|
}
|
|
376
395
|
this.setStatus('normal', 'Submit');
|
|
396
|
+
void this.updateRequiredStatus();
|
|
377
397
|
}
|
|
378
398
|
|
|
379
399
|
public async addBehaviours() {
|
|
400
|
+
this.addEventListener('validation-change', event => {
|
|
401
|
+
if (this.getFormElements().includes(event.target as TFormInputElement)) void this.updateRequiredStatus();
|
|
402
|
+
});
|
|
380
403
|
// Use event delegation
|
|
381
404
|
this.addEventListener('keydown', (event: KeyboardEvent) => {
|
|
382
405
|
const target = event.target as DeesElement;
|
|
@@ -203,6 +203,9 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
203
203
|
@property({ attribute: false })
|
|
204
204
|
accessor attachments: IHarnessAttachment[] = [];
|
|
205
205
|
|
|
206
|
+
@property({ attribute: false })
|
|
207
|
+
accessor attachmentsEnabled: boolean = true;
|
|
208
|
+
|
|
206
209
|
@property({ attribute: false })
|
|
207
210
|
accessor suggestions: IHarnessComposerSuggestion[] = [];
|
|
208
211
|
|
|
@@ -763,6 +766,7 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
763
766
|
.contextDescription=${this.heading ? `Replying to ${this.heading}` : ''}
|
|
764
767
|
.value=${this.composerValue}
|
|
765
768
|
.attachments=${this.attachments}
|
|
769
|
+
.attachmentsEnabled=${this.attachmentsEnabled}
|
|
766
770
|
.suggestions=${this.suggestions}
|
|
767
771
|
.maxAttachmentCount=${this.maxAttachmentCount}
|
|
768
772
|
.maxAttachmentBytes=${this.maxAttachmentBytes}
|
|
@@ -44,6 +44,9 @@ export const demoFunc = () => demoPage('Composer', 'Write, attach files, choose
|
|
|
44
44
|
<dees-harness-composer busy queuedCount="2"></dees-harness-composer>
|
|
45
45
|
<div class="dees-demo-label">Waiting for a connection</div>
|
|
46
46
|
<dees-harness-composer disabled placeholder="Connect to a project to chat."></dees-harness-composer>
|
|
47
|
+
<div class="dees-demo-label">Text-only conversation</div>
|
|
48
|
+
<dees-harness-composer .attachmentsEnabled=${false} .showModelControls=${false}
|
|
49
|
+
value="Summarize the outstanding invoices."></dees-harness-composer>
|
|
47
50
|
<div class="dees-demo-label">Files ready to send</div>
|
|
48
51
|
<dees-harness-composer
|
|
49
52
|
.showModelControls=${false}
|
|
@@ -112,6 +112,10 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
112
112
|
@property({ attribute: false })
|
|
113
113
|
accessor attachments: IHarnessAttachment[] = [];
|
|
114
114
|
|
|
115
|
+
/** Host capability: false disables every attachment entry point and sends text only. */
|
|
116
|
+
@property({ attribute: false })
|
|
117
|
+
accessor attachmentsEnabled: boolean = true;
|
|
118
|
+
|
|
115
119
|
@property({ attribute: false })
|
|
116
120
|
accessor suggestions: IHarnessComposerSuggestion[] = [];
|
|
117
121
|
|
|
@@ -260,7 +264,12 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
260
264
|
border: 0;
|
|
261
265
|
border-top: 1px solid var(--dees-color-border-subtle);
|
|
262
266
|
border-radius: 0;
|
|
263
|
-
|
|
267
|
+
/*
|
|
268
|
+
* The docked bar stays full-bleed — its background and top border belong to the whole
|
|
269
|
+
* column — while its content is inset to the transcript's measure, so the prompt lines up
|
|
270
|
+
* with the messages above it instead of floating as a narrower block.
|
|
271
|
+
*/
|
|
272
|
+
padding: 10px max(16px, calc((100% - var(--dees-harness-content-width, 820px)) / 2)) 12px;
|
|
264
273
|
}
|
|
265
274
|
|
|
266
275
|
:host([docked]) .composer.dragOver {
|
|
@@ -463,7 +472,7 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
463
472
|
this.value = '';
|
|
464
473
|
this.resetSuggestionInteraction();
|
|
465
474
|
this.emitInput('');
|
|
466
|
-
|
|
475
|
+
// Height is resized in updated(); measuring here would read the textarea before it rerenders.
|
|
467
476
|
}
|
|
468
477
|
|
|
469
478
|
public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
|
|
@@ -473,6 +482,14 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
473
482
|
|
|
474
483
|
public updated(changedProperties: Map<PropertyKey, unknown>): void {
|
|
475
484
|
super.updated(changedProperties);
|
|
485
|
+
if (['attachmentsEnabled', 'disabled'].some(key => changedProperties.has(key) && changedProperties.get(key) !== undefined)) {
|
|
486
|
+
this.attachmentGeneration++;
|
|
487
|
+
if (!this.attachmentsEnabled || this.disabled) this.handleDragLeave();
|
|
488
|
+
}
|
|
489
|
+
// The grown height is an inline style, so it has to be recomputed for every value change
|
|
490
|
+
// regardless of origin. A host that clears its bound value after send would otherwise leave
|
|
491
|
+
// the composer stuck at the height of the message it just sent.
|
|
492
|
+
if (changedProperties.has('value')) this.autogrow();
|
|
476
493
|
this.syncTextareaSuggestionAria();
|
|
477
494
|
}
|
|
478
495
|
|
|
@@ -486,13 +503,13 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
486
503
|
@dragleave=${this.handleDragLeave}
|
|
487
504
|
@drop=${this.handleDrop}
|
|
488
505
|
>
|
|
489
|
-
|
|
506
|
+
${this.attachmentsEnabled ? html`<input
|
|
490
507
|
class="fileInput"
|
|
491
508
|
type="file"
|
|
492
509
|
multiple
|
|
493
510
|
@change=${this.handleFileInputChange}
|
|
494
|
-
|
|
495
|
-
${this.attachments.length
|
|
511
|
+
/>` : ''}
|
|
512
|
+
${this.attachmentsEnabled && this.attachments.length
|
|
496
513
|
? html`
|
|
497
514
|
<div class="attachmentRow">
|
|
498
515
|
${this.attachments.map((attachment) => this.renderChip(attachment))}
|
|
@@ -505,7 +522,7 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
505
522
|
.value=${this.value}
|
|
506
523
|
aria-label="Message"
|
|
507
524
|
aria-description=${this.contextDescription}
|
|
508
|
-
placeholder=${this.placeholder}
|
|
525
|
+
placeholder=${!this.attachmentsEnabled && this.placeholder === 'Message the agent — drop or paste files to attach…' ? 'Message the agent…' : this.placeholder}
|
|
509
526
|
?disabled=${this.disabled}
|
|
510
527
|
rows="1"
|
|
511
528
|
@input=${this.handleTextInput}
|
|
@@ -518,14 +535,16 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
518
535
|
></textarea>
|
|
519
536
|
</div>
|
|
520
537
|
<div class="controlsRow">
|
|
521
|
-
|
|
538
|
+
${this.attachmentsEnabled ? html`<dees-button
|
|
539
|
+
class="attachButton"
|
|
522
540
|
size="sm"
|
|
523
541
|
type="ghost"
|
|
542
|
+
title="Attach files"
|
|
524
543
|
?disabled=${this.disabled}
|
|
525
544
|
@clicked=${() => this.shadowRoot?.querySelector<HTMLInputElement>('.fileInput')?.click()}
|
|
526
545
|
>
|
|
527
546
|
<dees-icon icon="lucide:Paperclip" iconSize="14"></dees-icon>
|
|
528
|
-
</dees-button
|
|
547
|
+
</dees-button>` : ''}
|
|
529
548
|
${this.modeOptions.length
|
|
530
549
|
? html`
|
|
531
550
|
<dees-input-multitoggle class="modeSwitch" label="Agent mode" .labelPosition=${'none'}
|
|
@@ -704,7 +723,7 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
704
723
|
this.value = (event.currentTarget as HTMLTextAreaElement).value;
|
|
705
724
|
this.resetSuggestionInteraction();
|
|
706
725
|
this.emitInput(this.value);
|
|
707
|
-
|
|
726
|
+
// updated() grows the textarea for every value change, so typing needs no separate call.
|
|
708
727
|
};
|
|
709
728
|
|
|
710
729
|
private handleKeydown = (event: KeyboardEvent): void => {
|
|
@@ -869,6 +888,7 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
869
888
|
};
|
|
870
889
|
|
|
871
890
|
private handlePaste = (event: ClipboardEvent): void => {
|
|
891
|
+
if (!this.attachmentsEnabled || this.disabled) return;
|
|
872
892
|
const files = Array.from(event.clipboardData?.files ?? []);
|
|
873
893
|
if (!files.length) return;
|
|
874
894
|
event.preventDefault();
|
|
@@ -878,6 +898,10 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
878
898
|
private handleDragOver = (event: DragEvent): void => {
|
|
879
899
|
if (!event.dataTransfer?.types.includes('Files')) return;
|
|
880
900
|
event.preventDefault();
|
|
901
|
+
if (!this.attachmentsEnabled || this.disabled) {
|
|
902
|
+
event.dataTransfer.dropEffect = 'none';
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
881
905
|
this.shadowRoot?.querySelector('.composer')?.classList.add('dragOver');
|
|
882
906
|
};
|
|
883
907
|
|
|
@@ -890,23 +914,32 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
890
914
|
const files = Array.from(event.dataTransfer?.files ?? []);
|
|
891
915
|
if (!files.length) return;
|
|
892
916
|
event.preventDefault();
|
|
917
|
+
if (!this.attachmentsEnabled || this.disabled) {
|
|
918
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'none';
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
893
921
|
void this.addFiles(files);
|
|
894
922
|
};
|
|
895
923
|
|
|
896
924
|
private attachmentAdditionTail = Promise.resolve();
|
|
925
|
+
private attachmentGeneration = 0;
|
|
897
926
|
|
|
898
927
|
public addFiles(fileList: FileList | File[] | null): Promise<void> {
|
|
899
928
|
const files = Array.from(fileList ?? []);
|
|
900
|
-
if (!files.length) return Promise.resolve();
|
|
901
|
-
const
|
|
929
|
+
if (!files.length || !this.attachmentsEnabled || this.disabled) return Promise.resolve();
|
|
930
|
+
const generation = this.attachmentGeneration;
|
|
931
|
+
const addition = this.attachmentAdditionTail.then(() => this.addFilesSequential(files, generation));
|
|
902
932
|
this.attachmentAdditionTail = addition.catch(() => undefined);
|
|
903
933
|
return addition;
|
|
904
934
|
}
|
|
905
935
|
|
|
906
|
-
private async addFilesSequential(files: File[]): Promise<void> {
|
|
936
|
+
private async addFilesSequential(files: File[], generation: number): Promise<void> {
|
|
937
|
+
const allowed = () => this.attachmentsEnabled && !this.disabled && generation === this.attachmentGeneration;
|
|
938
|
+
if (!allowed()) return;
|
|
907
939
|
const added: IHarnessAttachment[] = [];
|
|
908
940
|
let totalBytes = this.attachments.reduce((sum, attachment) => sum + attachment.size, 0);
|
|
909
941
|
for (const file of files) {
|
|
942
|
+
if (!allowed()) return;
|
|
910
943
|
if (this.attachments.length + added.length >= this.maxAttachmentCount) {
|
|
911
944
|
this.emit('harness-attachment-error', {
|
|
912
945
|
message: `A maximum of ${this.maxAttachmentCount} attachments is allowed.`,
|
|
@@ -928,6 +961,7 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
928
961
|
}
|
|
929
962
|
try {
|
|
930
963
|
const dataBase64 = await harnessFileToBase64(file);
|
|
964
|
+
if (!allowed()) return;
|
|
931
965
|
const kind = harnessAttachmentKindFor(mediaType);
|
|
932
966
|
added.push({
|
|
933
967
|
id: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
@@ -939,12 +973,13 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
939
973
|
});
|
|
940
974
|
totalBytes += file.size;
|
|
941
975
|
} catch (error) {
|
|
976
|
+
if (!allowed()) return;
|
|
942
977
|
this.emit('harness-attachment-error', {
|
|
943
978
|
message: error instanceof Error ? error.message : String(error),
|
|
944
979
|
});
|
|
945
980
|
}
|
|
946
981
|
}
|
|
947
|
-
if (added.length) {
|
|
982
|
+
if (added.length && allowed()) {
|
|
948
983
|
this.emitAttachments([...this.attachments, ...added]);
|
|
949
984
|
}
|
|
950
985
|
}
|
|
@@ -957,7 +992,7 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
957
992
|
&& (!this.busy || !this.inputSteeringEnabled || !this.value.trim())) return;
|
|
958
993
|
const detail: IHarnessComposerSendDetail = {
|
|
959
994
|
text: this.value,
|
|
960
|
-
attachments: this.attachments,
|
|
995
|
+
attachments: this.attachmentsEnabled ? this.attachments : [],
|
|
961
996
|
account: this.account || undefined,
|
|
962
997
|
model: this.model || undefined,
|
|
963
998
|
reasoningEffort: this.reasoningEffort || undefined,
|
package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts
CHANGED
|
@@ -325,8 +325,42 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
325
325
|
transform: translate3d(0, var(--harness-virtual-offset), 0);
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/*
|
|
329
|
+
* Prose is held to a readable measure while wide payloads keep the whole pane. Applied to
|
|
330
|
+
* the transcript rows themselves because a message host clips on both axes, so nothing
|
|
331
|
+
* nested inside one can bleed back out.
|
|
332
|
+
*/
|
|
333
|
+
.stack > *,
|
|
334
|
+
.virtualItem {
|
|
335
|
+
width: min(var(--dees-harness-content-width, 820px), 100%);
|
|
336
|
+
margin-inline: auto;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.stack > [data-tool-layout='full-row'],
|
|
340
|
+
.stack > [data-tool-layout='subtask'],
|
|
341
|
+
.virtualItem[data-tool-layout='full-row'],
|
|
342
|
+
.virtualItem[data-tool-layout='subtask'] {
|
|
343
|
+
width: 100%;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
* One or two subagents are read like any other content, so they stay on the measure. The
|
|
348
|
+
* pane is only worth taking once three or more cards have to sit side by side.
|
|
349
|
+
*/
|
|
350
|
+
.stack > [data-tool-layout='subtask'][data-tool-count='1'],
|
|
351
|
+
.stack > [data-tool-layout='subtask'][data-tool-count='2'],
|
|
352
|
+
.virtualItem[data-tool-layout='subtask'][data-tool-count='1'],
|
|
353
|
+
.virtualItem[data-tool-layout='subtask'][data-tool-count='2'] {
|
|
354
|
+
width: min(var(--dees-harness-content-width, 820px), 100%);
|
|
355
|
+
}
|
|
356
|
+
|
|
328
357
|
.toolGroup {
|
|
329
358
|
min-width: 0;
|
|
359
|
+
/*
|
|
360
|
+
* Column packing has to measure the row it happens in, not the scroller, now that a
|
|
361
|
+
* compact row is narrower than the pane.
|
|
362
|
+
*/
|
|
363
|
+
container: harness-transcript / inline-size;
|
|
330
364
|
}
|
|
331
365
|
|
|
332
366
|
.toolGrid {
|
|
@@ -882,6 +916,8 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
882
916
|
<article
|
|
883
917
|
class="virtualItem"
|
|
884
918
|
data-virtual-key=${entry.item.key}
|
|
919
|
+
data-tool-layout=${this.timelineItemLayout(entry.item)}
|
|
920
|
+
data-tool-count=${this.timelineItemCount(entry.item)}
|
|
885
921
|
aria-posinset=${entry.index + 1}
|
|
886
922
|
aria-setsize=${this.timelineRenderItems.length}
|
|
887
923
|
style=${`--harness-virtual-offset: ${this.virtualOffsets[entry.index] ?? 0}px;`}
|
|
@@ -906,6 +942,19 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
906
942
|
return indexes.sort((left, right) => left - right);
|
|
907
943
|
}
|
|
908
944
|
|
|
945
|
+
/** Mirrors the row's layout onto the virtual wrapper so width rules match the flowed stack. */
|
|
946
|
+
private timelineItemLayout(itemArg: TTimelineRenderItem): string {
|
|
947
|
+
if (itemArg.kind === 'tool-group') return itemArg.layout;
|
|
948
|
+
if (itemArg.kind === 'message') return itemArg.toolLayout ?? 'message';
|
|
949
|
+
return itemArg.kind;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** Group size, mirrored onto the virtual wrapper so width rules match the flowed stack. */
|
|
953
|
+
private timelineItemCount(itemArg: TTimelineRenderItem): number {
|
|
954
|
+
if (itemArg.kind !== 'tool-group') return 1;
|
|
955
|
+
return itemArg.messages.length >= 4 ? 4 : itemArg.messages.length;
|
|
956
|
+
}
|
|
957
|
+
|
|
909
958
|
private renderTimelineItem(itemArg: TTimelineRenderItem, logicalIndexArg: number): TemplateResult {
|
|
910
959
|
if (itemArg.kind === 'tool-group') return this.renderToolGroup(itemArg);
|
|
911
960
|
if (itemArg.kind === 'message') return this.renderMessage(itemArg, itemArg.key);
|
|
@@ -978,6 +1027,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
978
1027
|
data-timeline-key=${groupArg.key}
|
|
979
1028
|
data-tool-group-id=${groupArg.key}
|
|
980
1029
|
data-tool-layout=${groupArg.layout}
|
|
1030
|
+
data-tool-count=${count >= 4 ? 4 : count}
|
|
981
1031
|
>
|
|
982
1032
|
<div class=${`toolGrid ${countClass}`}>
|
|
983
1033
|
${repeat(
|
|
@@ -111,7 +111,7 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
111
111
|
private fullscreenPortal: HTMLElementTagNameMap['dees-harness-tool-fullscreen'] | null = null;
|
|
112
112
|
private disclosureFrame: number | null = null;
|
|
113
113
|
private returnScrollFrame: number | null = null;
|
|
114
|
-
private
|
|
114
|
+
private subtaskDetailElements: HTMLElement[] = [];
|
|
115
115
|
private subtaskResultResizeObserver: ResizeObserver | null = null;
|
|
116
116
|
private terminalTailFollowing = true;
|
|
117
117
|
private terminalScrollHydrated = false;
|
|
@@ -141,7 +141,11 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
141
141
|
.harness-card.subtaskPreviewOpen {
|
|
142
142
|
display: flex;
|
|
143
143
|
flex-direction: column;
|
|
144
|
-
|
|
144
|
+
/*
|
|
145
|
+
* A cap, not a fixed height: a short brief and result let the card size to their own
|
|
146
|
+
* text, and only content taller than the cap hands the overflow to the inner scrollers.
|
|
147
|
+
*/
|
|
148
|
+
max-height: var(--dees-harness-active-subtask-height, 320px);
|
|
145
149
|
box-sizing: border-box;
|
|
146
150
|
overflow: hidden;
|
|
147
151
|
}
|
|
@@ -169,7 +173,27 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
169
173
|
overflow: hidden;
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
|
|
176
|
+
/*
|
|
177
|
+
* The body takes its height from flex layout rather than a percentage: a capped card has no
|
|
178
|
+
* definite height for a percentage to resolve against, so a 100% height would collapse to
|
|
179
|
+
* auto and the inner scrollers would never engage. As a flex item it shrinks to the cap when
|
|
180
|
+
* the content overflows and sizes to its text when it does not.
|
|
181
|
+
*/
|
|
182
|
+
.harness-card.subtaskPreviewOpen .bodyClipFrame {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.harness-card.subtaskPreviewOpen .body {
|
|
188
|
+
flex: 1;
|
|
189
|
+
min-height: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* A streaming child fills the cap so its transcript has a stable window to scroll in. */
|
|
193
|
+
.harness-card.subtaskPreviewOpen:has(.liveSubtaskBody) {
|
|
194
|
+
height: var(--dees-harness-active-subtask-height, 320px);
|
|
195
|
+
}
|
|
196
|
+
|
|
173
197
|
.harness-card.subtaskPreviewOpen .subtaskPreview {
|
|
174
198
|
min-height: 0;
|
|
175
199
|
height: 100%;
|
|
@@ -259,6 +283,15 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
259
283
|
font-size: 12px;
|
|
260
284
|
}
|
|
261
285
|
|
|
286
|
+
/* Stands in for the body while a finished card is collapsed. */
|
|
287
|
+
.resultSummary {
|
|
288
|
+
display: block;
|
|
289
|
+
margin-top: 2px;
|
|
290
|
+
color: var(--dees-color-text-secondary);
|
|
291
|
+
font-family: var(--dees-font-family-mono, monospace);
|
|
292
|
+
font-size: 11px;
|
|
293
|
+
}
|
|
294
|
+
|
|
262
295
|
.metaColumn {
|
|
263
296
|
display: flex;
|
|
264
297
|
align-items: center;
|
|
@@ -680,10 +713,27 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
680
713
|
&& (this.call.subtask.status.type === 'busy' || this.call.subtask.status.type === 'error');
|
|
681
714
|
}
|
|
682
715
|
if (this.call.status === 'error' || this.call.isError) return true;
|
|
716
|
+
// A card that opened while it was live closes again once the work has settled, so a finished
|
|
717
|
+
// run stops holding transcript height. 'stopped' is deliberately excluded: a killed command
|
|
718
|
+
// keeps its partial output on screen, like an error does.
|
|
719
|
+
if (this.call.status === 'completed' && this.resolvedCollapseWhenFinished) return false;
|
|
683
720
|
if (typeof descriptor.defaultExpanded === 'function') return descriptor.defaultExpanded(this.enrichedCall);
|
|
684
721
|
return descriptor.defaultExpanded ?? false;
|
|
685
722
|
}
|
|
686
723
|
|
|
724
|
+
private get resolvedCollapseWhenFinished(): boolean {
|
|
725
|
+
const collapseWhenFinished = this.descriptor.collapseWhenFinished;
|
|
726
|
+
if (typeof collapseWhenFinished === 'function') return collapseWhenFinished(this.enrichedCall);
|
|
727
|
+
return collapseWhenFinished ?? false;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** Outcome line shown only while collapsed; the subtitle already carries the request. */
|
|
731
|
+
private get collapsedSummary(): string {
|
|
732
|
+
if (this.isOpen || !this.descriptor.summary) return '';
|
|
733
|
+
if (this.call.status !== 'completed' && this.call.status !== 'stopped') return '';
|
|
734
|
+
return this.descriptor.summary(this.enrichedCall).trim();
|
|
735
|
+
}
|
|
736
|
+
|
|
687
737
|
private get hasSubtaskPreview(): boolean {
|
|
688
738
|
return this.allowSubtaskStreams
|
|
689
739
|
&& this.descriptor.kind === 'subtask'
|
|
@@ -704,11 +754,19 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
704
754
|
const open = this.isOpen;
|
|
705
755
|
const renderBody = this.bodyMounted || open;
|
|
706
756
|
const label = typeof descriptor.label === 'function' ? descriptor.label(call) : descriptor.label;
|
|
707
|
-
const
|
|
757
|
+
const derivedSubtitle = descriptor.subtitle?.(call) ?? '';
|
|
758
|
+
// A producer-supplied title describes the call in its own words — "Read SKILL.md" rather than
|
|
759
|
+
// the sed invocation behind it — so it leads, and the derived line stays as the tooltip.
|
|
760
|
+
const producerTitle = typeof call.title === 'string' ? call.title.trim() : '';
|
|
761
|
+
const subtitle = producerTitle || derivedSubtitle;
|
|
762
|
+
const subtitleTooltip = producerTitle && derivedSubtitle && derivedSubtitle !== producerTitle
|
|
763
|
+
? `${producerTitle}\n${derivedSubtitle}`
|
|
764
|
+
: subtitle;
|
|
708
765
|
const effectiveStatus = harnessEffectiveToolStatus(call);
|
|
709
766
|
const statusTone = effectiveStatus === 'completed' ? 'success'
|
|
710
767
|
: effectiveStatus === 'stopped' ? 'warning' : effectiveStatus;
|
|
711
768
|
const duration = harnessFormatDuration(call.startedAt, call.finishedAt);
|
|
769
|
+
const collapsedSummary = this.collapsedSummary;
|
|
712
770
|
return html`
|
|
713
771
|
<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}` : ''}"
|
|
714
772
|
@mouseenter=${this.cancelTerminalTailReturn}
|
|
@@ -734,7 +792,10 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
734
792
|
${this.renderMcpBadges(call)}
|
|
735
793
|
</span>
|
|
736
794
|
${subtitle
|
|
737
|
-
? html`<dees-harness-overflow-text class="subtitle" title=${
|
|
795
|
+
? html`<dees-harness-overflow-text class="subtitle" title=${subtitleTooltip}>${subtitle}</dees-harness-overflow-text>`
|
|
796
|
+
: ''}
|
|
797
|
+
${collapsedSummary
|
|
798
|
+
? html`<dees-harness-overflow-text class="resultSummary" title=${collapsedSummary}>${collapsedSummary}</dees-harness-overflow-text>`
|
|
738
799
|
: ''}
|
|
739
800
|
</span>
|
|
740
801
|
<span class="metaColumn">
|
|
@@ -1047,30 +1108,39 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
1047
1108
|
};
|
|
1048
1109
|
|
|
1049
1110
|
private readonly syncSubtaskResultScroll = (): void => {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1111
|
+
// Both panels hide their scrollbar, so the mask is the only cue that there is more text.
|
|
1112
|
+
// Leaving the brief unwired made a long brief look truncated rather than scrollable.
|
|
1113
|
+
for (const panel of this.subtaskDetailElements) {
|
|
1114
|
+
panel.classList.toggle('fadeTop', panel.scrollTop > 1);
|
|
1115
|
+
panel.classList.toggle(
|
|
1116
|
+
'fadeBottom',
|
|
1117
|
+
panel.scrollHeight - panel.scrollTop - panel.clientHeight > 1,
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1057
1120
|
};
|
|
1058
1121
|
|
|
1059
1122
|
private connectSubtaskResultScroll(): void {
|
|
1060
|
-
const
|
|
1061
|
-
'.harness-subtask-detail-
|
|
1062
|
-
|
|
1063
|
-
|
|
1123
|
+
const nextPanels = [...(this.shadowRoot?.querySelectorAll<HTMLElement>(
|
|
1124
|
+
'.harness-subtask-detail-brief .harness-subtask-detail-text,'
|
|
1125
|
+
+ ' .harness-subtask-detail-result .harness-subtask-detail-text',
|
|
1126
|
+
) ?? [])];
|
|
1127
|
+
const unchanged = nextPanels.length === this.subtaskDetailElements.length
|
|
1128
|
+
&& nextPanels.every((panel, index) => panel === this.subtaskDetailElements[index]);
|
|
1129
|
+
if (unchanged) {
|
|
1064
1130
|
this.syncSubtaskResultScroll();
|
|
1065
1131
|
return;
|
|
1066
1132
|
}
|
|
1067
|
-
|
|
1133
|
+
for (const panel of this.subtaskDetailElements) {
|
|
1134
|
+
panel.removeEventListener('scroll', this.syncSubtaskResultScroll);
|
|
1135
|
+
}
|
|
1068
1136
|
this.subtaskResultResizeObserver?.disconnect();
|
|
1069
|
-
this.
|
|
1070
|
-
if (!
|
|
1071
|
-
nextResult.addEventListener('scroll', this.syncSubtaskResultScroll, { passive: true });
|
|
1137
|
+
this.subtaskDetailElements = nextPanels;
|
|
1138
|
+
if (!nextPanels.length) return;
|
|
1072
1139
|
this.subtaskResultResizeObserver ??= new ResizeObserver(this.syncSubtaskResultScroll);
|
|
1073
|
-
|
|
1140
|
+
for (const panel of nextPanels) {
|
|
1141
|
+
panel.addEventListener('scroll', this.syncSubtaskResultScroll, { passive: true });
|
|
1142
|
+
this.subtaskResultResizeObserver.observe(panel);
|
|
1143
|
+
}
|
|
1074
1144
|
this.syncSubtaskResultScroll();
|
|
1075
1145
|
}
|
|
1076
1146
|
|
|
@@ -1116,8 +1186,10 @@ export class DeesHarnessToolCard extends DeesElement {
|
|
|
1116
1186
|
this.bodyResizeObserver?.disconnect();
|
|
1117
1187
|
this.bodyResizeObserver = null;
|
|
1118
1188
|
this.observedBodyInner = null;
|
|
1119
|
-
|
|
1120
|
-
|
|
1189
|
+
for (const panel of this.subtaskDetailElements) {
|
|
1190
|
+
panel.removeEventListener('scroll', this.syncSubtaskResultScroll);
|
|
1191
|
+
}
|
|
1192
|
+
this.subtaskDetailElements = [];
|
|
1121
1193
|
this.subtaskResultResizeObserver?.disconnect();
|
|
1122
1194
|
this.subtaskResultResizeObserver = null;
|
|
1123
1195
|
await super.disconnectedCallback();
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
harnessLanguageForPath,
|
|
7
7
|
harnessParseTodos,
|
|
8
8
|
harnessStringifyValue,
|
|
9
|
+
harnessStripAnsi,
|
|
9
10
|
harnessSummarizeValue,
|
|
11
|
+
harnessTerminalOutcome,
|
|
10
12
|
} from '../harness.helpers.js';
|
|
11
13
|
import '../dees-harness-todos/dees-harness-todos.js';
|
|
12
14
|
|
|
@@ -125,35 +127,20 @@ export const applyPatchFirstPath = (call: IHarnessToolCall): string => {
|
|
|
125
127
|
return match ? match[1].trim() : '';
|
|
126
128
|
};
|
|
127
129
|
|
|
128
|
-
// eslint-disable-next-line no-control-regex
|
|
129
|
-
const ansiEscapePattern = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07]*(?:\x07|\x1b\\)|\x1b[=>]/g;
|
|
130
|
-
|
|
131
|
-
const stripAnsi = (text: string): string => text.replace(ansiEscapePattern, '');
|
|
132
|
-
|
|
133
130
|
const renderTerminal = (call: IHarnessToolCall, helpers: IHarnessToolRenderHelpers): TemplateResult => {
|
|
134
131
|
const input = inputRecord(call);
|
|
135
132
|
const command = String(input.command ?? '');
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
let stderr = '';
|
|
139
|
-
let nestedExitCode: number | undefined;
|
|
140
|
-
if (harnessIsRecord(output)) {
|
|
141
|
-
stdout = typeof output.stdout === 'string' ? output.stdout : harnessStringifyValue(output.stdout ?? '');
|
|
142
|
-
stderr = typeof output.stderr === 'string' ? output.stderr : '';
|
|
143
|
-
nestedExitCode = typeof output.exitCode === 'number' ? output.exitCode : undefined;
|
|
144
|
-
} else {
|
|
145
|
-
stdout = harnessStringifyValue(output);
|
|
146
|
-
}
|
|
147
|
-
const exitCode = typeof call.exitCode === 'number' ? call.exitCode : nestedExitCode;
|
|
133
|
+
// Shared with the collapsed-card summary so both read the same outcome.
|
|
134
|
+
const { stdout, stderr, exitCode } = harnessTerminalOutcome(call);
|
|
148
135
|
const running = call.status === 'pending' || call.status === 'running';
|
|
149
136
|
return html`
|
|
150
137
|
<div class="harness-terminal">
|
|
151
138
|
${command
|
|
152
139
|
? html`<div class="harness-terminal-command"><span class="prompt">$</span> ${command}</div>`
|
|
153
140
|
: ''}
|
|
154
|
-
${stdout ? html`<pre class="harness-terminal-output">${
|
|
155
|
-
${stderr ? html`<pre class="harness-terminal-output tone-error">${
|
|
156
|
-
${call.errorText ? html`<pre class="harness-terminal-output tone-error">${
|
|
141
|
+
${stdout ? html`<pre class="harness-terminal-output">${harnessStripAnsi(stdout)}</pre>` : ''}
|
|
142
|
+
${stderr ? html`<pre class="harness-terminal-output tone-error">${harnessStripAnsi(stderr)}</pre>` : ''}
|
|
143
|
+
${call.errorText ? html`<pre class="harness-terminal-output tone-error">${harnessStripAnsi(call.errorText)}</pre>` : ''}
|
|
157
144
|
${running && !stdout && !stderr
|
|
158
145
|
? html`<div class="harness-terminal-running"><span class="cursor"></span> running…</div>`
|
|
159
146
|
: ''}
|