@artooi/ag-ui-web-component 0.17.0 → 0.19.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.
@@ -26,6 +26,8 @@ export class CheckpointMenu {
26
26
  readonly #onPick: (runId: string, verb: CheckpointVerb) => void;
27
27
  readonly #list: HTMLDivElement;
28
28
  readonly #heading: HTMLSpanElement;
29
+ /** What had focus before the panel opened, restored on close. */
30
+ #lastFocused: HTMLElement | null = null;
29
31
  #strings: UiStrings;
30
32
  #runs: readonly RunRow[] = [];
31
33
 
@@ -41,6 +43,10 @@ export class CheckpointMenu {
41
43
  this.element.setAttribute("part", "checkpoints");
42
44
  this.element.setAttribute("role", "dialog");
43
45
  this.element.setAttribute("aria-label", strings.checkpoints);
46
+ // Focusable as a fallback: with no continuable runs the panel holds no
47
+ // controls at all, and a dialog that cannot take focus strands the
48
+ // keyboard user outside it with no way back except Tab-ing blind.
49
+ this.element.tabIndex = -1;
44
50
  this.element.hidden = true;
45
51
 
46
52
  const header = document.createElement("div");
@@ -57,12 +63,7 @@ export class CheckpointMenu {
57
63
  this.#list.setAttribute("part", "checkpoints-list");
58
64
 
59
65
  this.element.append(header, this.#list);
60
- this.element.addEventListener("keydown", (event) => {
61
- if (event.key === "Escape") {
62
- event.stopPropagation();
63
- this.close();
64
- }
65
- });
66
+ this.element.addEventListener("keydown", (event) => this.#onKeydown(event));
66
67
  }
67
68
 
68
69
  /** Replace the rows. The host passes only `continuable` runs. */
@@ -80,11 +81,61 @@ export class CheckpointMenu {
80
81
  }
81
82
 
82
83
  open(): void {
84
+ if (this.open_) {
85
+ return;
86
+ }
87
+ // Remember what had focus so it is restored on close, then move focus into
88
+ // the panel so a keyboard user lands inside the dialog rather than being
89
+ // left behind it. The thread drawer already does this; a dialog that takes
90
+ // no focus is one a keyboard user cannot reach and a screen reader does not
91
+ // announce, however correct its role and label.
92
+ this.#lastFocused = this.#activeElement() as HTMLElement | null;
83
93
  this.element.hidden = false;
94
+ (this.#focusables()[0] ?? this.element).focus();
84
95
  }
85
96
 
86
97
  close(): void {
98
+ if (!this.open_) {
99
+ return;
100
+ }
87
101
  this.element.hidden = true;
102
+ this.#lastFocused?.focus();
103
+ this.#lastFocused = null;
104
+ }
105
+
106
+ /** The focused element within this panel's root (shadow-aware). */
107
+ #activeElement(): Element | null {
108
+ return (this.element.getRootNode() as Document | ShadowRoot).activeElement;
109
+ }
110
+
111
+ /** The panel's tabbable controls, in document order. */
112
+ #focusables(): HTMLElement[] {
113
+ return Array.from(this.element.querySelectorAll<HTMLElement>("button, [tabindex]")).filter(
114
+ (el) => !el.hidden,
115
+ );
116
+ }
117
+
118
+ /** Escape closes; Tab is trapped so focus cannot wander behind the dialog. */
119
+ #onKeydown(event: KeyboardEvent): void {
120
+ if (event.key === "Escape") {
121
+ event.stopPropagation();
122
+ this.close();
123
+ return;
124
+ }
125
+ if (event.key !== "Tab") {
126
+ return;
127
+ }
128
+ const focusables = this.#focusables();
129
+ const first = focusables[0];
130
+ const last = focusables[focusables.length - 1];
131
+ const active = this.#activeElement();
132
+ if (event.shiftKey && active === first) {
133
+ event.preventDefault();
134
+ last?.focus();
135
+ } else if (!event.shiftKey && active === last) {
136
+ event.preventDefault();
137
+ first?.focus();
138
+ }
88
139
  }
89
140
 
90
141
  get open_(): boolean {
@@ -40,8 +40,14 @@ export interface ConfirmationOptions {
40
40
  * Unlike a modal overlay, the card lives in the transcript right where the
41
41
  * action is — it reads naturally after the assistant's explanation and never
42
42
  * steals focus from the page. Resolves ``true`` on confirm, ``false`` on
43
- * cancel. The card stays in the transcript as a resolved record (buttons
44
- * disabled, `data-resolved` set) rather than vanishing.
43
+ * cancel.
44
+ *
45
+ * **Answering it removes it.** A prompt and a record are two different objects:
46
+ * the prompt owns the user's attention until it is answered, and the record of
47
+ * what was decided belongs in the transcript, in order, scrolling with
48
+ * everything else. The tool card this gates is that record — it settles to
49
+ * `done` or `declined` and carries the decision. Leaving the spent form in
50
+ * place made an answered question read as outstanding.
45
51
  */
46
52
  export function requestConfirmation(
47
53
  host: Node & ParentNode,
@@ -66,6 +72,8 @@ export function requestConfirmation(
66
72
  args.className = "confirm-args";
67
73
  args.setAttribute("part", "confirm-args");
68
74
  args.textContent = JSON.stringify(request.args, null, 2);
75
+ // A call with no arguments rendered the string "{}" in a box of its own.
76
+ args.hidden = Object.keys(request.args).length === 0;
69
77
 
70
78
  const actions = document.createElement("div");
71
79
  actions.className = "confirm-actions";
@@ -80,9 +88,11 @@ export function requestConfirmation(
80
88
  return;
81
89
  }
82
90
  settled = true;
83
- cancel.disabled = true;
84
- confirm.disabled = true;
85
- card.setAttribute("data-resolved", accepted ? "confirmed" : "declined");
91
+ // The prompt leaves once it has been answered. What stays is the tool
92
+ // card next to it, which settles to the outcome and scrolls with the rest
93
+ // of the transcript -- a record of the action rather than a spent form
94
+ // sitting there as a standing reminder of one.
95
+ card.remove();
86
96
  resolve(accepted);
87
97
  };
88
98
 
package/src/ui/styles.ts CHANGED
@@ -512,6 +512,52 @@ export const STYLES = `
512
512
  padding: 0;
513
513
  }
514
514
 
515
+ /* The copy button sits inside the block, so it scrolls with wide code rather
516
+ than floating over the bubble. Positioning is on the pre; the button only
517
+ appears once one has been attached. */
518
+ .message--assistant pre.has-copy {
519
+ position: relative;
520
+ }
521
+
522
+ .code-copy {
523
+ position: absolute;
524
+ top: 4px;
525
+ right: 4px;
526
+ padding: 2px 8px;
527
+ font: inherit;
528
+ font-size: 0.75em;
529
+ line-height: 1.6;
530
+ color: var(--ag-ui-muted);
531
+ background: var(--ag-ui-surface);
532
+ border: 1px solid var(--ag-ui-border);
533
+ border-radius: 4px;
534
+ cursor: pointer;
535
+ opacity: 0;
536
+ transition: opacity 0.12s ease;
537
+ }
538
+
539
+ /* Revealed on hover or keyboard focus. Focus matters as much as hover here:
540
+ hidden-until-hover is invisible to a keyboard user otherwise. */
541
+ .message--assistant pre.has-copy:hover .code-copy,
542
+ .code-copy:focus-visible {
543
+ opacity: 1;
544
+ }
545
+
546
+ .code-copy:hover {
547
+ color: var(--ag-ui-text);
548
+ background: var(--ag-ui-hover);
549
+ }
550
+
551
+ .code-copy[data-state="copied"] {
552
+ opacity: 1;
553
+ color: var(--ag-ui-text);
554
+ }
555
+
556
+ .code-copy[data-state="failed"] {
557
+ opacity: 1;
558
+ color: var(--ag-ui-danger, var(--ag-ui-text));
559
+ }
560
+
515
561
  .message--assistant blockquote {
516
562
  padding-left: 10px;
517
563
  border-left: 3px solid var(--ag-ui-border);
@@ -726,10 +772,9 @@ export const STYLES = `
726
772
  }
727
773
  }
728
774
 
729
- /* Inline display mode: the lightest card drop the box chrome so the
730
- status row reads as one line of the answer; the result toggle still expands
731
- below it. */
732
- .tool-call[data-display="inline"] {
775
+ /* Inline display mode: the lightest card. Drop the box chrome so the status row
776
+ reads as one line of the answer; the result toggle still expands below it. */
777
+ :host([data-tool-display="inline"]) .tool-call {
733
778
  max-width: 100%;
734
779
  background: transparent;
735
780
  border: none;
@@ -759,6 +804,37 @@ export const STYLES = `
759
804
  color: var(--ag-ui-muted);
760
805
  }
761
806
 
807
+ .tool-call-body {
808
+ display: flex;
809
+ flex-direction: column;
810
+ gap: 6px;
811
+ }
812
+
813
+ .tool-call-section {
814
+ display: flex;
815
+ flex-direction: column;
816
+ gap: 3px;
817
+ }
818
+
819
+ /* The heading that tells the two payloads apart. Without it the arguments and
820
+ the result were one run of text and a reader had to guess the boundary. */
821
+ .tool-call-section-label {
822
+ font-size: 10px;
823
+ font-weight: 700;
824
+ letter-spacing: 0.06em;
825
+ text-transform: uppercase;
826
+ color: var(--ag-ui-muted);
827
+ }
828
+
829
+ /* The record of a human decision on a gated call. An approved call used to
830
+ look exactly like one that was never gated. */
831
+ .tool-call-decision {
832
+ flex: none;
833
+ font-size: 11px;
834
+ font-style: italic;
835
+ color: var(--ag-ui-muted);
836
+ }
837
+
762
838
  .tool-call-args,
763
839
  .tool-call-result {
764
840
  margin: 0;
@@ -773,6 +849,43 @@ export const STYLES = `
773
849
  color: var(--ag-ui-fg);
774
850
  }
775
851
 
852
+ /* Display modes are pure visibility over one DOM shape, selected from the host
853
+ attribute rather than a value stamped on the card when it was built. That is
854
+ what lets a host flip data-tool-display and have every card already on screen
855
+ re-read it, the way data-answer-well behaves. Baking the structure per mode
856
+ meant the setting only reached cards created afterwards.
857
+
858
+ Default (no attribute) is the full mode: arguments always visible, result
859
+ behind the toggle. */
860
+ .tool-call[data-expanded="false"] .tool-call-section--result {
861
+ display: none;
862
+ }
863
+
864
+ /* Compact: one toggle over both regions, so a settled card is a single line
865
+ until asked. */
866
+ :host([data-tool-display="compact"]) .tool-call[data-expanded="false"] .tool-call-section {
867
+ display: none;
868
+ }
869
+
870
+ /* Inline: the result only; the call's arguments are noise at this density. */
871
+ :host([data-tool-display="inline"]) .tool-call .tool-call-section--args {
872
+ display: none;
873
+ }
874
+
875
+ /* Minimal: the status row and nothing else, so there is no toggle to press. */
876
+ :host([data-tool-display="minimal"]) .tool-call .tool-call-toggle,
877
+ :host([data-tool-display="minimal"]) .tool-call .tool-call-body {
878
+ display: none;
879
+ }
880
+
881
+ /* A pending card has no result yet, and in the modes where the arguments are
882
+ hidden too there is nothing behind the toggle. Hide the control rather than
883
+ offer one that expands onto nothing. */
884
+ .tool-call[data-status="pending"] .tool-call-toggle,
885
+ :host([data-tool-display="inline"]) .tool-call[data-status="pending"] .tool-call-toggle {
886
+ display: none;
887
+ }
888
+
776
889
  .tool-call-toggle {
777
890
  align-self: flex-start;
778
891
  border: none;
@@ -1,4 +1,4 @@
1
- import { TOOL_CALL_STATUS, TOOL_DISPLAY } from "../constants.js";
1
+ import { TOOL_CALL_STATUS, type TOOL_DISPLAY } from "../constants.js";
2
2
  import { DEFAULT_UI_STRINGS, type UiStrings } from "./ui_strings.js";
3
3
 
4
4
  /** Any state a tool-call card can be in. */
@@ -20,7 +20,7 @@ function statusLabels(strings: UiStrings): Record<ToolCallStatus, string> {
20
20
  };
21
21
  }
22
22
 
23
- /** Toggle-button label for each settled outcome's collapsible body (full mode). */
23
+ /** Section-heading text for each settled outcome's result region. */
24
24
  function resultLabels(strings: UiStrings): Record<SettledStatus, string> {
25
25
  return {
26
26
  [TOOL_CALL_STATUS.DONE]: strings.resultLabel,
@@ -29,19 +29,32 @@ function resultLabels(strings: UiStrings): Record<SettledStatus, string> {
29
29
  };
30
30
  }
31
31
 
32
+ /** Pretty-print a JSON payload; fall back to the raw text if it isn't JSON. */
33
+ function formatPayload(text: string): string {
34
+ try {
35
+ return JSON.stringify(JSON.parse(text), null, 2);
36
+ } catch {
37
+ return text;
38
+ }
39
+ }
40
+
32
41
  /**
33
42
  * A live tool-call card for the chat transcript.
34
43
  *
35
- * Construction renders a status icon, the tool name, and a `running…` status
36
- * pill; in `full` mode it also shows the pretty-printed arguments inline.
37
- * {@link settle} later flips the pill to the outcome and depending on the
38
- * {@link ToolDisplayMode} appends a collapsible body:
44
+ * Construction renders a status icon, the tool name, a `running…` pill, and the
45
+ * card's body: an **arguments** region and a **result** region, each with its own
46
+ * heading and its own `part`. {@link settle} fills in the result and flips the
47
+ * pill. Both payloads are pretty-printed, and the two are never concatenated —
48
+ * the previous compact layout ran `args: {...}` and the result together in one
49
+ * `<pre>`, leaving no way to see where the call ended and the answer began.
39
50
  *
40
- * - `inline` no args; the result behind its own toggle (like `full` but with
41
- * the card chrome stripped to a single light row).
42
- * - `minimal` pill only; nothing to expand.
43
- * - `compact` one "Details" toggle revealing args *and* result together.
44
- * - `full` the result (or error / decline message) behind its own toggle.
51
+ * **The card renders one DOM shape in every display mode, and CSS decides what
52
+ * shows.** That is what makes `data-tool-display` behave like `data-answer-well`
53
+ * flip it on the host and every card already on screen re-reads it. Building a
54
+ * different structure per mode meant only cards created *after* the change
55
+ * picked it up, so the setting appeared not to work until the next conversation.
56
+ * Visibility is selected from the host attribute rather than a value copied onto
57
+ * the card at construction, for the same reason.
45
58
  *
46
59
  * The leading icon carries no text of its own: its glyph/spinner is drawn by
47
60
  * the shadow CSS keyed off the card's `data-status`, so a host themes it via
@@ -57,20 +70,20 @@ export class ToolCallCard {
57
70
  readonly element: HTMLDivElement;
58
71
 
59
72
  readonly #status: HTMLSpanElement;
60
- readonly #mode: ToolDisplayMode;
61
- readonly #args: Record<string, unknown>;
73
+ readonly #decision: HTMLSpanElement;
74
+ readonly #toggle: HTMLButtonElement;
75
+ readonly #resultSection: HTMLDivElement;
76
+ readonly #resultLabel: HTMLSpanElement;
77
+ readonly #resultBody: HTMLPreElement;
62
78
  readonly #strings: UiStrings;
63
79
  #settled = false;
64
80
 
65
81
  constructor(
66
82
  name: string,
67
83
  args: Record<string, unknown>,
68
- mode: ToolDisplayMode = TOOL_DISPLAY.FULL,
69
84
  summary?: string,
70
85
  strings: UiStrings = DEFAULT_UI_STRINGS,
71
86
  ) {
72
- this.#mode = mode;
73
- this.#args = args;
74
87
  this.#strings = strings;
75
88
 
76
89
  this.element = document.createElement("div");
@@ -78,7 +91,7 @@ export class ToolCallCard {
78
91
  this.element.setAttribute("part", "tool-card");
79
92
  this.element.setAttribute("data-tool-name", name);
80
93
  this.element.setAttribute("data-status", TOOL_CALL_STATUS.PENDING);
81
- this.element.setAttribute("data-display", mode);
94
+ this.element.setAttribute("data-expanded", "false");
82
95
 
83
96
  const head = document.createElement("div");
84
97
  head.className = "tool-call-head";
@@ -103,16 +116,56 @@ export class ToolCallCard {
103
116
  this.#status.setAttribute("part", "tool-card-status");
104
117
  this.#status.textContent = statusLabels(strings)[TOOL_CALL_STATUS.PENDING];
105
118
 
106
- head.append(icon, label, this.#status);
107
- this.element.append(head);
119
+ this.#decision = document.createElement("span");
120
+ this.#decision.className = "tool-call-decision";
121
+ this.#decision.setAttribute("part", "tool-card-decision");
122
+ this.#decision.hidden = true;
123
+
124
+ head.append(icon, label, this.#status, this.#decision);
125
+
126
+ const argsSection = this.#section("args", strings.argumentsLabel);
127
+ argsSection.body.textContent = JSON.stringify(args, null, 2);
128
+ // A call with no arguments renders an empty object in a box of its own,
129
+ // which is a frame around nothing. Drop the region instead.
130
+ argsSection.root.hidden = Object.keys(args).length === 0;
131
+
132
+ const resultSection = this.#section("result", strings.resultLabel);
133
+ this.#resultSection = resultSection.root;
134
+ this.#resultLabel = resultSection.label;
135
+ this.#resultBody = resultSection.body;
136
+ // Nothing to show until `settle` supplies it; a pending card would
137
+ // otherwise expand onto an empty region.
138
+ resultSection.root.hidden = true;
139
+
140
+ this.#toggle = document.createElement("button");
141
+ this.#toggle.type = "button";
142
+ this.#toggle.className = "tool-call-toggle";
143
+ this.#toggle.setAttribute("part", "tool-card-toggle");
144
+ this.#toggle.setAttribute("aria-expanded", "false");
145
+ this.#toggle.textContent = strings.details;
146
+ this.#toggle.addEventListener("click", () => this.#setExpanded(!this.#expanded()));
147
+
148
+ const body = document.createElement("div");
149
+ body.className = "tool-call-body";
150
+ body.setAttribute("part", "tool-card-body");
151
+ body.append(argsSection.root, resultSection.root);
152
+
153
+ this.element.append(head, this.#toggle, body);
154
+ }
108
155
 
109
- if (mode === TOOL_DISPLAY.FULL) {
110
- const argsEl = document.createElement("pre");
111
- argsEl.className = "tool-call-args";
112
- argsEl.setAttribute("part", "tool-card-args");
113
- argsEl.textContent = JSON.stringify(args, null, 2);
114
- this.element.append(argsEl);
115
- }
156
+ /**
157
+ * Record that a human approved or declined this call, as a line in the card.
158
+ *
159
+ * Approval used to leave no trace at all: a declined call became a tool
160
+ * result saying so, while an approved one simply ran, making the transcript
161
+ * of a gated call byte-identical to one that was never gated. The prompt is
162
+ * gone once answered, so this is where the decision lives.
163
+ */
164
+ recordDecision(kind: "approved" | "declined"): void {
165
+ this.element.setAttribute("data-decision", kind);
166
+ this.#decision.textContent =
167
+ kind === "approved" ? this.#strings.decisionApproved : this.#strings.decisionDeclined;
168
+ this.#decision.hidden = false;
116
169
  }
117
170
 
118
171
  /** Whether {@link settle} has already run (so a terminal sweep can skip it). */
@@ -121,50 +174,55 @@ export class ToolCallCard {
121
174
  }
122
175
 
123
176
  /**
124
- * Flip the status pill to ``status`` and, unless in `minimal` mode, append a
125
- * collapsed body behind a click-to-expand toggle: the result alone (`full` /
126
- * `inline`), or the args + result together (`compact`).
177
+ * Flip the status pill to `status` and fill in the result region, whose
178
+ * heading names the outcome (result / error / declined).
127
179
  */
128
180
  settle(status: SettledStatus, text: string): void {
129
181
  // Idempotent: a duplicate `TOOL_CALL_RESULT`, or a replayed tool message
130
- // for an already-settled card, must not append a second toggle+body. The
131
- // first settle wins; later calls are ignored.
182
+ // for an already-settled card, must not overwrite the first outcome.
132
183
  if (this.#settled) {
133
184
  return;
134
185
  }
135
186
  this.#settled = true;
136
187
  this.element.setAttribute("data-status", status);
137
188
  this.#status.textContent = statusLabels(this.#strings)[status];
189
+ this.#resultLabel.textContent = resultLabels(this.#strings)[status];
190
+ this.#resultBody.textContent = formatPayload(text);
191
+ this.#resultSection.hidden = false;
192
+ }
138
193
 
139
- if (this.#mode === TOOL_DISPLAY.MINIMAL) {
140
- return;
141
- }
194
+ /** Build one labelled region of the body: a heading plus a payload block. */
195
+ #section(
196
+ kind: string,
197
+ labelText: string,
198
+ ): {
199
+ root: HTMLDivElement;
200
+ label: HTMLSpanElement;
201
+ body: HTMLPreElement;
202
+ } {
203
+ const root = document.createElement("div");
204
+ root.className = `tool-call-section tool-call-section--${kind}`;
205
+ root.setAttribute("part", `tool-card-section tool-card-${kind}-section`);
142
206
 
143
- const toggle = document.createElement("button");
144
- toggle.type = "button";
145
- toggle.className = "tool-call-toggle";
146
- toggle.setAttribute("part", "tool-card-toggle");
147
- toggle.setAttribute("aria-expanded", "false");
148
-
149
- const output = document.createElement("pre");
150
- output.className = "tool-call-result";
151
- output.setAttribute("part", "tool-card-result");
152
- output.hidden = true;
153
-
154
- if (this.#mode === TOOL_DISPLAY.COMPACT) {
155
- toggle.textContent = this.#strings.details;
156
- output.textContent = `args: ${JSON.stringify(this.#args)}\n\n${text}`;
157
- } else {
158
- toggle.textContent = resultLabels(this.#strings)[status];
159
- output.textContent = text;
160
- }
207
+ const label = document.createElement("span");
208
+ label.className = "tool-call-section-label";
209
+ label.setAttribute("part", `tool-card-section-label tool-card-${kind}-label`);
210
+ label.textContent = labelText;
161
211
 
162
- toggle.addEventListener("click", () => {
163
- const expand = output.hidden;
164
- output.hidden = !expand;
165
- toggle.setAttribute("aria-expanded", String(expand));
166
- });
212
+ const body = document.createElement("pre");
213
+ body.className = `tool-call-${kind}`;
214
+ body.setAttribute("part", `tool-card-${kind}`);
215
+
216
+ root.append(label, body);
217
+ return { root, label, body };
218
+ }
219
+
220
+ #expanded(): boolean {
221
+ return this.element.getAttribute("data-expanded") === "true";
222
+ }
167
223
 
168
- this.element.append(toggle, output);
224
+ #setExpanded(expand: boolean): void {
225
+ this.element.setAttribute("data-expanded", String(expand));
226
+ this.#toggle.setAttribute("aria-expanded", String(expand));
169
227
  }
170
228
  }
@@ -89,13 +89,19 @@ export interface UiStrings {
89
89
  toolError: string;
90
90
  /** Status pill on a declined call. */
91
91
  toolDeclined: string;
92
- /** Toggle label revealing a successful result (full mode). */
92
+ /** Note on a tool card whose call a human approved. */
93
+ decisionApproved: string;
94
+ /** Note on a tool card whose call a human declined. */
95
+ decisionDeclined: string;
96
+ /** Heading over a tool card's arguments region. */
97
+ argumentsLabel: string;
98
+ /** Heading over a tool card's result region when the call succeeded. */
93
99
  resultLabel: string;
94
- /** Toggle label revealing an error (full mode). */
100
+ /** Heading over a tool card's result region when the call failed. */
95
101
  errorLabel: string;
96
- /** Toggle label revealing a declined call (full mode). */
102
+ /** Heading over a tool card's result region when the call was declined. */
97
103
  declinedLabel: string;
98
- /** Toggle label revealing args + result together (compact mode). */
104
+ /** Label on the toggle that expands a tool card's body. */
99
105
  details: string;
100
106
 
101
107
  // ── Confirmation card ───────────────────────────────────────────────────────
@@ -168,6 +174,12 @@ export interface UiStrings {
168
174
  /** Hours ago. Token: `{n}`. */
169
175
  hoursAgo: string;
170
176
  /** Title of the checkpoint panel. */
177
+ /** Label on a code block's copy button. */
178
+ copyCode: string;
179
+ /** Shown on the copy button after the code reached the clipboard. */
180
+ copied: string;
181
+ /** Shown when the clipboard was unavailable or refused the write. */
182
+ copyFailed: string;
171
183
  checkpoints: string;
172
184
  /** Empty state when no run can be continued. */
173
185
  noCheckpoints: string;
@@ -191,6 +203,9 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
191
203
  collapse: "Collapse",
192
204
  expand: "Expand",
193
205
  toggleTheme: "Toggle theme",
206
+ copyCode: "Copy",
207
+ copied: "Copied",
208
+ copyFailed: "Copy failed",
194
209
  checkpoints: "Continue a run",
195
210
  noCheckpoints: "Nothing to continue yet.",
196
211
  resumeRun: "Resume",
@@ -228,6 +243,9 @@ export const DEFAULT_UI_STRINGS: UiStrings = {
228
243
  toolDone: "✓ done",
229
244
  toolError: "⚠ error",
230
245
  toolDeclined: "⊘ declined",
246
+ decisionApproved: "approved by you",
247
+ decisionDeclined: "declined by you",
248
+ argumentsLabel: "Arguments",
231
249
  resultLabel: "Result",
232
250
  errorLabel: "Error",
233
251
  declinedLabel: "Declined",
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.17.0";
1
+ export const VERSION: string = "0.19.0";