@artooi/ag-ui-web-component 0.23.1 → 0.25.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 +194 -1
- package/README.md +188 -38
- package/dist/ag-ui-web-component.bundle.js +189 -49
- package/dist/ag-ui-web-component.bundle.js.map +3 -3
- package/dist/constants.d.ts +25 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +24 -0
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/conversation_store.d.ts +18 -0
- package/dist/core/conversation_store.d.ts.map +1 -1
- package/dist/core/remote_conversation_store.d.ts +2 -0
- package/dist/core/remote_conversation_store.d.ts.map +1 -1
- package/dist/core/run_index.d.ts +10 -0
- package/dist/core/run_index.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +335 -44
- package/dist/index.js.map +2 -2
- package/dist/ui/checkpoint_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/tool_call_card.d.ts +26 -2
- package/dist/ui/tool_call_card.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +2 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +26 -0
- package/src/core/ag_ui_chat.ts +186 -40
- package/src/core/conversation_store.ts +30 -0
- package/src/core/remote_conversation_store.ts +14 -0
- package/src/core/run_index.ts +10 -0
- package/src/index.ts +3 -0
- package/src/ui/checkpoint_menu.ts +32 -7
- package/src/ui/styles.ts +153 -13
- package/src/ui/tool_call_card.ts +41 -3
- package/src/ui/ui_strings.ts +3 -0
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var TOGGLE_EVENT = "ag-ui-toggle";
|
|
|
5
5
|
var UNREAD_EVENT = "ag-ui-unread";
|
|
6
6
|
var STATE_EVENT = "ag-ui-state";
|
|
7
7
|
var ATTACHMENT_EVENT = "ag-ui-attachments";
|
|
8
|
+
var RUN_FINISHED_EVENT = "ag-ui-run-finished";
|
|
8
9
|
var MESSAGE_ROLE = {
|
|
9
10
|
USER: "user",
|
|
10
11
|
ASSISTANT: "assistant"
|
|
@@ -17,6 +18,7 @@ var READ_PAGE_TOOL = "read_page";
|
|
|
17
18
|
var MAX_TOOL_ROUNDS = 10;
|
|
18
19
|
var TOOL_CALL_STATUS = {
|
|
19
20
|
PENDING: "pending",
|
|
21
|
+
DEFERRED: "deferred",
|
|
20
22
|
DONE: "done",
|
|
21
23
|
ERROR: "error",
|
|
22
24
|
DECLINED: "declined"
|
|
@@ -553,6 +555,7 @@ var DEFAULT_UI_STRINGS = {
|
|
|
553
555
|
transcribing: "Transcribing\u2026",
|
|
554
556
|
transcriptionFailed: "Transcription failed",
|
|
555
557
|
toolRunning: "running\u2026",
|
|
558
|
+
toolDeferred: "waiting for you",
|
|
556
559
|
toolDone: "\u2713 done",
|
|
557
560
|
toolError: "\u26A0 error",
|
|
558
561
|
toolDeclined: "\u2298 declined",
|
|
@@ -1121,12 +1124,27 @@ var CheckpointMenu = class {
|
|
|
1121
1124
|
const row = document.createElement("div");
|
|
1122
1125
|
row.className = "checkpoint-row";
|
|
1123
1126
|
row.setAttribute("part", "checkpoint-row");
|
|
1127
|
+
const preview = run.preview !== void 0 && run.preview !== null && run.preview !== "" ? run.preview : null;
|
|
1128
|
+
const time = run.started_at === null ? null : relativeTime(Date.parse(run.started_at), Date.now(), this.#strings);
|
|
1124
1129
|
const label = document.createElement("span");
|
|
1125
1130
|
label.className = "checkpoint-label";
|
|
1126
1131
|
label.setAttribute("part", "checkpoint-label");
|
|
1127
|
-
label.textContent =
|
|
1128
|
-
label.title = run.run_id;
|
|
1132
|
+
label.textContent = preview ?? time ?? run.run_id;
|
|
1129
1133
|
row.append(label);
|
|
1134
|
+
if (preview !== null && time !== null) {
|
|
1135
|
+
const when = document.createElement("span");
|
|
1136
|
+
when.className = "checkpoint-time";
|
|
1137
|
+
when.setAttribute("part", "checkpoint-time");
|
|
1138
|
+
when.textContent = time;
|
|
1139
|
+
row.append(when);
|
|
1140
|
+
} else if (run.started_at !== null) {
|
|
1141
|
+
const short = document.createElement("span");
|
|
1142
|
+
short.className = "checkpoint-id";
|
|
1143
|
+
short.setAttribute("part", "checkpoint-id");
|
|
1144
|
+
short.textContent = run.run_id.slice(0, 8);
|
|
1145
|
+
short.title = run.run_id;
|
|
1146
|
+
row.append(short);
|
|
1147
|
+
}
|
|
1130
1148
|
if (run.parent_run_id !== null) {
|
|
1131
1149
|
const branch = document.createElement("span");
|
|
1132
1150
|
branch.className = "checkpoint-branch";
|
|
@@ -5392,18 +5410,28 @@ var STYLES = `
|
|
|
5392
5410
|
color: var(--_tool-fg);
|
|
5393
5411
|
}
|
|
5394
5412
|
|
|
5413
|
+
/* Wraps, because the name is the only flexible child and every badge the row
|
|
5414
|
+
gains is taken out of it. An approved call adds a third fixed badge, which in
|
|
5415
|
+
a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to
|
|
5416
|
+
their own row instead. */
|
|
5395
5417
|
.tool-call-head {
|
|
5396
5418
|
display: flex;
|
|
5419
|
+
flex-wrap: wrap;
|
|
5397
5420
|
align-items: center;
|
|
5398
5421
|
justify-content: space-between;
|
|
5399
5422
|
gap: 8px;
|
|
5400
5423
|
}
|
|
5401
5424
|
|
|
5402
5425
|
.tool-call-name {
|
|
5403
|
-
|
|
5404
|
-
|
|
5426
|
+
/* An auto basis, and a min-width floor rather than zero: the name may shrink,
|
|
5427
|
+
but not below something readable, so wrapping moves a badge instead of
|
|
5428
|
+
shredding a word. Breaking anywhere still applies to a name that cannot fit
|
|
5429
|
+
on a line of its own, which is what keeps a long unbroken tool name inside
|
|
5430
|
+
the card. */
|
|
5431
|
+
flex: 1 1 auto;
|
|
5432
|
+
min-width: 6ch;
|
|
5405
5433
|
font-weight: 600;
|
|
5406
|
-
|
|
5434
|
+
overflow-wrap: anywhere;
|
|
5407
5435
|
}
|
|
5408
5436
|
|
|
5409
5437
|
/* Leading status icon. Empty in the DOM \u2014 the glyph/spinner is drawn
|
|
@@ -5432,6 +5460,13 @@ var STYLES = `
|
|
|
5432
5460
|
to { transform: rotate(360deg); }
|
|
5433
5461
|
}
|
|
5434
5462
|
|
|
5463
|
+
/* Deferred: no spinner, because nothing is spinning. A steady accent dot, since
|
|
5464
|
+
the state is waiting-on-you rather than an outcome. */
|
|
5465
|
+
.tool-call[data-status="deferred"] .tool-call-icon {
|
|
5466
|
+
border-radius: 50%;
|
|
5467
|
+
background: var(--_accent);
|
|
5468
|
+
}
|
|
5469
|
+
|
|
5435
5470
|
/* Settled: a themeable glyph coloured by outcome. */
|
|
5436
5471
|
.tool-call[data-status="done"] .tool-call-icon::before {
|
|
5437
5472
|
content: var(--_tool-icon-done);
|
|
@@ -5474,6 +5509,10 @@ var STYLES = `
|
|
|
5474
5509
|
color: var(--_muted);
|
|
5475
5510
|
}
|
|
5476
5511
|
|
|
5512
|
+
.tool-call[data-status="deferred"] .tool-call-status {
|
|
5513
|
+
color: var(--_accent);
|
|
5514
|
+
}
|
|
5515
|
+
|
|
5477
5516
|
.tool-call[data-status="done"] .tool-call-status {
|
|
5478
5517
|
color: var(--_success);
|
|
5479
5518
|
}
|
|
@@ -5567,12 +5606,41 @@ var STYLES = `
|
|
|
5567
5606
|
|
|
5568
5607
|
/* A pending card has no result yet, and in the modes where the arguments are
|
|
5569
5608
|
hidden too there is nothing behind the toggle. Hide the control rather than
|
|
5570
|
-
offer one that expands onto nothing.
|
|
5609
|
+
offer one that expands onto nothing. A deferred card is the same, and its
|
|
5610
|
+
arguments are shown unconditionally by the rules below. */
|
|
5571
5611
|
.tool-call[data-status="pending"] .tool-call-toggle,
|
|
5612
|
+
.tool-call[data-status="deferred"] .tool-call-toggle,
|
|
5572
5613
|
:host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
|
|
5573
5614
|
display: none;
|
|
5574
5615
|
}
|
|
5575
5616
|
|
|
5617
|
+
/* The approval prompt for a gated call, rendered inside that call's own card.
|
|
5618
|
+
Empty on every card nobody is being asked about, so it collapses instead of
|
|
5619
|
+
adding a gap to each one. */
|
|
5620
|
+
.tool-call-approval:empty {
|
|
5621
|
+
display: none;
|
|
5622
|
+
}
|
|
5623
|
+
|
|
5624
|
+
.tool-call-approval {
|
|
5625
|
+
margin-top: 8px;
|
|
5626
|
+
}
|
|
5627
|
+
|
|
5628
|
+
/* A card that is asking a question shows what it is asking about, in every
|
|
5629
|
+
display mode. Three gated calls of one tool ask the same words, so the
|
|
5630
|
+
arguments are the only thing telling them apart, and a density setting must
|
|
5631
|
+
not be able to hide the answer to "which one is this". */
|
|
5632
|
+
:host([data-tool-display="minimal"]) .tool-call[data-status="deferred"] .tool-call-body {
|
|
5633
|
+
display: flex;
|
|
5634
|
+
}
|
|
5635
|
+
|
|
5636
|
+
/* The arguments region only, never every section: the result region carries the
|
|
5637
|
+
hidden attribute until a result exists, and a display value here overrides it,
|
|
5638
|
+
framing an empty RESULT heading under the question. */
|
|
5639
|
+
:host([data-tool-display="compact"]) .tool-call[data-status="deferred"] .tool-call-section--args,
|
|
5640
|
+
:host([data-tool-display="inline"]) .tool-call[data-status="deferred"] .tool-call-section--args {
|
|
5641
|
+
display: flex;
|
|
5642
|
+
}
|
|
5643
|
+
|
|
5576
5644
|
.tool-call-toggle {
|
|
5577
5645
|
align-self: flex-start;
|
|
5578
5646
|
border: none;
|
|
@@ -5792,6 +5860,20 @@ var STYLES = `
|
|
|
5792
5860
|
opacity: 0.6;
|
|
5793
5861
|
}
|
|
5794
5862
|
|
|
5863
|
+
/* The same trap the attachment tray carries a note about, two rules along: an
|
|
5864
|
+
author display beats the UA stylesheet's rule for the hidden property, so a
|
|
5865
|
+
button the element has explicitly hidden keeps laying out and painting. The
|
|
5866
|
+
clip is hidden until a host supplies an upload handler or an attachments URL,
|
|
5867
|
+
and without this it is a visible control that cannot do anything.
|
|
5868
|
+
|
|
5869
|
+
The mic needs no such rule, and the asymmetry is worth knowing before adding
|
|
5870
|
+
one: it is not hidden when unconfigured, it is never built. The voice wiring
|
|
5871
|
+
returns before constructing the button, leaving only an empty voice slot that
|
|
5872
|
+
is display: contents. A hidden-state rule for the mic would match nothing. */
|
|
5873
|
+
.attach-btn[hidden] {
|
|
5874
|
+
display: none;
|
|
5875
|
+
}
|
|
5876
|
+
|
|
5795
5877
|
/* Send closes the row on the right: a circle, the only filled control in the
|
|
5796
5878
|
composer, so "the thing that acts" reads at a glance. */
|
|
5797
5879
|
.send {
|
|
@@ -6401,47 +6483,123 @@ var STYLES = `
|
|
|
6401
6483
|
opacity: 0.7;
|
|
6402
6484
|
}
|
|
6403
6485
|
|
|
6486
|
+
/* A row is a label and two buttons, and nothing about the row itself is
|
|
6487
|
+
pressable. It used to light up on hover, which is the affordance of something
|
|
6488
|
+
clickable and made the buttons look like decoration on a clickable strip. The
|
|
6489
|
+
resting surface groups the row instead, so hover can mean what it says: only
|
|
6490
|
+
the buttons respond to it. */
|
|
6491
|
+
/* A row is a label and two buttons, and nothing about the row itself is
|
|
6492
|
+
pressable. It used to light up on hover, which is the affordance of something
|
|
6493
|
+
clickable and made the buttons look like decoration on a clickable strip. The
|
|
6494
|
+
resting surface groups the row instead, so hover can mean what it says: only
|
|
6495
|
+
the buttons respond to it.
|
|
6496
|
+
|
|
6497
|
+
It wraps for the same reason the tool-call head does. Every child but the label
|
|
6498
|
+
is fixed-width, so in a narrow panel the label is the only thing that can give
|
|
6499
|
+
-- and a flex-basis of zero lets it give everything. Adding the run id was
|
|
6500
|
+
enough to crush "just now" to zero pixels: present, correct, and invisible.
|
|
6501
|
+
Wrapping puts the buttons on their own line instead. */
|
|
6404
6502
|
.checkpoint-row {
|
|
6405
6503
|
display: flex;
|
|
6504
|
+
flex-wrap: wrap;
|
|
6406
6505
|
align-items: center;
|
|
6407
6506
|
gap: 0.5rem;
|
|
6408
|
-
padding: 0.
|
|
6507
|
+
padding: 0.3125rem 0.4375rem;
|
|
6409
6508
|
border-radius: 0.375rem;
|
|
6410
|
-
}
|
|
6411
|
-
|
|
6412
|
-
.checkpoint-row:hover {
|
|
6413
6509
|
background: var(--_hover);
|
|
6414
6510
|
}
|
|
6415
6511
|
|
|
6512
|
+
/* Grows into spare room, and refuses to shrink past the shortest thing it ever
|
|
6513
|
+
says. A time is short and bounded, so there is no case for eliding it. */
|
|
6416
6514
|
.checkpoint-label {
|
|
6417
|
-
flex: 1;
|
|
6515
|
+
flex: 1 1 auto;
|
|
6516
|
+
min-width: 7ch;
|
|
6418
6517
|
font-size: 0.8125rem;
|
|
6419
6518
|
white-space: nowrap;
|
|
6420
6519
|
overflow: hidden;
|
|
6421
6520
|
text-overflow: ellipsis;
|
|
6422
6521
|
}
|
|
6423
6522
|
|
|
6523
|
+
/* When the label holds the run's first message, the time moves here: still worth
|
|
6524
|
+
showing, no longer what identifies the row. Muted and unshrinkable, so it does
|
|
6525
|
+
not compete with the words beside it. */
|
|
6526
|
+
.checkpoint-time {
|
|
6527
|
+
flex: 0 0 auto;
|
|
6528
|
+
font-size: 0.6875rem;
|
|
6529
|
+
opacity: 0.7;
|
|
6530
|
+
white-space: nowrap;
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6533
|
+
/* Enough of the run id to tell two runs apart when both say "just now". Muted
|
|
6534
|
+
and monospaced: it is a reference, not a name. */
|
|
6535
|
+
.checkpoint-id {
|
|
6536
|
+
flex: 0 0 auto;
|
|
6537
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
6538
|
+
font-size: 0.6875rem;
|
|
6539
|
+
opacity: 0.55;
|
|
6540
|
+
}
|
|
6541
|
+
|
|
6542
|
+
/* On the panel's own surface, not the row's: the row now paints the hover token
|
|
6543
|
+
itself, and a badge the same colour as what it sits on is not a badge. */
|
|
6424
6544
|
.checkpoint-branch {
|
|
6425
6545
|
font-size: 0.6875rem;
|
|
6426
6546
|
padding: 0 0.375rem;
|
|
6427
6547
|
border-radius: 999px;
|
|
6428
|
-
background: var(--
|
|
6548
|
+
background: var(--_assistant-bg);
|
|
6429
6549
|
opacity: 0.8;
|
|
6430
6550
|
}
|
|
6431
6551
|
|
|
6552
|
+
/* The two things in the row that actually do something, so they are the two
|
|
6553
|
+
things that look like it: a filled surface at rest rather than a transparent
|
|
6554
|
+
outline, which on top of the old row highlight was nearly invisible. */
|
|
6432
6555
|
.checkpoint-action {
|
|
6433
6556
|
font: inherit;
|
|
6434
6557
|
font-size: 0.75rem;
|
|
6558
|
+
line-height: 1.4;
|
|
6435
6559
|
cursor: pointer;
|
|
6436
|
-
padding: 0.
|
|
6560
|
+
padding: 0.1875rem 0.5625rem;
|
|
6437
6561
|
border: 1px solid var(--_border);
|
|
6438
6562
|
border-radius: 0.375rem;
|
|
6439
|
-
background:
|
|
6563
|
+
background: var(--_bg);
|
|
6440
6564
|
color: inherit;
|
|
6565
|
+
transition:
|
|
6566
|
+
background var(--_motion) var(--_ease),
|
|
6567
|
+
border-color var(--_motion) var(--_ease),
|
|
6568
|
+
transform var(--_motion) var(--_ease);
|
|
6441
6569
|
}
|
|
6442
6570
|
|
|
6443
|
-
|
|
6571
|
+
/* Resume is what a reader wants nine times in ten; fork is the deliberate choice
|
|
6572
|
+
beside it. Filled and outlined, the same pair the confirmation and approval
|
|
6573
|
+
cards already use for their primary and secondary action. */
|
|
6574
|
+
.checkpoint-resume {
|
|
6575
|
+
font-weight: 600;
|
|
6576
|
+
border-color: var(--_accent);
|
|
6577
|
+
background: var(--_accent);
|
|
6578
|
+
color: #ffffff;
|
|
6579
|
+
}
|
|
6580
|
+
|
|
6581
|
+
.checkpoint-fork:hover {
|
|
6444
6582
|
background: var(--_hover);
|
|
6583
|
+
border-color: var(--_accent);
|
|
6584
|
+
}
|
|
6585
|
+
|
|
6586
|
+
/* The filled one cannot go lighter on hover without losing its contrast with the
|
|
6587
|
+
white label, so it dims instead. */
|
|
6588
|
+
.checkpoint-resume:hover {
|
|
6589
|
+
opacity: 0.88;
|
|
6590
|
+
}
|
|
6591
|
+
|
|
6592
|
+
/* Pressed: a pixel down, so the click is felt as well as seen. */
|
|
6593
|
+
.checkpoint-action:active {
|
|
6594
|
+
transform: translateY(1px);
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6597
|
+
/* Keyboard focus was invisible here, in a panel that traps focus and is reached
|
|
6598
|
+
by Tab -- so the one navigation path guaranteed to land on these buttons was
|
|
6599
|
+
the one with nothing to show for it. */
|
|
6600
|
+
.checkpoint-action:focus-visible {
|
|
6601
|
+
outline: 2px solid var(--_accent);
|
|
6602
|
+
outline-offset: 2px;
|
|
6445
6603
|
}
|
|
6446
6604
|
|
|
6447
6605
|
.drawer-backdrop {
|
|
@@ -6938,6 +7096,7 @@ var ThreadDrawer = class {
|
|
|
6938
7096
|
function statusLabels(strings) {
|
|
6939
7097
|
return {
|
|
6940
7098
|
[TOOL_CALL_STATUS.PENDING]: strings.toolRunning,
|
|
7099
|
+
[TOOL_CALL_STATUS.DEFERRED]: strings.toolDeferred,
|
|
6941
7100
|
[TOOL_CALL_STATUS.DONE]: strings.toolDone,
|
|
6942
7101
|
[TOOL_CALL_STATUS.ERROR]: strings.toolError,
|
|
6943
7102
|
[TOOL_CALL_STATUS.DECLINED]: strings.toolDeclined
|
|
@@ -6960,6 +7119,20 @@ function formatPayload(text2) {
|
|
|
6960
7119
|
var ToolCallCard = class {
|
|
6961
7120
|
/** The card's root element; append this into the message list. */
|
|
6962
7121
|
element;
|
|
7122
|
+
/**
|
|
7123
|
+
* Where a question about *this* call renders — the approval prompt for a
|
|
7124
|
+
* server-side tool the run deferred.
|
|
7125
|
+
*
|
|
7126
|
+
* It belongs to the card rather than to the transcript because a run can defer
|
|
7127
|
+
* several calls at once, and a prompt written per *tool* ("Add this event to
|
|
7128
|
+
* the board?") is identical for every one of them. Rendered into the answer
|
|
7129
|
+
* group they were three anonymous copies of one question, below the three
|
|
7130
|
+
* cards they gated; rendered here, position identifies them and the arguments
|
|
7131
|
+
* are already on screen above the question.
|
|
7132
|
+
*
|
|
7133
|
+
* Empty until used, and hidden while empty by the shadow CSS.
|
|
7134
|
+
*/
|
|
7135
|
+
approvalSlot;
|
|
6963
7136
|
#status;
|
|
6964
7137
|
#decision;
|
|
6965
7138
|
#toggle;
|
|
@@ -7015,7 +7188,24 @@ var ToolCallCard = class {
|
|
|
7015
7188
|
body.className = "tool-call-body";
|
|
7016
7189
|
body.setAttribute("part", "tool-card-body");
|
|
7017
7190
|
body.append(argsSection.root, resultSection.root);
|
|
7018
|
-
this.
|
|
7191
|
+
this.approvalSlot = document.createElement("div");
|
|
7192
|
+
this.approvalSlot.className = "tool-call-approval";
|
|
7193
|
+
this.approvalSlot.setAttribute("part", "tool-card-approval");
|
|
7194
|
+
this.element.append(head, this.#toggle, body, this.approvalSlot);
|
|
7195
|
+
}
|
|
7196
|
+
/**
|
|
7197
|
+
* Move between the two states that are not an outcome — `pending` (running)
|
|
7198
|
+
* and `deferred` (gated, waiting on a person).
|
|
7199
|
+
*
|
|
7200
|
+
* Ignored once {@link settle} has run: a card that was declined must not be
|
|
7201
|
+
* talked back into looking live by a late event.
|
|
7202
|
+
*/
|
|
7203
|
+
mark(status) {
|
|
7204
|
+
if (this.#settled) {
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
this.element.setAttribute("data-status", status);
|
|
7208
|
+
this.#status.textContent = statusLabels(this.#strings)[status];
|
|
7019
7209
|
}
|
|
7020
7210
|
/**
|
|
7021
7211
|
* Record that a human approved or declined this call, as a line in the card.
|
|
@@ -7483,6 +7673,7 @@ var THREAD_SUFFIX = "thread";
|
|
|
7483
7673
|
var THREADS_SUFFIX = "threads";
|
|
7484
7674
|
var MESSAGES_SUFFIX = "messages:";
|
|
7485
7675
|
var CHECKPOINT_SUFFIX = "checkpoint:";
|
|
7676
|
+
var MINTED_SUFFIX = "minted:";
|
|
7486
7677
|
var TITLE_LIMIT = 60;
|
|
7487
7678
|
var PREVIEW_LIMIT = 100;
|
|
7488
7679
|
var DEFAULT_TITLE = "New conversation";
|
|
@@ -7502,13 +7693,18 @@ var SessionStorageStore = class {
|
|
|
7502
7693
|
}
|
|
7503
7694
|
const id = randomUUID3();
|
|
7504
7695
|
sessionStorage.setItem(key, id);
|
|
7696
|
+
sessionStorage.setItem(this.#key(MINTED_SUFFIX + id), "1");
|
|
7505
7697
|
return id;
|
|
7506
7698
|
}
|
|
7699
|
+
isUnsent(threadId) {
|
|
7700
|
+
return sessionStorage.getItem(this.#key(MINTED_SUFFIX + threadId)) !== null && sessionStorage.getItem(this.#key(MESSAGES_SUFFIX + threadId)) === null;
|
|
7701
|
+
}
|
|
7507
7702
|
loadMessages(threadId) {
|
|
7508
7703
|
return Promise.resolve(this.#readJson(this.#key(MESSAGES_SUFFIX + threadId)));
|
|
7509
7704
|
}
|
|
7510
7705
|
saveMessages(threadId, messages) {
|
|
7511
7706
|
sessionStorage.setItem(this.#key(MESSAGES_SUFFIX + threadId), JSON.stringify(messages));
|
|
7707
|
+
sessionStorage.removeItem(this.#key(MINTED_SUFFIX + threadId));
|
|
7512
7708
|
this.#touchThread(threadId, messages);
|
|
7513
7709
|
}
|
|
7514
7710
|
loadCheckpoint(threadId) {
|
|
@@ -7525,6 +7721,7 @@ var SessionStorageStore = class {
|
|
|
7525
7721
|
clear(threadId) {
|
|
7526
7722
|
sessionStorage.removeItem(this.#key(MESSAGES_SUFFIX + threadId));
|
|
7527
7723
|
sessionStorage.removeItem(this.#key(CHECKPOINT_SUFFIX + threadId));
|
|
7724
|
+
sessionStorage.removeItem(this.#key(MINTED_SUFFIX + threadId));
|
|
7528
7725
|
this.#writeThreads(this.#readThreads().filter((thread) => thread.threadId !== threadId));
|
|
7529
7726
|
if (sessionStorage.getItem(this.#key(THREAD_SUFFIX)) === threadId) {
|
|
7530
7727
|
sessionStorage.removeItem(this.#key(THREAD_SUFFIX));
|
|
@@ -7713,6 +7910,10 @@ var RemoteConversationStore = class {
|
|
|
7713
7910
|
setActiveThread(threadId) {
|
|
7714
7911
|
this.#local.setActiveThread(threadId);
|
|
7715
7912
|
}
|
|
7913
|
+
/** Delegated, so wrapping a store does not lose what it knows about its own ids. */
|
|
7914
|
+
isUnsent(threadId) {
|
|
7915
|
+
return this.#local.isUnsent?.(threadId) === true;
|
|
7916
|
+
}
|
|
7716
7917
|
saveMessages(threadId, messages) {
|
|
7717
7918
|
this.#local.saveMessages(threadId, messages);
|
|
7718
7919
|
}
|
|
@@ -7740,6 +7941,9 @@ var RemoteConversationStore = class {
|
|
|
7740
7941
|
return rows.filter((row) => !this.#dropped.has(row.thread_id)).map((row) => this.#toMeta(row));
|
|
7741
7942
|
}
|
|
7742
7943
|
async loadMessages(threadId) {
|
|
7944
|
+
if (this.#local.isUnsent?.(threadId) === true) {
|
|
7945
|
+
return null;
|
|
7946
|
+
}
|
|
7743
7947
|
const response = await this.#get(`${this.#url}${encodeURIComponent(threadId)}/`);
|
|
7744
7948
|
if (response === null || !response.ok) {
|
|
7745
7949
|
return this.#local.loadMessages(threadId);
|
|
@@ -8166,6 +8370,12 @@ var AgUiChat = class extends HTMLElement {
|
|
|
8166
8370
|
* the real output with the generic "executed on the server" fallback.
|
|
8167
8371
|
*/
|
|
8168
8372
|
#serverSettled = /* @__PURE__ */ new Set();
|
|
8373
|
+
/**
|
|
8374
|
+
* Tool calls made during the current interaction, in the order they started,
|
|
8375
|
+
* so {@link RUN_FINISHED_EVENT} can report them once the whole thing settles.
|
|
8376
|
+
* Spans tool rounds and an approval interrupt; cleared when the event fires.
|
|
8377
|
+
*/
|
|
8378
|
+
#runTools = [];
|
|
8169
8379
|
#root;
|
|
8170
8380
|
#chat;
|
|
8171
8381
|
#messages;
|
|
@@ -9196,6 +9406,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
9196
9406
|
* {@link toggleCollapsed} and {@link toggleTheme}.
|
|
9197
9407
|
*/
|
|
9198
9408
|
openThreads() {
|
|
9409
|
+
this.#checkpoints.close();
|
|
9199
9410
|
void this.#refreshDrawer();
|
|
9200
9411
|
this.#drawer.open();
|
|
9201
9412
|
}
|
|
@@ -9207,9 +9418,27 @@ var AgUiChat = class extends HTMLElement {
|
|
|
9207
9418
|
* an empty panel.
|
|
9208
9419
|
*/
|
|
9209
9420
|
openCheckpoints() {
|
|
9421
|
+
this.#drawer.close();
|
|
9210
9422
|
void this.#refreshCheckpoints();
|
|
9211
9423
|
this.#checkpoints.open();
|
|
9212
9424
|
}
|
|
9425
|
+
/** Close the checkpoints panel, if it is open. */
|
|
9426
|
+
closeCheckpoints() {
|
|
9427
|
+
this.#checkpoints.close();
|
|
9428
|
+
}
|
|
9429
|
+
/**
|
|
9430
|
+
* Open the checkpoints panel, or close it if it is already open — what the
|
|
9431
|
+
* built-in ⭯ button does, because a control that opens a panel is read as the
|
|
9432
|
+
* control that also dismisses it. {@link openCheckpoints} stays open-only for a
|
|
9433
|
+
* host that means exactly that.
|
|
9434
|
+
*/
|
|
9435
|
+
toggleCheckpoints() {
|
|
9436
|
+
if (this.#checkpoints.open_) {
|
|
9437
|
+
this.#checkpoints.close();
|
|
9438
|
+
return;
|
|
9439
|
+
}
|
|
9440
|
+
this.openCheckpoints();
|
|
9441
|
+
}
|
|
9213
9442
|
/**
|
|
9214
9443
|
* Start a fresh conversation: forget the persisted history, drop the
|
|
9215
9444
|
* in-memory run state, clear the transcript, and mint a new thread id.
|
|
@@ -9474,8 +9703,8 @@ var AgUiChat = class extends HTMLElement {
|
|
|
9474
9703
|
controls.setAttribute("part", "header-controls");
|
|
9475
9704
|
const history = this.#headerButton("history", this.#strings.chatHistory, "\u2630");
|
|
9476
9705
|
history.addEventListener("click", () => this.openThreads());
|
|
9477
|
-
const checkpoints = this.#headerButton("checkpoints", this.#strings.checkpoints, "\
|
|
9478
|
-
checkpoints.addEventListener("click", () => this.
|
|
9706
|
+
const checkpoints = this.#headerButton("checkpoints", this.#strings.checkpoints, "\u21BA");
|
|
9707
|
+
checkpoints.addEventListener("click", () => this.toggleCheckpoints());
|
|
9479
9708
|
const newChat = this.#headerButton("new", this.#strings.newChat, "\u271A");
|
|
9480
9709
|
newChat.addEventListener("click", () => this.newChat());
|
|
9481
9710
|
const collapse = this.#headerButton("collapse", this.#strings.collapse, "\u2014");
|
|
@@ -9576,6 +9805,16 @@ var AgUiChat = class extends HTMLElement {
|
|
|
9576
9805
|
this.#drawer.element,
|
|
9577
9806
|
this.#checkpoints.element
|
|
9578
9807
|
);
|
|
9808
|
+
this.#chat.addEventListener("pointerdown", (event) => {
|
|
9809
|
+
if (!this.#checkpoints.open_) {
|
|
9810
|
+
return;
|
|
9811
|
+
}
|
|
9812
|
+
const path = event.composedPath();
|
|
9813
|
+
if (path.includes(this.#checkpoints.element) || path.includes(checkpoints)) {
|
|
9814
|
+
return;
|
|
9815
|
+
}
|
|
9816
|
+
this.#checkpoints.close();
|
|
9817
|
+
});
|
|
9579
9818
|
this.#launcher.className = "launcher";
|
|
9580
9819
|
this.#launcher.type = "button";
|
|
9581
9820
|
this.#launcher.setAttribute("part", "launcher");
|
|
@@ -9993,39 +10232,61 @@ var AgUiChat = class extends HTMLElement {
|
|
|
9993
10232
|
* Render an approval card per server-side-tool interrupt and collect the
|
|
9994
10233
|
* user's decisions (approve → run it, deny → decline it).
|
|
9995
10234
|
*
|
|
10235
|
+
* **One card per gated call, in that call's own tool card, all at once.** A run
|
|
10236
|
+
* can defer several calls, and the wire answers each independently — so the UI
|
|
10237
|
+
* has to let a person answer each independently, which means saying which is
|
|
10238
|
+
* which. The prompt cannot: it comes from the tool's `x-confirm` and is
|
|
10239
|
+
* identical for every call of that tool. The tool card can, by position, and it
|
|
10240
|
+
* is already showing the arguments. Asking them serially was the other half of
|
|
10241
|
+
* the problem: the second question only appeared once the first was answered,
|
|
10242
|
+
* so a person could neither compare them nor tell that more were coming.
|
|
10243
|
+
*
|
|
10244
|
+
* Each gated card is marked `deferred` for the wait. That is not cosmetic — at
|
|
10245
|
+
* `pending` it read "running…" while the stream was over and the server idle.
|
|
10246
|
+
*
|
|
9996
10247
|
* The run is suspended on these cards. A Stop while any is open aborts the
|
|
9997
10248
|
* shared {@link #confirmAbort} controller, resolving every still-open card as
|
|
9998
10249
|
* denied. An approved tool runs on the follow-up resume run and streams its
|
|
9999
|
-
* result into the same
|
|
10000
|
-
* result will ever arrive.
|
|
10250
|
+
* result into the same card (returned to `pending`, since it now really is
|
|
10251
|
+
* running); a denied one settles here, as no result will ever arrive.
|
|
10001
10252
|
*/
|
|
10002
10253
|
async #resolveInterrupts(interrupts) {
|
|
10003
|
-
const responses = {};
|
|
10004
10254
|
this.#confirmAbort = new AbortController();
|
|
10005
10255
|
this.#hidePending();
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10256
|
+
const signal = this.#confirmAbort.signal;
|
|
10257
|
+
const answered = await Promise.all(
|
|
10258
|
+
interrupts.map(async (interrupt) => {
|
|
10259
|
+
const card = interrupt.toolCallId !== void 0 ? this.#toolCards.get(interrupt.toolCallId) : void 0;
|
|
10260
|
+
const request = {};
|
|
10261
|
+
const phrase = confirmPhrase(interrupt) ?? interrupt.message;
|
|
10262
|
+
if (phrase !== void 0) {
|
|
10263
|
+
request.message = phrase;
|
|
10264
|
+
}
|
|
10265
|
+
const toolName = card?.element.getAttribute("data-tool-name");
|
|
10266
|
+
if (toolName !== null && toolName !== void 0) {
|
|
10267
|
+
request.toolName = toolName;
|
|
10268
|
+
}
|
|
10269
|
+
card?.mark(TOOL_CALL_STATUS.DEFERRED);
|
|
10270
|
+
const approved = this.approvalRenderer !== null ? await this.approvalRenderer(request, { signal }) : await requestApproval(card?.approvalSlot ?? this.#ensureGroup(), request, {
|
|
10271
|
+
signal,
|
|
10272
|
+
strings: this.#strings
|
|
10273
|
+
});
|
|
10274
|
+
card?.recordDecision(approved ? "approved" : "declined");
|
|
10275
|
+
if (approved) {
|
|
10276
|
+
card?.mark(TOOL_CALL_STATUS.PENDING);
|
|
10277
|
+
} else {
|
|
10278
|
+
card?.settle(TOOL_CALL_STATUS.DECLINED, this.#strings.declinedAction);
|
|
10279
|
+
}
|
|
10280
|
+
return { id: interrupt.id, approved };
|
|
10281
|
+
})
|
|
10282
|
+
);
|
|
10283
|
+
this.#updateEmptyState();
|
|
10284
|
+
this.#messages.scrollTop = this.#messages.scrollHeight;
|
|
10028
10285
|
this.#confirmAbort = null;
|
|
10286
|
+
const responses = {};
|
|
10287
|
+
for (const { id, approved } of answered) {
|
|
10288
|
+
responses[id] = approved ? { status: "resolved", payload: { approved: true } } : { status: "cancelled" };
|
|
10289
|
+
}
|
|
10029
10290
|
return responses;
|
|
10030
10291
|
}
|
|
10031
10292
|
#handlers() {
|
|
@@ -10064,6 +10325,7 @@ var AgUiChat = class extends HTMLElement {
|
|
|
10064
10325
|
if (this.#noticeIfSkillLoad(call)) {
|
|
10065
10326
|
return;
|
|
10066
10327
|
}
|
|
10328
|
+
this.#runTools.push({ id: call.id, name: call.name });
|
|
10067
10329
|
this.#cardFor(call);
|
|
10068
10330
|
},
|
|
10069
10331
|
onActivity: (activityType, content) => {
|
|
@@ -10117,9 +10379,33 @@ var AgUiChat = class extends HTMLElement {
|
|
|
10117
10379
|
}
|
|
10118
10380
|
this.#currentGroup = null;
|
|
10119
10381
|
this.#thoughts = null;
|
|
10382
|
+
this.#dispatchRunFinished();
|
|
10120
10383
|
}
|
|
10121
10384
|
};
|
|
10122
10385
|
}
|
|
10386
|
+
/**
|
|
10387
|
+
* Tell the host the interaction is over and what ran in it.
|
|
10388
|
+
*
|
|
10389
|
+
* Last thing in `onSettled`, so a listener that refetches sees a transcript
|
|
10390
|
+
* that has already stopped changing. `side` is read from the streamed-result
|
|
10391
|
+
* bookkeeping rather than from the tool list: whether a call executed on the
|
|
10392
|
+
* server is a fact about the run, and a name can appear on both sides across a
|
|
10393
|
+
* conversation.
|
|
10394
|
+
*/
|
|
10395
|
+
#dispatchRunFinished() {
|
|
10396
|
+
const tools = this.#runTools.map(({ id, name }) => ({
|
|
10397
|
+
name,
|
|
10398
|
+
side: this.#serverSettled.has(id) ? "server" : "client"
|
|
10399
|
+
}));
|
|
10400
|
+
this.#runTools = [];
|
|
10401
|
+
this.dispatchEvent(
|
|
10402
|
+
new CustomEvent(RUN_FINISHED_EVENT, {
|
|
10403
|
+
detail: { tools },
|
|
10404
|
+
bubbles: true,
|
|
10405
|
+
composed: true
|
|
10406
|
+
})
|
|
10407
|
+
);
|
|
10408
|
+
}
|
|
10123
10409
|
/** A muted "⏹ Stopped" line in the transcript (distinct from the ⚠️ error bubble). */
|
|
10124
10410
|
#appendStoppedNote() {
|
|
10125
10411
|
const note = document.createElement("div");
|
|
@@ -10231,6 +10517,10 @@ var AgUiChat = class extends HTMLElement {
|
|
|
10231
10517
|
return card;
|
|
10232
10518
|
}
|
|
10233
10519
|
};
|
|
10520
|
+
function confirmPhrase(interrupt) {
|
|
10521
|
+
const phrase = interrupt.metadata?.[X_CONFIRM_KEY];
|
|
10522
|
+
return typeof phrase === "string" && phrase.trim() !== "" ? phrase : void 0;
|
|
10523
|
+
}
|
|
10234
10524
|
function restoredToolCalls(value) {
|
|
10235
10525
|
return Array.isArray(value) ? value.filter(isRestoredToolCall) : [];
|
|
10236
10526
|
}
|
|
@@ -10293,7 +10583,7 @@ function setControlValue(el, value) {
|
|
|
10293
10583
|
}
|
|
10294
10584
|
|
|
10295
10585
|
// src/version.ts
|
|
10296
|
-
var VERSION = "0.
|
|
10586
|
+
var VERSION = "0.25.0";
|
|
10297
10587
|
export {
|
|
10298
10588
|
ATTACHMENT_EVENT,
|
|
10299
10589
|
AgUiChat,
|
|
@@ -10308,6 +10598,7 @@ export {
|
|
|
10308
10598
|
MAX_TOOL_ROUNDS,
|
|
10309
10599
|
MESSAGE_ROLE,
|
|
10310
10600
|
PAGE_ACTIONS,
|
|
10601
|
+
RUN_FINISHED_EVENT,
|
|
10311
10602
|
RemoteConversationStore,
|
|
10312
10603
|
RunIndex,
|
|
10313
10604
|
STATE_EVENT,
|