@artooi/ag-ui-web-component 0.28.0 → 0.30.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 (65) hide show
  1. package/CHANGELOG.md +615 -1
  2. package/README.md +564 -35
  3. package/dist/ag-ui-web-component.bundle.js +491 -50
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +129 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +232 -1
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/agui_client.d.ts +56 -1
  10. package/dist/core/agui_client.d.ts.map +1 -1
  11. package/dist/index.d.ts +8 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2081 -98
  14. package/dist/index.js.map +4 -4
  15. package/dist/ui/approval_card.d.ts +18 -0
  16. package/dist/ui/approval_card.d.ts.map +1 -1
  17. package/dist/ui/checkpoint_menu.d.ts +10 -0
  18. package/dist/ui/checkpoint_menu.d.ts.map +1 -1
  19. package/dist/ui/confirmation_card.d.ts +16 -0
  20. package/dist/ui/confirmation_card.d.ts.map +1 -1
  21. package/dist/ui/message_actions.d.ts +56 -0
  22. package/dist/ui/message_actions.d.ts.map +1 -0
  23. package/dist/ui/page_quote_offer.d.ts +33 -0
  24. package/dist/ui/page_quote_offer.d.ts.map +1 -0
  25. package/dist/ui/quote_selection.d.ts +66 -0
  26. package/dist/ui/quote_selection.d.ts.map +1 -0
  27. package/dist/ui/relative_time.d.ts +10 -0
  28. package/dist/ui/relative_time.d.ts.map +1 -1
  29. package/dist/ui/stick_to_bottom.d.ts +55 -0
  30. package/dist/ui/stick_to_bottom.d.ts.map +1 -0
  31. package/dist/ui/styles.d.ts +1 -1
  32. package/dist/ui/styles.d.ts.map +1 -1
  33. package/dist/ui/subagent_panel.d.ts +92 -0
  34. package/dist/ui/subagent_panel.d.ts.map +1 -0
  35. package/dist/ui/subagent_update.d.ts +19 -0
  36. package/dist/ui/subagent_update.d.ts.map +1 -0
  37. package/dist/ui/suggestion_chips.d.ts +29 -0
  38. package/dist/ui/suggestion_chips.d.ts.map +1 -0
  39. package/dist/ui/thread_drawer.d.ts +10 -0
  40. package/dist/ui/thread_drawer.d.ts.map +1 -1
  41. package/dist/ui/tool_call_card.d.ts +81 -1
  42. package/dist/ui/tool_call_card.d.ts.map +1 -1
  43. package/dist/ui/ui_strings.d.ts +50 -0
  44. package/dist/ui/ui_strings.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/constants.ts +138 -1
  47. package/src/core/ag_ui_chat.ts +1081 -73
  48. package/src/core/agui_client.ts +89 -2
  49. package/src/index.ts +43 -0
  50. package/src/ui/approval_card.ts +90 -2
  51. package/src/ui/checkpoint_menu.ts +22 -5
  52. package/src/ui/confirmation_card.ts +29 -1
  53. package/src/ui/message_actions.ts +170 -0
  54. package/src/ui/page_quote_offer.ts +215 -0
  55. package/src/ui/quote_selection.ts +345 -0
  56. package/src/ui/relative_time.ts +11 -0
  57. package/src/ui/stick_to_bottom.ts +126 -0
  58. package/src/ui/styles.ts +410 -0
  59. package/src/ui/subagent_panel.ts +213 -0
  60. package/src/ui/subagent_update.ts +80 -0
  61. package/src/ui/suggestion_chips.ts +73 -0
  62. package/src/ui/thread_drawer.ts +22 -2
  63. package/src/ui/tool_call_card.ts +138 -3
  64. package/src/ui/ui_strings.ts +75 -0
  65. package/src/version.ts +1 -1
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Narrow one `ag_ui.subagent` payload into a {@link SubAgentUpdate}.
3
+ *
4
+ * Kept out of the panel for the reason `chartSpecFrom` is kept out of the
5
+ * renderer: the panel's job is drawing, and a value that reaches it has already
6
+ * been vouched for.
7
+ *
8
+ * Defensive about the payload, not about the name. A `CUSTOM` event's `value` is
9
+ * `unknown` by the protocol, so a server can put anything there, and a malformed
10
+ * announcement must not take a run down with it — the same rule the invalidation
11
+ * channel applies to the same field. What is refused here is only what cannot be
12
+ * rendered at all: without a `delegationId` there is no card to attach to, and
13
+ * without a known `phase` there is no state to be in. Everything else degrades to
14
+ * `null`, which the panel reads as "said nothing about this".
15
+ */
16
+
17
+ import { SUBAGENT_PHASE } from "../constants.js";
18
+ import type { SubAgentPhase, SubAgentTool, SubAgentUpdate } from "./subagent_panel.js";
19
+
20
+ const PHASES: readonly string[] = Object.values(SUBAGENT_PHASE);
21
+
22
+ /** A record view of `value`, or `null` for anything that is not an object. */
23
+ function asRecord(value: unknown): Record<string, unknown> | null {
24
+ // `typeof null` is "object", and an array is one too — neither carries the
25
+ // keys below, and both arrive from a JSON decoder without any warning.
26
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
27
+ return null;
28
+ }
29
+ return value as Record<string, unknown>;
30
+ }
31
+
32
+ /** A non-empty string, or `null`. */
33
+ function asText(value: unknown): string | null {
34
+ return typeof value === "string" && value !== "" ? value : null;
35
+ }
36
+
37
+ /**
38
+ * The `tool` record the two tool phases carry.
39
+ *
40
+ * All-or-nothing: the contract states all three keys on every tool phase, so a
41
+ * partial record is a payload this client does not understand rather than a step
42
+ * to draw half of. A step row keyed by an empty id would also collide with the
43
+ * next one, silently merging two of the child's calls into one row.
44
+ */
45
+ function asTool(value: unknown): SubAgentTool | null {
46
+ const record = asRecord(value);
47
+ if (record === null) {
48
+ return null;
49
+ }
50
+ const toolCallId = asText(record["toolCallId"]);
51
+ const name = asText(record["name"]);
52
+ const ok = record["ok"];
53
+ if (toolCallId === null || name === null) {
54
+ return null;
55
+ }
56
+ if (ok !== null && typeof ok !== "boolean") {
57
+ return null;
58
+ }
59
+ return { toolCallId, name, ok };
60
+ }
61
+
62
+ /** Narrow an `ag_ui.subagent` `CUSTOM` value, or `null` if it cannot be drawn. */
63
+ export function subAgentUpdate(value: unknown): SubAgentUpdate | null {
64
+ const record = asRecord(value);
65
+ if (record === null) {
66
+ return null;
67
+ }
68
+ const delegationId = asText(record["delegationId"]);
69
+ const phase = record["phase"];
70
+ if (delegationId === null || typeof phase !== "string" || !PHASES.includes(phase)) {
71
+ return null;
72
+ }
73
+ return {
74
+ delegationId,
75
+ phase: phase as SubAgentPhase,
76
+ agent: asText(record["agent"]),
77
+ status: asText(record["status"]),
78
+ tool: asTool(record["tool"]),
79
+ };
80
+ }
@@ -0,0 +1,73 @@
1
+ import type { UiStrings } from "./ui_strings.js";
2
+
3
+ /**
4
+ * Most prompts one push may draw.
5
+ *
6
+ * **Keep in step with `MAX_SUGGESTIONS` in django-ag-ui's
7
+ * `agent/suggestions_activity.py`**, which raises past it. Mirroring is the
8
+ * whole point: this side silently draws no more than its limit and has no
9
+ * channel to report the difference, so a producer that does not know the same
10
+ * number ships suggestions that never appear. That is the hole the chart bounds
11
+ * exist to close, and it was found there by shipping it.
12
+ */
13
+ export const MAX_SUGGESTIONS = 4;
14
+
15
+ /** Longest one prompt may be. Mirrored for the same reason as the count. */
16
+ export const MAX_SUGGESTION_CHARS = 120;
17
+
18
+ /** The `prompts` a `suggestions` activity carries, or `null` when it carries none. */
19
+ export function suggestionPrompts(content: unknown): string[] | null {
20
+ if (typeof content !== "object" || content === null) {
21
+ return null;
22
+ }
23
+ const raw = (content as { prompts?: unknown }).prompts;
24
+ if (!Array.isArray(raw)) {
25
+ return null;
26
+ }
27
+ const prompts = raw
28
+ .filter((prompt): prompt is string => typeof prompt === "string")
29
+ .map((prompt) => prompt.trim())
30
+ .filter((prompt) => prompt !== "" && prompt.length <= MAX_SUGGESTION_CHARS)
31
+ .slice(0, MAX_SUGGESTIONS);
32
+ return prompts.length === 0 ? null : prompts;
33
+ }
34
+
35
+ /**
36
+ * Draw follow-up prompts as chips that send themselves when clicked.
37
+ *
38
+ * Returns `null` when nothing survives, which is the registry's signal to draw
39
+ * nothing rather than an empty row -- the same contract the chart renderer uses
40
+ * for a spec it cannot draw.
41
+ *
42
+ * Buttons rather than links or list items: each one performs an action in the
43
+ * page, and the thing it sends is the label, so the accessible name is the
44
+ * prompt itself and needs no `aria-label` restating it.
45
+ */
46
+ export function renderSuggestionChips(
47
+ content: unknown,
48
+ strings: UiStrings,
49
+ onPick: (prompt: string) => void,
50
+ ): HTMLElement | null {
51
+ const prompts = suggestionPrompts(content);
52
+ if (prompts === null) {
53
+ return null;
54
+ }
55
+ const row = document.createElement("div");
56
+ row.className = "suggestions";
57
+ row.setAttribute("part", "suggestions");
58
+ // A group, labelled: without it a screen reader meets a row of unrelated
59
+ // buttons with no hint that they are the assistant's offer rather than the
60
+ // page's own controls.
61
+ row.setAttribute("role", "group");
62
+ row.setAttribute("aria-label", strings.suggestions);
63
+ for (const prompt of prompts) {
64
+ const chip = document.createElement("button");
65
+ chip.type = "button";
66
+ chip.className = "suggestion-chip";
67
+ chip.setAttribute("part", "suggestion-chip");
68
+ chip.textContent = prompt;
69
+ chip.addEventListener("click", () => onPick(prompt));
70
+ row.appendChild(chip);
71
+ }
72
+ return row;
73
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ThreadMeta } from "../core/conversation_store.js";
2
- import { relativeTime } from "./relative_time.js";
2
+ import { type RelativeTimeFormatter, relativeTime } from "./relative_time.js";
3
3
  import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
4
4
 
5
5
  /** Actions the host ({@link AgUiChat}) wires to the drawer's rows. */
@@ -35,6 +35,7 @@ export class ThreadDrawer {
35
35
  readonly #heading: HTMLSpanElement;
36
36
  readonly #newButton: HTMLButtonElement;
37
37
  readonly #list: HTMLDivElement;
38
+ #formatRelativeTime: RelativeTimeFormatter | null = null;
38
39
  #strings: UiStrings;
39
40
  #threads: readonly ThreadMeta[] = [];
40
41
  #activeId = "";
@@ -91,6 +92,25 @@ export class ThreadDrawer {
91
92
  }
92
93
 
93
94
  /** Re-localize the drawer's chrome and rows (the host calls this on connect). */
95
+ /**
96
+ * Replace the timestamp formatter, or restore the built-in with `null`.
97
+ *
98
+ * The built-in is deliberately locale-neutral -- there is no `Intl` anywhere
99
+ * in this component, so it never disagrees with a host's own formatting by
100
+ * guessing a locale. That is a defensible default and a poor requirement, so
101
+ * this is the way out.
102
+ */
103
+ setRelativeTimeFormatter(format: RelativeTimeFormatter | null): void {
104
+ this.#formatRelativeTime = format;
105
+ }
106
+
107
+ /** This row's timestamp, through the host's formatter when it set one. */
108
+ #formatTime(timestamp: number): string {
109
+ return this.#formatRelativeTime !== null
110
+ ? this.#formatRelativeTime(timestamp)
111
+ : relativeTime(timestamp, undefined, this.#strings);
112
+ }
113
+
94
114
  setStrings(strings: UiStrings): void {
95
115
  this.#strings = strings;
96
116
  this.#panel.setAttribute("aria-label", strings.chatHistory);
@@ -202,7 +222,7 @@ export class ThreadDrawer {
202
222
  const time = document.createElement("span");
203
223
  time.className = "drawer-row-time";
204
224
  time.setAttribute("part", "drawer-row-time");
205
- time.textContent = relativeTime(meta.updatedAt, undefined, this.#strings);
225
+ time.textContent = this.#formatTime(meta.updatedAt);
206
226
  const preview = document.createElement("span");
207
227
  preview.className = "drawer-row-preview";
208
228
  preview.setAttribute("part", "drawer-row-preview");
@@ -42,6 +42,61 @@ function formatPayload(text: string): string {
42
42
  }
43
43
  }
44
44
 
45
+ /**
46
+ * One region of a card's body, handed to a host {@link ToolPayloadFormatter}.
47
+ *
48
+ * A discriminated union rather than three positional parameters because the two
49
+ * halves do not carry the same thing: arguments are the parsed record the call
50
+ * was made with, and a result is the raw string the tool returned, which may not
51
+ * be JSON at all. Flattening both into one `payload` parameter would force every
52
+ * formatter to re-derive which it had before it could read it, and the
53
+ * arguments would arrive re-serialised for no reason.
54
+ *
55
+ * `toolName` is the raw tool name, not the card's `x-summary` label -- a
56
+ * formatter dispatches on identity, and the label is a display string a server
57
+ * may change.
58
+ */
59
+ export type ToolPayload =
60
+ | {
61
+ readonly kind: "arguments";
62
+ readonly toolName: string;
63
+ readonly args: Record<string, unknown>;
64
+ }
65
+ | {
66
+ readonly kind: "result";
67
+ readonly toolName: string;
68
+ readonly status: SettledStatus;
69
+ readonly text: string;
70
+ };
71
+
72
+ /**
73
+ * Renders one region of a tool card's body, for a host that would rather show a
74
+ * table or a summary line than a wall of pretty-printed JSON.
75
+ *
76
+ * Return a `Node` to take the region over, a `string` to replace its text, or
77
+ * `null` to fall through to the built-in pretty-print -- so a formatter that
78
+ * only cares about one tool, or only about results, declines the rest rather
79
+ * than reimplementing them.
80
+ *
81
+ * **Presentation only.** The model reads the tool result from its own copy of
82
+ * the message, which this never touches, so anything said here is said to the
83
+ * person and not to the agent. Translating a value -- an enum constant into a
84
+ * friendly label, an epoch into a date -- belongs on the server, where it also
85
+ * reaches the model's prose; doing it here would make the card and the answer
86
+ * beside it disagree about what happened.
87
+ *
88
+ * A returned string is set as text, never parsed as markup: this is not a
89
+ * second HTML channel into the transcript, and a host that wants elements
90
+ * builds them itself and returns the node.
91
+ */
92
+ export type ToolPayloadFormatter = (payload: ToolPayload) => Node | string | null;
93
+
94
+ /** Optional per-card wiring beyond the name, arguments, label and strings. */
95
+ export interface ToolCallCardOptions {
96
+ /** Host presentation hook for both body regions. See {@link ToolPayloadFormatter}. */
97
+ readonly formatPayload?: ToolPayloadFormatter;
98
+ }
99
+
45
100
  /**
46
101
  * A live tool-call card for the chat transcript.
47
102
  *
@@ -61,6 +116,12 @@ function formatPayload(text: string): string {
61
116
  * `--ag-ui-tool-icon-*` custom properties or the `tool-card-icon` part without
62
117
  * the card reaching into the host stylesheet.
63
118
  *
119
+ * Either region may be drawn by the host instead: `options.formatPayload` is
120
+ * asked about each one and pretty-prints as before whenever it declines. The
121
+ * arguments are offered from the constructor and the result from {@link settle},
122
+ * because that is when each exists -- so a formatter is asked twice per card,
123
+ * potentially long apart.
124
+ *
64
125
  * Pure DOM. The host appends {@link element} into its shadow root; all visible
65
126
  * text comes from {@link UiStrings}.
66
127
  */
@@ -83,6 +144,23 @@ export class ToolCallCard {
83
144
  */
84
145
  readonly approvalSlot: HTMLDivElement;
85
146
 
147
+ /**
148
+ * Where a *nested* run's progress renders — the sub-agent this call delegated
149
+ * to, narrating itself while the card waits.
150
+ *
151
+ * A slot rather than a rendered thing, on the same reasoning as
152
+ * {@link approvalSlot}: the card owns the position and something else owns the
153
+ * content. What makes the position right is that the wire keys a delegation on
154
+ * this card's own `toolCallId`, so the run being narrated is the one this card
155
+ * already stands for.
156
+ *
157
+ * Placed above the Details toggle rather than inside the body, because the
158
+ * body is what the display modes hide — and a progress line that only appears
159
+ * in `full` mode would leave exactly the stall it exists to end. Empty on every
160
+ * card that delegated nothing, and hidden while empty by the shadow CSS.
161
+ */
162
+ readonly subagentSlot: HTMLDivElement;
163
+
86
164
  readonly #status: HTMLSpanElement;
87
165
  readonly #decision: HTMLSpanElement;
88
166
  readonly #toggle: HTMLButtonElement;
@@ -90,6 +168,17 @@ export class ToolCallCard {
90
168
  readonly #resultLabel: HTMLSpanElement;
91
169
  readonly #resultBody: HTMLPreElement;
92
170
  readonly #strings: UiStrings;
171
+ /**
172
+ * The arguments this call was made with.
173
+ *
174
+ * Retained rather than only rendered, because an approval interrupt names a
175
+ * `toolCallId` and nothing else -- so this card is the only place the args
176
+ * still exist when the user is asked to approve, edit or deny the call.
177
+ */
178
+ readonly args: Record<string, unknown>;
179
+ /** The raw tool name, kept for the payloads handed to {@link ToolPayloadFormatter}. */
180
+ readonly #name: string;
181
+ readonly #formatPayload: ToolPayloadFormatter | null;
93
182
  #settled = false;
94
183
 
95
184
  constructor(
@@ -97,8 +186,12 @@ export class ToolCallCard {
97
186
  args: Record<string, unknown>,
98
187
  summary?: string,
99
188
  strings: UiStrings = DEFAULT_UI_STRINGS,
189
+ options: ToolCallCardOptions = {},
100
190
  ) {
101
191
  this.#strings = strings;
192
+ this.args = args;
193
+ this.#name = name;
194
+ this.#formatPayload = options.formatPayload ?? null;
102
195
 
103
196
  this.element = document.createElement("div");
104
197
  this.element.className = "tool-call";
@@ -137,7 +230,11 @@ export class ToolCallCard {
137
230
  head.append(icon, label, this.#status, this.#decision);
138
231
 
139
232
  const argsSection = this.#section("args", strings.argumentsLabel);
140
- argsSection.body.textContent = JSON.stringify(args, null, 2);
233
+ this.#renderPayload(
234
+ argsSection.body,
235
+ { kind: "arguments", toolName: name, args },
236
+ JSON.stringify(args, null, 2),
237
+ );
141
238
  // Drop the region rather than frame an empty object.
142
239
  argsSection.root.hidden = Object.keys(args).length === 0;
143
240
 
@@ -166,7 +263,11 @@ export class ToolCallCard {
166
263
  this.approvalSlot.className = "tool-call-approval";
167
264
  this.approvalSlot.setAttribute("part", "tool-card-approval");
168
265
 
169
- this.element.append(head, this.#toggle, body, this.approvalSlot);
266
+ this.subagentSlot = document.createElement("div");
267
+ this.subagentSlot.className = "tool-call-subagent";
268
+ this.subagentSlot.setAttribute("part", "tool-card-subagent");
269
+
270
+ this.element.append(head, this.subagentSlot, this.#toggle, body, this.approvalSlot);
170
271
  }
171
272
 
172
273
  /**
@@ -215,10 +316,44 @@ export class ToolCallCard {
215
316
  this.element.setAttribute("data-status", status);
216
317
  this.#status.textContent = statusLabels(this.#strings)[status];
217
318
  this.#resultLabel.textContent = resultLabels(this.#strings)[status];
218
- this.#resultBody.textContent = formatPayload(text);
319
+ this.#renderPayload(
320
+ this.#resultBody,
321
+ { kind: "result", toolName: this.#name, status, text },
322
+ formatPayload(text),
323
+ );
219
324
  this.#resultSection.hidden = false;
220
325
  }
221
326
 
327
+ /**
328
+ * Fill one body region, offering it to the host formatter first.
329
+ *
330
+ * `fallback` is computed by the caller rather than here because the two
331
+ * regions build it differently -- arguments are already parsed, a result is a
332
+ * string that may or may not be JSON -- and because a formatter that takes
333
+ * the region over should not have paid for a pretty-print nobody sees. It is
334
+ * cheap either way; the point is that the built-in rendering stays written
335
+ * once, beside the payload it belongs to.
336
+ *
337
+ * The `data-formatted` marker is what the shadow CSS reads to relax the
338
+ * preformatted whitespace on a region a host owns -- a table inherits it as
339
+ * mangled cell spacing, a sentence as line breaks nobody typed. Marked for a
340
+ * returned string too: one rule, and a summary line wants ordinary wrapping
341
+ * as much as a table does.
342
+ */
343
+ #renderPayload(body: HTMLPreElement, payload: ToolPayload, fallback: string): void {
344
+ const rendered = this.#formatPayload === null ? null : this.#formatPayload(payload);
345
+ if (rendered === null) {
346
+ body.textContent = fallback;
347
+ return;
348
+ }
349
+ body.setAttribute("data-formatted", "true");
350
+ if (typeof rendered === "string") {
351
+ body.textContent = rendered;
352
+ return;
353
+ }
354
+ body.replaceChildren(rendered);
355
+ }
356
+
222
357
  /** Build one labelled region of the body: a heading plus a payload block. */
223
358
  #section(
224
359
  kind: string,
@@ -33,6 +33,18 @@ export interface UiStrings {
33
33
  // ── Messages region ─────────────────────────────────────────────────────────
34
34
  /** `aria-label` of the scrolling message log. */
35
35
  conversation: string;
36
+ /** The button offering to return to the foot of the transcript. */
37
+ jumpToLatest: string;
38
+ /** Announced when a turn starts. Screen-reader only; never rendered. */
39
+ announceResponding: string;
40
+ /** Announced when the answer has finished arriving. Screen-reader only. */
41
+ announceAnswerReady: string;
42
+ /** Announced when a card is waiting for the user's decision. Token: `{count}`. */
43
+ announceAwaitingDecision: string;
44
+ /** Announced when the user stopped the run. Screen-reader only. */
45
+ announceStopped: string;
46
+ /** Announced when the run failed. Screen-reader only. */
47
+ announceFailed: string;
36
48
  /** `aria-label` of the "thinking" pending indicator, and the thoughts region's
37
49
  * header while the model is still reasoning. */
38
50
  thinking: string;
@@ -48,6 +60,10 @@ export interface UiStrings {
48
60
  declinedAction: string;
49
61
  /** A navigating tool's card text while the page reloads. */
50
62
  navigating: string;
63
+ /** Notice shown when the server replaced the conversation wholesale. */
64
+ historyReplaced: string;
65
+ /** Notice shown when a pushed chart could not be drawn and was removed. */
66
+ chartUndrawable: string;
51
67
  /** Missing-placeholder skill hint. Tokens: `{title}`, `{fields}`. */
52
68
  skillNeeds: string;
53
69
  /** Notice shown when the agent condensed earlier turns. Token: `{count}`. */
@@ -115,9 +131,45 @@ export interface UiStrings {
115
131
  /** Label on the toggle that expands a tool card's body. */
116
132
  details: string;
117
133
 
134
+ // ── Delegated sub-agent ─────────────────────────────────────────────────────
135
+ /**
136
+ * The delegation row's text before the server's own status line lands.
137
+ *
138
+ * A fallback, not a state: every announcement carries a pre-rendered `status`,
139
+ * and this only shows if one arrives unusable. The row is the expander, so it
140
+ * must never be blank.
141
+ */
142
+ subAgentWorking: string;
143
+ /** `aria-label` of the region holding the sub-agent's own tool calls. */
144
+ subAgentSteps: string;
145
+
118
146
  // ── Confirmation card ───────────────────────────────────────────────────────
147
+ /** `aria-label` of the editable arguments field on an approval card. */
148
+ approvalEditArgs: string;
149
+ /** Shown when the edited arguments are not valid JSON. */
150
+ approvalArgsInvalid: string;
151
+ /** Shown when the edited arguments parse but are not a JSON object. */
152
+ approvalArgsNotAnObject: string;
153
+ /** `aria-label` of the follow-up suggestion chips row. */
154
+ suggestions: string;
155
+ /** `aria-label` of a message's action row. */
156
+ messageActions: string;
157
+ /** The offer that floats beside a selection in the transcript. */
158
+ quoteSelection: string;
159
+ /** Copy this message (button `title` / `aria-label`). Its confirmation and
160
+ * failure text are the code block's `copied` / `copyFailed`, which say the
161
+ * same thing about the same clipboard. */
162
+ copyMessage: string;
163
+ /** Ask for a different answer to the same question. */
164
+ retryMessage: string;
165
+ /** Rate this answer as good. */
166
+ feedbackUp: string;
167
+ /** Rate this answer as poor. */
168
+ feedbackDown: string;
119
169
  /** `aria-label` of the inline confirmation card. */
120
170
  confirmAction: string;
171
+ /** Waive confirmation for this tool for the rest of the session. Token: `{tool}`. */
172
+ confirmAlways: string;
121
173
  /** Generic confirmation prompt when a tool has no `x-confirm`. Token: `{tool}`. */
122
174
  confirmRun: string;
123
175
  /** Confirm button. */
@@ -229,6 +281,12 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
229
281
  forkedRun: "branched",
230
282
 
231
283
  conversation: "Conversation",
284
+ jumpToLatest: "Jump to latest",
285
+ announceResponding: "Assistant is responding",
286
+ announceAnswerReady: "Assistant answered",
287
+ announceAwaitingDecision: "{count} action is waiting for your approval",
288
+ announceStopped: "Response stopped",
289
+ announceFailed: "The response failed",
232
290
  thinking: "Assistant is thinking…",
233
291
  thoughts: "Thoughts",
234
292
  stopped: "⏹ Stopped",
@@ -236,6 +294,9 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
236
294
  noResult: "No result returned.",
237
295
  declinedAction: "User declined the action.",
238
296
  navigating: "Navigating…",
297
+ historyReplaced:
298
+ "The server replaced this conversation's history. Reload to see the updated transcript.",
299
+ chartUndrawable: "A chart could not be drawn from the data sent, so it was removed.",
239
300
  historyCompacted: "Earlier turns condensed to fit the context window ({count} removed)",
240
301
  usingSkill: "Using skill {name}",
241
302
  runInterrupted: "The previous response didn’t finish — the page changed before it arrived.",
@@ -270,7 +331,21 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
270
331
  declinedLabel: "Declined",
271
332
  details: "Details",
272
333
 
334
+ subAgentWorking: "Working…",
335
+ subAgentSteps: "Steps the sub-agent took",
336
+
337
+ approvalEditArgs: "Edit the arguments before approving",
338
+ approvalArgsInvalid: "That is not valid JSON, so nothing was sent.",
339
+ approvalArgsNotAnObject: "Arguments have to be a JSON object.",
340
+ suggestions: "Suggested follow-ups",
341
+ messageActions: "Message actions",
342
+ quoteSelection: "Quote",
343
+ copyMessage: "Copy message",
344
+ retryMessage: "Try again",
345
+ feedbackUp: "Good answer",
346
+ feedbackDown: "Poor answer",
273
347
  confirmAction: "Confirm action",
348
+ confirmAlways: "Always allow",
274
349
  confirmRun: "Run “{tool}”?",
275
350
  confirm: "Confirm",
276
351
  cancel: "Cancel",
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.28.0";
1
+ export const VERSION: string = "0.30.0";