@artooi/ag-ui-web-component 0.10.0 → 0.12.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/CHANGELOG.md +78 -1
- package/README.md +144 -7
- package/dist/ag-ui-web-component.bundle.js +237 -40
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/core/ag_ui_chat.d.ts +29 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/agui_client.d.ts +28 -1
- package/dist/core/agui_client.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +50 -0
- package/dist/core/run_index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +942 -101
- package/dist/index.js.map +4 -4
- package/dist/ui/approval_card.d.ts +51 -0
- package/dist/ui/approval_card.d.ts.map +1 -0
- package/dist/ui/attachment_chips.d.ts.map +1 -1
- package/dist/ui/attachment_tray.d.ts.map +1 -1
- package/dist/ui/checkpoint_menu.d.ts +32 -0
- package/dist/ui/checkpoint_menu.d.ts.map +1 -0
- package/dist/ui/question_card.d.ts +52 -0
- package/dist/ui/question_card.d.ts.map +1 -0
- package/dist/ui/skills_menu.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thoughts_block.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +26 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/ag_ui_chat.ts +263 -4
- package/src/core/agui_client.ts +86 -11
- package/src/core/run_index.ts +91 -0
- package/src/index.ts +16 -0
- package/src/ui/approval_card.ts +119 -0
- package/src/ui/attachment_chips.ts +5 -0
- package/src/ui/attachment_tray.ts +8 -0
- package/src/ui/checkpoint_menu.ts +153 -0
- package/src/ui/question_card.ts +216 -0
- package/src/ui/skills_menu.ts +6 -0
- package/src/ui/styles.ts +197 -0
- package/src/ui/thoughts_block.ts +1 -0
- package/src/ui/thread_drawer.ts +11 -0
- package/src/ui/ui_strings.ts +45 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -443,62 +443,6 @@ function createStateHookTools(hook) {
|
|
|
443
443
|
return tools;
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
// src/ui/attachment_chips.ts
|
|
447
|
-
function renderAttachmentChips(refs) {
|
|
448
|
-
const list = document.createElement("div");
|
|
449
|
-
list.className = "attachment-chips";
|
|
450
|
-
for (const ref of refs) {
|
|
451
|
-
list.appendChild(renderChip(ref));
|
|
452
|
-
}
|
|
453
|
-
return list;
|
|
454
|
-
}
|
|
455
|
-
function renderChip(ref) {
|
|
456
|
-
const chip = document.createElement("div");
|
|
457
|
-
chip.className = "attachment-chip attachment-chip--ready";
|
|
458
|
-
const icon = document.createElement("span");
|
|
459
|
-
icon.className = "attachment-chip-icon";
|
|
460
|
-
icon.textContent = iconFor(ref.mime);
|
|
461
|
-
icon.setAttribute("aria-hidden", "true");
|
|
462
|
-
const name = document.createElement("span");
|
|
463
|
-
name.className = "attachment-chip-name";
|
|
464
|
-
name.textContent = ref.name;
|
|
465
|
-
name.title = ref.name;
|
|
466
|
-
const size = document.createElement("span");
|
|
467
|
-
size.className = "attachment-chip-size";
|
|
468
|
-
size.textContent = formatBytes(ref.size);
|
|
469
|
-
chip.append(icon, name, size);
|
|
470
|
-
return chip;
|
|
471
|
-
}
|
|
472
|
-
function iconFor(mime) {
|
|
473
|
-
if (mime.startsWith("image/")) {
|
|
474
|
-
return "\u{1F5BC}";
|
|
475
|
-
}
|
|
476
|
-
if (mime === "application/pdf") {
|
|
477
|
-
return "\u{1F4D5}";
|
|
478
|
-
}
|
|
479
|
-
if (mime.startsWith("text/")) {
|
|
480
|
-
return "\u{1F4C4}";
|
|
481
|
-
}
|
|
482
|
-
return "\u{1F4CE}";
|
|
483
|
-
}
|
|
484
|
-
function formatBytes(bytes) {
|
|
485
|
-
if (bytes < 1024) {
|
|
486
|
-
return `${bytes} B`;
|
|
487
|
-
}
|
|
488
|
-
const units = ["KB", "MB", "GB"];
|
|
489
|
-
let value = bytes / 1024;
|
|
490
|
-
let unit = 0;
|
|
491
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
492
|
-
value /= 1024;
|
|
493
|
-
unit += 1;
|
|
494
|
-
}
|
|
495
|
-
const rounded = value < 10 ? Math.round(value * 10) / 10 : Math.round(value);
|
|
496
|
-
return `${rounded} ${units[unit]}`;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// src/ui/attachment_tray.ts
|
|
500
|
-
import { randomUUID } from "@ag-ui/client";
|
|
501
|
-
|
|
502
446
|
// src/ui/ui_strings.ts
|
|
503
447
|
var DEFAULT_UI_STRINGS = {
|
|
504
448
|
title: "Assistant",
|
|
@@ -507,6 +451,11 @@ var DEFAULT_UI_STRINGS = {
|
|
|
507
451
|
collapse: "Collapse",
|
|
508
452
|
expand: "Expand",
|
|
509
453
|
toggleTheme: "Toggle theme",
|
|
454
|
+
checkpoints: "Continue a run",
|
|
455
|
+
noCheckpoints: "Nothing to continue yet.",
|
|
456
|
+
resumeRun: "Resume",
|
|
457
|
+
forkRun: "Fork",
|
|
458
|
+
forkedRun: "branched",
|
|
510
459
|
conversation: "Conversation",
|
|
511
460
|
thinking: "Assistant is thinking\u2026",
|
|
512
461
|
thoughts: "Thoughts",
|
|
@@ -537,6 +486,14 @@ var DEFAULT_UI_STRINGS = {
|
|
|
537
486
|
confirmRun: "Run \u201C{tool}\u201D?",
|
|
538
487
|
confirm: "Confirm",
|
|
539
488
|
cancel: "Cancel",
|
|
489
|
+
approveAction: "Approve action",
|
|
490
|
+
approvalPrompt: "Approve this action?",
|
|
491
|
+
approve: "Approve",
|
|
492
|
+
deny: "Deny",
|
|
493
|
+
askUserAction: "Question",
|
|
494
|
+
otherOption: "Other\u2026",
|
|
495
|
+
answerPlaceholder: "Type your answer\u2026",
|
|
496
|
+
submit: "Submit",
|
|
540
497
|
chats: "Chats",
|
|
541
498
|
noConversations: "No conversations yet.",
|
|
542
499
|
rename: "Rename",
|
|
@@ -568,7 +525,120 @@ function mergeUiStrings(overrides) {
|
|
|
568
525
|
return merged;
|
|
569
526
|
}
|
|
570
527
|
|
|
528
|
+
// src/ui/approval_card.ts
|
|
529
|
+
function actionButton(modifier, label) {
|
|
530
|
+
const button = document.createElement("button");
|
|
531
|
+
button.type = "button";
|
|
532
|
+
button.className = `approval-btn approval-btn--${modifier}`;
|
|
533
|
+
button.setAttribute("part", `approval-button approval-${modifier}`);
|
|
534
|
+
button.textContent = label;
|
|
535
|
+
return button;
|
|
536
|
+
}
|
|
537
|
+
function requestApproval(host, request, options = {}) {
|
|
538
|
+
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
539
|
+
return new Promise((resolve) => {
|
|
540
|
+
const card = document.createElement("div");
|
|
541
|
+
card.className = "approval";
|
|
542
|
+
card.setAttribute("part", "approval");
|
|
543
|
+
if (request.toolName !== void 0) {
|
|
544
|
+
card.setAttribute("data-tool-name", request.toolName);
|
|
545
|
+
}
|
|
546
|
+
card.setAttribute("role", "group");
|
|
547
|
+
card.setAttribute("aria-label", strings.approveAction);
|
|
548
|
+
const body = document.createElement("div");
|
|
549
|
+
body.className = "approval-body";
|
|
550
|
+
body.setAttribute("part", "approval-body");
|
|
551
|
+
body.textContent = request.message ?? strings.approvalPrompt;
|
|
552
|
+
const actions = document.createElement("div");
|
|
553
|
+
actions.className = "approval-actions";
|
|
554
|
+
actions.setAttribute("part", "approval-actions");
|
|
555
|
+
const deny = actionButton("deny", strings.deny);
|
|
556
|
+
const approve = actionButton("approve", strings.approve);
|
|
557
|
+
let settled = false;
|
|
558
|
+
const close = (approved) => {
|
|
559
|
+
if (settled) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
settled = true;
|
|
563
|
+
deny.disabled = true;
|
|
564
|
+
approve.disabled = true;
|
|
565
|
+
card.setAttribute("data-resolved", approved ? "approved" : "denied");
|
|
566
|
+
resolve(approved);
|
|
567
|
+
};
|
|
568
|
+
deny.addEventListener("click", () => close(false));
|
|
569
|
+
approve.addEventListener("click", () => close(true));
|
|
570
|
+
options.signal?.addEventListener("abort", () => close(false), { once: true });
|
|
571
|
+
actions.append(deny, approve);
|
|
572
|
+
card.append(body, actions);
|
|
573
|
+
host.appendChild(card);
|
|
574
|
+
if (options.signal?.aborted === true) {
|
|
575
|
+
close(false);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
approve.focus();
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/ui/attachment_chips.ts
|
|
583
|
+
function renderAttachmentChips(refs) {
|
|
584
|
+
const list = document.createElement("div");
|
|
585
|
+
list.className = "attachment-chips";
|
|
586
|
+
list.setAttribute("part", "attachment-chips");
|
|
587
|
+
for (const ref of refs) {
|
|
588
|
+
list.appendChild(renderChip(ref));
|
|
589
|
+
}
|
|
590
|
+
return list;
|
|
591
|
+
}
|
|
592
|
+
function renderChip(ref) {
|
|
593
|
+
const chip = document.createElement("div");
|
|
594
|
+
chip.className = "attachment-chip attachment-chip--ready";
|
|
595
|
+
chip.setAttribute("part", "attachment-chip");
|
|
596
|
+
const icon = document.createElement("span");
|
|
597
|
+
icon.className = "attachment-chip-icon";
|
|
598
|
+
icon.setAttribute("part", "attachment-chip-icon");
|
|
599
|
+
icon.textContent = iconFor(ref.mime);
|
|
600
|
+
icon.setAttribute("aria-hidden", "true");
|
|
601
|
+
const name = document.createElement("span");
|
|
602
|
+
name.className = "attachment-chip-name";
|
|
603
|
+
name.setAttribute("part", "attachment-chip-name");
|
|
604
|
+
name.textContent = ref.name;
|
|
605
|
+
name.title = ref.name;
|
|
606
|
+
const size = document.createElement("span");
|
|
607
|
+
size.className = "attachment-chip-size";
|
|
608
|
+
size.setAttribute("part", "attachment-chip-size");
|
|
609
|
+
size.textContent = formatBytes(ref.size);
|
|
610
|
+
chip.append(icon, name, size);
|
|
611
|
+
return chip;
|
|
612
|
+
}
|
|
613
|
+
function iconFor(mime) {
|
|
614
|
+
if (mime.startsWith("image/")) {
|
|
615
|
+
return "\u{1F5BC}";
|
|
616
|
+
}
|
|
617
|
+
if (mime === "application/pdf") {
|
|
618
|
+
return "\u{1F4D5}";
|
|
619
|
+
}
|
|
620
|
+
if (mime.startsWith("text/")) {
|
|
621
|
+
return "\u{1F4C4}";
|
|
622
|
+
}
|
|
623
|
+
return "\u{1F4CE}";
|
|
624
|
+
}
|
|
625
|
+
function formatBytes(bytes) {
|
|
626
|
+
if (bytes < 1024) {
|
|
627
|
+
return `${bytes} B`;
|
|
628
|
+
}
|
|
629
|
+
const units = ["KB", "MB", "GB"];
|
|
630
|
+
let value = bytes / 1024;
|
|
631
|
+
let unit = 0;
|
|
632
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
633
|
+
value /= 1024;
|
|
634
|
+
unit += 1;
|
|
635
|
+
}
|
|
636
|
+
const rounded = value < 10 ? Math.round(value * 10) / 10 : Math.round(value);
|
|
637
|
+
return `${rounded} ${units[unit]}`;
|
|
638
|
+
}
|
|
639
|
+
|
|
571
640
|
// src/ui/attachment_tray.ts
|
|
641
|
+
import { randomUUID } from "@ag-ui/client";
|
|
572
642
|
var AttachmentTray = class {
|
|
573
643
|
/** The tray root; append above the input row. Hidden while empty. */
|
|
574
644
|
element;
|
|
@@ -708,23 +778,29 @@ var AttachmentTray = class {
|
|
|
708
778
|
#renderChip(item) {
|
|
709
779
|
const chip = document.createElement("div");
|
|
710
780
|
chip.className = `attachment-chip attachment-chip--${item.status}`;
|
|
781
|
+
chip.setAttribute("part", "attachment-chip");
|
|
711
782
|
const icon = document.createElement("span");
|
|
712
783
|
icon.className = "attachment-chip-icon";
|
|
784
|
+
icon.setAttribute("part", "attachment-chip-icon");
|
|
713
785
|
icon.textContent = iconFor(item.file.type);
|
|
714
786
|
icon.setAttribute("aria-hidden", "true");
|
|
715
787
|
const name = document.createElement("span");
|
|
716
788
|
name.className = "attachment-chip-name";
|
|
789
|
+
name.setAttribute("part", "attachment-chip-name");
|
|
717
790
|
name.textContent = item.file.name;
|
|
718
791
|
name.title = item.file.name;
|
|
719
792
|
const meta = document.createElement("span");
|
|
720
793
|
meta.className = "attachment-chip-size";
|
|
794
|
+
meta.setAttribute("part", "attachment-chip-size");
|
|
721
795
|
meta.textContent = item.status === ATTACHMENT_STATUS.ERROR ? item.error : formatBytes(item.file.size);
|
|
722
796
|
chip.append(icon, name, meta);
|
|
723
797
|
if (item.status === ATTACHMENT_STATUS.UPLOADING) {
|
|
724
798
|
const bar = document.createElement("div");
|
|
725
799
|
bar.className = "attachment-chip-bar";
|
|
800
|
+
bar.setAttribute("part", "attachment-chip-bar");
|
|
726
801
|
const fill = document.createElement("div");
|
|
727
802
|
fill.className = "attachment-chip-bar-fill";
|
|
803
|
+
fill.setAttribute("part", "attachment-chip-bar-fill");
|
|
728
804
|
fill.style.width = `${Math.round(item.progress * 100)}%`;
|
|
729
805
|
bar.appendChild(fill);
|
|
730
806
|
chip.appendChild(bar);
|
|
@@ -733,6 +809,7 @@ var AttachmentTray = class {
|
|
|
733
809
|
const retry = document.createElement("button");
|
|
734
810
|
retry.type = "button";
|
|
735
811
|
retry.className = "attachment-chip-retry";
|
|
812
|
+
retry.setAttribute("part", "attachment-chip-retry");
|
|
736
813
|
retry.title = this.#strings.retry;
|
|
737
814
|
retry.setAttribute("aria-label", this.#strings.retryUpload);
|
|
738
815
|
retry.textContent = "\u21BB";
|
|
@@ -742,6 +819,7 @@ var AttachmentTray = class {
|
|
|
742
819
|
const remove = document.createElement("button");
|
|
743
820
|
remove.type = "button";
|
|
744
821
|
remove.className = "attachment-chip-remove";
|
|
822
|
+
remove.setAttribute("part", "attachment-chip-remove");
|
|
745
823
|
remove.title = this.#strings.remove;
|
|
746
824
|
remove.setAttribute("aria-label", this.#strings.removeAttachment);
|
|
747
825
|
remove.textContent = "\u2715";
|
|
@@ -768,8 +846,140 @@ function accepts(accept, file) {
|
|
|
768
846
|
});
|
|
769
847
|
}
|
|
770
848
|
|
|
849
|
+
// src/ui/relative_time.ts
|
|
850
|
+
function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
|
|
851
|
+
if (!Number.isFinite(timestamp)) {
|
|
852
|
+
return strings.justNow;
|
|
853
|
+
}
|
|
854
|
+
const seconds = Math.round((now - timestamp) / 1e3);
|
|
855
|
+
if (seconds < 60) {
|
|
856
|
+
return strings.justNow;
|
|
857
|
+
}
|
|
858
|
+
const minutes = Math.round(seconds / 60);
|
|
859
|
+
if (minutes < 60) {
|
|
860
|
+
return strings.minutesAgo.replace("{n}", String(minutes));
|
|
861
|
+
}
|
|
862
|
+
const hours = Math.round(minutes / 60);
|
|
863
|
+
if (hours < 24) {
|
|
864
|
+
return strings.hoursAgo.replace("{n}", String(hours));
|
|
865
|
+
}
|
|
866
|
+
const days = Math.round(hours / 24);
|
|
867
|
+
if (days < 7) {
|
|
868
|
+
return strings.daysAgo.replace("{n}", String(days));
|
|
869
|
+
}
|
|
870
|
+
return strings.weeksAgo.replace("{n}", String(Math.round(days / 7)));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/ui/checkpoint_menu.ts
|
|
874
|
+
var CheckpointMenu = class {
|
|
875
|
+
/** The panel root. Append to the chat shell; hidden until opened. */
|
|
876
|
+
element;
|
|
877
|
+
#onPick;
|
|
878
|
+
#list;
|
|
879
|
+
#heading;
|
|
880
|
+
#strings;
|
|
881
|
+
#runs = [];
|
|
882
|
+
constructor(onPick, strings = DEFAULT_UI_STRINGS) {
|
|
883
|
+
this.#onPick = onPick;
|
|
884
|
+
this.#strings = strings;
|
|
885
|
+
this.element = document.createElement("div");
|
|
886
|
+
this.element.className = "checkpoints";
|
|
887
|
+
this.element.setAttribute("part", "checkpoints");
|
|
888
|
+
this.element.setAttribute("role", "dialog");
|
|
889
|
+
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
890
|
+
this.element.hidden = true;
|
|
891
|
+
const header = document.createElement("div");
|
|
892
|
+
header.className = "checkpoints-header";
|
|
893
|
+
header.setAttribute("part", "checkpoints-header");
|
|
894
|
+
this.#heading = document.createElement("span");
|
|
895
|
+
this.#heading.className = "checkpoints-title";
|
|
896
|
+
this.#heading.textContent = strings.checkpoints;
|
|
897
|
+
header.append(this.#heading);
|
|
898
|
+
this.#list = document.createElement("div");
|
|
899
|
+
this.#list.className = "checkpoints-list";
|
|
900
|
+
this.#list.setAttribute("part", "checkpoints-list");
|
|
901
|
+
this.element.append(header, this.#list);
|
|
902
|
+
this.element.addEventListener("keydown", (event) => {
|
|
903
|
+
if (event.key === "Escape") {
|
|
904
|
+
event.stopPropagation();
|
|
905
|
+
this.close();
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
/** Replace the rows. The host passes only `continuable` runs. */
|
|
910
|
+
setRuns(runs) {
|
|
911
|
+
this.#runs = runs;
|
|
912
|
+
this.#render();
|
|
913
|
+
}
|
|
914
|
+
/** Re-localize a panel built before the host's strings resolved. */
|
|
915
|
+
setStrings(strings) {
|
|
916
|
+
this.#strings = strings;
|
|
917
|
+
this.element.setAttribute("aria-label", strings.checkpoints);
|
|
918
|
+
this.#heading.textContent = strings.checkpoints;
|
|
919
|
+
this.#render();
|
|
920
|
+
}
|
|
921
|
+
open() {
|
|
922
|
+
this.element.hidden = false;
|
|
923
|
+
}
|
|
924
|
+
close() {
|
|
925
|
+
this.element.hidden = true;
|
|
926
|
+
}
|
|
927
|
+
get open_() {
|
|
928
|
+
return !this.element.hidden;
|
|
929
|
+
}
|
|
930
|
+
#render() {
|
|
931
|
+
this.#list.replaceChildren();
|
|
932
|
+
if (this.#runs.length === 0) {
|
|
933
|
+
const empty = document.createElement("div");
|
|
934
|
+
empty.className = "checkpoints-empty";
|
|
935
|
+
empty.setAttribute("part", "checkpoints-empty");
|
|
936
|
+
empty.textContent = this.#strings.noCheckpoints;
|
|
937
|
+
this.#list.append(empty);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
for (const run of this.#runs) {
|
|
941
|
+
this.#list.append(this.#row(run));
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
#row(run) {
|
|
945
|
+
const row = document.createElement("div");
|
|
946
|
+
row.className = "checkpoint-row";
|
|
947
|
+
row.setAttribute("part", "checkpoint-row");
|
|
948
|
+
const label = document.createElement("span");
|
|
949
|
+
label.className = "checkpoint-label";
|
|
950
|
+
label.textContent = run.started_at === null ? run.run_id : relativeTime(Date.parse(run.started_at), Date.now(), this.#strings);
|
|
951
|
+
label.title = run.run_id;
|
|
952
|
+
row.append(label);
|
|
953
|
+
if (run.parent_run_id !== null) {
|
|
954
|
+
const branch = document.createElement("span");
|
|
955
|
+
branch.className = "checkpoint-branch";
|
|
956
|
+
branch.setAttribute("part", "checkpoint-branch");
|
|
957
|
+
branch.textContent = this.#strings.forkedRun;
|
|
958
|
+
branch.title = run.parent_run_id;
|
|
959
|
+
row.append(branch);
|
|
960
|
+
}
|
|
961
|
+
row.append(
|
|
962
|
+
this.#action(run.run_id, "resume", this.#strings.resumeRun),
|
|
963
|
+
this.#action(run.run_id, "fork", this.#strings.forkRun)
|
|
964
|
+
);
|
|
965
|
+
return row;
|
|
966
|
+
}
|
|
967
|
+
#action(runId, verb, label) {
|
|
968
|
+
const button = document.createElement("button");
|
|
969
|
+
button.type = "button";
|
|
970
|
+
button.className = `checkpoint-action checkpoint-${verb}`;
|
|
971
|
+
button.setAttribute("part", `checkpoint-action checkpoint-${verb}`);
|
|
972
|
+
button.textContent = label;
|
|
973
|
+
button.addEventListener("click", () => {
|
|
974
|
+
this.close();
|
|
975
|
+
this.#onPick(runId, verb);
|
|
976
|
+
});
|
|
977
|
+
return button;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
|
|
771
981
|
// src/ui/confirmation_card.ts
|
|
772
|
-
function
|
|
982
|
+
function actionButton2(modifier, label) {
|
|
773
983
|
const button = document.createElement("button");
|
|
774
984
|
button.type = "button";
|
|
775
985
|
button.className = `confirm-btn confirm-btn--${modifier}`;
|
|
@@ -797,8 +1007,8 @@ function requestConfirmation(host, request, options = {}) {
|
|
|
797
1007
|
const actions = document.createElement("div");
|
|
798
1008
|
actions.className = "confirm-actions";
|
|
799
1009
|
actions.setAttribute("part", "confirm-actions");
|
|
800
|
-
const cancel =
|
|
801
|
-
const confirm =
|
|
1010
|
+
const cancel = actionButton2("cancel", strings.cancel);
|
|
1011
|
+
const confirm = actionButton2("confirm", strings.confirm);
|
|
802
1012
|
let settled = false;
|
|
803
1013
|
const close = (accepted) => {
|
|
804
1014
|
if (settled) {
|
|
@@ -833,6 +1043,149 @@ function prettifyToolName(name) {
|
|
|
833
1043
|
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
834
1044
|
}
|
|
835
1045
|
|
|
1046
|
+
// src/ui/question_card.ts
|
|
1047
|
+
function answerInput(placeholder) {
|
|
1048
|
+
const input = document.createElement("input");
|
|
1049
|
+
input.type = "text";
|
|
1050
|
+
input.className = "question-input";
|
|
1051
|
+
input.setAttribute("part", "question-input");
|
|
1052
|
+
input.placeholder = placeholder;
|
|
1053
|
+
return input;
|
|
1054
|
+
}
|
|
1055
|
+
function requestQuestion(host, request, options = {}) {
|
|
1056
|
+
const strings = options.strings ?? DEFAULT_UI_STRINGS;
|
|
1057
|
+
const choices = request.options ?? [];
|
|
1058
|
+
const hasChoices = choices.length > 0;
|
|
1059
|
+
const allowsText = !hasChoices || request.allowCustom === true;
|
|
1060
|
+
return new Promise((resolve) => {
|
|
1061
|
+
const card = document.createElement("div");
|
|
1062
|
+
card.className = "question";
|
|
1063
|
+
card.setAttribute("part", "question");
|
|
1064
|
+
card.setAttribute("role", "group");
|
|
1065
|
+
card.setAttribute("aria-label", strings.askUserAction);
|
|
1066
|
+
const body = document.createElement("div");
|
|
1067
|
+
body.className = "question-body";
|
|
1068
|
+
body.setAttribute("part", "question-body");
|
|
1069
|
+
body.textContent = request.question;
|
|
1070
|
+
const form = document.createElement("div");
|
|
1071
|
+
form.className = "question-options";
|
|
1072
|
+
form.setAttribute("part", "question-options");
|
|
1073
|
+
const group = `q-${choices.length}-${request.question.length}`;
|
|
1074
|
+
const radios = [];
|
|
1075
|
+
for (const choice of choices) {
|
|
1076
|
+
const label = document.createElement("label");
|
|
1077
|
+
label.className = "question-choice";
|
|
1078
|
+
label.setAttribute("part", "question-choice");
|
|
1079
|
+
const radio = document.createElement("input");
|
|
1080
|
+
radio.type = "radio";
|
|
1081
|
+
radio.name = group;
|
|
1082
|
+
radio.value = choice;
|
|
1083
|
+
radio.setAttribute("part", "question-radio");
|
|
1084
|
+
const text2 = document.createElement("span");
|
|
1085
|
+
text2.setAttribute("part", "question-choice-text");
|
|
1086
|
+
text2.textContent = choice;
|
|
1087
|
+
label.append(radio, text2);
|
|
1088
|
+
form.appendChild(label);
|
|
1089
|
+
radios.push(radio);
|
|
1090
|
+
}
|
|
1091
|
+
let otherRadio = null;
|
|
1092
|
+
let input = null;
|
|
1093
|
+
if (allowsText) {
|
|
1094
|
+
input = answerInput(strings.answerPlaceholder);
|
|
1095
|
+
if (hasChoices) {
|
|
1096
|
+
const label = document.createElement("label");
|
|
1097
|
+
label.className = "question-choice";
|
|
1098
|
+
label.setAttribute("part", "question-choice");
|
|
1099
|
+
otherRadio = document.createElement("input");
|
|
1100
|
+
otherRadio.type = "radio";
|
|
1101
|
+
otherRadio.name = group;
|
|
1102
|
+
otherRadio.value = "";
|
|
1103
|
+
otherRadio.setAttribute("part", "question-radio");
|
|
1104
|
+
const text2 = document.createElement("span");
|
|
1105
|
+
text2.setAttribute("part", "question-choice-text");
|
|
1106
|
+
text2.textContent = strings.otherOption;
|
|
1107
|
+
label.append(otherRadio, text2);
|
|
1108
|
+
form.appendChild(label);
|
|
1109
|
+
input.disabled = true;
|
|
1110
|
+
}
|
|
1111
|
+
form.appendChild(input);
|
|
1112
|
+
}
|
|
1113
|
+
const actions = document.createElement("div");
|
|
1114
|
+
actions.className = "question-actions";
|
|
1115
|
+
actions.setAttribute("part", "question-actions");
|
|
1116
|
+
const submit = document.createElement("button");
|
|
1117
|
+
submit.type = "button";
|
|
1118
|
+
submit.className = "question-btn";
|
|
1119
|
+
submit.setAttribute("part", "question-button");
|
|
1120
|
+
submit.textContent = strings.submit;
|
|
1121
|
+
actions.appendChild(submit);
|
|
1122
|
+
let settled = false;
|
|
1123
|
+
const answerFor = () => {
|
|
1124
|
+
const picked = radios.find((r) => r.checked);
|
|
1125
|
+
if (picked !== void 0) {
|
|
1126
|
+
return picked.value;
|
|
1127
|
+
}
|
|
1128
|
+
if (input !== null && (otherRadio === null || otherRadio.checked)) {
|
|
1129
|
+
const typed = input.value.trim();
|
|
1130
|
+
return typed === "" ? null : typed;
|
|
1131
|
+
}
|
|
1132
|
+
return null;
|
|
1133
|
+
};
|
|
1134
|
+
const refresh = () => {
|
|
1135
|
+
if (input !== null && otherRadio !== null) {
|
|
1136
|
+
input.disabled = !otherRadio.checked;
|
|
1137
|
+
}
|
|
1138
|
+
submit.disabled = answerFor() === null;
|
|
1139
|
+
};
|
|
1140
|
+
const close = (answer) => {
|
|
1141
|
+
if (settled) {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
settled = true;
|
|
1145
|
+
submit.disabled = true;
|
|
1146
|
+
for (const radio of radios) {
|
|
1147
|
+
radio.disabled = true;
|
|
1148
|
+
}
|
|
1149
|
+
if (otherRadio !== null) {
|
|
1150
|
+
otherRadio.disabled = true;
|
|
1151
|
+
}
|
|
1152
|
+
if (input !== null) {
|
|
1153
|
+
input.disabled = true;
|
|
1154
|
+
}
|
|
1155
|
+
card.setAttribute("data-resolved", answer === "" ? "cancelled" : "answered");
|
|
1156
|
+
resolve(answer);
|
|
1157
|
+
};
|
|
1158
|
+
for (const radio of [...radios, ...otherRadio !== null ? [otherRadio] : []]) {
|
|
1159
|
+
radio.addEventListener("change", refresh);
|
|
1160
|
+
}
|
|
1161
|
+
input?.addEventListener("input", refresh);
|
|
1162
|
+
input?.addEventListener("keydown", (event) => {
|
|
1163
|
+
if (event.key === "Enter") {
|
|
1164
|
+
event.preventDefault();
|
|
1165
|
+
const answer = answerFor();
|
|
1166
|
+
if (answer !== null) {
|
|
1167
|
+
close(answer);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
submit.addEventListener("click", () => {
|
|
1172
|
+
const answer = answerFor();
|
|
1173
|
+
if (answer !== null) {
|
|
1174
|
+
close(answer);
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
options.signal?.addEventListener("abort", () => close(""), { once: true });
|
|
1178
|
+
card.append(body, form, actions);
|
|
1179
|
+
host.appendChild(card);
|
|
1180
|
+
if (options.signal?.aborted === true) {
|
|
1181
|
+
close("");
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
refresh();
|
|
1185
|
+
(hasChoices ? radios[0] : input)?.focus();
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
|
|
836
1189
|
// node_modules/.pnpm/dompurify@3.4.7/node_modules/dompurify/dist/purify.es.mjs
|
|
837
1190
|
function _arrayLikeToArray(r, a) {
|
|
838
1191
|
(null == a || a > r.length) && (a = r.length);
|
|
@@ -2033,7 +2386,7 @@ function createDOMPurify() {
|
|
|
2033
2386
|
}
|
|
2034
2387
|
var purify = createDOMPurify();
|
|
2035
2388
|
|
|
2036
|
-
// node_modules/.pnpm/marked@18.0.
|
|
2389
|
+
// node_modules/.pnpm/marked@18.0.5/node_modules/marked/lib/marked.esm.js
|
|
2037
2390
|
function M() {
|
|
2038
2391
|
return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
|
|
2039
2392
|
}
|
|
@@ -2077,15 +2430,15 @@ var F = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|
|
|
|
2077
2430
|
var $e = /^[^\n]+/;
|
|
2078
2431
|
var U = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
|
|
2079
2432
|
var Le = d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", U).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
|
|
2080
|
-
var _e = d(/^(bull)([ \t][^\n]
|
|
2433
|
+
var _e = d(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, j).getRegex();
|
|
2081
2434
|
var H = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
|
|
2082
2435
|
var K = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
2083
2436
|
var ze = d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", K).replace("tag", H).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
|
|
2084
|
-
var le = d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
|
|
2437
|
+
var le = d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
|
|
2085
2438
|
var Me = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", le).getRegex();
|
|
2086
2439
|
var W = { blockquote: Me, code: we, def: Le, fences: ye, heading: Pe, hr: B, html: ze, lheading: ae, list: _e, newline: Oe, paragraph: le, table: _, text: $e };
|
|
2087
2440
|
var se = d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
|
|
2088
|
-
var Ee = { ...W, lheading: Se, table: se, paragraph: d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex() };
|
|
2441
|
+
var Ee = { ...W, lheading: Se, table: se, paragraph: d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex() };
|
|
2089
2442
|
var Ie = { ...W, html: d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", K).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: _, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d(F).replace("hr", B).replace("heading", ` *#{1,6} *[^
|
|
2090
2443
|
]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
|
|
2091
2444
|
var Ae = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
|
@@ -3376,9 +3729,11 @@ var SkillsMenu = class {
|
|
|
3376
3729
|
this.#onPick = onPick;
|
|
3377
3730
|
this.chips = document.createElement("div");
|
|
3378
3731
|
this.chips.className = "skill-chips";
|
|
3732
|
+
this.chips.setAttribute("part", "skill-chips");
|
|
3379
3733
|
this.chips.hidden = true;
|
|
3380
3734
|
this.palette = document.createElement("div");
|
|
3381
3735
|
this.palette.className = "skill-palette";
|
|
3736
|
+
this.palette.setAttribute("part", "skill-palette");
|
|
3382
3737
|
this.palette.setAttribute("role", "listbox");
|
|
3383
3738
|
this.palette.hidden = true;
|
|
3384
3739
|
}
|
|
@@ -3473,6 +3828,7 @@ var SkillsMenu = class {
|
|
|
3473
3828
|
const button = document.createElement("button");
|
|
3474
3829
|
button.type = "button";
|
|
3475
3830
|
button.className = "skill-chip";
|
|
3831
|
+
button.setAttribute("part", "skill-chip");
|
|
3476
3832
|
button.textContent = skill.title;
|
|
3477
3833
|
button.addEventListener("click", () => this.#pick(skill));
|
|
3478
3834
|
this.chips.appendChild(button);
|
|
@@ -3484,15 +3840,18 @@ var SkillsMenu = class {
|
|
|
3484
3840
|
const item = document.createElement("button");
|
|
3485
3841
|
item.type = "button";
|
|
3486
3842
|
item.className = "skill-item";
|
|
3843
|
+
item.setAttribute("part", "skill-item");
|
|
3487
3844
|
item.setAttribute("role", "option");
|
|
3488
3845
|
item.setAttribute("aria-selected", index === this.#activeIndex ? "true" : "false");
|
|
3489
3846
|
const title = document.createElement("span");
|
|
3490
3847
|
title.className = "skill-item-title";
|
|
3848
|
+
title.setAttribute("part", "skill-item-title");
|
|
3491
3849
|
title.textContent = skill.title;
|
|
3492
3850
|
item.appendChild(title);
|
|
3493
3851
|
if (skill.description !== void 0) {
|
|
3494
3852
|
const desc = document.createElement("span");
|
|
3495
3853
|
desc.className = "skill-item-desc";
|
|
3854
|
+
desc.setAttribute("part", "skill-item-desc");
|
|
3496
3855
|
desc.textContent = skill.description;
|
|
3497
3856
|
item.appendChild(desc);
|
|
3498
3857
|
}
|
|
@@ -4529,10 +4888,131 @@ var STYLES = `
|
|
|
4529
4888
|
color: #ffffff;
|
|
4530
4889
|
}
|
|
4531
4890
|
|
|
4532
|
-
/*
|
|
4533
|
-
.
|
|
4891
|
+
/* Approval card \u2014 the server-side-tool gate (approve/deny an interrupt). */
|
|
4892
|
+
.approval {
|
|
4893
|
+
align-self: stretch;
|
|
4894
|
+
box-sizing: border-box;
|
|
4534
4895
|
display: flex;
|
|
4535
|
-
flex-
|
|
4896
|
+
flex-direction: column;
|
|
4897
|
+
gap: 8px;
|
|
4898
|
+
padding: 12px;
|
|
4899
|
+
background: var(--ag-ui-bg);
|
|
4900
|
+
border: 1px solid var(--ag-ui-accent);
|
|
4901
|
+
border-radius: 10px;
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
.approval[data-resolved] {
|
|
4905
|
+
opacity: 0.7;
|
|
4906
|
+
border-color: var(--ag-ui-border);
|
|
4907
|
+
}
|
|
4908
|
+
|
|
4909
|
+
.approval-body {
|
|
4910
|
+
font-weight: 600;
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4913
|
+
.approval-actions {
|
|
4914
|
+
display: flex;
|
|
4915
|
+
gap: 8px;
|
|
4916
|
+
justify-content: flex-end;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
.approval-btn {
|
|
4920
|
+
border: 1px solid var(--ag-ui-border);
|
|
4921
|
+
border-radius: 8px;
|
|
4922
|
+
padding: 8px 14px;
|
|
4923
|
+
font: inherit;
|
|
4924
|
+
font-weight: 600;
|
|
4925
|
+
cursor: pointer;
|
|
4926
|
+
background: var(--ag-ui-bg);
|
|
4927
|
+
color: var(--ag-ui-fg);
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
.approval-btn:disabled {
|
|
4931
|
+
cursor: default;
|
|
4932
|
+
opacity: 0.6;
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4935
|
+
.approval-btn--approve {
|
|
4936
|
+
border-color: var(--ag-ui-accent);
|
|
4937
|
+
background: var(--ag-ui-accent);
|
|
4938
|
+
color: #ffffff;
|
|
4939
|
+
}
|
|
4940
|
+
|
|
4941
|
+
/* Question card \u2014 the built-in ask_user prompt (radios and/or free text). */
|
|
4942
|
+
.question {
|
|
4943
|
+
align-self: stretch;
|
|
4944
|
+
box-sizing: border-box;
|
|
4945
|
+
display: flex;
|
|
4946
|
+
flex-direction: column;
|
|
4947
|
+
gap: 8px;
|
|
4948
|
+
padding: 12px;
|
|
4949
|
+
background: var(--ag-ui-bg);
|
|
4950
|
+
border: 1px solid var(--ag-ui-accent);
|
|
4951
|
+
border-radius: 10px;
|
|
4952
|
+
}
|
|
4953
|
+
|
|
4954
|
+
.question[data-resolved] {
|
|
4955
|
+
opacity: 0.7;
|
|
4956
|
+
border-color: var(--ag-ui-border);
|
|
4957
|
+
}
|
|
4958
|
+
|
|
4959
|
+
.question-body {
|
|
4960
|
+
font-weight: 600;
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
.question-options {
|
|
4964
|
+
display: flex;
|
|
4965
|
+
flex-direction: column;
|
|
4966
|
+
gap: 6px;
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4969
|
+
.question-choice {
|
|
4970
|
+
display: flex;
|
|
4971
|
+
align-items: center;
|
|
4972
|
+
gap: 8px;
|
|
4973
|
+
cursor: pointer;
|
|
4974
|
+
}
|
|
4975
|
+
|
|
4976
|
+
.question-input {
|
|
4977
|
+
box-sizing: border-box;
|
|
4978
|
+
width: 100%;
|
|
4979
|
+
padding: 8px 10px;
|
|
4980
|
+
font: inherit;
|
|
4981
|
+
color: var(--ag-ui-fg);
|
|
4982
|
+
background: var(--ag-ui-bg);
|
|
4983
|
+
border: 1px solid var(--ag-ui-border);
|
|
4984
|
+
border-radius: 8px;
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4987
|
+
.question-input:disabled {
|
|
4988
|
+
opacity: 0.6;
|
|
4989
|
+
}
|
|
4990
|
+
|
|
4991
|
+
.question-actions {
|
|
4992
|
+
display: flex;
|
|
4993
|
+
justify-content: flex-end;
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
.question-btn {
|
|
4997
|
+
border: 1px solid var(--ag-ui-accent);
|
|
4998
|
+
border-radius: 8px;
|
|
4999
|
+
padding: 8px 14px;
|
|
5000
|
+
font: inherit;
|
|
5001
|
+
font-weight: 600;
|
|
5002
|
+
cursor: pointer;
|
|
5003
|
+
background: var(--ag-ui-accent);
|
|
5004
|
+
color: #ffffff;
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
.question-btn:disabled {
|
|
5008
|
+
cursor: default;
|
|
5009
|
+
opacity: 0.6;
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
/* Skills \u2014 chips row + the /-command palette, above the input. */
|
|
5013
|
+
.skill-chips {
|
|
5014
|
+
display: flex;
|
|
5015
|
+
flex-wrap: wrap;
|
|
4536
5016
|
gap: 6px;
|
|
4537
5017
|
padding: 10px 12px;
|
|
4538
5018
|
}
|
|
@@ -4609,6 +5089,82 @@ var STYLES = `
|
|
|
4609
5089
|
display: none;
|
|
4610
5090
|
}
|
|
4611
5091
|
|
|
5092
|
+
.checkpoints {
|
|
5093
|
+
position: absolute;
|
|
5094
|
+
inset-block-start: 3rem;
|
|
5095
|
+
inset-inline: 0.75rem;
|
|
5096
|
+
z-index: 6;
|
|
5097
|
+
display: flex;
|
|
5098
|
+
flex-direction: column;
|
|
5099
|
+
gap: 0.25rem;
|
|
5100
|
+
padding: 0.5rem;
|
|
5101
|
+
border: 1px solid var(--agui-border, #d7d7dc);
|
|
5102
|
+
border-radius: 0.5rem;
|
|
5103
|
+
background: var(--agui-surface, #fff);
|
|
5104
|
+
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
5105
|
+
max-height: 60%;
|
|
5106
|
+
overflow-y: auto;
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5109
|
+
.checkpoints[hidden] {
|
|
5110
|
+
display: none;
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
.checkpoints-title {
|
|
5114
|
+
font-size: 0.75rem;
|
|
5115
|
+
font-weight: 600;
|
|
5116
|
+
opacity: 0.7;
|
|
5117
|
+
}
|
|
5118
|
+
|
|
5119
|
+
.checkpoints-empty {
|
|
5120
|
+
padding: 0.5rem 0.25rem;
|
|
5121
|
+
font-size: 0.8125rem;
|
|
5122
|
+
opacity: 0.7;
|
|
5123
|
+
}
|
|
5124
|
+
|
|
5125
|
+
.checkpoint-row {
|
|
5126
|
+
display: flex;
|
|
5127
|
+
align-items: center;
|
|
5128
|
+
gap: 0.5rem;
|
|
5129
|
+
padding: 0.25rem;
|
|
5130
|
+
border-radius: 0.375rem;
|
|
5131
|
+
}
|
|
5132
|
+
|
|
5133
|
+
.checkpoint-row:hover {
|
|
5134
|
+
background: var(--agui-hover, #f3f3f5);
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
.checkpoint-label {
|
|
5138
|
+
flex: 1;
|
|
5139
|
+
font-size: 0.8125rem;
|
|
5140
|
+
white-space: nowrap;
|
|
5141
|
+
overflow: hidden;
|
|
5142
|
+
text-overflow: ellipsis;
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
.checkpoint-branch {
|
|
5146
|
+
font-size: 0.6875rem;
|
|
5147
|
+
padding: 0 0.375rem;
|
|
5148
|
+
border-radius: 999px;
|
|
5149
|
+
background: var(--agui-hover, #f3f3f5);
|
|
5150
|
+
opacity: 0.8;
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
.checkpoint-action {
|
|
5154
|
+
font: inherit;
|
|
5155
|
+
font-size: 0.75rem;
|
|
5156
|
+
cursor: pointer;
|
|
5157
|
+
padding: 0.125rem 0.5rem;
|
|
5158
|
+
border: 1px solid var(--agui-border, #d7d7dc);
|
|
5159
|
+
border-radius: 0.375rem;
|
|
5160
|
+
background: transparent;
|
|
5161
|
+
color: inherit;
|
|
5162
|
+
}
|
|
5163
|
+
|
|
5164
|
+
.checkpoint-action:hover {
|
|
5165
|
+
background: var(--agui-hover, #f3f3f5);
|
|
5166
|
+
}
|
|
5167
|
+
|
|
4612
5168
|
.drawer-backdrop {
|
|
4613
5169
|
position: absolute;
|
|
4614
5170
|
inset: 0;
|
|
@@ -4804,6 +5360,7 @@ var ThoughtsBlock = class {
|
|
|
4804
5360
|
this.#toggle.setAttribute("aria-expanded", "true");
|
|
4805
5361
|
this.#label = document.createElement("span");
|
|
4806
5362
|
this.#label.className = "thoughts-label";
|
|
5363
|
+
this.#label.setAttribute("part", "thoughts-label");
|
|
4807
5364
|
this.#label.textContent = strings.thinking;
|
|
4808
5365
|
this.#toggle.append(this.#label);
|
|
4809
5366
|
this.#body = document.createElement("pre");
|
|
@@ -4838,30 +5395,6 @@ var ThoughtsBlock = class {
|
|
|
4838
5395
|
}
|
|
4839
5396
|
};
|
|
4840
5397
|
|
|
4841
|
-
// src/ui/relative_time.ts
|
|
4842
|
-
function relativeTime(timestamp, now = Date.now(), strings = DEFAULT_UI_STRINGS) {
|
|
4843
|
-
if (!Number.isFinite(timestamp)) {
|
|
4844
|
-
return strings.justNow;
|
|
4845
|
-
}
|
|
4846
|
-
const seconds = Math.round((now - timestamp) / 1e3);
|
|
4847
|
-
if (seconds < 60) {
|
|
4848
|
-
return strings.justNow;
|
|
4849
|
-
}
|
|
4850
|
-
const minutes = Math.round(seconds / 60);
|
|
4851
|
-
if (minutes < 60) {
|
|
4852
|
-
return strings.minutesAgo.replace("{n}", String(minutes));
|
|
4853
|
-
}
|
|
4854
|
-
const hours = Math.round(minutes / 60);
|
|
4855
|
-
if (hours < 24) {
|
|
4856
|
-
return strings.hoursAgo.replace("{n}", String(hours));
|
|
4857
|
-
}
|
|
4858
|
-
const days = Math.round(hours / 24);
|
|
4859
|
-
if (days < 7) {
|
|
4860
|
-
return strings.daysAgo.replace("{n}", String(days));
|
|
4861
|
-
}
|
|
4862
|
-
return strings.weeksAgo.replace("{n}", String(Math.round(days / 7)));
|
|
4863
|
-
}
|
|
4864
|
-
|
|
4865
5398
|
// src/ui/thread_drawer.ts
|
|
4866
5399
|
var ThreadDrawer = class {
|
|
4867
5400
|
/** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
|
|
@@ -5012,12 +5545,15 @@ var ThreadDrawer = class {
|
|
|
5012
5545
|
select.setAttribute("part", "drawer-row-select");
|
|
5013
5546
|
const title = document.createElement("span");
|
|
5014
5547
|
title.className = "drawer-row-title";
|
|
5548
|
+
title.setAttribute("part", "drawer-row-title");
|
|
5015
5549
|
title.textContent = meta.title;
|
|
5016
5550
|
const time = document.createElement("span");
|
|
5017
5551
|
time.className = "drawer-row-time";
|
|
5552
|
+
time.setAttribute("part", "drawer-row-time");
|
|
5018
5553
|
time.textContent = relativeTime(meta.updatedAt, void 0, this.#strings);
|
|
5019
5554
|
const preview = document.createElement("span");
|
|
5020
5555
|
preview.className = "drawer-row-preview";
|
|
5556
|
+
preview.setAttribute("part", "drawer-row-preview");
|
|
5021
5557
|
preview.textContent = meta.preview;
|
|
5022
5558
|
select.append(title, time, preview);
|
|
5023
5559
|
select.addEventListener("click", () => {
|
|
@@ -5027,6 +5563,7 @@ var ThreadDrawer = class {
|
|
|
5027
5563
|
const rename = document.createElement("button");
|
|
5028
5564
|
rename.type = "button";
|
|
5029
5565
|
rename.className = "drawer-row-rename";
|
|
5566
|
+
rename.setAttribute("part", "drawer-row-rename");
|
|
5030
5567
|
rename.title = this.#strings.rename;
|
|
5031
5568
|
rename.setAttribute("aria-label", this.#strings.renameConversation);
|
|
5032
5569
|
rename.textContent = "\u270E";
|
|
@@ -5034,12 +5571,14 @@ var ThreadDrawer = class {
|
|
|
5034
5571
|
const remove = document.createElement("button");
|
|
5035
5572
|
remove.type = "button";
|
|
5036
5573
|
remove.className = "drawer-row-delete";
|
|
5574
|
+
remove.setAttribute("part", "drawer-row-delete");
|
|
5037
5575
|
remove.title = this.#strings.delete;
|
|
5038
5576
|
remove.setAttribute("aria-label", this.#strings.deleteConversation);
|
|
5039
5577
|
remove.textContent = "\u{1F5D1}";
|
|
5040
5578
|
remove.addEventListener("click", () => this.#confirmDelete(row, meta));
|
|
5041
5579
|
const actions = document.createElement("div");
|
|
5042
5580
|
actions.className = "drawer-row-actions";
|
|
5581
|
+
actions.setAttribute("part", "drawer-row-actions");
|
|
5043
5582
|
actions.append(rename, remove);
|
|
5044
5583
|
row.append(select, actions);
|
|
5045
5584
|
return row;
|
|
@@ -5049,6 +5588,7 @@ var ThreadDrawer = class {
|
|
|
5049
5588
|
const input = document.createElement("input");
|
|
5050
5589
|
input.type = "text";
|
|
5051
5590
|
input.className = "drawer-rename-input";
|
|
5591
|
+
input.setAttribute("part", "drawer-rename-input");
|
|
5052
5592
|
input.value = meta.title;
|
|
5053
5593
|
let done = false;
|
|
5054
5594
|
const commit = () => {
|
|
@@ -5089,17 +5629,21 @@ var ThreadDrawer = class {
|
|
|
5089
5629
|
#confirmDelete(row, meta) {
|
|
5090
5630
|
const confirm = document.createElement("div");
|
|
5091
5631
|
confirm.className = "drawer-confirm";
|
|
5632
|
+
confirm.setAttribute("part", "drawer-confirm");
|
|
5092
5633
|
const label = document.createElement("span");
|
|
5093
5634
|
label.className = "drawer-confirm-label";
|
|
5635
|
+
label.setAttribute("part", "drawer-confirm-label");
|
|
5094
5636
|
label.textContent = this.#strings.deletePrompt;
|
|
5095
5637
|
const yes = document.createElement("button");
|
|
5096
5638
|
yes.type = "button";
|
|
5097
5639
|
yes.className = "drawer-confirm-yes";
|
|
5640
|
+
yes.setAttribute("part", "drawer-confirm-yes");
|
|
5098
5641
|
yes.textContent = this.#strings.delete;
|
|
5099
5642
|
yes.addEventListener("click", () => this.#callbacks.onDelete(meta.threadId));
|
|
5100
5643
|
const no = document.createElement("button");
|
|
5101
5644
|
no.type = "button";
|
|
5102
5645
|
no.className = "drawer-confirm-no";
|
|
5646
|
+
no.setAttribute("part", "drawer-confirm-no");
|
|
5103
5647
|
no.textContent = this.#strings.cancel;
|
|
5104
5648
|
no.addEventListener("click", () => this.#renderList());
|
|
5105
5649
|
confirm.append(label, yes, no);
|
|
@@ -5341,7 +5885,10 @@ var VoiceInput = class {
|
|
|
5341
5885
|
};
|
|
5342
5886
|
|
|
5343
5887
|
// src/core/agui_client.ts
|
|
5344
|
-
import {
|
|
5888
|
+
import {
|
|
5889
|
+
buildResumeArray,
|
|
5890
|
+
randomUUID as randomUUID2
|
|
5891
|
+
} from "@ag-ui/client";
|
|
5345
5892
|
var ConnectionLostError = class extends Error {
|
|
5346
5893
|
constructor(message) {
|
|
5347
5894
|
super(message);
|
|
@@ -5354,6 +5901,7 @@ var AgUiClient = class {
|
|
|
5354
5901
|
#getTools;
|
|
5355
5902
|
#getContext;
|
|
5356
5903
|
#executeTool;
|
|
5904
|
+
#resolveInterrupts;
|
|
5357
5905
|
#onPersist;
|
|
5358
5906
|
#connectionLostMessage;
|
|
5359
5907
|
// Set by cancel(); reset at the top of each #run(). Checked by the loop so
|
|
@@ -5365,6 +5913,7 @@ var AgUiClient = class {
|
|
|
5365
5913
|
this.#getTools = config.getTools ?? (() => []);
|
|
5366
5914
|
this.#getContext = config.getContext ?? (() => []);
|
|
5367
5915
|
this.#executeTool = config.executeTool ?? null;
|
|
5916
|
+
this.#resolveInterrupts = config.resolveInterrupts ?? null;
|
|
5368
5917
|
this.#onPersist = config.onPersist ?? (() => {
|
|
5369
5918
|
});
|
|
5370
5919
|
this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
|
|
@@ -5444,16 +5993,22 @@ var AgUiClient = class {
|
|
|
5444
5993
|
this.#handlers.onCancelled();
|
|
5445
5994
|
}
|
|
5446
5995
|
async #runLoop() {
|
|
5996
|
+
let resume;
|
|
5447
5997
|
for (let round = 0; round < MAX_TOOL_ROUNDS; round += 1) {
|
|
5448
5998
|
if (this.#cancelled) {
|
|
5449
5999
|
return;
|
|
5450
6000
|
}
|
|
5451
6001
|
const pending = [];
|
|
5452
|
-
const runState = { terminal: false, errored: false };
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
this.#
|
|
5456
|
-
|
|
6002
|
+
const runState = { terminal: false, errored: false, interrupts: [] };
|
|
6003
|
+
const params = {
|
|
6004
|
+
tools: this.#getTools(),
|
|
6005
|
+
context: this.#getContext()
|
|
6006
|
+
};
|
|
6007
|
+
if (resume !== void 0) {
|
|
6008
|
+
params.resume = resume;
|
|
6009
|
+
}
|
|
6010
|
+
await this.#agent.runAgent(params, this.#buildSubscriber(pending, runState));
|
|
6011
|
+
resume = void 0;
|
|
5457
6012
|
this.#onPersist(this.#agent.messages);
|
|
5458
6013
|
if (this.#cancelled) {
|
|
5459
6014
|
return;
|
|
@@ -5464,6 +6019,17 @@ var AgUiClient = class {
|
|
|
5464
6019
|
if (runState.errored) {
|
|
5465
6020
|
return;
|
|
5466
6021
|
}
|
|
6022
|
+
if (runState.interrupts.length > 0) {
|
|
6023
|
+
if (this.#resolveInterrupts === null) {
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
const responses = await this.#resolveInterrupts(runState.interrupts);
|
|
6027
|
+
if (this.#cancelled) {
|
|
6028
|
+
return;
|
|
6029
|
+
}
|
|
6030
|
+
resume = buildResumeArray(runState.interrupts, responses);
|
|
6031
|
+
continue;
|
|
6032
|
+
}
|
|
5467
6033
|
if (this.#executeTool === null || pending.length === 0) {
|
|
5468
6034
|
return;
|
|
5469
6035
|
}
|
|
@@ -5526,6 +6092,12 @@ var AgUiClient = class {
|
|
|
5526
6092
|
onReasoningEndEvent() {
|
|
5527
6093
|
h.onReasoningEnd();
|
|
5528
6094
|
},
|
|
6095
|
+
onRunFinishedEvent(params) {
|
|
6096
|
+
runState.terminal = true;
|
|
6097
|
+
if (params.outcome === "interrupt") {
|
|
6098
|
+
runState.interrupts = params.interrupts;
|
|
6099
|
+
}
|
|
6100
|
+
},
|
|
5529
6101
|
onRunErrorEvent({ event }) {
|
|
5530
6102
|
runState.terminal = true;
|
|
5531
6103
|
runState.errored = true;
|
|
@@ -5873,6 +6445,60 @@ var RemoteConversationStore = class {
|
|
|
5873
6445
|
}
|
|
5874
6446
|
};
|
|
5875
6447
|
|
|
6448
|
+
// src/core/run_index.ts
|
|
6449
|
+
var RunIndex = class {
|
|
6450
|
+
#url;
|
|
6451
|
+
#headers;
|
|
6452
|
+
constructor(url, headers = () => ({})) {
|
|
6453
|
+
this.#url = url.endsWith("/") ? url : `${url}/`;
|
|
6454
|
+
this.#headers = headers;
|
|
6455
|
+
}
|
|
6456
|
+
/**
|
|
6457
|
+
* The user's runs, or `[]` when the endpoint is unreachable or answers with
|
|
6458
|
+
* an error. A history affordance that cannot load is empty, never broken:
|
|
6459
|
+
* the caller renders its empty state rather than surfacing a transport fault
|
|
6460
|
+
* the user can do nothing about.
|
|
6461
|
+
*/
|
|
6462
|
+
async list() {
|
|
6463
|
+
try {
|
|
6464
|
+
const response = await fetch(this.#url, {
|
|
6465
|
+
method: "GET",
|
|
6466
|
+
headers: { Accept: "application/json", ...this.#headers() }
|
|
6467
|
+
});
|
|
6468
|
+
if (!response.ok) {
|
|
6469
|
+
return [];
|
|
6470
|
+
}
|
|
6471
|
+
const body = await response.json();
|
|
6472
|
+
return body.runs ?? [];
|
|
6473
|
+
} catch {
|
|
6474
|
+
return [];
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
/** The rows a client may actually continue. */
|
|
6478
|
+
async continuable() {
|
|
6479
|
+
return (await this.list()).filter((run) => run.continuable);
|
|
6480
|
+
}
|
|
6481
|
+
/** The endpoint that continues `runId` as a new run. */
|
|
6482
|
+
resumeUrl(runId) {
|
|
6483
|
+
return this.#sibling("resume", runId);
|
|
6484
|
+
}
|
|
6485
|
+
/** The endpoint that branches `runId` into a new run, leaving the source untouched. */
|
|
6486
|
+
forkUrl(runId) {
|
|
6487
|
+
return this.#sibling("fork", runId);
|
|
6488
|
+
}
|
|
6489
|
+
/**
|
|
6490
|
+
* `<mount>/<verb>/<runId>/`, derived from the index URL's own prefix.
|
|
6491
|
+
*
|
|
6492
|
+
* Built by string surgery on the trailing `runs/` rather than with `new URL`,
|
|
6493
|
+
* because the configured value may be root-relative (`/agent/runs/`) — the
|
|
6494
|
+
* common case in a Django template — and `new URL` needs an absolute base.
|
|
6495
|
+
*/
|
|
6496
|
+
#sibling(verb, runId) {
|
|
6497
|
+
const prefix = this.#url.slice(0, -"runs/".length);
|
|
6498
|
+
return `${prefix}${verb}/${encodeURIComponent(runId)}/`;
|
|
6499
|
+
}
|
|
6500
|
+
};
|
|
6501
|
+
|
|
5876
6502
|
// src/core/transcribe_audio.ts
|
|
5877
6503
|
async function transcribeAudio(audio, options) {
|
|
5878
6504
|
const form = new FormData();
|
|
@@ -5983,6 +6609,33 @@ var AgUiChat = class extends HTMLElement {
|
|
|
5983
6609
|
allowImages = false;
|
|
5984
6610
|
/** When true, destructive tools execute without a confirmation modal. */
|
|
5985
6611
|
autoConfirm = false;
|
|
6612
|
+
/**
|
|
6613
|
+
* When true, the built-in `ask_user` frontend tool is offered to the agent:
|
|
6614
|
+
* calling it renders an inline question card (radio choices and/or a free-text
|
|
6615
|
+
* field) and returns the user's answer. Off by default — like the other
|
|
6616
|
+
* built-in tool groups (route / page-action), it is opt-in so it doesn't
|
|
6617
|
+
* change the advertised catalog until a host asks for it.
|
|
6618
|
+
*/
|
|
6619
|
+
askUser = false;
|
|
6620
|
+
/**
|
|
6621
|
+
* Optional full replacement for the `ask_user` question UI. When set, calling
|
|
6622
|
+
* `ask_user` invokes this instead of the built-in inline card: the host
|
|
6623
|
+
* renders whatever it likes (a native modal, a framework component, …) and
|
|
6624
|
+
* resolves with the answer. Unset (default) uses the built-in
|
|
6625
|
+
* {@link requestQuestion} card — style that via the `strings` override and the
|
|
6626
|
+
* `question*` CSS `::part()`s. Requires {@link askUser} to be enabled.
|
|
6627
|
+
*/
|
|
6628
|
+
askUserRenderer = null;
|
|
6629
|
+
/**
|
|
6630
|
+
* Optional full replacement for the server-side-tool approval UI. When set, an
|
|
6631
|
+
* approval interrupt invokes this instead of the built-in inline approval
|
|
6632
|
+
* card: the host renders whatever it likes and resolves `true` to approve /
|
|
6633
|
+
* `false` to deny. Unset (default) uses the built-in {@link requestApproval}
|
|
6634
|
+
* card — style that via the `strings` override and the `approval*` CSS
|
|
6635
|
+
* `::part()`s. The gate itself is enabled server-side; this only changes how
|
|
6636
|
+
* the decision is collected.
|
|
6637
|
+
*/
|
|
6638
|
+
approvalRenderer = null;
|
|
5986
6639
|
/**
|
|
5987
6640
|
* Optional per-call confirmation predicate. When set, it is authoritative:
|
|
5988
6641
|
* given a tool name + args it decides whether *this* call needs confirmation
|
|
@@ -6118,6 +6771,10 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6118
6771
|
#title;
|
|
6119
6772
|
#skillsMenu;
|
|
6120
6773
|
#drawer;
|
|
6774
|
+
/** Checkpoint panel; rows load only when `data-runs-url` is set. */
|
|
6775
|
+
#checkpoints;
|
|
6776
|
+
/** Built lazily from `data-runs-url`; `null` when the host didn't opt in. */
|
|
6777
|
+
#runIndex = null;
|
|
6121
6778
|
#skillHint;
|
|
6122
6779
|
/** File-picker button + hidden input + tray slot; the tray mounts on connect. */
|
|
6123
6780
|
#attachButton;
|
|
@@ -6208,6 +6865,70 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6208
6865
|
this.#deleteThread(threadId);
|
|
6209
6866
|
}
|
|
6210
6867
|
});
|
|
6868
|
+
this.#checkpoints = new CheckpointMenu((runId, verb) => {
|
|
6869
|
+
void this.#continueRun(runId, verb);
|
|
6870
|
+
});
|
|
6871
|
+
}
|
|
6872
|
+
/** The run index, built once from `data-runs-url`; `null` when unset. */
|
|
6873
|
+
#runs() {
|
|
6874
|
+
const url = this.getAttribute("data-runs-url");
|
|
6875
|
+
if (url === null || url === "") {
|
|
6876
|
+
return null;
|
|
6877
|
+
}
|
|
6878
|
+
if (this.#runIndex === null) {
|
|
6879
|
+
this.#runIndex = new RunIndex(url, () => this.headers);
|
|
6880
|
+
}
|
|
6881
|
+
return this.#runIndex;
|
|
6882
|
+
}
|
|
6883
|
+
/**
|
|
6884
|
+
* Continue `runId` as a **new** run, seeded server-side from its snapshot.
|
|
6885
|
+
*
|
|
6886
|
+
* Uses a short-lived agent pointed at the resume / fork endpoint and seeded
|
|
6887
|
+
* with **no** history, so the request carries only the turn typed here — the
|
|
6888
|
+
* contract those endpoints assume, since the server supplies the prior turns
|
|
6889
|
+
* from the snapshot and re-sending them would duplicate. Building a separate
|
|
6890
|
+
* agent makes that structural: the main agent keeps its own history, and
|
|
6891
|
+
* "only the new turn" can't be got wrong by forgetting to clear it. The
|
|
6892
|
+
* fresh `run_id` the endpoints also require comes free — a new agent mints
|
|
6893
|
+
* one per run.
|
|
6894
|
+
*
|
|
6895
|
+
* Handlers are the element's own, so the continuation streams into the same
|
|
6896
|
+
* transcript the user is looking at.
|
|
6897
|
+
*/
|
|
6898
|
+
async #continueRun(runId, verb) {
|
|
6899
|
+
const index = this.#runs();
|
|
6900
|
+
if (index === null) {
|
|
6901
|
+
return;
|
|
6902
|
+
}
|
|
6903
|
+
const content = this.#input.value.trim();
|
|
6904
|
+
if (content === "") {
|
|
6905
|
+
return;
|
|
6906
|
+
}
|
|
6907
|
+
this.#input.value = "";
|
|
6908
|
+
const endpoint = verb === "resume" ? index.resumeUrl(runId) : index.forkUrl(runId);
|
|
6909
|
+
const agent = this.agentFactory({
|
|
6910
|
+
endpoint,
|
|
6911
|
+
headers: this.headers,
|
|
6912
|
+
getHeaders: () => this.headers,
|
|
6913
|
+
threadId: this.#threadId,
|
|
6914
|
+
// The seed the endpoints assume: nothing. The snapshot is the history.
|
|
6915
|
+
initialMessages: []
|
|
6916
|
+
});
|
|
6917
|
+
const client = new AgUiClient({
|
|
6918
|
+
agent,
|
|
6919
|
+
handlers: this.#handlers(),
|
|
6920
|
+
getTools: () => this.getTools(),
|
|
6921
|
+
getContext: () => this.getContext(),
|
|
6922
|
+
executeTool: (call) => this.#executeTool(call),
|
|
6923
|
+
resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
|
|
6924
|
+
connectionLostMessage: this.#strings.connectionLost
|
|
6925
|
+
});
|
|
6926
|
+
await client.send(content);
|
|
6927
|
+
}
|
|
6928
|
+
/** Load the checkpoint panel with the runs that can actually be continued. */
|
|
6929
|
+
async #refreshCheckpoints() {
|
|
6930
|
+
const index = this.#runs();
|
|
6931
|
+
this.#checkpoints.setRuns(index === null ? [] : await index.continuable());
|
|
6211
6932
|
}
|
|
6212
6933
|
/** Attributes whose late changes must reflect in already-rendered chrome. */
|
|
6213
6934
|
static get observedAttributes() {
|
|
@@ -6276,9 +6997,73 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6276
6997
|
);
|
|
6277
6998
|
return createPageActionTools(enabled, (target) => this.resolvePageTarget(target));
|
|
6278
6999
|
}
|
|
6279
|
-
/** All built-in (route + page + page-action) frontend tools. */
|
|
7000
|
+
/** All built-in (route + page + page-action + ask_user) frontend tools. */
|
|
6280
7001
|
#builtinTools() {
|
|
6281
|
-
return [
|
|
7002
|
+
return [
|
|
7003
|
+
...this.#routeTools(),
|
|
7004
|
+
...this.#pageTools(),
|
|
7005
|
+
...this.#pageActionTools(),
|
|
7006
|
+
...this.#askUserTool()
|
|
7007
|
+
];
|
|
7008
|
+
}
|
|
7009
|
+
/**
|
|
7010
|
+
* The built-in `ask_user` frontend tool, or `[]` when {@link askUser} is off.
|
|
7011
|
+
*
|
|
7012
|
+
* A generic "ask the user a typed question" primitive: the agent calls it, the
|
|
7013
|
+
* client executes it locally via the normal frontend-tool path (rendering a
|
|
7014
|
+
* {@link requestQuestion} card), and the chosen/typed answer flows back as the
|
|
7015
|
+
* tool result — no new protocol, reusing the machinery already in place.
|
|
7016
|
+
*/
|
|
7017
|
+
#askUserTool() {
|
|
7018
|
+
if (!this.askUser) {
|
|
7019
|
+
return [];
|
|
7020
|
+
}
|
|
7021
|
+
return [
|
|
7022
|
+
{
|
|
7023
|
+
name: "ask_user",
|
|
7024
|
+
description: "Ask the user a question and wait for their answer. Provide `options` for a multiple-choice prompt; set `allow_custom` to also accept a free-text answer.",
|
|
7025
|
+
parameters: {
|
|
7026
|
+
type: "object",
|
|
7027
|
+
properties: {
|
|
7028
|
+
question: { type: "string", description: "The question to ask the user." },
|
|
7029
|
+
options: {
|
|
7030
|
+
type: "array",
|
|
7031
|
+
items: { type: "string" },
|
|
7032
|
+
description: "Preset choices offered as radio buttons."
|
|
7033
|
+
},
|
|
7034
|
+
allow_custom: {
|
|
7035
|
+
type: "boolean",
|
|
7036
|
+
description: "Allow a free-text answer in addition to any options."
|
|
7037
|
+
}
|
|
7038
|
+
},
|
|
7039
|
+
required: ["question"]
|
|
7040
|
+
},
|
|
7041
|
+
handler: (args) => this.#askUser(args)
|
|
7042
|
+
}
|
|
7043
|
+
];
|
|
7044
|
+
}
|
|
7045
|
+
/** Render the `ask_user` question card and resolve with the user's answer. */
|
|
7046
|
+
async #askUser(args) {
|
|
7047
|
+
const question = typeof args["question"] === "string" ? args["question"] : "";
|
|
7048
|
+
const request = { question };
|
|
7049
|
+
const rawOptions = args["options"];
|
|
7050
|
+
if (Array.isArray(rawOptions)) {
|
|
7051
|
+
request.options = rawOptions.filter((option) => typeof option === "string");
|
|
7052
|
+
}
|
|
7053
|
+
if (args["allow_custom"] === true) {
|
|
7054
|
+
request.allowCustom = true;
|
|
7055
|
+
}
|
|
7056
|
+
this.#confirmAbort = new AbortController();
|
|
7057
|
+
const signal = this.#confirmAbort.signal;
|
|
7058
|
+
this.#hidePending();
|
|
7059
|
+
const answer = this.askUserRenderer !== null ? await this.askUserRenderer(request, { signal }) : await requestQuestion(this.#ensureGroup(), request, {
|
|
7060
|
+
signal,
|
|
7061
|
+
strings: this.#strings
|
|
7062
|
+
});
|
|
7063
|
+
this.#confirmAbort = null;
|
|
7064
|
+
this.#updateEmptyState();
|
|
7065
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
7066
|
+
return answer;
|
|
6282
7067
|
}
|
|
6283
7068
|
/** Resolve a tool by name: built-in tools first, then the registry. */
|
|
6284
7069
|
#resolveTool(name) {
|
|
@@ -6323,6 +7108,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
6323
7108
|
}
|
|
6324
7109
|
this.#render();
|
|
6325
7110
|
this.#drawer.setStrings(this.#strings);
|
|
7111
|
+
this.#checkpoints.setStrings(this.#strings);
|
|
6326
7112
|
if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
|
|
6327
7113
|
this.setAttribute("collapsed", "");
|
|
6328
7114
|
}
|
|
@@ -6864,11 +7650,20 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6864
7650
|
void this.#refreshDrawer();
|
|
6865
7651
|
this.#drawer.open();
|
|
6866
7652
|
});
|
|
7653
|
+
const checkpoints = this.#headerButton("checkpoints", this.#strings.checkpoints, "\u2B6F");
|
|
7654
|
+
checkpoints.addEventListener("click", () => {
|
|
7655
|
+
void this.#refreshCheckpoints();
|
|
7656
|
+
this.#checkpoints.open();
|
|
7657
|
+
});
|
|
6867
7658
|
const newChat = this.#headerButton("new", this.#strings.newChat, "\u271A");
|
|
6868
7659
|
newChat.addEventListener("click", () => this.newChat());
|
|
6869
7660
|
const collapse = this.#headerButton("collapse", this.#strings.collapse, "\u2014");
|
|
6870
7661
|
collapse.addEventListener("click", () => this.toggleCollapsed());
|
|
6871
|
-
|
|
7662
|
+
if (this.#runs() !== null) {
|
|
7663
|
+
controls.append(history, checkpoints, newChat);
|
|
7664
|
+
} else {
|
|
7665
|
+
controls.append(history, newChat);
|
|
7666
|
+
}
|
|
6872
7667
|
if (this.getAttribute("data-theme-toggle") !== null) {
|
|
6873
7668
|
this.#themeToggle.type = "button";
|
|
6874
7669
|
this.#themeToggle.className = "header-btn header-btn--theme";
|
|
@@ -6916,6 +7711,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6916
7711
|
void this.#submit();
|
|
6917
7712
|
});
|
|
6918
7713
|
this.#skillHint.className = "skill-hint";
|
|
7714
|
+
this.#skillHint.setAttribute("part", "skill-hint");
|
|
6919
7715
|
this.#skillHint.hidden = true;
|
|
6920
7716
|
this.#attachButton.className = "attach-btn";
|
|
6921
7717
|
this.#attachButton.type = "button";
|
|
@@ -6944,7 +7740,8 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
6944
7740
|
this.#attachSlot,
|
|
6945
7741
|
inputRow,
|
|
6946
7742
|
footer,
|
|
6947
|
-
this.#drawer.element
|
|
7743
|
+
this.#drawer.element,
|
|
7744
|
+
this.#checkpoints.element
|
|
6948
7745
|
);
|
|
6949
7746
|
this.#rail.className = "rail";
|
|
6950
7747
|
this.#rail.type = "button";
|
|
@@ -7083,6 +7880,7 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
7083
7880
|
getTools: () => this.getTools(),
|
|
7084
7881
|
getContext: () => this.getContext(),
|
|
7085
7882
|
executeTool: (call) => this.#executeTool(call),
|
|
7883
|
+
resolveInterrupts: (interrupts) => this.#resolveInterrupts(interrupts),
|
|
7086
7884
|
onPersist: (messages) => this.conversationStore.saveMessages(this.#threadId, messages),
|
|
7087
7885
|
connectionLostMessage: this.#strings.connectionLost
|
|
7088
7886
|
});
|
|
@@ -7155,6 +7953,45 @@ Use the read_attachment tool with an id to read a file's contents.`
|
|
|
7155
7953
|
return { content: `Error: ${message}`, error: message };
|
|
7156
7954
|
}
|
|
7157
7955
|
}
|
|
7956
|
+
/**
|
|
7957
|
+
* Render an approval card per server-side-tool interrupt and collect the
|
|
7958
|
+
* user's decisions (approve → run it, deny → decline it).
|
|
7959
|
+
*
|
|
7960
|
+
* The run is suspended on these cards; a Stop while any is open aborts the
|
|
7961
|
+
* shared {@link #confirmAbort} controller, resolving every still-open card as
|
|
7962
|
+
* denied (and the client loop then sees the cancellation and stops). An
|
|
7963
|
+
* approved tool runs on the follow-up (resume) run and streams its result
|
|
7964
|
+
* back into the same pending card; a denied one is settled here, since no
|
|
7965
|
+
* result will ever arrive for it.
|
|
7966
|
+
*/
|
|
7967
|
+
async #resolveInterrupts(interrupts) {
|
|
7968
|
+
const responses = {};
|
|
7969
|
+
this.#confirmAbort = new AbortController();
|
|
7970
|
+
this.#hidePending();
|
|
7971
|
+
for (const interrupt of interrupts) {
|
|
7972
|
+
const request = {};
|
|
7973
|
+
if (interrupt.message !== void 0) {
|
|
7974
|
+
request.message = interrupt.message;
|
|
7975
|
+
}
|
|
7976
|
+
const card = interrupt.toolCallId !== void 0 ? this.#toolCards.get(interrupt.toolCallId) : void 0;
|
|
7977
|
+
const toolName = card?.element.getAttribute("data-tool-name");
|
|
7978
|
+
if (toolName !== null && toolName !== void 0) {
|
|
7979
|
+
request.toolName = toolName;
|
|
7980
|
+
}
|
|
7981
|
+
const signal = this.#confirmAbort.signal;
|
|
7982
|
+
const approved = this.approvalRenderer !== null ? await this.approvalRenderer(request, { signal }) : await requestApproval(this.#ensureGroup(), request, { signal, strings: this.#strings });
|
|
7983
|
+
this.#updateEmptyState();
|
|
7984
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
7985
|
+
if (approved) {
|
|
7986
|
+
responses[interrupt.id] = { status: "resolved", payload: { approved: true } };
|
|
7987
|
+
} else {
|
|
7988
|
+
responses[interrupt.id] = { status: "cancelled" };
|
|
7989
|
+
card?.settle(TOOL_CALL_STATUS.DECLINED, this.#strings.declinedAction);
|
|
7990
|
+
}
|
|
7991
|
+
}
|
|
7992
|
+
this.#confirmAbort = null;
|
|
7993
|
+
return responses;
|
|
7994
|
+
}
|
|
7158
7995
|
#handlers() {
|
|
7159
7996
|
return {
|
|
7160
7997
|
onRunStart: () => {
|
|
@@ -7355,10 +8192,11 @@ function setControlValue(el, value) {
|
|
|
7355
8192
|
}
|
|
7356
8193
|
|
|
7357
8194
|
// src/version.ts
|
|
7358
|
-
var VERSION = "0.
|
|
8195
|
+
var VERSION = "0.12.0";
|
|
7359
8196
|
export {
|
|
7360
8197
|
AgUiChat,
|
|
7361
8198
|
AgUiClient,
|
|
8199
|
+
CheckpointMenu,
|
|
7362
8200
|
ClientToolRegistry,
|
|
7363
8201
|
ConnectionLostError,
|
|
7364
8202
|
DEFAULT_UI_STRINGS,
|
|
@@ -7367,6 +8205,7 @@ export {
|
|
|
7367
8205
|
MESSAGE_ROLE,
|
|
7368
8206
|
PAGE_ACTIONS,
|
|
7369
8207
|
RemoteConversationStore,
|
|
8208
|
+
RunIndex,
|
|
7370
8209
|
SUBMIT_EVENT,
|
|
7371
8210
|
SessionStorageStore,
|
|
7372
8211
|
TOGGLE_EVENT,
|
|
@@ -7398,7 +8237,9 @@ export {
|
|
|
7398
8237
|
pressThenClick,
|
|
7399
8238
|
prettifyToolName,
|
|
7400
8239
|
renderMarkdown,
|
|
8240
|
+
requestApproval,
|
|
7401
8241
|
requestConfirmation,
|
|
8242
|
+
requestQuestion,
|
|
7402
8243
|
scrollIntoCenterView,
|
|
7403
8244
|
selectControl,
|
|
7404
8245
|
selectOption,
|