@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +194 -1
  2. package/README.md +188 -38
  3. package/dist/ag-ui-web-component.bundle.js +189 -49
  4. package/dist/ag-ui-web-component.bundle.js.map +3 -3
  5. package/dist/constants.d.ts +25 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +24 -0
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/conversation_store.d.ts +18 -0
  10. package/dist/core/conversation_store.d.ts.map +1 -1
  11. package/dist/core/remote_conversation_store.d.ts +2 -0
  12. package/dist/core/remote_conversation_store.d.ts.map +1 -1
  13. package/dist/core/run_index.d.ts +10 -0
  14. package/dist/core/run_index.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +335 -44
  18. package/dist/index.js.map +2 -2
  19. package/dist/ui/checkpoint_menu.d.ts.map +1 -1
  20. package/dist/ui/styles.d.ts +1 -1
  21. package/dist/ui/styles.d.ts.map +1 -1
  22. package/dist/ui/tool_call_card.d.ts +26 -2
  23. package/dist/ui/tool_call_card.d.ts.map +1 -1
  24. package/dist/ui/ui_strings.d.ts +2 -0
  25. package/dist/ui/ui_strings.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/constants.ts +26 -0
  28. package/src/core/ag_ui_chat.ts +186 -40
  29. package/src/core/conversation_store.ts +30 -0
  30. package/src/core/remote_conversation_store.ts +14 -0
  31. package/src/core/run_index.ts +10 -0
  32. package/src/index.ts +3 -0
  33. package/src/ui/checkpoint_menu.ts +32 -7
  34. package/src/ui/styles.ts +153 -13
  35. package/src/ui/tool_call_card.ts +41 -3
  36. package/src/ui/ui_strings.ts +3 -0
  37. package/src/version.ts +1 -1
package/src/ui/styles.ts CHANGED
@@ -923,18 +923,28 @@ export const STYLES = `
923
923
  color: var(--_tool-fg);
924
924
  }
925
925
 
926
+ /* Wraps, because the name is the only flexible child and every badge the row
927
+ gains is taken out of it. An approved call adds a third fixed badge, which in
928
+ a sidebar-width panel left the name 37px and broke it mid-word. Badges drop to
929
+ their own row instead. */
926
930
  .tool-call-head {
927
931
  display: flex;
932
+ flex-wrap: wrap;
928
933
  align-items: center;
929
934
  justify-content: space-between;
930
935
  gap: 8px;
931
936
  }
932
937
 
933
938
  .tool-call-name {
934
- flex: 1;
935
- min-width: 0;
939
+ /* An auto basis, and a min-width floor rather than zero: the name may shrink,
940
+ but not below something readable, so wrapping moves a badge instead of
941
+ shredding a word. Breaking anywhere still applies to a name that cannot fit
942
+ on a line of its own, which is what keeps a long unbroken tool name inside
943
+ the card. */
944
+ flex: 1 1 auto;
945
+ min-width: 6ch;
936
946
  font-weight: 600;
937
- word-break: break-word;
947
+ overflow-wrap: anywhere;
938
948
  }
939
949
 
940
950
  /* Leading status icon. Empty in the DOM — the glyph/spinner is drawn
@@ -963,6 +973,13 @@ export const STYLES = `
963
973
  to { transform: rotate(360deg); }
964
974
  }
965
975
 
976
+ /* Deferred: no spinner, because nothing is spinning. A steady accent dot, since
977
+ the state is waiting-on-you rather than an outcome. */
978
+ .tool-call[data-status="deferred"] .tool-call-icon {
979
+ border-radius: 50%;
980
+ background: var(--_accent);
981
+ }
982
+
966
983
  /* Settled: a themeable glyph coloured by outcome. */
967
984
  .tool-call[data-status="done"] .tool-call-icon::before {
968
985
  content: var(--_tool-icon-done);
@@ -1005,6 +1022,10 @@ export const STYLES = `
1005
1022
  color: var(--_muted);
1006
1023
  }
1007
1024
 
1025
+ .tool-call[data-status="deferred"] .tool-call-status {
1026
+ color: var(--_accent);
1027
+ }
1028
+
1008
1029
  .tool-call[data-status="done"] .tool-call-status {
1009
1030
  color: var(--_success);
1010
1031
  }
@@ -1098,12 +1119,41 @@ export const STYLES = `
1098
1119
 
1099
1120
  /* A pending card has no result yet, and in the modes where the arguments are
1100
1121
  hidden too there is nothing behind the toggle. Hide the control rather than
1101
- offer one that expands onto nothing. */
1122
+ offer one that expands onto nothing. A deferred card is the same, and its
1123
+ arguments are shown unconditionally by the rules below. */
1102
1124
  .tool-call[data-status="pending"] .tool-call-toggle,
1125
+ .tool-call[data-status="deferred"] .tool-call-toggle,
1103
1126
  :host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
1104
1127
  display: none;
1105
1128
  }
1106
1129
 
1130
+ /* The approval prompt for a gated call, rendered inside that call's own card.
1131
+ Empty on every card nobody is being asked about, so it collapses instead of
1132
+ adding a gap to each one. */
1133
+ .tool-call-approval:empty {
1134
+ display: none;
1135
+ }
1136
+
1137
+ .tool-call-approval {
1138
+ margin-top: 8px;
1139
+ }
1140
+
1141
+ /* A card that is asking a question shows what it is asking about, in every
1142
+ display mode. Three gated calls of one tool ask the same words, so the
1143
+ arguments are the only thing telling them apart, and a density setting must
1144
+ not be able to hide the answer to "which one is this". */
1145
+ :host([data-tool-display="minimal"]) .tool-call[data-status="deferred"] .tool-call-body {
1146
+ display: flex;
1147
+ }
1148
+
1149
+ /* The arguments region only, never every section: the result region carries the
1150
+ hidden attribute until a result exists, and a display value here overrides it,
1151
+ framing an empty RESULT heading under the question. */
1152
+ :host([data-tool-display="compact"]) .tool-call[data-status="deferred"] .tool-call-section--args,
1153
+ :host([data-tool-display="inline"]) .tool-call[data-status="deferred"] .tool-call-section--args {
1154
+ display: flex;
1155
+ }
1156
+
1107
1157
  .tool-call-toggle {
1108
1158
  align-self: flex-start;
1109
1159
  border: none;
@@ -1323,6 +1373,20 @@ export const STYLES = `
1323
1373
  opacity: 0.6;
1324
1374
  }
1325
1375
 
1376
+ /* The same trap the attachment tray carries a note about, two rules along: an
1377
+ author display beats the UA stylesheet's rule for the hidden property, so a
1378
+ button the element has explicitly hidden keeps laying out and painting. The
1379
+ clip is hidden until a host supplies an upload handler or an attachments URL,
1380
+ and without this it is a visible control that cannot do anything.
1381
+
1382
+ The mic needs no such rule, and the asymmetry is worth knowing before adding
1383
+ one: it is not hidden when unconfigured, it is never built. The voice wiring
1384
+ returns before constructing the button, leaving only an empty voice slot that
1385
+ is display: contents. A hidden-state rule for the mic would match nothing. */
1386
+ .attach-btn[hidden] {
1387
+ display: none;
1388
+ }
1389
+
1326
1390
  /* Send closes the row on the right: a circle, the only filled control in the
1327
1391
  composer, so "the thing that acts" reads at a glance. */
1328
1392
  .send {
@@ -1932,47 +1996,123 @@ export const STYLES = `
1932
1996
  opacity: 0.7;
1933
1997
  }
1934
1998
 
1999
+ /* A row is a label and two buttons, and nothing about the row itself is
2000
+ pressable. It used to light up on hover, which is the affordance of something
2001
+ clickable and made the buttons look like decoration on a clickable strip. The
2002
+ resting surface groups the row instead, so hover can mean what it says: only
2003
+ the buttons respond to it. */
2004
+ /* A row is a label and two buttons, and nothing about the row itself is
2005
+ pressable. It used to light up on hover, which is the affordance of something
2006
+ clickable and made the buttons look like decoration on a clickable strip. The
2007
+ resting surface groups the row instead, so hover can mean what it says: only
2008
+ the buttons respond to it.
2009
+
2010
+ It wraps for the same reason the tool-call head does. Every child but the label
2011
+ is fixed-width, so in a narrow panel the label is the only thing that can give
2012
+ -- and a flex-basis of zero lets it give everything. Adding the run id was
2013
+ enough to crush "just now" to zero pixels: present, correct, and invisible.
2014
+ Wrapping puts the buttons on their own line instead. */
1935
2015
  .checkpoint-row {
1936
2016
  display: flex;
2017
+ flex-wrap: wrap;
1937
2018
  align-items: center;
1938
2019
  gap: 0.5rem;
1939
- padding: 0.25rem;
2020
+ padding: 0.3125rem 0.4375rem;
1940
2021
  border-radius: 0.375rem;
1941
- }
1942
-
1943
- .checkpoint-row:hover {
1944
2022
  background: var(--_hover);
1945
2023
  }
1946
2024
 
2025
+ /* Grows into spare room, and refuses to shrink past the shortest thing it ever
2026
+ says. A time is short and bounded, so there is no case for eliding it. */
1947
2027
  .checkpoint-label {
1948
- flex: 1;
2028
+ flex: 1 1 auto;
2029
+ min-width: 7ch;
1949
2030
  font-size: 0.8125rem;
1950
2031
  white-space: nowrap;
1951
2032
  overflow: hidden;
1952
2033
  text-overflow: ellipsis;
1953
2034
  }
1954
2035
 
2036
+ /* When the label holds the run's first message, the time moves here: still worth
2037
+ showing, no longer what identifies the row. Muted and unshrinkable, so it does
2038
+ not compete with the words beside it. */
2039
+ .checkpoint-time {
2040
+ flex: 0 0 auto;
2041
+ font-size: 0.6875rem;
2042
+ opacity: 0.7;
2043
+ white-space: nowrap;
2044
+ }
2045
+
2046
+ /* Enough of the run id to tell two runs apart when both say "just now". Muted
2047
+ and monospaced: it is a reference, not a name. */
2048
+ .checkpoint-id {
2049
+ flex: 0 0 auto;
2050
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
2051
+ font-size: 0.6875rem;
2052
+ opacity: 0.55;
2053
+ }
2054
+
2055
+ /* On the panel's own surface, not the row's: the row now paints the hover token
2056
+ itself, and a badge the same colour as what it sits on is not a badge. */
1955
2057
  .checkpoint-branch {
1956
2058
  font-size: 0.6875rem;
1957
2059
  padding: 0 0.375rem;
1958
2060
  border-radius: 999px;
1959
- background: var(--_hover);
2061
+ background: var(--_assistant-bg);
1960
2062
  opacity: 0.8;
1961
2063
  }
1962
2064
 
2065
+ /* The two things in the row that actually do something, so they are the two
2066
+ things that look like it: a filled surface at rest rather than a transparent
2067
+ outline, which on top of the old row highlight was nearly invisible. */
1963
2068
  .checkpoint-action {
1964
2069
  font: inherit;
1965
2070
  font-size: 0.75rem;
2071
+ line-height: 1.4;
1966
2072
  cursor: pointer;
1967
- padding: 0.125rem 0.5rem;
2073
+ padding: 0.1875rem 0.5625rem;
1968
2074
  border: 1px solid var(--_border);
1969
2075
  border-radius: 0.375rem;
1970
- background: transparent;
2076
+ background: var(--_bg);
1971
2077
  color: inherit;
2078
+ transition:
2079
+ background var(--_motion) var(--_ease),
2080
+ border-color var(--_motion) var(--_ease),
2081
+ transform var(--_motion) var(--_ease);
1972
2082
  }
1973
2083
 
1974
- .checkpoint-action:hover {
2084
+ /* Resume is what a reader wants nine times in ten; fork is the deliberate choice
2085
+ beside it. Filled and outlined, the same pair the confirmation and approval
2086
+ cards already use for their primary and secondary action. */
2087
+ .checkpoint-resume {
2088
+ font-weight: 600;
2089
+ border-color: var(--_accent);
2090
+ background: var(--_accent);
2091
+ color: #ffffff;
2092
+ }
2093
+
2094
+ .checkpoint-fork:hover {
1975
2095
  background: var(--_hover);
2096
+ border-color: var(--_accent);
2097
+ }
2098
+
2099
+ /* The filled one cannot go lighter on hover without losing its contrast with the
2100
+ white label, so it dims instead. */
2101
+ .checkpoint-resume:hover {
2102
+ opacity: 0.88;
2103
+ }
2104
+
2105
+ /* Pressed: a pixel down, so the click is felt as well as seen. */
2106
+ .checkpoint-action:active {
2107
+ transform: translateY(1px);
2108
+ }
2109
+
2110
+ /* Keyboard focus was invisible here, in a panel that traps focus and is reached
2111
+ by Tab -- so the one navigation path guaranteed to land on these buttons was
2112
+ the one with nothing to show for it. */
2113
+ .checkpoint-action:focus-visible {
2114
+ outline: 2px solid var(--_accent);
2115
+ outline-offset: 2px;
1976
2116
  }
1977
2117
 
1978
2118
  .drawer-backdrop {
@@ -7,13 +7,17 @@ export type ToolCallStatus = (typeof TOOL_CALL_STATUS)[keyof typeof TOOL_CALL_ST
7
7
  /** How much detail a card renders. */
8
8
  export type ToolDisplayMode = (typeof TOOL_DISPLAY)[keyof typeof TOOL_DISPLAY];
9
9
 
10
- /** The terminal states a card settles into (everything but `pending`). */
11
- export type SettledStatus = Exclude<ToolCallStatus, typeof TOOL_CALL_STATUS.PENDING>;
10
+ /** The two states a card can sit in before an outcome exists. */
11
+ export type UnsettledStatus = typeof TOOL_CALL_STATUS.PENDING | typeof TOOL_CALL_STATUS.DEFERRED;
12
+
13
+ /** The terminal states a card settles into (everything unsettled excluded). */
14
+ export type SettledStatus = Exclude<ToolCallStatus, UnsettledStatus>;
12
15
 
13
16
  /** Short pill text shown for each status, drawn from the string table. */
14
17
  function statusLabels(strings: UiStrings): Record<ToolCallStatus, string> {
15
18
  return {
16
19
  [TOOL_CALL_STATUS.PENDING]: strings.toolRunning,
20
+ [TOOL_CALL_STATUS.DEFERRED]: strings.toolDeferred,
17
21
  [TOOL_CALL_STATUS.DONE]: strings.toolDone,
18
22
  [TOOL_CALL_STATUS.ERROR]: strings.toolError,
19
23
  [TOOL_CALL_STATUS.DECLINED]: strings.toolDeclined,
@@ -64,6 +68,21 @@ export class ToolCallCard {
64
68
  /** The card's root element; append this into the message list. */
65
69
  readonly element: HTMLDivElement;
66
70
 
71
+ /**
72
+ * Where a question about *this* call renders — the approval prompt for a
73
+ * server-side tool the run deferred.
74
+ *
75
+ * It belongs to the card rather than to the transcript because a run can defer
76
+ * several calls at once, and a prompt written per *tool* ("Add this event to
77
+ * the board?") is identical for every one of them. Rendered into the answer
78
+ * group they were three anonymous copies of one question, below the three
79
+ * cards they gated; rendered here, position identifies them and the arguments
80
+ * are already on screen above the question.
81
+ *
82
+ * Empty until used, and hidden while empty by the shadow CSS.
83
+ */
84
+ readonly approvalSlot: HTMLDivElement;
85
+
67
86
  readonly #status: HTMLSpanElement;
68
87
  readonly #decision: HTMLSpanElement;
69
88
  readonly #toggle: HTMLButtonElement;
@@ -143,7 +162,26 @@ export class ToolCallCard {
143
162
  body.setAttribute("part", "tool-card-body");
144
163
  body.append(argsSection.root, resultSection.root);
145
164
 
146
- this.element.append(head, this.#toggle, body);
165
+ this.approvalSlot = document.createElement("div");
166
+ this.approvalSlot.className = "tool-call-approval";
167
+ this.approvalSlot.setAttribute("part", "tool-card-approval");
168
+
169
+ this.element.append(head, this.#toggle, body, this.approvalSlot);
170
+ }
171
+
172
+ /**
173
+ * Move between the two states that are not an outcome — `pending` (running)
174
+ * and `deferred` (gated, waiting on a person).
175
+ *
176
+ * Ignored once {@link settle} has run: a card that was declined must not be
177
+ * talked back into looking live by a late event.
178
+ */
179
+ mark(status: UnsettledStatus): void {
180
+ if (this.#settled) {
181
+ return;
182
+ }
183
+ this.element.setAttribute("data-status", status);
184
+ this.#status.textContent = statusLabels(this.#strings)[status];
147
185
  }
148
186
 
149
187
  /**
@@ -84,6 +84,8 @@ export interface UiStrings {
84
84
  // ── Tool-call card ──────────────────────────────────────────────────────────
85
85
  /** Status pill while the call runs. */
86
86
  toolRunning: string;
87
+ /** Status pill on a gated call the run deferred, waiting on a person. */
88
+ toolDeferred: string;
87
89
  /** Status pill on success. */
88
90
  toolDone: string;
89
91
  /** Status pill on error. */
@@ -244,6 +246,7 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
244
246
  transcriptionFailed: "Transcription failed",
245
247
 
246
248
  toolRunning: "running…",
249
+ toolDeferred: "waiting for you",
247
250
  toolDone: "✓ done",
248
251
  toolError: "⚠ error",
249
252
  toolDeclined: "⊘ declined",
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.23.1";
1
+ export const VERSION: string = "0.25.0";