@tangle-network/agent-app 0.26.0 → 0.28.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 (101) hide show
  1. package/dist/DesignCanvas-AOIVN3SJ.js +12 -0
  2. package/dist/DesignCanvasEditor-TN7CWUJW.js +13 -0
  3. package/dist/{TimelineEditor-JDG54OBP.js → TimelineEditor-Z7C6BFP5.js} +2 -2
  4. package/dist/{apply-Cp8c3K9D.d.ts → apply-Gk4raT2j.d.ts} +3 -1
  5. package/dist/brand-extraction/index.d.ts +212 -0
  6. package/dist/brand-extraction/index.js +446 -0
  7. package/dist/brand-extraction/index.js.map +1 -0
  8. package/dist/{chunk-AFDROJ64.js → chunk-2QI7XV2T.js} +1 -1
  9. package/dist/chunk-2QI7XV2T.js.map +1 -0
  10. package/dist/{chunk-LUE4HO5C.js → chunk-4IQMTTNE.js} +10 -10
  11. package/dist/chunk-4IQMTTNE.js.map +1 -0
  12. package/dist/{chunk-FSUR2752.js → chunk-7I37CO5D.js} +274 -388
  13. package/dist/chunk-7I37CO5D.js.map +1 -0
  14. package/dist/{chunk-5RT6KY4G.js → chunk-GQRL6VNU.js} +1 -1
  15. package/dist/chunk-GQRL6VNU.js.map +1 -0
  16. package/dist/{chunk-SCG5JJ4C.js → chunk-HFC4BTWJ.js} +32 -4
  17. package/dist/chunk-HFC4BTWJ.js.map +1 -0
  18. package/dist/{chunk-7QCIYDGC.js → chunk-IOREXWLM.js} +2 -2
  19. package/dist/chunk-J7JNV5BH.js +14 -0
  20. package/dist/chunk-J7JNV5BH.js.map +1 -0
  21. package/dist/{chunk-6UOE5CTA.js → chunk-JT6HHO5P.js} +5 -10
  22. package/dist/chunk-JT6HHO5P.js.map +1 -0
  23. package/dist/{chunk-JZAJE3JL.js → chunk-M2EBUDZ7.js} +19 -5
  24. package/dist/chunk-M2EBUDZ7.js.map +1 -0
  25. package/dist/{chunk-RJ2ZHK5D.js → chunk-S6WS2B32.js} +132 -7
  26. package/dist/chunk-S6WS2B32.js.map +1 -0
  27. package/dist/chunk-SQV7NUEM.js +136 -0
  28. package/dist/chunk-SQV7NUEM.js.map +1 -0
  29. package/dist/chunk-UAKFCMWK.js +1 -0
  30. package/dist/chunk-UAKFCMWK.js.map +1 -0
  31. package/dist/chunk-V5XZKHCL.js +296 -0
  32. package/dist/chunk-V5XZKHCL.js.map +1 -0
  33. package/dist/chunk-XWJXZ6WE.js +604 -0
  34. package/dist/chunk-XWJXZ6WE.js.map +1 -0
  35. package/dist/chunk-Y2SZVW35.js +125 -0
  36. package/dist/chunk-Y2SZVW35.js.map +1 -0
  37. package/dist/{chunk-LPAVBGVY.js → chunk-YEWTVDDT.js} +399 -860
  38. package/dist/chunk-YEWTVDDT.js.map +1 -0
  39. package/dist/{chunk-UDXMR3AD.js → chunk-YNALYIQ3.js} +56 -18
  40. package/dist/chunk-YNALYIQ3.js.map +1 -0
  41. package/dist/command-stack-CkjelfK_.d.ts +267 -0
  42. package/dist/design-canvas/drizzle.d.ts +2 -2
  43. package/dist/design-canvas/index.d.ts +7 -6
  44. package/dist/design-canvas/index.js +3 -2
  45. package/dist/design-canvas-react/engine.d.ts +349 -0
  46. package/dist/design-canvas-react/engine.js +86 -0
  47. package/dist/design-canvas-react/engine.js.map +1 -0
  48. package/dist/design-canvas-react/index.d.ts +80 -618
  49. package/dist/design-canvas-react/index.js +85 -172
  50. package/dist/design-canvas-react/index.js.map +1 -1
  51. package/dist/design-canvas-react/lazy.d.ts +6 -0
  52. package/dist/design-canvas-react/lazy.js +9 -0
  53. package/dist/design-canvas-react/lazy.js.map +1 -0
  54. package/dist/{export-presets-Dl5Aa5xj.d.ts → export-presets-DAsU5Wal.d.ts} +2 -143
  55. package/dist/flow-types-Cb_AblZs.d.ts +25 -0
  56. package/dist/index.d.ts +11 -8
  57. package/dist/index.js +21 -8
  58. package/dist/lazy-BHHqS8-j.d.ts +72 -0
  59. package/dist/missions/index.d.ts +12 -0
  60. package/dist/missions/index.js +1 -1
  61. package/dist/{model-BHLN208Z.d.ts → model-BBwyOuWG.d.ts} +3 -1
  62. package/dist/operations-DLIvvD00.d.ts +144 -0
  63. package/dist/platform/index.js +1 -1
  64. package/dist/runtime/index.d.ts +33 -33
  65. package/dist/runtime/index.js +1 -1
  66. package/dist/sequences/index.d.ts +1 -1
  67. package/dist/sequences/index.js +2 -1
  68. package/dist/sequences-react/index.d.ts +15 -1
  69. package/dist/sequences-react/index.js +2 -2
  70. package/dist/{store-CUStmtdH.d.ts → store-CFyxgTlD.d.ts} +1 -1
  71. package/dist/theme/index.d.ts +80 -0
  72. package/dist/theme/index.js +14 -0
  73. package/dist/theme/index.js.map +1 -0
  74. package/dist/theme/tailwind-preset.d.ts +65 -0
  75. package/dist/theme/tailwind-preset.js +33 -0
  76. package/dist/theme/tailwind-preset.js.map +1 -0
  77. package/dist/theme/tokens.css +95 -0
  78. package/dist/trace/index.d.ts +2 -18
  79. package/dist/trace/index.js +2 -2
  80. package/dist/web/index.d.ts +25 -2
  81. package/dist/web/index.js +3 -1
  82. package/dist/web-react/index.d.ts +71 -31
  83. package/dist/web-react/index.js +625 -486
  84. package/dist/web-react/index.js.map +1 -1
  85. package/package.json +33 -3
  86. package/dist/DesignCanvas-MA5EH324.js +0 -10
  87. package/dist/DesignCanvasEditor-YLCO7I5I.js +0 -9
  88. package/dist/chunk-5RT6KY4G.js.map +0 -1
  89. package/dist/chunk-6UOE5CTA.js.map +0 -1
  90. package/dist/chunk-AFDROJ64.js.map +0 -1
  91. package/dist/chunk-FSUR2752.js.map +0 -1
  92. package/dist/chunk-JZAJE3JL.js.map +0 -1
  93. package/dist/chunk-LPAVBGVY.js.map +0 -1
  94. package/dist/chunk-LUE4HO5C.js.map +0 -1
  95. package/dist/chunk-RJ2ZHK5D.js.map +0 -1
  96. package/dist/chunk-SCG5JJ4C.js.map +0 -1
  97. package/dist/chunk-UDXMR3AD.js.map +0 -1
  98. /package/dist/{DesignCanvas-MA5EH324.js.map → DesignCanvas-AOIVN3SJ.js.map} +0 -0
  99. /package/dist/{DesignCanvasEditor-YLCO7I5I.js.map → DesignCanvasEditor-TN7CWUJW.js.map} +0 -0
  100. /package/dist/{TimelineEditor-JDG54OBP.js.map → TimelineEditor-Z7C6BFP5.js.map} +0 -0
  101. /package/dist/{chunk-7QCIYDGC.js.map → chunk-IOREXWLM.js.map} +0 -0
@@ -1,13 +1,56 @@
1
1
  import {
2
2
  stepActivityFlowTrace
3
- } from "../chunk-AFDROJ64.js";
3
+ } from "../chunk-2QI7XV2T.js";
4
4
  import {
5
5
  snapHarnessToModel,
6
6
  snapModelToHarness
7
7
  } from "../chunk-5VXPDXZJ.js";
8
8
 
9
9
  // src/web-react/index.tsx
10
- import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef3, useState as useState5 } from "react";
10
+ import { useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState6, memo } from "react";
11
+
12
+ // src/web-react/smooth-text.ts
13
+ import { useEffect, useRef, useState } from "react";
14
+ function nextRevealCount(shown, targetLength, dtMs, opts = {}) {
15
+ if (shown >= targetLength) return targetLength;
16
+ const base = opts.baseCharsPerSecond ?? 90;
17
+ const catchUp = opts.catchUpPerChar ?? 5;
18
+ const max = opts.maxCharsPerSecond ?? 2400;
19
+ const backlog = targetLength - shown;
20
+ const rate = Math.min(max, base + backlog * catchUp);
21
+ return Math.min(targetLength, shown + rate * dtMs / 1e3);
22
+ }
23
+ function useSmoothText(target, enabled, opts) {
24
+ const [, force] = useState(0);
25
+ const shownRef = useRef(0);
26
+ const lastTargetRef = useRef("");
27
+ if (!target.startsWith(lastTargetRef.current.slice(0, Math.floor(shownRef.current)))) {
28
+ shownRef.current = 0;
29
+ }
30
+ lastTargetRef.current = target;
31
+ if (!enabled) shownRef.current = target.length;
32
+ useEffect(() => {
33
+ if (!enabled) return;
34
+ let raf = 0;
35
+ let last = null;
36
+ const tick = (t) => {
37
+ const dt = last == null ? 16 : Math.min(t - last, 100);
38
+ last = t;
39
+ const targetLen = lastTargetRef.current.length;
40
+ if (shownRef.current < targetLen) {
41
+ shownRef.current = nextRevealCount(shownRef.current, targetLen, dt, opts);
42
+ force((n) => n + 1);
43
+ raf = requestAnimationFrame(tick);
44
+ }
45
+ };
46
+ raf = requestAnimationFrame(tick);
47
+ return () => cancelAnimationFrame(raf);
48
+ }, [enabled, target]);
49
+ return target.slice(0, Math.floor(shownRef.current));
50
+ }
51
+
52
+ // src/web-react/controls.tsx
53
+ import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
11
54
 
12
55
  // src/web-react/provider-logo.tsx
13
56
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -49,44 +92,246 @@ function ProviderLogo({ provider, size = 16 }) {
49
92
  ] });
50
93
  }
51
94
 
52
- // src/web-react/smooth-text.ts
53
- import { useEffect, useRef, useState } from "react";
54
- function nextRevealCount(shown, targetLength, dtMs, opts = {}) {
55
- if (shown >= targetLength) return targetLength;
56
- const base = opts.baseCharsPerSecond ?? 90;
57
- const catchUp = opts.catchUpPerChar ?? 5;
58
- const max = opts.maxCharsPerSecond ?? 2400;
59
- const backlog = targetLength - shown;
60
- const rate = Math.min(max, base + backlog * catchUp);
61
- return Math.min(targetLength, shown + rate * dtMs / 1e3);
95
+ // src/web-react/controls.tsx
96
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
97
+ function ChevronDown({ className }) {
98
+ return /* @__PURE__ */ jsx2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" }) });
62
99
  }
63
- function useSmoothText(target, enabled, opts) {
64
- const [, force] = useState(0);
65
- const shownRef = useRef(0);
66
- const lastTargetRef = useRef("");
67
- if (!target.startsWith(lastTargetRef.current.slice(0, Math.floor(shownRef.current)))) {
68
- shownRef.current = 0;
69
- }
70
- lastTargetRef.current = target;
71
- if (!enabled) shownRef.current = target.length;
72
- useEffect(() => {
73
- if (!enabled) return;
74
- let raf = 0;
75
- let last = null;
76
- const tick = (t) => {
77
- const dt = last == null ? 16 : Math.min(t - last, 100);
78
- last = t;
79
- const targetLen = lastTargetRef.current.length;
80
- if (shownRef.current < targetLen) {
81
- shownRef.current = nextRevealCount(shownRef.current, targetLen, dt, opts);
82
- force((n) => n + 1);
100
+ function SearchGlyph({ className }) {
101
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
102
+ /* @__PURE__ */ jsx2("circle", { cx: "11", cy: "11", r: "8" }),
103
+ /* @__PURE__ */ jsx2("path", { d: "m21 21-4.3-4.3" })
104
+ ] });
105
+ }
106
+ function SparkleGlyph({ className }) {
107
+ return /* @__PURE__ */ jsx2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1" }) });
108
+ }
109
+ function usePopover(open, setOpen) {
110
+ const containerRef = useRef2(null);
111
+ const triggerRef = useRef2(null);
112
+ useEffect2(() => {
113
+ if (!open) return;
114
+ function onMouseDown(e) {
115
+ if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
116
+ }
117
+ function onKeyDown(e) {
118
+ if (e.key === "Escape") {
119
+ setOpen(false);
120
+ triggerRef.current?.focus();
83
121
  }
84
- raf = requestAnimationFrame(tick);
122
+ }
123
+ document.addEventListener("mousedown", onMouseDown);
124
+ document.addEventListener("keydown", onKeyDown);
125
+ return () => {
126
+ document.removeEventListener("mousedown", onMouseDown);
127
+ document.removeEventListener("keydown", onKeyDown);
85
128
  };
86
- raf = requestAnimationFrame(tick);
87
- return () => cancelAnimationFrame(raf);
88
- }, [enabled]);
89
- return target.slice(0, Math.floor(shownRef.current));
129
+ }, [open, setOpen]);
130
+ return {
131
+ containerRef,
132
+ triggerRef,
133
+ triggerProps: {
134
+ ref: triggerRef,
135
+ "aria-haspopup": true,
136
+ "aria-expanded": open
137
+ }
138
+ };
139
+ }
140
+ var POPOVER_OPTION_FOCUS = "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card";
141
+ function usePending() {
142
+ const [pending, setPending] = useState2(false);
143
+ const inFlight = useRef2(false);
144
+ const mounted = useRef2(true);
145
+ useEffect2(() => {
146
+ mounted.current = true;
147
+ return () => {
148
+ mounted.current = false;
149
+ };
150
+ }, []);
151
+ const run = (action) => {
152
+ if (inFlight.current) return;
153
+ let result;
154
+ try {
155
+ result = action();
156
+ } catch {
157
+ return;
158
+ }
159
+ if (!(result instanceof Promise)) return;
160
+ inFlight.current = true;
161
+ setPending(true);
162
+ void result.finally(() => {
163
+ inFlight.current = false;
164
+ if (mounted.current) setPending(false);
165
+ });
166
+ };
167
+ return { pending, run };
168
+ }
169
+ function formatPrice(p) {
170
+ if (!p) return void 0;
171
+ const n = Number(p);
172
+ if (isNaN(n) || n === 0) return void 0;
173
+ const perM = n * 1e6;
174
+ return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`;
175
+ }
176
+ function formatContext(len) {
177
+ if (!len) return void 0;
178
+ if (len >= 1e6) return `${(len / 1e6).toFixed(1)}M ctx`;
179
+ if (len >= 1e3) return `${Math.round(len / 1e3)}K ctx`;
180
+ return `${len} ctx`;
181
+ }
182
+ function SectionHeader({ children }) {
183
+ return /* @__PURE__ */ jsx2("div", { className: "px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children });
184
+ }
185
+ function ModelRow({
186
+ model,
187
+ selected,
188
+ onSelect,
189
+ renderProviderBadge
190
+ }) {
191
+ const price = formatPrice(model.pricing?.prompt);
192
+ const ctx = formatContext(model.contextLength);
193
+ return /* @__PURE__ */ jsxs2(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ onClick: onSelect,
198
+ className: `flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${selected ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
199
+ children: [
200
+ renderProviderBadge ? renderProviderBadge(model.provider) : /* @__PURE__ */ jsx2(ProviderLogo, { provider: model.provider, size: 16 }),
201
+ /* @__PURE__ */ jsx2("span", { className: "truncate", children: model.name }),
202
+ !model.supportsTools && /* @__PURE__ */ jsx2("span", { className: "shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: "no tools" }),
203
+ /* @__PURE__ */ jsxs2("span", { className: "ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground", children: [
204
+ ctx && /* @__PURE__ */ jsx2("span", { children: ctx }),
205
+ price && /* @__PURE__ */ jsx2("span", { children: price })
206
+ ] })
207
+ ]
208
+ }
209
+ );
210
+ }
211
+ function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = "Recommended" }) {
212
+ const [open, setOpen] = useState2(false);
213
+ const [query, setQuery] = useState2("");
214
+ const { containerRef, triggerProps } = usePopover(open, setOpen);
215
+ const inputRef = useRef2(null);
216
+ useEffect2(() => {
217
+ if (open) inputRef.current?.focus();
218
+ }, [open]);
219
+ const selected = models.find((m) => m.id === value);
220
+ const filtered = useMemo(() => {
221
+ const q = query.trim().toLowerCase();
222
+ if (!q) return null;
223
+ return models.filter(
224
+ (m) => m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q) || (m.description?.toLowerCase() ?? "").includes(q) || m.provider.toLowerCase().includes(q)
225
+ );
226
+ }, [models, query]);
227
+ const sections = useMemo(() => {
228
+ const recommended = models.filter((m) => m.featured);
229
+ const byProvider = [];
230
+ for (const m of models) {
231
+ if (m.featured) continue;
232
+ const last = byProvider[byProvider.length - 1];
233
+ if (last && last.provider === m.provider) last.items.push(m);
234
+ else byProvider.push({ provider: m.provider, items: [m] });
235
+ }
236
+ return { recommended, byProvider };
237
+ }, [models]);
238
+ const select = (id) => {
239
+ onChange(id);
240
+ setOpen(false);
241
+ setQuery("");
242
+ };
243
+ return /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative inline-flex", children: [
244
+ /* @__PURE__ */ jsxs2(
245
+ "button",
246
+ {
247
+ type: "button",
248
+ ...triggerProps,
249
+ onClick: () => setOpen(!open),
250
+ className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
251
+ children: [
252
+ selected ? renderProviderBadge ? renderProviderBadge(selected.provider) : /* @__PURE__ */ jsx2(ProviderLogo, { provider: selected.provider, size: 16 }) : /* @__PURE__ */ jsx2(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
253
+ /* @__PURE__ */ jsx2("span", { className: "max-w-[160px] truncate", children: selected?.name ?? value }),
254
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
255
+ ]
256
+ }
257
+ ),
258
+ open && /* @__PURE__ */ jsxs2("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg", children: [
259
+ /* @__PURE__ */ jsx2("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2", children: [
260
+ /* @__PURE__ */ jsx2(SearchGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
261
+ /* @__PURE__ */ jsx2(
262
+ "input",
263
+ {
264
+ ref: inputRef,
265
+ type: "text",
266
+ value: query,
267
+ onChange: (e) => setQuery(e.target.value),
268
+ placeholder: "Search models...",
269
+ className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
270
+ }
271
+ )
272
+ ] }) }),
273
+ /* @__PURE__ */ jsxs2("div", { className: "max-h-[400px] overflow-y-auto p-1 pb-2", children: [
274
+ loading && /* @__PURE__ */ jsx2("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "Loading models..." }),
275
+ !loading && filtered && /* @__PURE__ */ jsxs2(Fragment, { children: [
276
+ filtered.length === 0 && /* @__PURE__ */ jsx2("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No models match your search" }),
277
+ filtered.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
278
+ ] }),
279
+ !loading && !filtered && /* @__PURE__ */ jsxs2(Fragment, { children: [
280
+ sections.recommended.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
281
+ /* @__PURE__ */ jsx2(SectionHeader, { children: recommendedLabel }),
282
+ sections.recommended.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
283
+ ] }),
284
+ sections.byProvider.map((g) => /* @__PURE__ */ jsxs2("div", { children: [
285
+ /* @__PURE__ */ jsx2(SectionHeader, { children: g.provider }),
286
+ g.items.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
287
+ ] }, g.provider))
288
+ ] })
289
+ ] })
290
+ ] })
291
+ ] });
292
+ }
293
+ var EFFORT_LEVELS = [
294
+ { id: "off", label: "Off" },
295
+ { id: "low", label: "Low" },
296
+ { id: "medium", label: "Medium" },
297
+ { id: "high", label: "High" }
298
+ ];
299
+ function EffortPicker({ value, onChange }) {
300
+ const [open, setOpen] = useState2(false);
301
+ const { containerRef, triggerProps } = usePopover(open, setOpen);
302
+ const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2];
303
+ return /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative inline-flex", children: [
304
+ /* @__PURE__ */ jsxs2(
305
+ "button",
306
+ {
307
+ type: "button",
308
+ ...triggerProps,
309
+ onClick: () => setOpen(!open),
310
+ title: "Reasoning effort",
311
+ className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
312
+ children: [
313
+ /* @__PURE__ */ jsx2(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
314
+ /* @__PURE__ */ jsx2("span", { children: selected.label }),
315
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
316
+ ]
317
+ }
318
+ ),
319
+ open && /* @__PURE__ */ jsx2("div", { role: "menu", className: "absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg", children: EFFORT_LEVELS.map((l) => /* @__PURE__ */ jsx2(
320
+ "button",
321
+ {
322
+ type: "button",
323
+ role: "menuitemradio",
324
+ "aria-checked": l.id === value,
325
+ onClick: () => {
326
+ onChange(l.id);
327
+ setOpen(false);
328
+ },
329
+ className: `flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${POPOVER_OPTION_FOCUS} ${l.id === value ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
330
+ children: l.label
331
+ },
332
+ l.id
333
+ )) })
334
+ ] });
90
335
  }
91
336
 
92
337
  // src/web-react/chat-stream.ts
@@ -215,8 +460,8 @@ async function streamChatTurn(opts) {
215
460
  }
216
461
 
217
462
  // src/web-react/mission-activity.tsx
218
- import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
219
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
463
+ import { useCallback, useEffect as useEffect3, useState as useState3 } from "react";
464
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
220
465
  var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
221
466
  var OK_STATUSES = /* @__PURE__ */ new Set(["completed", "done", "succeeded"]);
222
467
  var ERROR_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "cancelled", "aborted"]);
@@ -263,19 +508,57 @@ function waterfallLayout(trace) {
263
508
  });
264
509
  }
265
510
  function ChevronGlyph({ className }) {
266
- return /* @__PURE__ */ jsx2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" }) });
511
+ return /* @__PURE__ */ jsx3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx3("path", { d: "m6 9 6 6 6-6" }) });
267
512
  }
268
513
  function RefreshGlyph({ className }) {
269
- return /* @__PURE__ */ jsx2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6" }) });
514
+ return /* @__PURE__ */ jsx3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6" }) });
270
515
  }
271
- function StatusDot({ tone }) {
272
- return /* @__PURE__ */ jsx2(
273
- "span",
516
+ function CopyGlyph({ className }) {
517
+ return /* @__PURE__ */ jsxs3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
518
+ /* @__PURE__ */ jsx3("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
519
+ /* @__PURE__ */ jsx3("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
520
+ ] });
521
+ }
522
+ function TraceIdCopy({ traceId }) {
523
+ const [copied, setCopied] = useState3(false);
524
+ const copy = useCallback(() => {
525
+ void navigator.clipboard?.writeText(traceId).then(
526
+ () => {
527
+ setCopied(true);
528
+ setTimeout(() => setCopied(false), 1200);
529
+ },
530
+ () => {
531
+ }
532
+ );
533
+ }, [traceId]);
534
+ return /* @__PURE__ */ jsxs3(
535
+ "button",
274
536
  {
275
- className: `h-2 w-2 shrink-0 rounded-full ${tone === "live" ? "animate-pulse bg-yellow-500" : tone === "ok" ? "bg-green-500" : tone === "error" ? "bg-red-500" : "bg-muted-foreground/40"}`
537
+ type: "button",
538
+ onClick: copy,
539
+ title: "Copy trace id",
540
+ "aria-label": "Copy trace id",
541
+ className: "inline-flex min-w-0 items-center gap-1.5 rounded text-left font-mono text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card",
542
+ children: [
543
+ /* @__PURE__ */ jsx3("span", { className: "truncate", children: traceId }),
544
+ /* @__PURE__ */ jsx3(CopyGlyph, { className: "h-3 w-3 shrink-0" }),
545
+ copied && /* @__PURE__ */ jsx3("span", { className: "shrink-0 not-italic text-success", children: "copied" })
546
+ ]
276
547
  }
277
548
  );
278
549
  }
550
+ function StatusDot({ tone }) {
551
+ return /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center", children: [
552
+ /* @__PURE__ */ jsx3(
553
+ "span",
554
+ {
555
+ "aria-hidden": true,
556
+ className: `h-2 w-2 shrink-0 rounded-full ${tone === "live" ? "animate-pulse bg-warning" : tone === "ok" ? "bg-success" : tone === "error" ? "bg-destructive" : "bg-muted-foreground/40"}`
557
+ }
558
+ ),
559
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: tone })
560
+ ] });
561
+ }
279
562
  var BAR_CLASS = {
280
563
  pipeline: "bg-muted-foreground/30",
281
564
  model: "bg-primary/60",
@@ -285,19 +568,19 @@ function FlowWaterfall({ trace }) {
285
568
  const rows = waterfallLayout(trace);
286
569
  if (rows.length === 0) return null;
287
570
  const cost = formatActivityCost(trace.costUsd);
288
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
289
- rows.map((row, i) => /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)_auto] items-center gap-2", children: [
290
- /* @__PURE__ */ jsx2("span", { className: "truncate font-mono text-[11px] text-muted-foreground", title: row.name, children: row.name }),
291
- /* @__PURE__ */ jsx2("div", { className: "relative h-2 rounded-sm bg-muted/40", children: /* @__PURE__ */ jsx2(
571
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
572
+ rows.map((row, i) => /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)_auto] items-center gap-2", children: [
573
+ /* @__PURE__ */ jsx3("span", { className: "truncate font-mono text-[11px] text-muted-foreground", title: row.name, children: row.name }),
574
+ /* @__PURE__ */ jsx3("div", { className: "relative h-2 rounded-sm bg-muted/40", children: /* @__PURE__ */ jsx3(
292
575
  "div",
293
576
  {
294
- className: `absolute inset-y-0 rounded-sm ${row.ok ? BAR_CLASS[row.kind] : "bg-red-500/80"} ${row.approx ? "opacity-70" : ""}`,
577
+ className: `absolute inset-y-0 rounded-sm ${row.ok ? BAR_CLASS[row.kind] : "bg-destructive/80"} ${row.approx ? "opacity-70" : ""}`,
295
578
  style: { left: `${row.offsetPct}%`, width: `${row.widthPct}%` }
296
579
  }
297
580
  ) }),
298
- /* @__PURE__ */ jsx2("span", { className: "shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: row.durationLabel })
581
+ /* @__PURE__ */ jsx3("span", { className: "shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: row.durationLabel })
299
582
  ] }, i)),
300
- /* @__PURE__ */ jsxs2("p", { className: "pt-0.5 text-right font-mono text-[10px] tabular-nums text-muted-foreground/60", children: [
583
+ /* @__PURE__ */ jsxs3("p", { className: "pt-0.5 text-right font-mono text-[10px] tabular-nums text-muted-foreground/60", children: [
301
584
  (trace.totalMs / 1e3).toFixed(1),
302
585
  "s",
303
586
  cost ? ` \xB7 ${cost}` : ""
@@ -305,43 +588,43 @@ function FlowWaterfall({ trace }) {
305
588
  ] });
306
589
  }
307
590
  function MissionActivityLane({ activity, startedAt, nowMs }) {
308
- const [expanded, setExpanded] = useState2(false);
591
+ const [expanded, setExpanded] = useState3(false);
309
592
  if (activity.length === 0) return null;
310
- return /* @__PURE__ */ jsxs2("div", { className: "mt-1 border-l border-border/50 pl-3", children: [
593
+ return /* @__PURE__ */ jsxs3("div", { className: "mt-1 border-l border-border/50 pl-3", children: [
311
594
  activity.map((run) => {
312
595
  const tone = activityTone(run.status);
313
596
  const cost = formatActivityCost(run.costUsd);
314
597
  const duration = formatActivityDuration(run.durationMs);
315
- return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
316
- /* @__PURE__ */ jsx2(StatusDot, { tone }),
317
- /* @__PURE__ */ jsxs2("span", { className: "min-w-0 flex-1 truncate", children: [
318
- /* @__PURE__ */ jsx2("span", { className: "font-medium", children: run.tool }),
319
- /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
598
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
599
+ /* @__PURE__ */ jsx3(StatusDot, { tone }),
600
+ /* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1 truncate", children: [
601
+ /* @__PURE__ */ jsx3("span", { className: "font-medium", children: run.tool }),
602
+ /* @__PURE__ */ jsxs3("span", { className: "text-muted-foreground", children: [
320
603
  " \u2014 ",
321
604
  run.detail
322
605
  ] })
323
606
  ] }),
324
- tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx2("span", { className: "shrink-0 rounded-full bg-yellow-500/10 px-1.5 py-0.5 font-mono text-[10px] text-yellow-700 dark:text-yellow-400", children: [run.iteration !== void 0 ? `iter ${run.iteration}` : null, run.phase ?? null].filter(Boolean).join(" \xB7 ") }),
325
- /* @__PURE__ */ jsxs2("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: [
326
- tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx2("span", { children: run.status }),
327
- cost && /* @__PURE__ */ jsx2("span", { children: cost }),
328
- duration && /* @__PURE__ */ jsx2("span", { children: duration })
607
+ tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx3("span", { className: "shrink-0 rounded-full bg-warning/10 px-1.5 py-0.5 font-mono text-[10px] text-warning", children: [run.iteration !== void 0 ? `iter ${run.iteration}` : null, run.phase ?? null].filter(Boolean).join(" \xB7 ") }),
608
+ /* @__PURE__ */ jsxs3("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: [
609
+ tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx3("span", { children: run.status }),
610
+ cost && /* @__PURE__ */ jsx3("span", { children: cost }),
611
+ duration && /* @__PURE__ */ jsx3("span", { children: duration })
329
612
  ] })
330
613
  ] }, run.taskId);
331
614
  }),
332
- /* @__PURE__ */ jsxs2(
615
+ /* @__PURE__ */ jsxs3(
333
616
  "button",
334
617
  {
335
618
  type: "button",
336
619
  onClick: () => setExpanded((v) => !v),
337
620
  className: "flex items-center gap-1 py-0.5 text-[10px] font-medium text-muted-foreground/70 transition hover:text-foreground",
338
621
  children: [
339
- /* @__PURE__ */ jsx2(ChevronGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
622
+ /* @__PURE__ */ jsx3(ChevronGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
340
623
  "timeline"
341
624
  ]
342
625
  }
343
626
  ),
344
- expanded && /* @__PURE__ */ jsx2("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx2(
627
+ expanded && /* @__PURE__ */ jsx3("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx3(
345
628
  FlowWaterfall,
346
629
  {
347
630
  trace: stepActivityFlowTrace(activity, {
@@ -356,44 +639,45 @@ function ActivityRow({
356
639
  record,
357
640
  renderMissionRef
358
641
  }) {
359
- const [open, setOpen] = useState2(false);
642
+ const [open, setOpen] = useState3(false);
360
643
  const tone = activityTone(record.status);
361
644
  const cost = formatActivityCost(record.costUsd);
362
645
  const duration = formatActivityDuration(record.durationMs);
363
- return /* @__PURE__ */ jsxs2("div", { className: "rounded-lg border border-border/60 bg-card", children: [
364
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm", children: [
365
- /* @__PURE__ */ jsx2(StatusDot, { tone }),
366
- /* @__PURE__ */ jsxs2("span", { className: "min-w-0 flex-1 truncate", children: [
367
- /* @__PURE__ */ jsx2("span", { className: "font-medium", children: record.tool }),
368
- /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
646
+ return /* @__PURE__ */ jsxs3("div", { className: "rounded-lg border border-border/60 bg-card", children: [
647
+ /* @__PURE__ */ jsxs3("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm", children: [
648
+ /* @__PURE__ */ jsx3(StatusDot, { tone }),
649
+ /* @__PURE__ */ jsxs3("span", { className: "min-w-0 flex-1 truncate", children: [
650
+ /* @__PURE__ */ jsx3("span", { className: "font-medium", children: record.tool }),
651
+ /* @__PURE__ */ jsxs3("span", { className: "text-muted-foreground", children: [
369
652
  " \u2014 ",
370
653
  record.detail
371
654
  ] })
372
655
  ] }),
373
- tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */ jsx2("span", { className: "shrink-0 rounded-full bg-yellow-500/10 px-2 py-0.5 font-mono text-[10px] text-yellow-700 dark:text-yellow-400", children: [record.iteration !== void 0 ? `iter ${record.iteration}` : null, record.phase ?? null].filter(Boolean).join(" \xB7 ") }),
374
- /* @__PURE__ */ jsx2(
656
+ tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */ jsx3("span", { className: "shrink-0 rounded-full bg-warning/10 px-2 py-0.5 font-mono text-[10px] text-warning", children: [record.iteration !== void 0 ? `iter ${record.iteration}` : null, record.phase ?? null].filter(Boolean).join(" \xB7 ") }),
657
+ /* @__PURE__ */ jsx3(
375
658
  "span",
376
659
  {
377
- className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${tone === "ok" ? "bg-green-500/10 text-green-700 dark:text-green-400" : tone === "error" ? "bg-red-500/10 text-red-700 dark:text-red-400" : tone === "live" ? "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400" : "bg-muted/60 text-muted-foreground"}`,
660
+ className: `shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${tone === "ok" ? "bg-success/10 text-success" : tone === "error" ? "bg-destructive/10 text-destructive" : tone === "live" ? "bg-warning/10 text-warning" : "bg-muted/60 text-muted-foreground"}`,
378
661
  children: record.status
379
662
  }
380
663
  ),
381
- cost && /* @__PURE__ */ jsx2("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: cost }),
382
- /* @__PURE__ */ jsx2(ChevronGlyph, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
664
+ cost && /* @__PURE__ */ jsx3("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: cost }),
665
+ /* @__PURE__ */ jsx3(ChevronGlyph, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
383
666
  ] }),
384
- open && /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5 border-t border-border/40 px-3 py-2.5", children: [
385
- /* @__PURE__ */ jsxs2("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[11px]", children: [
386
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground/60", children: "task" }),
387
- /* @__PURE__ */ jsx2("dd", { className: "truncate text-muted-foreground", children: record.taskId }),
388
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground/60", children: "started" }),
389
- /* @__PURE__ */ jsx2("dd", { className: "text-muted-foreground", children: new Date(record.startedAt).toLocaleString() }),
390
- duration && /* @__PURE__ */ jsxs2(Fragment, { children: [
391
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground/60", children: "duration" }),
392
- /* @__PURE__ */ jsx2("dd", { className: "text-muted-foreground", children: duration })
667
+ open && /* @__PURE__ */ jsxs3("div", { className: "space-y-2.5 border-t border-border/40 px-3 py-2.5", children: [
668
+ record.durationMs !== void 0 && /* @__PURE__ */ jsx3("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx3(FlowWaterfall, { trace: stepActivityFlowTrace([record]) }) }),
669
+ /* @__PURE__ */ jsxs3("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[11px]", children: [
670
+ /* @__PURE__ */ jsx3("dt", { className: "text-muted-foreground/60", children: "task" }),
671
+ /* @__PURE__ */ jsx3("dd", { className: "truncate text-muted-foreground", children: record.taskId }),
672
+ /* @__PURE__ */ jsx3("dt", { className: "text-muted-foreground/60", children: "started" }),
673
+ /* @__PURE__ */ jsx3("dd", { className: "text-muted-foreground", children: new Date(record.startedAt).toLocaleString() }),
674
+ duration && /* @__PURE__ */ jsxs3(Fragment2, { children: [
675
+ /* @__PURE__ */ jsx3("dt", { className: "text-muted-foreground/60", children: "duration" }),
676
+ /* @__PURE__ */ jsx3("dd", { className: "text-muted-foreground", children: duration })
393
677
  ] }),
394
- record.traceId && /* @__PURE__ */ jsxs2(Fragment, { children: [
395
- /* @__PURE__ */ jsx2("dt", { className: "text-muted-foreground/60", children: "trace" }),
396
- /* @__PURE__ */ jsx2("dd", { className: "truncate text-muted-foreground", children: record.traceId })
678
+ record.traceId && /* @__PURE__ */ jsxs3(Fragment2, { children: [
679
+ /* @__PURE__ */ jsx3("dt", { className: "text-muted-foreground/60", children: "trace" }),
680
+ /* @__PURE__ */ jsx3("dd", { className: "min-w-0", children: /* @__PURE__ */ jsx3(TraceIdCopy, { traceId: record.traceId }) })
397
681
  ] })
398
682
  ] }),
399
683
  record.missionRef && renderMissionRef?.(record.missionRef, record)
@@ -401,10 +685,10 @@ function ActivityRow({
401
685
  ] });
402
686
  }
403
687
  function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
404
- const [rows, setRows] = useState2([]);
405
- const [cursor, setCursor] = useState2(void 0);
406
- const [loading, setLoading] = useState2(false);
407
- const [error, setError] = useState2(null);
688
+ const [rows, setRows] = useState3([]);
689
+ const [cursor, setCursor] = useState3(void 0);
690
+ const [loading, setLoading] = useState3(false);
691
+ const [error, setError] = useState3(null);
408
692
  const load = useCallback(
409
693
  async (from) => {
410
694
  setLoading(true);
@@ -421,13 +705,13 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
421
705
  },
422
706
  [fetchActivity]
423
707
  );
424
- useEffect2(() => {
708
+ useEffect3(() => {
425
709
  void load();
426
710
  }, [load]);
427
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
428
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
429
- /* @__PURE__ */ jsx2("h2", { className: "flex-1 text-sm font-semibold", children: title }),
430
- /* @__PURE__ */ jsx2(
711
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
712
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
713
+ /* @__PURE__ */ jsx3("h2", { className: "flex-1 text-sm font-semibold", children: title }),
714
+ /* @__PURE__ */ jsx3(
431
715
  "button",
432
716
  {
433
717
  type: "button",
@@ -435,14 +719,14 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
435
719
  disabled: loading,
436
720
  "aria-label": "Refresh",
437
721
  className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground disabled:opacity-50",
438
- children: /* @__PURE__ */ jsx2(RefreshGlyph, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` })
722
+ children: /* @__PURE__ */ jsx3(RefreshGlyph, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` })
439
723
  }
440
724
  )
441
725
  ] }),
442
- error && /* @__PURE__ */ jsx2("p", { className: "rounded-md border border-red-300/60 bg-red-500/5 px-3 py-2 text-xs text-red-600", children: error }),
443
- !error && rows.length === 0 && !loading && /* @__PURE__ */ jsx2("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
444
- /* @__PURE__ */ jsx2("div", { className: "space-y-1.5", children: rows.map((record) => /* @__PURE__ */ jsx2(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
445
- cursor && /* @__PURE__ */ jsx2(
726
+ error && /* @__PURE__ */ jsx3("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
727
+ !error && rows.length === 0 && !loading && /* @__PURE__ */ jsx3("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
728
+ /* @__PURE__ */ jsx3("div", { className: "space-y-1.5", children: rows.map((record) => /* @__PURE__ */ jsx3(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
729
+ cursor && /* @__PURE__ */ jsx3(
446
730
  "button",
447
731
  {
448
732
  type: "button",
@@ -456,7 +740,7 @@ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent ac
456
740
  }
457
741
 
458
742
  // src/web-react/sandbox-terminal.ts
459
- import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
743
+ import { useCallback as useCallback2, useEffect as useEffect4, useState as useState4 } from "react";
460
744
  var DEFAULT_PROVISION_POLL_INTERVAL_MS = 2e3;
461
745
  var DEFAULT_PROVISION_POLL_TIMEOUT_MS = 9e4;
462
746
  var DEFAULT_TOKEN_REFRESH_SKEW_MS = 12e4;
@@ -470,7 +754,7 @@ var EMPTY_CONNECTION = {
470
754
  loading: false
471
755
  };
472
756
  function useSandboxTerminalConnection(opts) {
473
- const [conn, setConn] = useState3(EMPTY_CONNECTION);
757
+ const [conn, setConn] = useState4(EMPTY_CONNECTION);
474
758
  const fetcher = opts.fetcher ?? fetch;
475
759
  const pollIntervalMs = opts.provisionPollIntervalMs ?? DEFAULT_PROVISION_POLL_INTERVAL_MS;
476
760
  const pollTimeoutMs = opts.provisionPollTimeoutMs ?? DEFAULT_PROVISION_POLL_TIMEOUT_MS;
@@ -552,10 +836,10 @@ function useSandboxTerminalConnection(opts) {
552
836
  }
553
837
  }
554
838
  }, [connectionUrl, fetcher, pollIntervalMs, pollTimeoutMs]);
555
- useEffect3(() => {
839
+ useEffect4(() => {
556
840
  void connect();
557
841
  }, [connect]);
558
- useEffect3(() => {
842
+ useEffect4(() => {
559
843
  if (!conn.runtimeUrl || !conn.token || !conn.expiresAt) return;
560
844
  const refreshAt = Date.parse(conn.expiresAt) - Date.now() - refreshSkewMs;
561
845
  if (!Number.isFinite(refreshAt)) {
@@ -582,9 +866,9 @@ function sleep(ms) {
582
866
  }
583
867
 
584
868
  // src/web-react/seat-paywall.tsx
585
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
869
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
586
870
  function CheckGlyph() {
587
- return /* @__PURE__ */ jsx3(
871
+ return /* @__PURE__ */ jsx4(
588
872
  "svg",
589
873
  {
590
874
  className: "h-4 w-4 shrink-0 text-primary",
@@ -595,14 +879,14 @@ function CheckGlyph() {
595
879
  strokeLinecap: "round",
596
880
  strokeLinejoin: "round",
597
881
  "aria-hidden": true,
598
- children: /* @__PURE__ */ jsx3("path", { d: "M20 6 9 17l-5-5" })
882
+ children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" })
599
883
  }
600
884
  );
601
885
  }
602
886
  function Benefit({ children }) {
603
- return /* @__PURE__ */ jsxs3("li", { className: "flex items-start gap-2.5 text-sm text-foreground", children: [
604
- /* @__PURE__ */ jsx3("span", { className: "mt-0.5", children: /* @__PURE__ */ jsx3(CheckGlyph, {}) }),
605
- /* @__PURE__ */ jsx3("span", { children })
887
+ return /* @__PURE__ */ jsxs4("li", { className: "flex items-start gap-2.5 text-sm text-foreground", children: [
888
+ /* @__PURE__ */ jsx4("span", { className: "mt-0.5", children: /* @__PURE__ */ jsx4(CheckGlyph, {}) }),
889
+ /* @__PURE__ */ jsx4("span", { children })
606
890
  ] });
607
891
  }
608
892
  function SeatPaywall({
@@ -613,53 +897,55 @@ function SeatPaywall({
613
897
  tagline,
614
898
  ctaLabel
615
899
  }) {
616
- return /* @__PURE__ */ jsx3("div", { className: "flex min-h-[60vh] w-full items-center justify-center p-6", children: /* @__PURE__ */ jsxs3("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-8 shadow-sm", children: [
617
- /* @__PURE__ */ jsx3("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: product }),
618
- /* @__PURE__ */ jsxs3("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: [
900
+ const { pending, run } = usePending();
901
+ return /* @__PURE__ */ jsx4("div", { className: "flex min-h-[60vh] w-full items-center justify-center p-6", children: /* @__PURE__ */ jsxs4("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-8 shadow-sm", children: [
902
+ /* @__PURE__ */ jsx4("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: product }),
903
+ /* @__PURE__ */ jsxs4("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: [
619
904
  "Unlock ",
620
905
  product
621
906
  ] }),
622
- tagline && /* @__PURE__ */ jsx3("p", { className: "mt-2 text-sm text-muted-foreground", children: tagline }),
623
- /* @__PURE__ */ jsxs3("div", { className: "mt-6 flex items-baseline gap-1.5", children: [
624
- /* @__PURE__ */ jsxs3("span", { className: "text-3xl font-semibold text-foreground", children: [
907
+ tagline && /* @__PURE__ */ jsx4("p", { className: "mt-2 text-sm text-muted-foreground", children: tagline }),
908
+ /* @__PURE__ */ jsxs4("div", { className: "mt-6 flex items-baseline gap-1.5", children: [
909
+ /* @__PURE__ */ jsxs4("span", { className: "text-3xl font-semibold text-foreground", children: [
625
910
  "$",
626
911
  priceUsd
627
912
  ] }),
628
- /* @__PURE__ */ jsx3("span", { className: "text-sm text-muted-foreground", children: "/mo" })
913
+ /* @__PURE__ */ jsx4("span", { className: "text-sm text-muted-foreground", children: "/mo" })
629
914
  ] }),
630
- /* @__PURE__ */ jsxs3("p", { className: "mt-1 text-sm text-muted-foreground", children: [
915
+ /* @__PURE__ */ jsxs4("p", { className: "mt-1 text-sm text-muted-foreground", children: [
631
916
  "Includes $",
632
917
  includedUsageUsd,
633
918
  "/mo of AI usage"
634
919
  ] }),
635
- /* @__PURE__ */ jsxs3("ul", { className: "mt-6 space-y-2.5", children: [
636
- /* @__PURE__ */ jsxs3(Benefit, { children: [
920
+ /* @__PURE__ */ jsxs4("ul", { className: "mt-6 space-y-2.5", children: [
921
+ /* @__PURE__ */ jsxs4(Benefit, { children: [
637
922
  "Full access to ",
638
923
  product
639
924
  ] }),
640
- /* @__PURE__ */ jsxs3(Benefit, { children: [
925
+ /* @__PURE__ */ jsxs4(Benefit, { children: [
641
926
  "$",
642
927
  includedUsageUsd,
643
928
  "/mo of AI usage included, every month"
644
929
  ] }),
645
- /* @__PURE__ */ jsx3(Benefit, { children: "One Tangle wallet across every product you run" })
930
+ /* @__PURE__ */ jsx4(Benefit, { children: "One Tangle wallet across every product you run" })
646
931
  ] }),
647
- /* @__PURE__ */ jsx3(
932
+ /* @__PURE__ */ jsx4(
648
933
  "button",
649
934
  {
650
935
  type: "button",
651
- onClick: onCheckout,
652
- className: "mt-7 inline-flex w-full items-center justify-center rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90",
653
- children: ctaLabel ?? `Unlock ${product}`
936
+ disabled: pending,
937
+ onClick: () => run(onCheckout),
938
+ className: "mt-7 inline-flex w-full items-center justify-center rounded-xl bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-70",
939
+ children: pending ? "Opening checkout\u2026" : ctaLabel ?? `Unlock ${product}`
654
940
  }
655
941
  ),
656
- /* @__PURE__ */ jsx3("p", { className: "mt-3 text-center text-xs text-muted-foreground/70", children: "Cancel anytime." })
942
+ /* @__PURE__ */ jsx4("p", { className: "mt-3 text-center text-xs text-muted-foreground/70", children: "Cancel anytime." })
657
943
  ] }) });
658
944
  }
659
945
 
660
946
  // src/web-react/agent-session-controls.tsx
661
- import { useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState4 } from "react";
662
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
947
+ import { useMemo as useMemo2, useState as useState5 } from "react";
948
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
663
949
  var HARNESS_LABELS = {
664
950
  opencode: "OpenCode (any model)",
665
951
  "claude-code": "Claude Code (Anthropic)",
@@ -678,58 +964,50 @@ var HARNESS_LABELS = {
678
964
  function harnessLabel(h) {
679
965
  return HARNESS_LABELS[h] ?? h;
680
966
  }
681
- function ChevronDown({ className }) {
682
- return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx4("path", { d: "m6 9 6 6 6-6" }) });
967
+ function ChevronDown2({ className }) {
968
+ return /* @__PURE__ */ jsx5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "m6 9 6 6 6-6" }) });
683
969
  }
684
970
  function GearGlyph({ className }) {
685
- return /* @__PURE__ */ jsxs4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
686
- /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "3" }),
687
- /* @__PURE__ */ jsx4("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
971
+ return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
972
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "3" }),
973
+ /* @__PURE__ */ jsx5("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" })
688
974
  ] });
689
975
  }
690
- function useClickOutside(active, onOutside) {
691
- const ref = useRef2(null);
692
- useEffect4(() => {
693
- if (!active) return;
694
- function handler(e) {
695
- if (ref.current && !ref.current.contains(e.target)) onOutside();
696
- }
697
- document.addEventListener("mousedown", handler);
698
- return () => document.removeEventListener("mousedown", handler);
699
- }, [active, onOutside]);
700
- return ref;
701
- }
976
+ var FOCUS_RING = "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background";
702
977
  function HarnessPicker({
703
978
  value,
704
979
  onChange,
705
980
  available
706
981
  }) {
707
- const [open, setOpen] = useState4(false);
708
- const containerRef = useClickOutside(open, () => setOpen(false));
982
+ const [open, setOpen] = useState5(false);
983
+ const { containerRef, triggerProps } = usePopover(open, setOpen);
709
984
  const options = available ?? Object.keys(HARNESS_LABELS);
710
- return /* @__PURE__ */ jsxs4("div", { ref: containerRef, className: "relative inline-flex", children: [
711
- /* @__PURE__ */ jsxs4(
985
+ return /* @__PURE__ */ jsxs5("div", { ref: containerRef, className: "relative inline-flex", children: [
986
+ /* @__PURE__ */ jsxs5(
712
987
  "button",
713
988
  {
714
989
  type: "button",
715
- onClick: () => setOpen((v) => !v),
990
+ ...triggerProps,
991
+ onClick: () => setOpen(!open),
716
992
  title: "Agent backend",
717
- className: "inline-flex w-full items-center justify-between gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30",
993
+ className: `inline-flex w-full items-center justify-between gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30 ${FOCUS_RING}`,
718
994
  children: [
719
- /* @__PURE__ */ jsx4("span", { className: "truncate", children: harnessLabel(value) }),
720
- /* @__PURE__ */ jsx4(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
995
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: harnessLabel(value) }),
996
+ /* @__PURE__ */ jsx5(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
721
997
  ]
722
998
  }
723
999
  ),
724
- open && /* @__PURE__ */ jsx4("div", { className: "absolute bottom-full left-0 z-50 mb-2 max-h-64 w-full min-w-[220px] overflow-y-auto rounded-xl border border-border bg-card p-1 shadow-lg", children: options.map((h) => /* @__PURE__ */ jsx4(
1000
+ open && /* @__PURE__ */ jsx5("div", { role: "menu", className: "absolute bottom-full left-0 z-50 mb-2 max-h-64 w-full min-w-[220px] overflow-y-auto rounded-xl border border-border bg-card p-1 shadow-lg", children: options.map((h) => /* @__PURE__ */ jsx5(
725
1001
  "button",
726
1002
  {
727
1003
  type: "button",
1004
+ role: "menuitemradio",
1005
+ "aria-checked": h === value,
728
1006
  onClick: () => {
729
1007
  onChange(h);
730
1008
  setOpen(false);
731
1009
  },
732
- className: `flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${h === value ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
1010
+ className: `flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${FOCUS_RING} ${h === value ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
733
1011
  children: harnessLabel(h)
734
1012
  },
735
1013
  h
@@ -738,7 +1016,7 @@ function HarnessPicker({
738
1016
  }
739
1017
  function useCoherentHandlers(props) {
740
1018
  const { model, models, harness, onModelChange, onHarnessChange } = props;
741
- const canonicalIds = useMemo(() => models.map((m) => m.id), [models]);
1019
+ const canonicalIds = useMemo2(() => models.map((m) => m.id), [models]);
742
1020
  const onModel = (next) => {
743
1021
  onModelChange(next);
744
1022
  const nextHarness = snapHarnessToModel(harness, next);
@@ -766,11 +1044,11 @@ function AgentSessionControls(props) {
766
1044
  className
767
1045
  } = props;
768
1046
  const { onModel, onHarness } = useCoherentHandlers(props);
769
- const [open, setOpen] = useState4(false);
770
- const popoverRef = useClickOutside(open, () => setOpen(false));
1047
+ const [open, setOpen] = useState5(false);
1048
+ const { containerRef: popoverRef, triggerProps } = usePopover(open, setOpen);
771
1049
  const selectedModel = models.find((m) => m.id === model);
772
1050
  const showEffort = selectedModel?.supportsReasoning ?? true;
773
- const modelPicker = /* @__PURE__ */ jsx4(
1051
+ const modelPicker = /* @__PURE__ */ jsx5(
774
1052
  ModelPicker,
775
1053
  {
776
1054
  value: model,
@@ -781,37 +1059,38 @@ function AgentSessionControls(props) {
781
1059
  }
782
1060
  );
783
1061
  if (layout === "inline") {
784
- return /* @__PURE__ */ jsxs4("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
1062
+ return /* @__PURE__ */ jsxs5("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
785
1063
  modelPicker,
786
- showHarness && /* @__PURE__ */ jsx4(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
787
- showEffort && /* @__PURE__ */ jsx4(EffortPicker, { value: effort, onChange: onEffortChange })
1064
+ showHarness && /* @__PURE__ */ jsx5(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
1065
+ showEffort && /* @__PURE__ */ jsx5(EffortPicker, { value: effort, onChange: onEffortChange })
788
1066
  ] });
789
1067
  }
790
1068
  const hasAdvanced = showHarness || showEffort;
791
- return /* @__PURE__ */ jsxs4("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
1069
+ return /* @__PURE__ */ jsxs5("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
792
1070
  modelPicker,
793
- hasAdvanced && /* @__PURE__ */ jsxs4("div", { ref: popoverRef, className: "relative inline-flex", children: [
794
- /* @__PURE__ */ jsx4(
1071
+ hasAdvanced && /* @__PURE__ */ jsxs5("div", { ref: popoverRef, className: "relative inline-flex", children: [
1072
+ /* @__PURE__ */ jsx5(
795
1073
  "button",
796
1074
  {
797
1075
  type: "button",
798
- onClick: () => setOpen((v) => !v),
1076
+ ...triggerProps,
1077
+ onClick: () => setOpen(!open),
799
1078
  title: "Model settings \u2014 pick the agent backend and how hard it thinks",
800
- className: "flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted",
1079
+ className: `flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted ${FOCUS_RING}`,
801
1080
  "data-state": open ? "open" : "closed",
802
- children: /* @__PURE__ */ jsx4(GearGlyph, { className: "h-4 w-4" })
1081
+ children: /* @__PURE__ */ jsx5(GearGlyph, { className: "h-4 w-4" })
803
1082
  }
804
1083
  ),
805
- open && /* @__PURE__ */ jsxs4("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-72 space-y-3 rounded-xl border border-border bg-card p-3 shadow-lg", children: [
806
- showHarness && /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
807
- /* @__PURE__ */ jsx4("p", { className: "text-xs font-medium text-foreground", children: "Agent backend" }),
808
- /* @__PURE__ */ jsx4(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
809
- /* @__PURE__ */ jsx4("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "The engine that runs the agent. Switching it keeps your model choice compatible." })
1084
+ open && /* @__PURE__ */ jsxs5("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-72 space-y-3 rounded-xl border border-border bg-card p-3 shadow-lg", children: [
1085
+ showHarness && /* @__PURE__ */ jsxs5("div", { className: "space-y-1.5", children: [
1086
+ /* @__PURE__ */ jsx5("p", { className: "text-xs font-medium text-foreground", children: "Agent backend" }),
1087
+ /* @__PURE__ */ jsx5(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
1088
+ /* @__PURE__ */ jsx5("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "The engine that runs the agent. Switching it keeps your model choice compatible." })
810
1089
  ] }),
811
- showEffort && /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
812
- /* @__PURE__ */ jsx4("p", { className: "text-xs font-medium text-foreground", children: "Reasoning effort" }),
813
- /* @__PURE__ */ jsx4(EffortPicker, { value: effort, onChange: onEffortChange }),
814
- /* @__PURE__ */ jsx4("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "How hard the agent thinks before answering. Higher is slower but more thorough." })
1090
+ showEffort && /* @__PURE__ */ jsxs5("div", { className: "space-y-1.5", children: [
1091
+ /* @__PURE__ */ jsx5("p", { className: "text-xs font-medium text-foreground", children: "Reasoning effort" }),
1092
+ /* @__PURE__ */ jsx5(EffortPicker, { value: effort, onChange: onEffortChange }),
1093
+ /* @__PURE__ */ jsx5("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "How hard the agent thinks before answering. Higher is slower but more thorough." })
815
1094
  ] })
816
1095
  ] })
817
1096
  ] })
@@ -819,30 +1098,7 @@ function AgentSessionControls(props) {
819
1098
  }
820
1099
 
821
1100
  // src/web-react/index.tsx
822
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
823
- function ChevronDown2({ className }) {
824
- return /* @__PURE__ */ jsx5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "m6 9 6 6 6-6" }) });
825
- }
826
- function SearchGlyph({ className }) {
827
- return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
828
- /* @__PURE__ */ jsx5("circle", { cx: "11", cy: "11", r: "8" }),
829
- /* @__PURE__ */ jsx5("path", { d: "m21 21-4.3-4.3" })
830
- ] });
831
- }
832
- function SparkleGlyph({ className }) {
833
- return /* @__PURE__ */ jsx5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1" }) });
834
- }
835
- function useClickOutside2(onOutside) {
836
- const ref = useRef3(null);
837
- useEffect5(() => {
838
- function handler(e) {
839
- if (ref.current && !ref.current.contains(e.target)) onOutside();
840
- }
841
- document.addEventListener("mousedown", handler);
842
- return () => document.removeEventListener("mousedown", handler);
843
- });
844
- return ref;
845
- }
1101
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
846
1102
  function formatModelCost(msg, models) {
847
1103
  if (msg.promptTokens == null && msg.completionTokens == null) return null;
848
1104
  const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing;
@@ -855,205 +1111,42 @@ function formatTokensPerSecond(msg) {
855
1111
  if (msg.completionTokens == null || !msg.durationMs) return null;
856
1112
  return `${Math.round(msg.completionTokens / (msg.durationMs / 1e3))} tok/s`;
857
1113
  }
858
- function formatPrice(p) {
859
- if (!p) return void 0;
860
- const n = Number(p);
861
- if (isNaN(n) || n === 0) return void 0;
862
- const perM = n * 1e6;
863
- return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`;
864
- }
865
- function formatContext(len) {
866
- if (!len) return void 0;
867
- if (len >= 1e6) return `${(len / 1e6).toFixed(1)}M ctx`;
868
- if (len >= 1e3) return `${Math.round(len / 1e3)}K ctx`;
869
- return `${len} ctx`;
870
- }
871
- function SectionHeader({ children }) {
872
- return /* @__PURE__ */ jsx5("div", { className: "px-3 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground/70", children });
873
- }
874
- function ModelRow({
875
- model,
876
- selected,
877
- onSelect,
878
- renderProviderBadge
879
- }) {
880
- const price = formatPrice(model.pricing?.prompt);
881
- const ctx = formatContext(model.contextLength);
882
- return /* @__PURE__ */ jsxs5(
883
- "button",
884
- {
885
- type: "button",
886
- onClick: onSelect,
887
- className: `flex w-full items-center gap-2.5 rounded-md px-3 py-2.5 text-left text-sm transition ${selected ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
888
- children: [
889
- renderProviderBadge ? renderProviderBadge(model.provider) : /* @__PURE__ */ jsx5(ProviderLogo, { provider: model.provider, size: 16 }),
890
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: model.name }),
891
- !model.supportsTools && /* @__PURE__ */ jsx5("span", { className: "shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: "no tools" }),
892
- /* @__PURE__ */ jsxs5("span", { className: "ml-auto flex shrink-0 items-center gap-2 text-xs text-muted-foreground", children: [
893
- ctx && /* @__PURE__ */ jsx5("span", { children: ctx }),
894
- price && /* @__PURE__ */ jsx5("span", { children: price })
895
- ] })
896
- ]
897
- }
898
- );
899
- }
900
- function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = "Recommended" }) {
901
- const [open, setOpen] = useState5(false);
902
- const [query, setQuery] = useState5("");
903
- const containerRef = useClickOutside2(() => setOpen(false));
904
- const inputRef = useRef3(null);
905
- useEffect5(() => {
906
- if (open) inputRef.current?.focus();
907
- }, [open]);
908
- const selected = models.find((m) => m.id === value);
909
- const filtered = useMemo2(() => {
910
- const q = query.trim().toLowerCase();
911
- if (!q) return null;
912
- return models.filter(
913
- (m) => m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q) || (m.description?.toLowerCase() ?? "").includes(q) || m.provider.toLowerCase().includes(q)
914
- );
915
- }, [models, query]);
916
- const sections = useMemo2(() => {
917
- const recommended = models.filter((m) => m.featured);
918
- const byProvider = [];
919
- for (const m of models) {
920
- if (m.featured) continue;
921
- const last = byProvider[byProvider.length - 1];
922
- if (last && last.provider === m.provider) last.items.push(m);
923
- else byProvider.push({ provider: m.provider, items: [m] });
924
- }
925
- return { recommended, byProvider };
926
- }, [models]);
927
- const select = (id) => {
928
- onChange(id);
929
- setOpen(false);
930
- setQuery("");
931
- };
932
- return /* @__PURE__ */ jsxs5("div", { ref: containerRef, className: "relative inline-flex", children: [
933
- /* @__PURE__ */ jsxs5(
934
- "button",
935
- {
936
- type: "button",
937
- onClick: () => setOpen((v) => !v),
938
- className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30",
939
- children: [
940
- selected ? renderProviderBadge ? renderProviderBadge(selected.provider) : /* @__PURE__ */ jsx5(ProviderLogo, { provider: selected.provider, size: 16 }) : /* @__PURE__ */ jsx5(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
941
- /* @__PURE__ */ jsx5("span", { className: "max-w-[160px] truncate", children: selected?.name ?? value }),
942
- /* @__PURE__ */ jsx5(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
943
- ]
944
- }
945
- ),
946
- open && /* @__PURE__ */ jsxs5("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg", children: [
947
- /* @__PURE__ */ jsx5("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2", children: [
948
- /* @__PURE__ */ jsx5(SearchGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
949
- /* @__PURE__ */ jsx5(
950
- "input",
951
- {
952
- ref: inputRef,
953
- type: "text",
954
- value: query,
955
- onChange: (e) => setQuery(e.target.value),
956
- placeholder: "Search models...",
957
- className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
958
- }
959
- )
960
- ] }) }),
961
- /* @__PURE__ */ jsxs5("div", { className: "max-h-[400px] overflow-y-auto p-1 pb-2", children: [
962
- loading && /* @__PURE__ */ jsx5("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "Loading models..." }),
963
- !loading && filtered && /* @__PURE__ */ jsxs5(Fragment2, { children: [
964
- filtered.length === 0 && /* @__PURE__ */ jsx5("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No models match your search" }),
965
- filtered.map((m) => /* @__PURE__ */ jsx5(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
966
- ] }),
967
- !loading && !filtered && /* @__PURE__ */ jsxs5(Fragment2, { children: [
968
- sections.recommended.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
969
- /* @__PURE__ */ jsx5(SectionHeader, { children: recommendedLabel }),
970
- sections.recommended.map((m) => /* @__PURE__ */ jsx5(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
971
- ] }),
972
- sections.byProvider.map((g) => /* @__PURE__ */ jsxs5("div", { children: [
973
- /* @__PURE__ */ jsx5(SectionHeader, { children: g.provider }),
974
- g.items.map((m) => /* @__PURE__ */ jsx5(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
975
- ] }, g.provider))
976
- ] })
977
- ] })
978
- ] })
979
- ] });
980
- }
981
- var EFFORT_LEVELS = [
982
- { id: "off", label: "Off" },
983
- { id: "low", label: "Low" },
984
- { id: "medium", label: "Medium" },
985
- { id: "high", label: "High" }
986
- ];
987
- function EffortPicker({ value, onChange }) {
988
- const [open, setOpen] = useState5(false);
989
- const containerRef = useClickOutside2(() => setOpen(false));
990
- const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2];
991
- return /* @__PURE__ */ jsxs5("div", { ref: containerRef, className: "relative inline-flex", children: [
992
- /* @__PURE__ */ jsxs5(
993
- "button",
994
- {
995
- type: "button",
996
- onClick: () => setOpen((v) => !v),
997
- title: "Reasoning effort",
998
- className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30",
999
- children: [
1000
- /* @__PURE__ */ jsx5(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
1001
- /* @__PURE__ */ jsx5("span", { children: selected.label }),
1002
- /* @__PURE__ */ jsx5(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
1003
- ]
1004
- }
1005
- ),
1006
- open && /* @__PURE__ */ jsx5("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg", children: EFFORT_LEVELS.map((l) => /* @__PURE__ */ jsx5(
1007
- "button",
1008
- {
1009
- type: "button",
1010
- onClick: () => {
1011
- onChange(l.id);
1012
- setOpen(false);
1013
- },
1014
- className: `flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${l.id === value ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
1015
- children: l.label
1016
- },
1017
- l.id
1018
- )) })
1019
- ] });
1020
- }
1021
1114
  function RunDrillIn({ run, onClose }) {
1022
- return /* @__PURE__ */ jsxs5("div", { className: "fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl", children: [
1023
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
1024
- /* @__PURE__ */ jsx5(
1115
+ return /* @__PURE__ */ jsxs6("div", { className: "fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl", children: [
1116
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
1117
+ /* @__PURE__ */ jsx6(
1025
1118
  "span",
1026
1119
  {
1027
- className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-yellow-500" : run.status === "error" ? "bg-red-500" : "bg-green-500"}`
1120
+ className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-warning" : run.status === "error" ? "bg-destructive" : "bg-success"}`
1028
1121
  }
1029
1122
  ),
1030
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
1031
- /* @__PURE__ */ jsx5("p", { className: "truncate text-sm font-semibold", children: run.title }),
1032
- /* @__PURE__ */ jsx5("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
1123
+ /* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
1124
+ /* @__PURE__ */ jsx6("p", { className: "truncate text-sm font-semibold", children: run.title }),
1125
+ /* @__PURE__ */ jsx6("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
1033
1126
  ] }),
1034
- /* @__PURE__ */ jsx5(
1127
+ /* @__PURE__ */ jsx6(
1035
1128
  "button",
1036
1129
  {
1037
1130
  type: "button",
1038
1131
  onClick: onClose,
1039
1132
  "aria-label": "Close",
1040
1133
  className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
1041
- children: /* @__PURE__ */ jsx5("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "M18 6 6 18M6 6l12 12" }) })
1134
+ children: /* @__PURE__ */ jsx6("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx6("path", { d: "M18 6 6 18M6 6l12 12" }) })
1042
1135
  }
1043
1136
  )
1044
1137
  ] }),
1045
- /* @__PURE__ */ jsxs5("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
1046
- run.steps.length === 0 && /* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
1047
- run.steps.map((step, i) => /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border border-border/60 bg-background", children: [
1048
- /* @__PURE__ */ jsxs5("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
1049
- /* @__PURE__ */ jsx5("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-red-600" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
1050
- /* @__PURE__ */ jsx5("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
1051
- /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-[10px] text-muted-foreground/60", children: new Date(step.at).toLocaleTimeString() })
1138
+ /* @__PURE__ */ jsxs6("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
1139
+ run.steps.length === 0 && /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
1140
+ run.steps.map((step, i) => /* @__PURE__ */ jsxs6("div", { className: "rounded-lg border border-border/60 bg-background", children: [
1141
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
1142
+ /* @__PURE__ */ jsx6("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-destructive" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
1143
+ /* @__PURE__ */ jsx6("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
1144
+ /* @__PURE__ */ jsx6("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: new Date(step.at).toLocaleTimeString() })
1052
1145
  ] }),
1053
- step.detail && /* @__PURE__ */ jsx5("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: step.detail })
1146
+ step.detail && /* @__PURE__ */ jsx6("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: step.detail })
1054
1147
  ] }, i))
1055
1148
  ] }),
1056
- /* @__PURE__ */ jsx5("p", { className: "border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60", children: "Readonly drill-in. Follow up in the main chat." })
1149
+ /* @__PURE__ */ jsx6("p", { className: "border-t border-border px-4 py-2 text-[11px] text-muted-foreground", children: "Readonly drill-in. Follow up in the main chat." })
1057
1150
  ] });
1058
1151
  }
1059
1152
  function pendingApprovalOf(call) {
@@ -1063,26 +1156,26 @@ function pendingApprovalOf(call) {
1063
1156
  }
1064
1157
  function ToolGlyph({ name, className }) {
1065
1158
  if (name.startsWith("sandbox_")) {
1066
- return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1067
- /* @__PURE__ */ jsx5("polyline", { points: "4 17 10 11 4 5" }),
1068
- /* @__PURE__ */ jsx5("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
1159
+ return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1160
+ /* @__PURE__ */ jsx6("polyline", { points: "4 17 10 11 4 5" }),
1161
+ /* @__PURE__ */ jsx6("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
1069
1162
  ] });
1070
1163
  }
1071
1164
  if (name === "submit_proposal") {
1072
- return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1073
- /* @__PURE__ */ jsx5("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1074
- /* @__PURE__ */ jsx5("path", { d: "M14 2v6h6M9 15l2 2 4-4" })
1165
+ return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1166
+ /* @__PURE__ */ jsx6("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1167
+ /* @__PURE__ */ jsx6("path", { d: "M14 2v6h6M9 15l2 2 4-4" })
1075
1168
  ] });
1076
1169
  }
1077
1170
  if (name === "schedule_followup") {
1078
- return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
1079
- /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "9" }),
1080
- /* @__PURE__ */ jsx5("path", { d: "M12 7v5l3 3" })
1171
+ return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
1172
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "9" }),
1173
+ /* @__PURE__ */ jsx6("path", { d: "M12 7v5l3 3" })
1081
1174
  ] });
1082
1175
  }
1083
- return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1084
- /* @__PURE__ */ jsx5("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
1085
- /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "4" })
1176
+ return /* @__PURE__ */ jsxs6("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1177
+ /* @__PURE__ */ jsx6("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
1178
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "4" })
1086
1179
  ] });
1087
1180
  }
1088
1181
  function toolOutcomeOf(call) {
@@ -1116,36 +1209,36 @@ function truncate(v, max = 240) {
1116
1209
  function KvRows({ data }) {
1117
1210
  const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null && v !== "");
1118
1211
  if (!entries.length) return null;
1119
- return /* @__PURE__ */ jsx5("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxs5("div", { className: "contents", children: [
1120
- /* @__PURE__ */ jsx5("dt", { className: "font-mono text-[11px] text-muted-foreground/70", children: k }),
1121
- /* @__PURE__ */ jsx5("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
1212
+ return /* @__PURE__ */ jsx6("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxs6("div", { className: "contents", children: [
1213
+ /* @__PURE__ */ jsx6("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
1214
+ /* @__PURE__ */ jsx6("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
1122
1215
  ] }, k)) });
1123
1216
  }
1124
1217
  function ShellDetail({ call }) {
1125
1218
  const outcome = toolOutcomeOf(call);
1126
1219
  const r = outcome?.result ?? {};
1127
- return /* @__PURE__ */ jsxs5("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
1128
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
1129
- /* @__PURE__ */ jsx5("span", { className: "select-none text-zinc-500", children: "$" }),
1130
- /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
1131
- r.exitCode != null && /* @__PURE__ */ jsxs5("span", { className: r.exitCode === 0 ? "text-emerald-400" : "text-red-400", children: [
1220
+ return /* @__PURE__ */ jsxs6("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
1221
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
1222
+ /* @__PURE__ */ jsx6("span", { className: "select-none text-zinc-500", children: "$" }),
1223
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
1224
+ r.exitCode != null && /* @__PURE__ */ jsxs6("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
1132
1225
  "exit ",
1133
1226
  r.exitCode
1134
1227
  ] })
1135
1228
  ] }),
1136
- /* @__PURE__ */ jsx5("pre", { className: "max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300", children: outcome?.ok === false ? outcome.message ?? "failed" : [r.stdout, r.stderr].filter(Boolean).join("\n") || "(no output)" })
1229
+ /* @__PURE__ */ jsx6("pre", { className: "max-h-56 overflow-auto whitespace-pre-wrap px-3 pb-2.5 pt-1.5 text-zinc-300", children: outcome?.ok === false ? outcome.message ?? "failed" : [r.stdout, r.stderr].filter(Boolean).join("\n") || "(no output)" })
1137
1230
  ] });
1138
1231
  }
1139
1232
  function DefaultToolDetail({ call }) {
1140
1233
  const outcome = toolOutcomeOf(call);
1141
- return /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
1142
- call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs5("div", { children: [
1143
- /* @__PURE__ */ jsx5("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/50", children: "Called with" }),
1144
- /* @__PURE__ */ jsx5(KvRows, { data: call.args })
1234
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
1235
+ call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs6("div", { children: [
1236
+ /* @__PURE__ */ jsx6("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
1237
+ /* @__PURE__ */ jsx6(KvRows, { data: call.args })
1145
1238
  ] }),
1146
- outcome && /* @__PURE__ */ jsxs5("div", { children: [
1147
- /* @__PURE__ */ jsx5("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/50", children: outcome.ok === false ? "Failed" : "Result" }),
1148
- outcome.ok === false ? /* @__PURE__ */ jsx5("p", { className: "text-xs text-red-600", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx5(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx5("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
1239
+ outcome && /* @__PURE__ */ jsxs6("div", { children: [
1240
+ /* @__PURE__ */ jsx6("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: outcome.ok === false ? "Failed" : "Result" }),
1241
+ outcome.ok === false ? /* @__PURE__ */ jsx6("p", { className: "text-xs text-destructive", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx6(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx6("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
1149
1242
  ] })
1150
1243
  ] });
1151
1244
  }
@@ -1156,58 +1249,74 @@ function ToolCallCard({
1156
1249
  onOpenRun,
1157
1250
  renderers
1158
1251
  }) {
1159
- const [expanded, setExpanded] = useState5(false);
1252
+ const [expanded, setExpanded] = useState6(false);
1160
1253
  const pending = call.status === "done" ? pendingApprovalOf(call) : null;
1161
1254
  const failed = call.status === "error" || toolOutcomeOf(call)?.ok === false;
1162
1255
  const custom = renderers?.[call.name]?.(call, message);
1163
- return /* @__PURE__ */ jsxs5(
1256
+ const { pending: deciding, run: decide } = usePending();
1257
+ return /* @__PURE__ */ jsxs6(
1164
1258
  "div",
1165
1259
  {
1166
- className: `w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${pending ? "border-amber-300/60 bg-amber-500/5" : failed ? "border-red-300/60 bg-red-500/5" : "border-border/60 bg-muted/20"}`,
1260
+ className: `w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${pending ? "border-warning/40 bg-warning/5" : failed ? "border-destructive/40 bg-destructive/5" : "border-border/60 bg-muted/20"}`,
1167
1261
  children: [
1168
- /* @__PURE__ */ jsxs5(
1169
- "button",
1170
- {
1171
- type: "button",
1172
- onClick: () => setExpanded((v) => !v),
1173
- className: "flex w-full items-center gap-2 px-3 py-2 text-left",
1174
- children: [
1175
- /* @__PURE__ */ jsx5(
1176
- "span",
1177
- {
1178
- className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-yellow-500" : pending ? "bg-amber-500" : failed ? "bg-red-500" : "bg-green-500"}`
1179
- }
1180
- ),
1181
- /* @__PURE__ */ jsx5(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
1182
- /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate font-medium", children: friendlyToolTitle(call) }),
1183
- pending && approval && /* @__PURE__ */ jsxs5("span", { className: "flex shrink-0 items-center gap-1", onClick: (e) => e.stopPropagation(), children: [
1184
- /* @__PURE__ */ jsx5(
1185
- "button",
1262
+ /* @__PURE__ */ jsxs6("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
1263
+ /* @__PURE__ */ jsxs6(
1264
+ "button",
1265
+ {
1266
+ type: "button",
1267
+ onClick: () => setExpanded((v) => !v),
1268
+ "aria-expanded": expanded,
1269
+ className: "flex min-w-0 flex-1 items-center gap-2 rounded text-left focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1270
+ children: [
1271
+ /* @__PURE__ */ jsx6(
1272
+ "span",
1186
1273
  {
1187
- type: "button",
1188
- onClick: () => approval.onApprove(pending.proposalId, call.id),
1189
- className: "rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600",
1190
- children: "Approve"
1274
+ className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-warning" : pending ? "bg-warning" : failed ? "bg-destructive" : "bg-success"}`
1191
1275
  }
1192
1276
  ),
1193
- /* @__PURE__ */ jsx5(
1194
- "button",
1195
- {
1196
- type: "button",
1197
- onClick: () => approval.onReject(pending.proposalId, call.id),
1198
- className: "rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30",
1199
- children: "Reject"
1200
- }
1201
- )
1202
- ] }),
1203
- /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-[11px] text-muted-foreground/70", children: call.status === "running" ? "running\u2026" : pending ? "awaiting approval" : failed ? "failed" : "done" }),
1204
- /* @__PURE__ */ jsx5(ChevronDown2, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
1205
- ]
1206
- }
1207
- ),
1208
- expanded && /* @__PURE__ */ jsxs5("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
1209
- custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx5(ShellDetail, { call }) : /* @__PURE__ */ jsx5(DefaultToolDetail, { call })),
1210
- onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx5(
1277
+ /* @__PURE__ */ jsx6(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
1278
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate font-medium", children: friendlyToolTitle(call) })
1279
+ ]
1280
+ }
1281
+ ),
1282
+ pending && approval && /* @__PURE__ */ jsxs6("span", { className: "flex shrink-0 items-center gap-1", children: [
1283
+ /* @__PURE__ */ jsx6(
1284
+ "button",
1285
+ {
1286
+ type: "button",
1287
+ disabled: deciding,
1288
+ onClick: () => decide(() => approval.onApprove(pending.proposalId, call.id)),
1289
+ className: "rounded bg-success px-2 py-0.5 text-[11px] font-semibold text-success-foreground transition hover:bg-success/90 disabled:cursor-not-allowed disabled:opacity-60",
1290
+ children: "Approve"
1291
+ }
1292
+ ),
1293
+ /* @__PURE__ */ jsx6(
1294
+ "button",
1295
+ {
1296
+ type: "button",
1297
+ disabled: deciding,
1298
+ onClick: () => decide(() => approval.onReject(pending.proposalId, call.id)),
1299
+ className: "rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30 disabled:cursor-not-allowed disabled:opacity-60",
1300
+ children: "Reject"
1301
+ }
1302
+ )
1303
+ ] }),
1304
+ /* @__PURE__ */ jsx6("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : pending ? "awaiting approval" : failed ? "failed" : "done" }),
1305
+ /* @__PURE__ */ jsx6(
1306
+ "button",
1307
+ {
1308
+ type: "button",
1309
+ onClick: () => setExpanded((v) => !v),
1310
+ "aria-label": expanded ? "Collapse details" : "Expand details",
1311
+ "aria-expanded": expanded,
1312
+ className: "shrink-0 rounded p-0.5 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1313
+ children: /* @__PURE__ */ jsx6(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
1314
+ }
1315
+ )
1316
+ ] }),
1317
+ expanded && /* @__PURE__ */ jsxs6("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
1318
+ custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx6(ShellDetail, { call }) : /* @__PURE__ */ jsx6(DefaultToolDetail, { call })),
1319
+ onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx6(
1211
1320
  "button",
1212
1321
  {
1213
1322
  type: "button",
@@ -1221,7 +1330,7 @@ function ToolCallCard({
1221
1330
  }
1222
1331
  );
1223
1332
  }
1224
- function AssistantMessage({
1333
+ function AssistantMessageImpl({
1225
1334
  msg,
1226
1335
  streaming,
1227
1336
  models,
@@ -1234,6 +1343,7 @@ function AssistantMessage({
1234
1343
  }) {
1235
1344
  const content = useSmoothText(msg.content, streaming);
1236
1345
  const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
1346
+ const body = useMemo3(() => renderBody(content), [renderBody, content]);
1237
1347
  const reasoningScrollRef = useRef3(null);
1238
1348
  const thinkStartRef = useRef3(null);
1239
1349
  const thinkMsRef = useRef3(null);
@@ -1247,22 +1357,22 @@ function AssistantMessage({
1247
1357
  const el = reasoningScrollRef.current;
1248
1358
  if (el && streaming && !content) el.scrollTop = el.scrollHeight;
1249
1359
  }, [reasoning, streaming, content]);
1250
- return /* @__PURE__ */ jsxs5("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
1251
- /* @__PURE__ */ jsxs5("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60", children: [
1252
- /* @__PURE__ */ jsx5("span", { className: "font-semibold uppercase", children: agentLabel }),
1253
- msg.modelUsed && /* @__PURE__ */ jsx5("span", { className: "font-mono normal-case", children: msg.modelUsed }),
1254
- formatTokensPerSecond(msg) && /* @__PURE__ */ jsx5("span", { children: formatTokensPerSecond(msg) }),
1255
- formatModelCost(msg, models) && /* @__PURE__ */ jsx5("span", { children: formatModelCost(msg, models) })
1360
+ return /* @__PURE__ */ jsxs6("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
1361
+ /* @__PURE__ */ jsxs6("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
1362
+ /* @__PURE__ */ jsx6("span", { className: "font-semibold uppercase", children: agentLabel }),
1363
+ msg.modelUsed && /* @__PURE__ */ jsx6("span", { className: "font-mono normal-case", children: msg.modelUsed }),
1364
+ formatTokensPerSecond(msg) && /* @__PURE__ */ jsx6("span", { children: formatTokensPerSecond(msg) }),
1365
+ formatModelCost(msg, models) && /* @__PURE__ */ jsx6("span", { children: formatModelCost(msg, models) })
1256
1366
  ] }),
1257
- reasoning && /* @__PURE__ */ jsxs5("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !content, children: [
1258
- /* @__PURE__ */ jsx5("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !content ? /* @__PURE__ */ jsx5("span", { className: "animate-pulse", children: "Thinking\u2026" }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
1259
- /* @__PURE__ */ jsx5("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground/70", children: reasoning })
1367
+ reasoning && /* @__PURE__ */ jsxs6("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !content, children: [
1368
+ /* @__PURE__ */ jsx6("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !content ? /* @__PURE__ */ jsx6("span", { className: "animate-pulse", children: "Thinking\u2026" }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
1369
+ /* @__PURE__ */ jsx6("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground", children: reasoning })
1260
1370
  ] }),
1261
- /* @__PURE__ */ jsxs5("div", { className: "text-base leading-[1.75]", children: [
1262
- renderBody(content),
1263
- streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx5("span", { className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70", "aria-hidden": true })
1371
+ /* @__PURE__ */ jsxs6("div", { className: "text-base leading-[1.75]", children: [
1372
+ body,
1373
+ streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx6("span", { className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70", "aria-hidden": true })
1264
1374
  ] }),
1265
- msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx5("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx5(
1375
+ msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx6("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx6(
1266
1376
  ToolCallCard,
1267
1377
  {
1268
1378
  call: tc,
@@ -1276,21 +1386,40 @@ function AssistantMessage({
1276
1386
  renderExtras?.(msg)
1277
1387
  ] });
1278
1388
  }
1389
+ var AssistantMessage = memo(AssistantMessageImpl);
1279
1390
  function ThinkingRow({ agentLabel }) {
1280
- const [seconds, setSeconds] = useState5(0);
1391
+ const [seconds, setSeconds] = useState6(0);
1281
1392
  useEffect5(() => {
1282
1393
  const id = setInterval(() => setSeconds((s) => s + 1), 1e3);
1283
1394
  return () => clearInterval(id);
1284
1395
  }, []);
1285
- return /* @__PURE__ */ jsxs5("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
1286
- /* @__PURE__ */ jsx5("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60", children: agentLabel }),
1287
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
1288
- /* @__PURE__ */ jsx5("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx5("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
1396
+ return /* @__PURE__ */ jsxs6("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
1397
+ /* @__PURE__ */ jsx6("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
1398
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
1399
+ /* @__PURE__ */ jsx6("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx6("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
1289
1400
  "Thinking",
1290
1401
  seconds >= 3 ? ` \xB7 ${seconds}s` : "..."
1291
1402
  ] })
1292
1403
  ] });
1293
1404
  }
1405
+ function StreamErrorRow({ message, onRetry }) {
1406
+ return /* @__PURE__ */ jsx6("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs6("div", { role: "alert", className: "flex items-start gap-2.5 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2.5 text-sm text-destructive", children: [
1407
+ /* @__PURE__ */ jsxs6("svg", { className: "mt-0.5 h-4 w-4 shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1408
+ /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "9" }),
1409
+ /* @__PURE__ */ jsx6("path", { d: "M12 8v4m0 4h.01" })
1410
+ ] }),
1411
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 break-words", children: message }),
1412
+ onRetry && /* @__PURE__ */ jsx6(
1413
+ "button",
1414
+ {
1415
+ type: "button",
1416
+ onClick: onRetry,
1417
+ className: `shrink-0 rounded border border-destructive/40 bg-card px-2 py-0.5 text-[11px] font-medium text-destructive transition hover:bg-destructive/10 ${POPOVER_OPTION_FOCUS}`,
1418
+ children: "Retry"
1419
+ }
1420
+ )
1421
+ ] }) });
1422
+ }
1294
1423
  function ChatMessages({
1295
1424
  messages,
1296
1425
  models = [],
@@ -1301,16 +1430,23 @@ function ChatMessages({
1301
1430
  loading,
1302
1431
  approval,
1303
1432
  onToolCallClick,
1304
- toolRenderers
1433
+ toolRenderers,
1434
+ error,
1435
+ onRetry,
1436
+ renderEmpty
1305
1437
  }) {
1306
- const renderBody = renderMarkdown ?? ((content) => /* @__PURE__ */ jsx5("p", { className: "whitespace-pre-wrap", children: content }));
1438
+ const renderBody = useMemo3(
1439
+ () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap", children: content })),
1440
+ [renderMarkdown]
1441
+ );
1307
1442
  const lastIsUser = messages[messages.length - 1]?.role === "user";
1308
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1443
+ if (messages.length === 0 && !loading && !error && renderEmpty) return /* @__PURE__ */ jsx6(Fragment3, { children: renderEmpty() });
1444
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
1309
1445
  messages.map(
1310
- (msg) => msg.role === "user" ? /* @__PURE__ */ jsx5("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs5("div", { className: "ml-auto w-fit max-w-[85%]", children: [
1311
- /* @__PURE__ */ jsx5("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60", children: userLabel }),
1312
- /* @__PURE__ */ jsx5("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx5("p", { className: "whitespace-pre-wrap", children: msg.content }) })
1313
- ] }) }, msg.id) : /* @__PURE__ */ jsx5(
1446
+ (msg) => msg.role === "user" ? /* @__PURE__ */ jsx6("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs6("div", { className: "ml-auto w-fit max-w-[85%]", children: [
1447
+ /* @__PURE__ */ jsx6("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
1448
+ /* @__PURE__ */ jsx6("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx6("p", { className: "whitespace-pre-wrap", children: msg.content }) })
1449
+ ] }) }, msg.id) : /* @__PURE__ */ jsx6(
1314
1450
  AssistantMessage,
1315
1451
  {
1316
1452
  msg,
@@ -1326,7 +1462,8 @@ function ChatMessages({
1326
1462
  msg.id
1327
1463
  )
1328
1464
  ),
1329
- loading && lastIsUser && /* @__PURE__ */ jsx5(ThinkingRow, { agentLabel })
1465
+ loading && lastIsUser && /* @__PURE__ */ jsx6(ThinkingRow, { agentLabel }),
1466
+ error && !loading && /* @__PURE__ */ jsx6(StreamErrorRow, { message: error, onRetry })
1330
1467
  ] });
1331
1468
  }
1332
1469
  export {
@@ -1351,6 +1488,8 @@ export {
1351
1488
  nextRevealCount,
1352
1489
  pendingApprovalOf,
1353
1490
  streamChatTurn,
1491
+ usePending,
1492
+ usePopover,
1354
1493
  useSandboxTerminalConnection,
1355
1494
  useSmoothText,
1356
1495
  waterfallLayout