@tangle-network/agent-app 0.42.15 → 0.42.18

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.
@@ -0,0 +1,2158 @@
1
+ import {
2
+ stepActivityFlowTrace
3
+ } from "./chunk-2QI7XV2T.js";
4
+ import {
5
+ snapHarnessToModel,
6
+ snapModelToHarness
7
+ } from "./chunk-E7QYOOON.js";
8
+
9
+ // src/web-react/index.tsx
10
+ import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState7, 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";
54
+
55
+ // src/web-react/provider-logo.tsx
56
+ import { jsx, jsxs } from "react/jsx-runtime";
57
+ var LOGOS = {
58
+ anthropic: { viewBox: "0 0 24 24", fill: "#D97757", paths: ["M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"] },
59
+ google: { viewBox: "0 0 24 24", fill: "#8E75B2", paths: ["M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81"] },
60
+ deepseek: { viewBox: "0 0 24 24", fill: "#4D6BFE", paths: ["M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45"] },
61
+ mistral: { viewBox: "0 0 24 24", fill: "#FA520F", paths: ["M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z"] },
62
+ xai: { viewBox: "0 0 24 24", fill: "#000000", paths: ["M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"] },
63
+ nvidia: { viewBox: "0 0 24 24", fill: "#76B900", paths: ["M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z"] },
64
+ meta: { viewBox: "0 0 24 24", fill: "#0467DF", paths: ["M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"] },
65
+ moonshotai: { viewBox: "0 0 24 24", fill: "#16191E", paths: ["m1.053 16.91 9.538 2.55a21 20.981 0 0 0 .06 2.031l5.956 1.592a12 11.99 0 0 1-15.554-6.172m-1.02-5.79 11.352 3.035a21 20.981 0 0 0-.469 2.01l10.817 2.89a12 11.99 0 0 1-1.845 2.004L.658 15.918a12 11.99 0 0 1-.625-4.796m1.593-5.146L13.573 9.17a21 20.981 0 0 0-1.01 1.874l11.297 3.02a21 20.981 0 0 1-.67 2.362l-11.55-3.087L.125 10.26a12 11.99 0 0 1 1.499-4.285ZM6.067 1.58l11.285 3.016a21 20.981 0 0 0-1.688 1.719l7.824 2.091a21 20.981 0 0 1 .513 2.664L2.107 5.218a12 11.99 0 0 1 3.96-3.638M21.68 4.866 7.222 1.003A12 11.99 0 0 1 21.68 4.866"] },
66
+ openai: { viewBox: "0 0 256 260", fill: "#10A37F", paths: ["M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z"] }
67
+ };
68
+ var ALIASES = {
69
+ moonshot: "moonshotai",
70
+ deepseek_ai: "deepseek",
71
+ "x-ai": "xai",
72
+ "meta-llama": "meta"
73
+ };
74
+ var MONOGRAM = {
75
+ cohere: { bg: "#fae8ff", fg: "#c026d3" },
76
+ groq: { bg: "#fce7f3", fg: "#db2777" },
77
+ cerebras: { bg: "#ccfbf1", fg: "#0d9488" },
78
+ zai: { bg: "#ede9fe", fg: "#7c3aed" },
79
+ "z-ai": { bg: "#ede9fe", fg: "#7c3aed" },
80
+ tuner: { bg: "#dbeafe", fg: "#2563eb" }
81
+ };
82
+ function ProviderLogo({ provider, size = 16 }) {
83
+ const key = ALIASES[provider ?? ""] ?? provider ?? "";
84
+ const logo = LOGOS[key];
85
+ if (logo) {
86
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: logo.viewBox, role: "img", "aria-label": key, children: logo.paths.map((d, i) => /* @__PURE__ */ jsx("path", { d, fill: logo.fill }, i)) });
87
+ }
88
+ const mono = MONOGRAM[key] ?? { bg: "#f3f4f6", fg: "#6b7280" };
89
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 16 16", role: "img", "aria-label": key || "model", children: [
90
+ /* @__PURE__ */ jsx("rect", { width: "16", height: "16", rx: "4", fill: mono.bg }),
91
+ /* @__PURE__ */ jsx("text", { x: "8", y: "11.6", textAnchor: "middle", fill: mono.fg, fontSize: "9", fontWeight: "700", fontFamily: "system-ui, sans-serif", children: (key || "?").charAt(0).toUpperCase() })
92
+ ] });
93
+ }
94
+
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" }) });
99
+ }
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();
121
+ }
122
+ }
123
+ document.addEventListener("mousedown", onMouseDown);
124
+ document.addEventListener("keydown", onKeyDown);
125
+ return () => {
126
+ document.removeEventListener("mousedown", onMouseDown);
127
+ document.removeEventListener("keydown", onKeyDown);
128
+ };
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", priorityGroup }) {
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 isPriority = priorityGroup ? (m) => priorityGroup.match(m) : () => false;
229
+ const priority = priorityGroup ? models.filter(isPriority) : [];
230
+ const recommended = models.filter((m) => m.featured && !isPriority(m));
231
+ const byProvider = [];
232
+ for (const m of models) {
233
+ if (m.featured || isPriority(m)) continue;
234
+ const last = byProvider[byProvider.length - 1];
235
+ if (last && last.provider === m.provider) last.items.push(m);
236
+ else byProvider.push({ provider: m.provider, items: [m] });
237
+ }
238
+ return { priority, recommended, byProvider };
239
+ }, [models, priorityGroup]);
240
+ const select = (id) => {
241
+ onChange(id);
242
+ setOpen(false);
243
+ setQuery("");
244
+ };
245
+ return /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative inline-flex", children: [
246
+ /* @__PURE__ */ jsxs2(
247
+ "button",
248
+ {
249
+ type: "button",
250
+ ...triggerProps,
251
+ onClick: () => setOpen(!open),
252
+ 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",
253
+ children: [
254
+ 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" }),
255
+ /* @__PURE__ */ jsx2("span", { className: "max-w-[160px] truncate", children: selected?.name ?? value }),
256
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
257
+ ]
258
+ }
259
+ ),
260
+ open && /* @__PURE__ */ jsxs2("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-[420px] max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl border border-border bg-card shadow-lg", children: [
261
+ /* @__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: [
262
+ /* @__PURE__ */ jsx2(SearchGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
263
+ /* @__PURE__ */ jsx2(
264
+ "input",
265
+ {
266
+ ref: inputRef,
267
+ type: "text",
268
+ value: query,
269
+ onChange: (e) => setQuery(e.target.value),
270
+ placeholder: "Search models...",
271
+ className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
272
+ }
273
+ )
274
+ ] }) }),
275
+ /* @__PURE__ */ jsxs2("div", { className: "max-h-[400px] overflow-y-auto p-1 pb-2", children: [
276
+ loading && /* @__PURE__ */ jsx2("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "Loading models..." }),
277
+ !loading && filtered && /* @__PURE__ */ jsxs2(Fragment, { children: [
278
+ filtered.length === 0 && /* @__PURE__ */ jsx2("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No models match your search" }),
279
+ filtered.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
280
+ ] }),
281
+ !loading && !filtered && /* @__PURE__ */ jsxs2(Fragment, { children: [
282
+ priorityGroup && sections.priority.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
283
+ /* @__PURE__ */ jsx2(SectionHeader, { children: priorityGroup.label }),
284
+ sections.priority.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
285
+ ] }),
286
+ sections.recommended.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
287
+ /* @__PURE__ */ jsx2(SectionHeader, { children: recommendedLabel }),
288
+ sections.recommended.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
289
+ ] }),
290
+ sections.byProvider.map((g) => /* @__PURE__ */ jsxs2("div", { children: [
291
+ /* @__PURE__ */ jsx2(SectionHeader, { children: g.provider }),
292
+ g.items.map((m) => /* @__PURE__ */ jsx2(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
293
+ ] }, g.provider))
294
+ ] })
295
+ ] })
296
+ ] })
297
+ ] });
298
+ }
299
+ var DEFAULT_EFFORT_LEVELS = [
300
+ { id: "off", label: "Off" },
301
+ { id: "low", label: "Quick" },
302
+ { id: "medium", label: "Standard" },
303
+ { id: "high", label: "Extended" }
304
+ ];
305
+ function EffortPicker({ value, onChange, levels = DEFAULT_EFFORT_LEVELS, label = "Thinking" }) {
306
+ const [open, setOpen] = useState2(false);
307
+ const { containerRef, triggerProps } = usePopover(open, setOpen);
308
+ const selected = levels.find((l) => l.id === value) ?? levels[2] ?? levels[0];
309
+ return /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative inline-flex", children: [
310
+ /* @__PURE__ */ jsxs2(
311
+ "button",
312
+ {
313
+ type: "button",
314
+ ...triggerProps,
315
+ onClick: () => setOpen(!open),
316
+ title: label ? `${label} \u2014 how hard the agent reasons before answering` : "Reasoning effort",
317
+ className: "inline-flex min-h-[36px] 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",
318
+ children: [
319
+ /* @__PURE__ */ jsx2(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
320
+ /* @__PURE__ */ jsxs2("span", { children: [
321
+ label ? /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
322
+ label,
323
+ ": "
324
+ ] }) : null,
325
+ selected?.label
326
+ ] }),
327
+ /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
328
+ ]
329
+ }
330
+ ),
331
+ open && /* @__PURE__ */ jsx2("div", { role: "menu", className: "absolute bottom-full left-0 z-50 mb-2 w-40 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg", children: levels.map((l) => /* @__PURE__ */ jsx2(
332
+ "button",
333
+ {
334
+ type: "button",
335
+ role: "menuitemradio",
336
+ "aria-checked": l.id === value,
337
+ onClick: () => {
338
+ onChange(l.id);
339
+ setOpen(false);
340
+ },
341
+ className: `flex min-h-[40px] 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"}`,
342
+ children: l.label
343
+ },
344
+ l.id
345
+ )) })
346
+ ] });
347
+ }
348
+
349
+ // src/web-react/brand-mark.tsx
350
+ import { lazy, Suspense } from "react";
351
+ import { jsx as jsx3 } from "react/jsx-runtime";
352
+ function MarkSpacer({ size = 24, className }) {
353
+ return /* @__PURE__ */ jsx3("span", { "aria-hidden": true, style: { display: "inline-block", width: size, height: size }, className });
354
+ }
355
+ var LazyKnot = lazy(async () => {
356
+ try {
357
+ const mod = await import("./brand/index.js");
358
+ return { default: mod.TangleKnot };
359
+ } catch {
360
+ return { default: MarkSpacer };
361
+ }
362
+ });
363
+ function BrandMark({ size = 24, className }) {
364
+ return /* @__PURE__ */ jsx3(Suspense, { fallback: /* @__PURE__ */ jsx3(MarkSpacer, { size, className }), children: /* @__PURE__ */ jsx3(LazyKnot, { size, className }) });
365
+ }
366
+
367
+ // src/web-react/chat-stream.ts
368
+ function dispatchChatStreamLine(line, cb) {
369
+ let receivedContent = false;
370
+ let turnId;
371
+ if (!line.trim()) return { receivedContent };
372
+ let parsed;
373
+ try {
374
+ parsed = JSON.parse(line);
375
+ } catch {
376
+ return { receivedContent };
377
+ }
378
+ if (parsed.kind === "tool_result") {
379
+ cb.onToolResult?.({
380
+ toolCallId: parsed.toolCallId,
381
+ toolName: parsed.toolName,
382
+ label: parsed.label,
383
+ outcome: parsed.outcome ?? parsed.result
384
+ });
385
+ return { receivedContent: true };
386
+ }
387
+ const evt = parsed.kind === "event" ? parsed.event : parsed;
388
+ if (!evt || typeof evt !== "object") return { receivedContent };
389
+ switch (evt.type) {
390
+ case "turn":
391
+ if (typeof evt.turnId === "string") turnId = evt.turnId;
392
+ break;
393
+ case "text":
394
+ if (typeof evt.text === "string") {
395
+ cb.onText?.(evt.text);
396
+ receivedContent = true;
397
+ }
398
+ break;
399
+ case "reasoning":
400
+ if (typeof evt.text === "string") {
401
+ cb.onReasoning?.(evt.text);
402
+ receivedContent = true;
403
+ }
404
+ break;
405
+ case "tool_call": {
406
+ const call = evt.call ?? evt;
407
+ cb.onToolCall?.({
408
+ toolCallId: call.toolCallId ?? call.id,
409
+ toolName: String(call.toolName ?? call.name ?? "unknown"),
410
+ args: call.args ?? {}
411
+ });
412
+ receivedContent = true;
413
+ break;
414
+ }
415
+ case "tool_result":
416
+ cb.onToolResult?.({
417
+ toolCallId: evt.toolCallId,
418
+ toolName: evt.toolName,
419
+ label: evt.label,
420
+ outcome: evt.outcome ?? evt.result
421
+ });
422
+ receivedContent = true;
423
+ break;
424
+ case "usage": {
425
+ const u = evt.usage;
426
+ if (u) cb.onUsage?.({ promptTokens: u.promptTokens ?? 0, completionTokens: u.completionTokens ?? 0 });
427
+ break;
428
+ }
429
+ case "metadata":
430
+ cb.onMetadata?.(evt.data ?? {});
431
+ break;
432
+ case "error":
433
+ cb.onErrorEvent?.(String(evt.details ?? evt.error ?? "Unknown stream error"));
434
+ break;
435
+ default:
436
+ break;
437
+ }
438
+ return { turnId, receivedContent };
439
+ }
440
+ async function consumeChatStream(body, cb) {
441
+ const reader = body.getReader();
442
+ const decoder = new TextDecoder();
443
+ let buffer = "";
444
+ let turnId = null;
445
+ let receivedContent = false;
446
+ const handle = (line) => {
447
+ const r = dispatchChatStreamLine(line, cb);
448
+ if (r.turnId) {
449
+ turnId = r.turnId;
450
+ cb.onTurnId?.(r.turnId);
451
+ }
452
+ if (r.receivedContent) receivedContent = true;
453
+ };
454
+ for (; ; ) {
455
+ const { done, value } = await reader.read();
456
+ if (done) {
457
+ if (buffer.trim()) handle(buffer);
458
+ break;
459
+ }
460
+ buffer += decoder.decode(value, { stream: true });
461
+ const lines = buffer.split("\n");
462
+ buffer = lines.pop() ?? "";
463
+ for (const line of lines) handle(line);
464
+ }
465
+ return { turnId, receivedContent };
466
+ }
467
+ async function streamChatTurn(opts) {
468
+ const res = await opts.start();
469
+ if (!res.ok || !res.body) {
470
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
471
+ throw new Error(err.error ?? `HTTP ${res.status}`);
472
+ }
473
+ let turnId = null;
474
+ const cb = {
475
+ ...opts.callbacks,
476
+ onTurnId: (id) => {
477
+ turnId = id;
478
+ opts.callbacks.onTurnId?.(id);
479
+ }
480
+ };
481
+ try {
482
+ return await consumeChatStream(res.body, cb);
483
+ } catch (transportErr) {
484
+ if (!turnId || !opts.resume) throw transportErr;
485
+ opts.onResetForResume?.();
486
+ const resumed = await opts.resume(turnId, 0);
487
+ if (!resumed.ok || !resumed.body) throw transportErr;
488
+ return await consumeChatStream(resumed.body, cb);
489
+ }
490
+ }
491
+
492
+ // src/web-react/chat-composer.tsx
493
+ import {
494
+ useCallback,
495
+ useEffect as useEffect3,
496
+ useRef as useRef3,
497
+ useState as useState3
498
+ } from "react";
499
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
500
+ function SendGlyph({ className }) {
501
+ 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: "M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z" }) });
502
+ }
503
+ function StopGlyph({ className }) {
504
+ return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ jsx4("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) });
505
+ }
506
+ function PaperclipGlyph({ className }) {
507
+ 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: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
508
+ }
509
+ function FolderGlyph({ className }) {
510
+ return /* @__PURE__ */ jsxs3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
511
+ /* @__PURE__ */ jsx4("path", { d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z" }),
512
+ /* @__PURE__ */ jsx4("path", { d: "M12 10v6m-3-3h6" })
513
+ ] });
514
+ }
515
+ function CloseGlyph({ className }) {
516
+ return /* @__PURE__ */ jsx4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx4("path", { d: "M18 6 6 18M6 6l12 12" }) });
517
+ }
518
+ function UploadGlyph({ className }) {
519
+ 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: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" }) });
520
+ }
521
+ var MAX_HEIGHT = 168;
522
+ function ChatComposer({
523
+ onSend,
524
+ onCancel,
525
+ isStreaming = false,
526
+ disabled = false,
527
+ placeholder = "Message the agent\u2026",
528
+ value,
529
+ onValueChange,
530
+ initialValue,
531
+ controls,
532
+ controlsPlacement = "above",
533
+ onAttach,
534
+ onAttachFolder,
535
+ pendingFiles = [],
536
+ onRemoveFile,
537
+ accept,
538
+ dropTitle = "Drop files to add context",
539
+ dropDescription = "They attach to your next message.",
540
+ focusShortcut = true,
541
+ sendLabel = "Send",
542
+ className
543
+ }) {
544
+ const isControlled = value !== void 0;
545
+ const [internal, setInternal] = useState3(initialValue ?? "");
546
+ const text = isControlled ? value : internal;
547
+ const textareaRef = useRef3(null);
548
+ const fileInputRef = useRef3(null);
549
+ const folderInputRef = useRef3(null);
550
+ const [dragOver, setDragOver] = useState3(false);
551
+ const dragDepth = useRef3(0);
552
+ const setText = useCallback(
553
+ (next) => {
554
+ if (!isControlled) setInternal(next);
555
+ onValueChange?.(next);
556
+ },
557
+ [isControlled, onValueChange]
558
+ );
559
+ useEffect3(() => {
560
+ const el = textareaRef.current;
561
+ if (!el) return;
562
+ el.style.height = "auto";
563
+ el.style.height = `${Math.min(el.scrollHeight, MAX_HEIGHT)}px`;
564
+ }, [text]);
565
+ useEffect3(() => {
566
+ if (!focusShortcut || disabled) return;
567
+ function onKeyDown(e) {
568
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "l") {
569
+ e.preventDefault();
570
+ textareaRef.current?.focus();
571
+ }
572
+ }
573
+ document.addEventListener("keydown", onKeyDown);
574
+ return () => document.removeEventListener("keydown", onKeyDown);
575
+ }, [focusShortcut, disabled]);
576
+ const canSend = text.trim().length > 0 && !isStreaming && !disabled;
577
+ const send = useCallback(() => {
578
+ const trimmed = text.trim();
579
+ if (!trimmed || isStreaming || disabled) return;
580
+ onSend(trimmed);
581
+ setText("");
582
+ }, [text, isStreaming, disabled, onSend, setText]);
583
+ const handleKeyDown = (e) => {
584
+ if (e.nativeEvent.isComposing) return;
585
+ if (e.key === "Enter" && !e.shiftKey) {
586
+ e.preventDefault();
587
+ send();
588
+ }
589
+ };
590
+ const handleFileChange = (e) => {
591
+ if (e.target.files?.length) onAttach?.(e.target.files);
592
+ e.target.value = "";
593
+ };
594
+ const handleFolderChange = (e) => {
595
+ if (e.target.files?.length) (onAttachFolder ?? onAttach)?.(e.target.files);
596
+ e.target.value = "";
597
+ };
598
+ const handleDragEnter = useCallback((e) => {
599
+ e.preventDefault();
600
+ e.stopPropagation();
601
+ dragDepth.current++;
602
+ if (e.dataTransfer?.types.includes("Files")) setDragOver(true);
603
+ }, []);
604
+ const handleDragLeave = useCallback((e) => {
605
+ e.preventDefault();
606
+ e.stopPropagation();
607
+ dragDepth.current--;
608
+ if (dragDepth.current <= 0) {
609
+ dragDepth.current = 0;
610
+ setDragOver(false);
611
+ }
612
+ }, []);
613
+ const handleDragOver = useCallback((e) => {
614
+ e.preventDefault();
615
+ e.stopPropagation();
616
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
617
+ }, []);
618
+ const handleDrop = useCallback(
619
+ (e) => {
620
+ e.preventDefault();
621
+ e.stopPropagation();
622
+ dragDepth.current = 0;
623
+ setDragOver(false);
624
+ const files = e.dataTransfer?.files;
625
+ if (files?.length) onAttach?.(files);
626
+ },
627
+ [onAttach]
628
+ );
629
+ const folderChips = pendingFiles.filter((f) => f.kind === "folder");
630
+ const fileChips = pendingFiles.filter((f) => f.kind !== "folder");
631
+ const showFooter = controls != null && controlsPlacement === "footer";
632
+ const showAbove = controls != null && controlsPlacement === "above";
633
+ return /* @__PURE__ */ jsxs3(
634
+ "div",
635
+ {
636
+ className: `relative ${className ?? ""}`,
637
+ onDragEnter: onAttach ? handleDragEnter : void 0,
638
+ onDragLeave: onAttach ? handleDragLeave : void 0,
639
+ onDragOver: onAttach ? handleDragOver : void 0,
640
+ onDrop: onAttach ? handleDrop : void 0,
641
+ children: [
642
+ dragOver && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary/50 bg-card/95", children: /* @__PURE__ */ jsxs3("div", { className: "text-center", children: [
643
+ /* @__PURE__ */ jsx4("span", { className: "mx-auto mb-2 flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx4(UploadGlyph, { className: "h-5 w-5" }) }),
644
+ /* @__PURE__ */ jsx4("p", { className: "text-sm font-semibold text-foreground", children: dropTitle }),
645
+ /* @__PURE__ */ jsx4("p", { className: "mt-0.5 text-xs text-muted-foreground", children: dropDescription })
646
+ ] }) }),
647
+ showAbove && /* @__PURE__ */ jsx4("div", { className: "mb-1.5 flex flex-wrap items-center gap-1.5 px-1", children: controls }),
648
+ pendingFiles.length > 0 && /* @__PURE__ */ jsx4("div", { className: "mb-2 flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((f) => /* @__PURE__ */ jsxs3(
649
+ "span",
650
+ {
651
+ className: `inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${f.status === "error" ? "border-destructive/40 text-destructive" : "border-border bg-muted/50 text-foreground"}`,
652
+ children: [
653
+ f.kind === "folder" ? /* @__PURE__ */ jsx4(FolderGlyph, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx4(PaperclipGlyph, { className: "h-3 w-3 shrink-0" }),
654
+ /* @__PURE__ */ jsx4("span", { className: "max-w-[150px] truncate", children: f.name }),
655
+ f.fileCount !== void 0 && /* @__PURE__ */ jsxs3("span", { className: "text-muted-foreground", children: [
656
+ "(",
657
+ f.fileCount,
658
+ ")"
659
+ ] }),
660
+ f.status === "uploading" && /* @__PURE__ */ jsx4("span", { className: "h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
661
+ onRemoveFile && /* @__PURE__ */ jsx4(
662
+ "button",
663
+ {
664
+ type: "button",
665
+ "aria-label": `Remove ${f.name}`,
666
+ onClick: () => onRemoveFile(f.id),
667
+ className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
668
+ children: /* @__PURE__ */ jsx4(CloseGlyph, { className: "h-3 w-3" })
669
+ }
670
+ )
671
+ ]
672
+ },
673
+ f.id
674
+ )) }),
675
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-end gap-2 rounded-2xl border border-border bg-card px-2.5 py-2 transition focus-within:border-primary/40 focus-within:ring-2 focus-within:ring-primary/15", children: [
676
+ onAttach && /* @__PURE__ */ jsxs3(Fragment2, { children: [
677
+ /* @__PURE__ */ jsx4(
678
+ "button",
679
+ {
680
+ type: "button",
681
+ onClick: () => fileInputRef.current?.click(),
682
+ disabled,
683
+ "aria-label": "Attach files",
684
+ title: "Attach files",
685
+ className: "mb-0.5 shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
686
+ children: /* @__PURE__ */ jsx4(PaperclipGlyph, { className: "h-4 w-4" })
687
+ }
688
+ ),
689
+ /* @__PURE__ */ jsx4("input", { ref: fileInputRef, type: "file", multiple: true, className: "hidden", accept, onChange: handleFileChange })
690
+ ] }),
691
+ onAttachFolder && /* @__PURE__ */ jsxs3(Fragment2, { children: [
692
+ /* @__PURE__ */ jsx4(
693
+ "button",
694
+ {
695
+ type: "button",
696
+ onClick: () => folderInputRef.current?.click(),
697
+ disabled,
698
+ "aria-label": "Attach folder",
699
+ title: "Attach folder",
700
+ className: "mb-0.5 shrink-0 rounded-lg p-2 text-muted-foreground transition hover:bg-accent/40 hover:text-foreground disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
701
+ children: /* @__PURE__ */ jsx4(FolderGlyph, { className: "h-4 w-4" })
702
+ }
703
+ ),
704
+ /* @__PURE__ */ jsx4(
705
+ "input",
706
+ {
707
+ ref: folderInputRef,
708
+ type: "file",
709
+ multiple: true,
710
+ className: "hidden",
711
+ onChange: handleFolderChange,
712
+ ...{ webkitdirectory: "" }
713
+ }
714
+ )
715
+ ] }),
716
+ /* @__PURE__ */ jsx4(
717
+ "textarea",
718
+ {
719
+ ref: textareaRef,
720
+ value: text,
721
+ onChange: (e) => setText(e.target.value),
722
+ onKeyDown: handleKeyDown,
723
+ placeholder,
724
+ disabled,
725
+ rows: 1,
726
+ "aria-label": "Message input",
727
+ className: "max-h-[168px] min-h-[40px] flex-1 resize-none bg-transparent px-1.5 py-2 text-[15px] leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50"
728
+ }
729
+ ),
730
+ showFooter && /* @__PURE__ */ jsx4("div", { className: "mb-0.5 flex shrink-0 items-center gap-1.5", children: controls }),
731
+ isStreaming ? /* @__PURE__ */ jsxs3(
732
+ "button",
733
+ {
734
+ type: "button",
735
+ onClick: onCancel,
736
+ "aria-label": "Stop response",
737
+ className: "mb-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full bg-destructive/15 px-3.5 py-2 text-sm font-medium text-destructive transition hover:bg-destructive/25 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50",
738
+ children: [
739
+ /* @__PURE__ */ jsx4(StopGlyph, { className: "h-3.5 w-3.5" }),
740
+ /* @__PURE__ */ jsx4("span", { children: "Stop" })
741
+ ]
742
+ }
743
+ ) : /* @__PURE__ */ jsxs3(
744
+ "button",
745
+ {
746
+ type: "button",
747
+ onClick: send,
748
+ disabled: !canSend,
749
+ "aria-label": "Send message",
750
+ className: "mb-0.5 inline-flex shrink-0 items-center gap-1.5 rounded-full bg-primary px-3.5 py-2 text-sm font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-card",
751
+ children: [
752
+ /* @__PURE__ */ jsx4(SendGlyph, { className: "h-3.5 w-3.5" }),
753
+ /* @__PURE__ */ jsx4("span", { children: sendLabel })
754
+ ]
755
+ }
756
+ )
757
+ ] }),
758
+ focusShortcut && /* @__PURE__ */ jsx4("div", { className: "mt-1.5 flex justify-end px-1", children: /* @__PURE__ */ jsxs3("span", { className: "text-xs text-muted-foreground", children: [
759
+ /* @__PURE__ */ jsx4("kbd", { className: "rounded border border-border bg-background px-1 py-0.5 text-[10px]", children: "Cmd" }),
760
+ /* @__PURE__ */ jsx4("kbd", { className: "ml-0.5 rounded border border-border bg-background px-1 py-0.5 text-[10px]", children: "L" }),
761
+ /* @__PURE__ */ jsx4("span", { className: "ml-1", children: "to focus" })
762
+ ] }) })
763
+ ]
764
+ }
765
+ );
766
+ }
767
+
768
+ // src/web-react/mission-activity.tsx
769
+ import { useCallback as useCallback2, useEffect as useEffect4, useState as useState4 } from "react";
770
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
771
+ var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "running"]);
772
+ var OK_STATUSES = /* @__PURE__ */ new Set(["completed", "done", "succeeded"]);
773
+ var ERROR_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "cancelled", "aborted"]);
774
+ function activityTone(status) {
775
+ const s = status.toLowerCase();
776
+ if (LIVE_STATUSES.has(s)) return "live";
777
+ if (OK_STATUSES.has(s)) return "ok";
778
+ if (ERROR_STATUSES.has(s)) return "error";
779
+ return "neutral";
780
+ }
781
+ function formatActivityCost(costUsd) {
782
+ if (costUsd === void 0 || !isFinite(costUsd) || costUsd <= 0) return null;
783
+ return costUsd < 0.01 ? `$${costUsd.toFixed(4)}` : `$${costUsd.toFixed(2)}`;
784
+ }
785
+ function formatActivityDuration(durationMs) {
786
+ if (durationMs === void 0 || !isFinite(durationMs) || durationMs < 0) return null;
787
+ const totalSeconds = Math.round(durationMs / 1e3);
788
+ if (totalSeconds < 60) return `${totalSeconds}s`;
789
+ const minutes = Math.floor(totalSeconds / 60);
790
+ const seconds = totalSeconds % 60;
791
+ if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
792
+ return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, "0")}m`;
793
+ }
794
+ function mergeActivityPages(existing, incoming) {
795
+ const byTask = /* @__PURE__ */ new Map();
796
+ for (const row of existing) byTask.set(row.taskId, row);
797
+ for (const row of incoming) byTask.set(row.taskId, row);
798
+ return [...byTask.values()].sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
799
+ }
800
+ function waterfallLayout(trace) {
801
+ const total = trace.totalMs > 0 ? trace.totalMs : 1;
802
+ return [...trace.spans].sort((a, b) => a.startMs - b.startMs).map((span) => {
803
+ const meta = span.meta ?? {};
804
+ const failed = meta.ok === false || typeof meta.status === "string" && activityTone(meta.status) === "error";
805
+ return {
806
+ name: span.name,
807
+ kind: span.kind,
808
+ offsetPct: Math.max(0, Math.min(100, span.startMs / total * 100)),
809
+ widthPct: Math.max(0.5, Math.min(100, (span.endMs - span.startMs) / total * 100)),
810
+ durationLabel: `${((span.endMs - span.startMs) / 1e3).toFixed(1)}s${span.approx ? "~" : ""}`,
811
+ approx: span.approx === true,
812
+ ok: !failed
813
+ };
814
+ });
815
+ }
816
+ function ChevronGlyph({ className }) {
817
+ 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" }) });
818
+ }
819
+ function RefreshGlyph({ className }) {
820
+ 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: "M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6" }) });
821
+ }
822
+ function CopyGlyph({ className }) {
823
+ return /* @__PURE__ */ jsxs4("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
824
+ /* @__PURE__ */ jsx5("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
825
+ /* @__PURE__ */ jsx5("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
826
+ ] });
827
+ }
828
+ function TraceIdCopy({ traceId }) {
829
+ const [copied, setCopied] = useState4(false);
830
+ const copy = useCallback2(() => {
831
+ void navigator.clipboard?.writeText(traceId).then(
832
+ () => {
833
+ setCopied(true);
834
+ setTimeout(() => setCopied(false), 1200);
835
+ },
836
+ () => {
837
+ }
838
+ );
839
+ }, [traceId]);
840
+ return /* @__PURE__ */ jsxs4(
841
+ "button",
842
+ {
843
+ type: "button",
844
+ onClick: copy,
845
+ title: "Copy trace id",
846
+ "aria-label": "Copy trace id",
847
+ 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",
848
+ children: [
849
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: traceId }),
850
+ /* @__PURE__ */ jsx5(CopyGlyph, { className: "h-3 w-3 shrink-0" }),
851
+ copied && /* @__PURE__ */ jsx5("span", { className: "shrink-0 not-italic text-success", children: "copied" })
852
+ ]
853
+ }
854
+ );
855
+ }
856
+ function StatusDot({ tone }) {
857
+ return /* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center", children: [
858
+ /* @__PURE__ */ jsx5(
859
+ "span",
860
+ {
861
+ "aria-hidden": true,
862
+ 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"}`
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsx5("span", { className: "sr-only", children: tone })
866
+ ] });
867
+ }
868
+ var BAR_CLASS = {
869
+ pipeline: "bg-muted-foreground/30",
870
+ model: "bg-primary/60",
871
+ tool: "bg-primary"
872
+ };
873
+ function FlowWaterfall({ trace }) {
874
+ const rows = waterfallLayout(trace);
875
+ if (rows.length === 0) return null;
876
+ const cost = formatActivityCost(trace.costUsd);
877
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
878
+ rows.map((row, i) => /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-[minmax(0,2fr)_minmax(0,3fr)_auto] items-center gap-2", children: [
879
+ /* @__PURE__ */ jsx5("span", { className: "truncate font-mono text-[11px] text-muted-foreground", title: row.name, children: row.name }),
880
+ /* @__PURE__ */ jsx5("div", { className: "relative h-2 rounded-sm bg-muted/40", children: /* @__PURE__ */ jsx5(
881
+ "div",
882
+ {
883
+ className: `absolute inset-y-0 rounded-sm ${row.ok ? BAR_CLASS[row.kind] : "bg-destructive/80"} ${row.approx ? "opacity-70" : ""}`,
884
+ style: { left: `${row.offsetPct}%`, width: `${row.widthPct}%` }
885
+ }
886
+ ) }),
887
+ /* @__PURE__ */ jsx5("span", { className: "shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: row.durationLabel })
888
+ ] }, i)),
889
+ /* @__PURE__ */ jsxs4("p", { className: "pt-0.5 text-right font-mono text-[10px] tabular-nums text-muted-foreground/60", children: [
890
+ (trace.totalMs / 1e3).toFixed(1),
891
+ "s",
892
+ cost ? ` \xB7 ${cost}` : ""
893
+ ] })
894
+ ] });
895
+ }
896
+ function MissionActivityLane({ activity, startedAt, nowMs }) {
897
+ const [expanded, setExpanded] = useState4(false);
898
+ if (activity.length === 0) return null;
899
+ return /* @__PURE__ */ jsxs4("div", { className: "mt-1 border-l border-border/50 pl-3", children: [
900
+ activity.map((run) => {
901
+ const tone = activityTone(run.status);
902
+ const cost = formatActivityCost(run.costUsd);
903
+ const duration = formatActivityDuration(run.durationMs);
904
+ return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 py-1 text-xs", children: [
905
+ /* @__PURE__ */ jsx5(StatusDot, { tone }),
906
+ /* @__PURE__ */ jsxs4("span", { className: "min-w-0 flex-1 truncate", children: [
907
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: run.tool }),
908
+ /* @__PURE__ */ jsxs4("span", { className: "text-muted-foreground", children: [
909
+ " \u2014 ",
910
+ run.detail
911
+ ] })
912
+ ] }),
913
+ tone === "live" && (run.iteration !== void 0 || run.phase !== void 0) && /* @__PURE__ */ jsx5("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 ") }),
914
+ /* @__PURE__ */ jsxs4("span", { className: "flex shrink-0 items-center gap-1.5 font-mono text-[10px] tabular-nums text-muted-foreground/70", children: [
915
+ tone !== "live" && tone !== "ok" && /* @__PURE__ */ jsx5("span", { children: run.status }),
916
+ cost && /* @__PURE__ */ jsx5("span", { children: cost }),
917
+ duration && /* @__PURE__ */ jsx5("span", { children: duration })
918
+ ] })
919
+ ] }, run.taskId);
920
+ }),
921
+ /* @__PURE__ */ jsxs4(
922
+ "button",
923
+ {
924
+ type: "button",
925
+ onClick: () => setExpanded((v) => !v),
926
+ className: "flex items-center gap-1 py-0.5 text-[10px] font-medium text-muted-foreground/70 transition hover:text-foreground",
927
+ children: [
928
+ /* @__PURE__ */ jsx5(ChevronGlyph, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` }),
929
+ "timeline"
930
+ ]
931
+ }
932
+ ),
933
+ expanded && /* @__PURE__ */ jsx5("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx5(
934
+ FlowWaterfall,
935
+ {
936
+ trace: stepActivityFlowTrace(activity, {
937
+ ...startedAt !== void 0 ? { startedAt } : {},
938
+ ...nowMs !== void 0 ? { nowMs } : {}
939
+ })
940
+ }
941
+ ) })
942
+ ] });
943
+ }
944
+ function ActivityRow({
945
+ record,
946
+ renderMissionRef
947
+ }) {
948
+ const [open, setOpen] = useState4(false);
949
+ const tone = activityTone(record.status);
950
+ const cost = formatActivityCost(record.costUsd);
951
+ const duration = formatActivityDuration(record.durationMs);
952
+ return /* @__PURE__ */ jsxs4("div", { className: "rounded-lg border border-border/60 bg-card", children: [
953
+ /* @__PURE__ */ jsxs4("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: [
954
+ /* @__PURE__ */ jsx5(StatusDot, { tone }),
955
+ /* @__PURE__ */ jsxs4("span", { className: "min-w-0 flex-1 truncate", children: [
956
+ /* @__PURE__ */ jsx5("span", { className: "font-medium", children: record.tool }),
957
+ /* @__PURE__ */ jsxs4("span", { className: "text-muted-foreground", children: [
958
+ " \u2014 ",
959
+ record.detail
960
+ ] })
961
+ ] }),
962
+ tone === "live" && (record.iteration !== void 0 || record.phase !== void 0) && /* @__PURE__ */ jsx5("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 ") }),
963
+ /* @__PURE__ */ jsx5(
964
+ "span",
965
+ {
966
+ 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"}`,
967
+ children: record.status
968
+ }
969
+ ),
970
+ cost && /* @__PURE__ */ jsx5("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: cost }),
971
+ /* @__PURE__ */ jsx5(ChevronGlyph, { className: `h-3 w-3 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
972
+ ] }),
973
+ open && /* @__PURE__ */ jsxs4("div", { className: "space-y-2.5 border-t border-border/40 px-3 py-2.5", children: [
974
+ record.durationMs !== void 0 && /* @__PURE__ */ jsx5("div", { className: "rounded-md border border-border/50 bg-muted/10 p-2", children: /* @__PURE__ */ jsx5(FlowWaterfall, { trace: stepActivityFlowTrace([record]) }) }),
975
+ /* @__PURE__ */ jsxs4("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[11px]", children: [
976
+ /* @__PURE__ */ jsx5("dt", { className: "text-muted-foreground/60", children: "task" }),
977
+ /* @__PURE__ */ jsx5("dd", { className: "truncate text-muted-foreground", children: record.taskId }),
978
+ /* @__PURE__ */ jsx5("dt", { className: "text-muted-foreground/60", children: "started" }),
979
+ /* @__PURE__ */ jsx5("dd", { className: "text-muted-foreground", children: new Date(record.startedAt).toLocaleString() }),
980
+ duration && /* @__PURE__ */ jsxs4(Fragment3, { children: [
981
+ /* @__PURE__ */ jsx5("dt", { className: "text-muted-foreground/60", children: "duration" }),
982
+ /* @__PURE__ */ jsx5("dd", { className: "text-muted-foreground", children: duration })
983
+ ] }),
984
+ record.traceId && /* @__PURE__ */ jsxs4(Fragment3, { children: [
985
+ /* @__PURE__ */ jsx5("dt", { className: "text-muted-foreground/60", children: "trace" }),
986
+ /* @__PURE__ */ jsx5("dd", { className: "min-w-0", children: /* @__PURE__ */ jsx5(TraceIdCopy, { traceId: record.traceId }) })
987
+ ] })
988
+ ] }),
989
+ record.missionRef && renderMissionRef?.(record.missionRef, record)
990
+ ] })
991
+ ] });
992
+ }
993
+ function AgentActivityPanel({ fetchActivity, renderMissionRef, title = "Agent activity", emptyLabel = "No agent runs yet." }) {
994
+ const [rows, setRows] = useState4([]);
995
+ const [cursor, setCursor] = useState4(void 0);
996
+ const [loading, setLoading] = useState4(false);
997
+ const [error, setError] = useState4(null);
998
+ const load = useCallback2(
999
+ async (from) => {
1000
+ setLoading(true);
1001
+ setError(null);
1002
+ try {
1003
+ const page = await fetchActivity(from);
1004
+ setRows((prev) => mergeActivityPages(from === void 0 ? [] : prev, page.items));
1005
+ setCursor(page.nextCursor);
1006
+ } catch (e) {
1007
+ setError(e instanceof Error ? e.message : String(e));
1008
+ } finally {
1009
+ setLoading(false);
1010
+ }
1011
+ },
1012
+ [fetchActivity]
1013
+ );
1014
+ useEffect4(() => {
1015
+ void load();
1016
+ }, [load]);
1017
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
1018
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
1019
+ /* @__PURE__ */ jsx5("h2", { className: "flex-1 text-sm font-semibold", children: title }),
1020
+ /* @__PURE__ */ jsx5(
1021
+ "button",
1022
+ {
1023
+ type: "button",
1024
+ onClick: () => void load(),
1025
+ disabled: loading,
1026
+ "aria-label": "Refresh",
1027
+ className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground disabled:opacity-50",
1028
+ children: /* @__PURE__ */ jsx5(RefreshGlyph, { className: `h-3.5 w-3.5 ${loading ? "animate-spin" : ""}` })
1029
+ }
1030
+ )
1031
+ ] }),
1032
+ error && /* @__PURE__ */ jsx5("p", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: error }),
1033
+ !error && rows.length === 0 && !loading && /* @__PURE__ */ jsx5("p", { className: "px-1 text-sm text-muted-foreground", children: emptyLabel }),
1034
+ /* @__PURE__ */ jsx5("div", { className: "space-y-1.5", children: rows.map((record) => /* @__PURE__ */ jsx5(ActivityRow, { record, renderMissionRef }, record.taskId)) }),
1035
+ cursor && /* @__PURE__ */ jsx5(
1036
+ "button",
1037
+ {
1038
+ type: "button",
1039
+ onClick: () => void load(cursor),
1040
+ disabled: loading,
1041
+ className: "w-full rounded-md border border-border bg-card px-3 py-1.5 text-xs font-medium text-muted-foreground transition hover:bg-accent/30 disabled:opacity-50",
1042
+ children: "Older runs"
1043
+ }
1044
+ )
1045
+ ] });
1046
+ }
1047
+
1048
+ // src/web-react/sandbox-terminal.ts
1049
+ import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
1050
+ var DEFAULT_PROVISION_POLL_INTERVAL_MS = 2e3;
1051
+ var DEFAULT_PROVISION_POLL_TIMEOUT_MS = 9e4;
1052
+ var DEFAULT_TOKEN_REFRESH_SKEW_MS = 12e4;
1053
+ var EMPTY_CONNECTION = {
1054
+ runtimeUrl: null,
1055
+ sidecarUrl: null,
1056
+ token: null,
1057
+ expiresAt: null,
1058
+ status: "idle",
1059
+ error: null,
1060
+ loading: false
1061
+ };
1062
+ function useSandboxTerminalConnection(opts) {
1063
+ const [conn, setConn] = useState5(EMPTY_CONNECTION);
1064
+ const mountedRef = useRef4(false);
1065
+ const generationRef = useRef4(0);
1066
+ const fetcher = opts.fetcher ?? fetch;
1067
+ const pollIntervalMs = opts.provisionPollIntervalMs ?? DEFAULT_PROVISION_POLL_INTERVAL_MS;
1068
+ const pollTimeoutMs = opts.provisionPollTimeoutMs ?? DEFAULT_PROVISION_POLL_TIMEOUT_MS;
1069
+ const refreshSkewMs = opts.tokenRefreshSkewMs ?? DEFAULT_TOKEN_REFRESH_SKEW_MS;
1070
+ const connectionUrl = useCallback3(() => {
1071
+ if (typeof opts.connectionUrl === "function") return opts.connectionUrl(opts.workspaceId);
1072
+ return opts.connectionUrl ?? `/api/workspaces/${encodeURIComponent(opts.workspaceId)}/sandbox/connection`;
1073
+ }, [opts.connectionUrl, opts.workspaceId]);
1074
+ const connect = useCallback3(async () => {
1075
+ const generation = generationRef.current + 1;
1076
+ generationRef.current = generation;
1077
+ const isCurrent = () => mountedRef.current && generationRef.current === generation;
1078
+ const setCurrentConn = (value) => {
1079
+ if (!isCurrent()) return;
1080
+ setConn(value);
1081
+ };
1082
+ setCurrentConn((current) => ({ ...current, loading: true, error: null }));
1083
+ const deadline = Date.now() + pollTimeoutMs;
1084
+ while (isCurrent()) {
1085
+ try {
1086
+ const res = await fetcher(connectionUrl());
1087
+ const data = await res.json();
1088
+ if (!isCurrent()) return;
1089
+ const runtimeUrl = data.runtimeUrl ?? data.sidecarUrl;
1090
+ if (res.ok && runtimeUrl && data.token && data.expiresAt) {
1091
+ setCurrentConn({
1092
+ runtimeUrl,
1093
+ sidecarUrl: data.sidecarUrl ?? runtimeUrl,
1094
+ token: data.token,
1095
+ expiresAt: data.expiresAt,
1096
+ status: data.status ?? "running",
1097
+ error: null,
1098
+ loading: false,
1099
+ ...data.sandboxId ? { sandboxId: data.sandboxId } : {}
1100
+ });
1101
+ return;
1102
+ }
1103
+ if (res.ok) {
1104
+ setCurrentConn({
1105
+ runtimeUrl: null,
1106
+ sidecarUrl: null,
1107
+ token: null,
1108
+ expiresAt: null,
1109
+ loading: false,
1110
+ error: "Sandbox connection response is missing required fields",
1111
+ status: data.status ?? "error",
1112
+ ...data.sandboxId ? { sandboxId: data.sandboxId } : {}
1113
+ });
1114
+ return;
1115
+ }
1116
+ if (res.status === 503 && Date.now() < deadline) {
1117
+ setCurrentConn((current) => ({
1118
+ ...current,
1119
+ loading: true,
1120
+ status: data.status ?? "provisioning",
1121
+ error: null
1122
+ }));
1123
+ await sleep(pollIntervalMs);
1124
+ continue;
1125
+ }
1126
+ setCurrentConn((current) => ({
1127
+ ...current,
1128
+ runtimeUrl: null,
1129
+ sidecarUrl: null,
1130
+ token: null,
1131
+ expiresAt: null,
1132
+ loading: false,
1133
+ error: data.error ?? "Sandbox not available",
1134
+ status: data.status ?? "error"
1135
+ }));
1136
+ return;
1137
+ } catch (err) {
1138
+ if (!isCurrent()) return;
1139
+ if (Date.now() < deadline) {
1140
+ await sleep(pollIntervalMs);
1141
+ continue;
1142
+ }
1143
+ setCurrentConn((current) => ({
1144
+ ...current,
1145
+ runtimeUrl: null,
1146
+ sidecarUrl: null,
1147
+ token: null,
1148
+ expiresAt: null,
1149
+ loading: false,
1150
+ error: err instanceof Error ? err.message : "Connection failed"
1151
+ }));
1152
+ return;
1153
+ }
1154
+ }
1155
+ }, [connectionUrl, fetcher, pollIntervalMs, pollTimeoutMs]);
1156
+ useEffect5(() => {
1157
+ mountedRef.current = true;
1158
+ return () => {
1159
+ mountedRef.current = false;
1160
+ generationRef.current += 1;
1161
+ };
1162
+ }, []);
1163
+ useEffect5(() => {
1164
+ void connect();
1165
+ }, [connect]);
1166
+ useEffect5(() => {
1167
+ if (!conn.runtimeUrl || !conn.token || !conn.expiresAt) return;
1168
+ const refreshAt = Date.parse(conn.expiresAt) - Date.now() - refreshSkewMs;
1169
+ if (!Number.isFinite(refreshAt)) {
1170
+ setConn((current) => ({
1171
+ ...current,
1172
+ runtimeUrl: null,
1173
+ sidecarUrl: null,
1174
+ token: null,
1175
+ expiresAt: null,
1176
+ status: "error",
1177
+ error: "Sandbox token expiry is invalid"
1178
+ }));
1179
+ return;
1180
+ }
1181
+ const timer = window.setTimeout(() => {
1182
+ void connect();
1183
+ }, Math.max(1e3, refreshAt));
1184
+ return () => window.clearTimeout(timer);
1185
+ }, [conn.runtimeUrl, conn.token, conn.expiresAt, connect, refreshSkewMs]);
1186
+ return { ...conn, connect };
1187
+ }
1188
+ function sleep(ms) {
1189
+ return new Promise((resolve) => window.setTimeout(resolve, ms));
1190
+ }
1191
+ var DEFAULT_TERMINAL_CID_KEY = "agent-app:terminal-connection-id";
1192
+ function newConnectionId() {
1193
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
1194
+ return `cid-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;
1195
+ }
1196
+ function tabTerminalConnectionId(storageKey = DEFAULT_TERMINAL_CID_KEY) {
1197
+ try {
1198
+ const store = globalThis.sessionStorage;
1199
+ const existing = store?.getItem(storageKey);
1200
+ if (existing) return existing;
1201
+ const id = newConnectionId();
1202
+ store?.setItem(storageKey, id);
1203
+ return id;
1204
+ } catch {
1205
+ return newConnectionId();
1206
+ }
1207
+ }
1208
+
1209
+ // src/web-react/workspace-terminal-panel.tsx
1210
+ import { lazy as lazy2, Suspense as Suspense2 } from "react";
1211
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1212
+ var TerminalView = lazy2(
1213
+ () => import("@tangle-network/sandbox-ui/terminal").then((m) => ({ default: m.TerminalView }))
1214
+ );
1215
+ var TONE_DOT = {
1216
+ idle: "bg-muted-foreground/50",
1217
+ connecting: "animate-pulse bg-warning",
1218
+ connected: "bg-success",
1219
+ error: "bg-destructive"
1220
+ };
1221
+ function defaultStatusDisplay(conn) {
1222
+ if (conn.error) return { tone: "error", label: "Disconnected" };
1223
+ if (conn.runtimeUrl && conn.token) return { tone: "connected", label: "Connected" };
1224
+ if (conn.loading) return { tone: "connecting", label: conn.status === "provisioning" ? "Provisioning\u2026" : "Connecting\u2026" };
1225
+ return { tone: "idle", label: "Idle" };
1226
+ }
1227
+ function WorkspaceTerminalPanel({
1228
+ connection,
1229
+ connectionId,
1230
+ title = "Terminal",
1231
+ subtitle,
1232
+ isActive,
1233
+ onRetry,
1234
+ statusDisplay,
1235
+ headerExtra,
1236
+ className
1237
+ }) {
1238
+ const status = (statusDisplay ?? defaultStatusDisplay)(connection);
1239
+ const apiUrl = connection.runtimeUrl ?? connection.sidecarUrl;
1240
+ const ready = Boolean(apiUrl && connection.token);
1241
+ return /* @__PURE__ */ jsxs5("div", { className: `flex h-full min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card ${className ?? ""}`, children: [
1242
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
1243
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
1244
+ /* @__PURE__ */ jsx6("p", { className: "truncate text-sm font-medium text-foreground", children: title }),
1245
+ subtitle && /* @__PURE__ */ jsx6("p", { className: "truncate text-xs text-muted-foreground", children: subtitle })
1246
+ ] }),
1247
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
1248
+ /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
1249
+ /* @__PURE__ */ jsx6("span", { className: `h-1.5 w-1.5 rounded-full ${TONE_DOT[status.tone]}`, "aria-hidden": true }),
1250
+ status.label
1251
+ ] }),
1252
+ headerExtra
1253
+ ] })
1254
+ ] }),
1255
+ /* @__PURE__ */ jsx6("div", { className: "relative min-h-0 flex-1", children: ready ? /* @__PURE__ */ jsx6(Suspense2, { fallback: /* @__PURE__ */ jsx6(TerminalMessage, { children: "Loading terminal\u2026" }), children: /* @__PURE__ */ jsx6(
1256
+ TerminalView,
1257
+ {
1258
+ apiUrl,
1259
+ token: connection.token,
1260
+ connectionId,
1261
+ title,
1262
+ subtitle,
1263
+ isActive
1264
+ }
1265
+ ) }) : /* @__PURE__ */ jsx6(TerminalMessage, { children: connection.error ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
1266
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-destructive", children: connection.error }),
1267
+ onRetry && /* @__PURE__ */ jsx6(
1268
+ "button",
1269
+ {
1270
+ type: "button",
1271
+ onClick: onRetry,
1272
+ className: "mt-3 inline-flex items-center justify-center rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-muted",
1273
+ children: "Reconnect"
1274
+ }
1275
+ )
1276
+ ] }) : connection.loading ? /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: connection.status === "provisioning" ? "Provisioning sandbox\u2026" : "Connecting\u2026" }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
1277
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: "Terminal not connected." }),
1278
+ onRetry && /* @__PURE__ */ jsx6(
1279
+ "button",
1280
+ {
1281
+ type: "button",
1282
+ onClick: onRetry,
1283
+ className: "mt-3 inline-flex items-center justify-center rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-muted",
1284
+ children: "Connect"
1285
+ }
1286
+ )
1287
+ ] }) }) })
1288
+ ] });
1289
+ }
1290
+ function TerminalMessage({ children }) {
1291
+ return /* @__PURE__ */ jsx6("div", { className: "absolute inset-0 flex flex-col items-center justify-center p-6 text-center", children });
1292
+ }
1293
+
1294
+ // src/web-react/seat-paywall.tsx
1295
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1296
+ function CheckGlyph() {
1297
+ return /* @__PURE__ */ jsx7(
1298
+ "svg",
1299
+ {
1300
+ className: "h-4 w-4 shrink-0 text-primary",
1301
+ viewBox: "0 0 24 24",
1302
+ fill: "none",
1303
+ stroke: "currentColor",
1304
+ strokeWidth: "2.5",
1305
+ strokeLinecap: "round",
1306
+ strokeLinejoin: "round",
1307
+ "aria-hidden": true,
1308
+ children: /* @__PURE__ */ jsx7("path", { d: "M20 6 9 17l-5-5" })
1309
+ }
1310
+ );
1311
+ }
1312
+ function Benefit({ children }) {
1313
+ return /* @__PURE__ */ jsxs6("li", { className: "flex items-start gap-2.5 text-sm text-foreground", children: [
1314
+ /* @__PURE__ */ jsx7("span", { className: "mt-0.5", children: /* @__PURE__ */ jsx7(CheckGlyph, {}) }),
1315
+ /* @__PURE__ */ jsx7("span", { children })
1316
+ ] });
1317
+ }
1318
+ function SeatPaywall({
1319
+ product,
1320
+ onCheckout,
1321
+ priceUsd = 100,
1322
+ includedUsageUsd = 50,
1323
+ tagline,
1324
+ ctaLabel,
1325
+ benefits,
1326
+ footnote
1327
+ }) {
1328
+ const { pending, run } = usePending();
1329
+ return /* @__PURE__ */ jsx7("div", { className: "flex min-h-[60vh] w-full items-center justify-center p-6", children: /* @__PURE__ */ jsxs6("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-8 shadow-sm", children: [
1330
+ /* @__PURE__ */ jsx7("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: product }),
1331
+ /* @__PURE__ */ jsxs6("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: [
1332
+ "Unlock ",
1333
+ product
1334
+ ] }),
1335
+ tagline && /* @__PURE__ */ jsx7("p", { className: "mt-2 text-sm text-muted-foreground", children: tagline }),
1336
+ /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex items-baseline gap-1.5", children: [
1337
+ /* @__PURE__ */ jsxs6("span", { className: "text-3xl font-semibold text-foreground", children: [
1338
+ "$",
1339
+ priceUsd
1340
+ ] }),
1341
+ /* @__PURE__ */ jsx7("span", { className: "text-sm text-muted-foreground", children: "/mo" })
1342
+ ] }),
1343
+ /* @__PURE__ */ jsxs6("p", { className: "mt-1 text-sm text-muted-foreground", children: [
1344
+ "Includes $",
1345
+ includedUsageUsd,
1346
+ "/mo of AI usage"
1347
+ ] }),
1348
+ /* @__PURE__ */ jsx7("ul", { className: "mt-6 space-y-2.5", children: (benefits ?? [
1349
+ `Full access to ${product}`,
1350
+ `$${includedUsageUsd}/mo of AI usage included, every month`
1351
+ ]).map((benefit, i) => /* @__PURE__ */ jsx7(Benefit, { children: benefit }, i)) }),
1352
+ /* @__PURE__ */ jsx7(
1353
+ "button",
1354
+ {
1355
+ type: "button",
1356
+ disabled: pending,
1357
+ onClick: () => run(onCheckout),
1358
+ 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",
1359
+ children: pending ? "Opening checkout\u2026" : ctaLabel ?? `Unlock ${product}`
1360
+ }
1361
+ ),
1362
+ footnote && /* @__PURE__ */ jsx7("p", { className: "mt-3 text-center text-xs text-muted-foreground/70", children: footnote })
1363
+ ] }) });
1364
+ }
1365
+
1366
+ // src/web-react/agent-session-controls.tsx
1367
+ import { useMemo as useMemo2, useState as useState6 } from "react";
1368
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1369
+ var HARNESS_LABELS = {
1370
+ opencode: "OpenCode (any model)",
1371
+ "claude-code": "Claude Code (Anthropic)",
1372
+ codex: "Codex (OpenAI)",
1373
+ "kimi-code": "Kimi (Moonshot)",
1374
+ amp: "Amp",
1375
+ "factory-droids": "Factory Droids",
1376
+ cursor: "Cursor",
1377
+ hermes: "Hermes",
1378
+ forge: "Forge",
1379
+ pi: "Pi",
1380
+ openclaw: "OpenClaw",
1381
+ acp: "ACP",
1382
+ "cli-base": "CLI"
1383
+ };
1384
+ function harnessLabel(h) {
1385
+ return HARNESS_LABELS[h] ?? h;
1386
+ }
1387
+ function ChevronDown2({ className }) {
1388
+ return /* @__PURE__ */ jsx8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx8("path", { d: "m6 9 6 6 6-6" }) });
1389
+ }
1390
+ function GearGlyph({ className }) {
1391
+ return /* @__PURE__ */ jsxs7("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1392
+ /* @__PURE__ */ jsx8("circle", { cx: "12", cy: "12", r: "3" }),
1393
+ /* @__PURE__ */ jsx8("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" })
1394
+ ] });
1395
+ }
1396
+ var FOCUS_RING = "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background";
1397
+ function HarnessPicker({
1398
+ value,
1399
+ onChange,
1400
+ available
1401
+ }) {
1402
+ const [open, setOpen] = useState6(false);
1403
+ const { containerRef, triggerProps } = usePopover(open, setOpen);
1404
+ const options = available ?? Object.keys(HARNESS_LABELS);
1405
+ return /* @__PURE__ */ jsxs7("div", { ref: containerRef, className: "relative inline-flex", children: [
1406
+ /* @__PURE__ */ jsxs7(
1407
+ "button",
1408
+ {
1409
+ type: "button",
1410
+ ...triggerProps,
1411
+ onClick: () => setOpen(!open),
1412
+ title: "Agent backend",
1413
+ 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}`,
1414
+ children: [
1415
+ /* @__PURE__ */ jsx8("span", { className: "truncate", children: harnessLabel(value) }),
1416
+ /* @__PURE__ */ jsx8(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
1417
+ ]
1418
+ }
1419
+ ),
1420
+ open && /* @__PURE__ */ jsx8("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__ */ jsx8(
1421
+ "button",
1422
+ {
1423
+ type: "button",
1424
+ role: "menuitemradio",
1425
+ "aria-checked": h === value,
1426
+ onClick: () => {
1427
+ onChange(h);
1428
+ setOpen(false);
1429
+ },
1430
+ 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"}`,
1431
+ children: harnessLabel(h)
1432
+ },
1433
+ h
1434
+ )) })
1435
+ ] });
1436
+ }
1437
+ function useCoherentHandlers(props) {
1438
+ const { model, models, harness, onModelChange, onHarnessChange } = props;
1439
+ const canonicalIds = useMemo2(() => models.map((m) => m.id), [models]);
1440
+ const onModel = (next) => {
1441
+ onModelChange(next);
1442
+ const nextHarness = snapHarnessToModel(harness, next);
1443
+ if (nextHarness !== harness) onHarnessChange(nextHarness);
1444
+ };
1445
+ const onHarness = (next) => {
1446
+ onHarnessChange(next);
1447
+ const snapped = snapModelToHarness(next, model, canonicalIds);
1448
+ if (snapped !== model) onModelChange(snapped);
1449
+ };
1450
+ return { onModel, onHarness };
1451
+ }
1452
+ function AgentSessionControls(props) {
1453
+ const {
1454
+ models,
1455
+ modelsLoading,
1456
+ model,
1457
+ harness,
1458
+ availableHarnesses,
1459
+ effort,
1460
+ onEffortChange,
1461
+ layout = "inline",
1462
+ showHarness = true,
1463
+ renderProviderBadge,
1464
+ className
1465
+ } = props;
1466
+ const { onModel, onHarness } = useCoherentHandlers(props);
1467
+ const [open, setOpen] = useState6(false);
1468
+ const { containerRef: popoverRef, triggerProps } = usePopover(open, setOpen);
1469
+ const selectedModel = models.find((m) => m.id === model);
1470
+ const showEffort = selectedModel?.supportsReasoning ?? true;
1471
+ const modelPicker = /* @__PURE__ */ jsx8(
1472
+ ModelPicker,
1473
+ {
1474
+ value: model,
1475
+ onChange: onModel,
1476
+ models,
1477
+ loading: modelsLoading,
1478
+ renderProviderBadge
1479
+ }
1480
+ );
1481
+ if (layout === "inline") {
1482
+ return /* @__PURE__ */ jsxs7("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
1483
+ modelPicker,
1484
+ showHarness && /* @__PURE__ */ jsx8(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
1485
+ showEffort && /* @__PURE__ */ jsx8(EffortPicker, { value: effort, onChange: onEffortChange })
1486
+ ] });
1487
+ }
1488
+ const hasAdvanced = showHarness || showEffort;
1489
+ return /* @__PURE__ */ jsxs7("div", { className: `flex items-center gap-1.5 ${className ?? ""}`, children: [
1490
+ modelPicker,
1491
+ hasAdvanced && /* @__PURE__ */ jsxs7("div", { ref: popoverRef, className: "relative inline-flex", children: [
1492
+ /* @__PURE__ */ jsx8(
1493
+ "button",
1494
+ {
1495
+ type: "button",
1496
+ ...triggerProps,
1497
+ onClick: () => setOpen(!open),
1498
+ title: "Model settings \u2014 pick the agent backend and how hard it thinks",
1499
+ 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}`,
1500
+ "data-state": open ? "open" : "closed",
1501
+ children: /* @__PURE__ */ jsx8(GearGlyph, { className: "h-4 w-4" })
1502
+ }
1503
+ ),
1504
+ open && /* @__PURE__ */ jsxs7("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: [
1505
+ showHarness && /* @__PURE__ */ jsxs7("div", { className: "space-y-1.5", children: [
1506
+ /* @__PURE__ */ jsx8("p", { className: "text-xs font-medium text-foreground", children: "Agent backend" }),
1507
+ /* @__PURE__ */ jsx8(HarnessPicker, { value: harness, onChange: onHarness, available: availableHarnesses }),
1508
+ /* @__PURE__ */ jsx8("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "The engine that runs the agent. Switching it keeps your model choice compatible." })
1509
+ ] }),
1510
+ showEffort && /* @__PURE__ */ jsxs7("div", { className: "space-y-1.5", children: [
1511
+ /* @__PURE__ */ jsx8("p", { className: "text-xs font-medium text-foreground", children: "Thinking" }),
1512
+ /* @__PURE__ */ jsx8(EffortPicker, { value: effort, onChange: onEffortChange, label: "" }),
1513
+ /* @__PURE__ */ jsx8("p", { className: "text-[11px] leading-snug text-muted-foreground", children: "How hard the agent thinks before answering. Higher is slower but more thorough." })
1514
+ ] })
1515
+ ] })
1516
+ ] })
1517
+ ] });
1518
+ }
1519
+
1520
+ // src/web-react/index.tsx
1521
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1522
+ function formatModelCost(msg, models) {
1523
+ if (msg.promptTokens == null && msg.completionTokens == null) return null;
1524
+ const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing;
1525
+ if (!pricing) return null;
1526
+ const cost = (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) + (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0);
1527
+ if (!isFinite(cost) || cost <= 0) return null;
1528
+ return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`;
1529
+ }
1530
+ function formatTokensPerSecond(msg) {
1531
+ if (msg.completionTokens == null || !msg.durationMs) return null;
1532
+ return `${Math.round(msg.completionTokens / (msg.durationMs / 1e3))} tok/s`;
1533
+ }
1534
+ function RunDrillIn({ run, onClose }) {
1535
+ return /* @__PURE__ */ jsxs8("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: [
1536
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
1537
+ /* @__PURE__ */ jsx9(
1538
+ "span",
1539
+ {
1540
+ className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-warning" : run.status === "error" ? "bg-destructive" : "bg-success"}`
1541
+ }
1542
+ ),
1543
+ /* @__PURE__ */ jsxs8("div", { className: "min-w-0 flex-1", children: [
1544
+ /* @__PURE__ */ jsx9("p", { className: "truncate text-sm font-semibold", children: run.title }),
1545
+ /* @__PURE__ */ jsx9("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
1546
+ ] }),
1547
+ /* @__PURE__ */ jsx9(
1548
+ "button",
1549
+ {
1550
+ type: "button",
1551
+ onClick: onClose,
1552
+ "aria-label": "Close",
1553
+ className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
1554
+ children: /* @__PURE__ */ jsx9("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx9("path", { d: "M18 6 6 18M6 6l12 12" }) })
1555
+ }
1556
+ )
1557
+ ] }),
1558
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
1559
+ run.steps.length === 0 && /* @__PURE__ */ jsx9("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
1560
+ run.steps.map((step, i) => /* @__PURE__ */ jsxs8("div", { className: "rounded-lg border border-border/60 bg-background", children: [
1561
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
1562
+ /* @__PURE__ */ jsx9("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-destructive" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
1563
+ /* @__PURE__ */ jsx9("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
1564
+ /* @__PURE__ */ jsx9("span", { className: "shrink-0 text-[10px] text-muted-foreground", children: new Date(step.at).toLocaleTimeString() })
1565
+ ] }),
1566
+ step.detail && /* @__PURE__ */ jsx9("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 })
1567
+ ] }, i))
1568
+ ] }),
1569
+ /* @__PURE__ */ jsx9("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." })
1570
+ ] });
1571
+ }
1572
+ function pendingApprovalOf(call) {
1573
+ const outcome = call.result;
1574
+ if (!outcome?.ok || outcome.result?.status !== "queued_for_approval" || !outcome.result.proposalId) return null;
1575
+ return { proposalId: outcome.result.proposalId };
1576
+ }
1577
+ function ChatEmptyState({
1578
+ productName = "Agent",
1579
+ headline = "Ask the agent to do something",
1580
+ subline = "Describe the outcome you want. The agent works through it step by step, and pauses for your approval before anything irreversible.",
1581
+ doors
1582
+ }) {
1583
+ return /* @__PURE__ */ jsxs8("div", { className: "mx-auto flex w-full max-w-2xl flex-col items-center px-6 py-12 text-center sm:py-20", children: [
1584
+ /* @__PURE__ */ jsx9("span", { className: "mb-5 inline-flex h-14 w-14 items-center justify-center rounded-2xl bg-primary/10 ring-1 ring-primary/15", children: /* @__PURE__ */ jsx9(BrandMark, { size: 32, className: "shrink-0" }) }),
1585
+ /* @__PURE__ */ jsx9("p", { className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground", children: productName }),
1586
+ /* @__PURE__ */ jsx9("h2", { className: "mt-1.5 text-balance text-2xl font-semibold leading-tight text-foreground sm:text-[28px]", children: headline }),
1587
+ subline && /* @__PURE__ */ jsx9("p", { className: "mt-3 max-w-md text-[15px] leading-relaxed text-muted-foreground", children: subline }),
1588
+ doors && doors.length > 0 && /* @__PURE__ */ jsx9("div", { className: "mt-7 grid w-full gap-2.5 sm:grid-cols-3", children: doors.slice(0, 3).map((door, i) => /* @__PURE__ */ jsxs8(
1589
+ "button",
1590
+ {
1591
+ type: "button",
1592
+ onClick: door.onSelect,
1593
+ className: "group flex min-h-[44px] flex-col items-start rounded-xl border border-border bg-card px-4 py-3 text-left transition hover:border-primary/40 hover:bg-accent/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
1594
+ children: [
1595
+ /* @__PURE__ */ jsx9("span", { className: "text-sm font-semibold text-foreground", children: door.label }),
1596
+ door.description && /* @__PURE__ */ jsx9("span", { className: "mt-0.5 text-[12px] leading-snug text-muted-foreground", children: door.description })
1597
+ ]
1598
+ },
1599
+ i
1600
+ )) })
1601
+ ] });
1602
+ }
1603
+ function ToolGlyph({ name, className }) {
1604
+ if (name.startsWith("sandbox_")) {
1605
+ return /* @__PURE__ */ jsxs8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1606
+ /* @__PURE__ */ jsx9("polyline", { points: "4 17 10 11 4 5" }),
1607
+ /* @__PURE__ */ jsx9("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
1608
+ ] });
1609
+ }
1610
+ if (name === "submit_proposal") {
1611
+ return /* @__PURE__ */ jsxs8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1612
+ /* @__PURE__ */ jsx9("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1613
+ /* @__PURE__ */ jsx9("path", { d: "M14 2v6h6M9 15l2 2 4-4" })
1614
+ ] });
1615
+ }
1616
+ if (name === "schedule_followup") {
1617
+ return /* @__PURE__ */ jsxs8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
1618
+ /* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "9" }),
1619
+ /* @__PURE__ */ jsx9("path", { d: "M12 7v5l3 3" })
1620
+ ] });
1621
+ }
1622
+ return /* @__PURE__ */ jsxs8("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1623
+ /* @__PURE__ */ jsx9("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3" }),
1624
+ /* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "4" })
1625
+ ] });
1626
+ }
1627
+ function toolOutcomeOf(call) {
1628
+ return call.result;
1629
+ }
1630
+ function blockKindOf(call) {
1631
+ if (call.name === "submit_proposal") return "proposal";
1632
+ if (call.name === "schedule_followup") return "followup";
1633
+ if (call.name.startsWith("sandbox_")) return "command";
1634
+ return "generic";
1635
+ }
1636
+ function humanizeToolName(name) {
1637
+ const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
1638
+ if (!words) return name;
1639
+ return words.charAt(0).toUpperCase() + words.slice(1);
1640
+ }
1641
+ function friendlyToolTitle(call) {
1642
+ const a = call.args ?? {};
1643
+ switch (call.name) {
1644
+ case "submit_proposal":
1645
+ return a.title ? `Approve: ${String(a.title)}?` : "Approve this action?";
1646
+ case "sandbox_create":
1647
+ return `Created sandbox (${String(a.environment ?? "universal")})`;
1648
+ case "sandbox_run_command":
1649
+ return `Ran ${String(a.command ?? "command")}`;
1650
+ case "sandbox_destroy":
1651
+ return `Destroyed sandbox ${String(a.sandbox_id ?? "")}`;
1652
+ case "schedule_followup":
1653
+ return `Scheduled: ${String(a.title ?? "follow-up")}`;
1654
+ case "render_ui":
1655
+ return `Rendered view \xB7 ${String(a.title ?? "")}`;
1656
+ case "add_citation":
1657
+ return `Cited ${String(a.path ?? "")}`;
1658
+ default:
1659
+ return humanizeToolName(call.name);
1660
+ }
1661
+ }
1662
+ function proposalPreview(call) {
1663
+ const a = call.args ?? {};
1664
+ const asString = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
1665
+ const asList = (v) => Array.isArray(v) ? v.map((x) => typeof x === "string" ? x : null).filter((x) => !!x) : asString(v) ? [asString(v)] : [];
1666
+ const verbPhrase = asString(a.summary) ?? asString(a.description) ?? (asString(a.type) ? `${String(a.type).replace(/_/g, " ")}${asString(a.title) ? `: ${asString(a.title)}` : ""}` : null);
1667
+ const destinations = [
1668
+ ...asList(a.destinations),
1669
+ ...asList(a.channels),
1670
+ ...asList(a.targets),
1671
+ ...asList(a.platforms)
1672
+ ];
1673
+ const dest = destinations.length ? ` to ${destinations.join(" and ")}` : "";
1674
+ const summary = verbPhrase ? `${verbPhrase}${dest}` : destinations.length ? `Publish to ${destinations.join(" and ")}` : null;
1675
+ const meta = [];
1676
+ const cost = a.cost ?? a.price ?? a.estimatedCost;
1677
+ if (typeof cost === "number" && cost > 0) meta.push(`~$${cost < 0.01 ? cost.toFixed(4) : cost.toFixed(2)}`);
1678
+ else if (asString(cost)) meta.push(asString(cost));
1679
+ const reach = a.reach ?? a.audience ?? a.estimatedReach;
1680
+ if (typeof reach === "number" && reach > 0) meta.push(`reaches ~${reach.toLocaleString()}`);
1681
+ else if (asString(reach)) meta.push(asString(reach));
1682
+ return { summary, meta };
1683
+ }
1684
+ function truncate(v, max = 240) {
1685
+ const s = typeof v === "string" ? v : JSON.stringify(v);
1686
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
1687
+ }
1688
+ function KvRows({ data }) {
1689
+ const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null && v !== "");
1690
+ if (!entries.length) return null;
1691
+ return /* @__PURE__ */ jsx9("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1", children: entries.map(([k, v]) => /* @__PURE__ */ jsxs8("div", { className: "contents", children: [
1692
+ /* @__PURE__ */ jsx9("dt", { className: "font-mono text-[11px] text-muted-foreground", children: k }),
1693
+ /* @__PURE__ */ jsx9("dd", { className: "min-w-0 whitespace-pre-wrap break-words font-mono text-[11px] text-muted-foreground", children: truncate(v) })
1694
+ ] }, k)) });
1695
+ }
1696
+ function ShellDetail({ call }) {
1697
+ const outcome = toolOutcomeOf(call);
1698
+ const r = outcome?.result ?? {};
1699
+ return /* @__PURE__ */ jsxs8("div", { className: "overflow-hidden rounded-md bg-zinc-900 font-mono text-[11px] leading-relaxed", children: [
1700
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 px-3 pt-2 text-zinc-400", children: [
1701
+ /* @__PURE__ */ jsx9("span", { className: "select-none text-zinc-500", children: "$" }),
1702
+ /* @__PURE__ */ jsx9("span", { className: "min-w-0 flex-1 truncate text-zinc-200", children: String(call.args?.command ?? "") }),
1703
+ r.exitCode != null && /* @__PURE__ */ jsxs8("span", { className: r.exitCode === 0 ? "text-success" : "text-destructive", children: [
1704
+ "exit ",
1705
+ r.exitCode
1706
+ ] })
1707
+ ] }),
1708
+ /* @__PURE__ */ jsx9("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)" })
1709
+ ] });
1710
+ }
1711
+ function DefaultToolDetail({ call }) {
1712
+ const outcome = toolOutcomeOf(call);
1713
+ return /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1714
+ call.args && Object.keys(call.args).length > 0 && /* @__PURE__ */ jsxs8("div", { children: [
1715
+ /* @__PURE__ */ jsx9("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Called with" }),
1716
+ /* @__PURE__ */ jsx9(KvRows, { data: call.args })
1717
+ ] }),
1718
+ outcome && /* @__PURE__ */ jsxs8("div", { children: [
1719
+ /* @__PURE__ */ jsx9("p", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: outcome.ok === false ? "Failed" : "Result" }),
1720
+ outcome.ok === false ? /* @__PURE__ */ jsx9("p", { className: "text-xs text-destructive", children: outcome.message ?? "Tool failed" }) : outcome.result && typeof outcome.result === "object" ? /* @__PURE__ */ jsx9(KvRows, { data: outcome.result }) : /* @__PURE__ */ jsx9("p", { className: "font-mono text-[11px] text-muted-foreground", children: truncate(outcome.result) })
1721
+ ] })
1722
+ ] });
1723
+ }
1724
+ function ProposalCard({
1725
+ call,
1726
+ message,
1727
+ pending,
1728
+ approval,
1729
+ renderers
1730
+ }) {
1731
+ const [expanded, setExpanded] = useState7(false);
1732
+ const { summary, meta } = proposalPreview(call);
1733
+ const custom = renderers?.[call.name]?.(call, message);
1734
+ const { pending: deciding, run: decide } = usePending();
1735
+ return /* @__PURE__ */ jsxs8("div", { className: "w-full max-w-full rounded-xl border border-warning/50 bg-warning/[0.06] text-sm shadow-sm ring-1 ring-warning/10", children: [
1736
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2.5 px-4 pt-3.5", children: [
1737
+ /* @__PURE__ */ jsx9("span", { className: "mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-warning/15 text-warning", children: /* @__PURE__ */ jsx9(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5" }) }),
1738
+ /* @__PURE__ */ jsxs8("div", { className: "min-w-0 flex-1", children: [
1739
+ /* @__PURE__ */ jsx9("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-warning-foreground", children: "Needs your approval" }),
1740
+ /* @__PURE__ */ jsx9("p", { className: "mt-0.5 text-[15px] font-semibold leading-snug text-foreground", children: friendlyToolTitle(call) }),
1741
+ summary && /* @__PURE__ */ jsx9("p", { className: "mt-1 text-[13px] leading-relaxed text-muted-foreground", children: summary }),
1742
+ meta.length > 0 && /* @__PURE__ */ jsx9("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: meta.map((m, i) => /* @__PURE__ */ jsx9("span", { className: "rounded-full bg-muted/60 px-2 py-0.5 text-[11px] font-medium text-muted-foreground", children: m }, i)) })
1743
+ ] })
1744
+ ] }),
1745
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-2 px-4 pb-3.5 pt-3", children: [
1746
+ approval ? /* @__PURE__ */ jsxs8(Fragment5, { children: [
1747
+ /* @__PURE__ */ jsx9(
1748
+ "button",
1749
+ {
1750
+ type: "button",
1751
+ disabled: deciding,
1752
+ onClick: () => decide(() => approval.onApprove(pending.proposalId, call.id)),
1753
+ className: "inline-flex min-h-[40px] flex-1 items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground shadow-sm transition hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card disabled:cursor-not-allowed disabled:opacity-60 sm:flex-none sm:min-w-[160px]",
1754
+ children: "Approve & run"
1755
+ }
1756
+ ),
1757
+ /* @__PURE__ */ jsx9(
1758
+ "button",
1759
+ {
1760
+ type: "button",
1761
+ disabled: deciding,
1762
+ onClick: () => decide(() => approval.onReject(pending.proposalId, call.id)),
1763
+ className: "inline-flex min-h-[40px] items-center justify-center rounded-lg border border-border bg-transparent px-4 py-2 text-sm font-medium text-muted-foreground transition hover:bg-accent/30 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card disabled:cursor-not-allowed disabled:opacity-60",
1764
+ children: "Reject"
1765
+ }
1766
+ )
1767
+ ] }) : /* @__PURE__ */ jsx9("span", { className: "text-[12px] font-medium text-muted-foreground", children: "Awaiting approval\u2026" }),
1768
+ /* @__PURE__ */ jsxs8(
1769
+ "button",
1770
+ {
1771
+ type: "button",
1772
+ onClick: () => setExpanded((v) => !v),
1773
+ "aria-expanded": expanded,
1774
+ className: "ml-auto inline-flex items-center gap-1 rounded text-[12px] font-medium text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1775
+ children: [
1776
+ expanded ? "Hide details" : "View details",
1777
+ /* @__PURE__ */ jsx9(ChevronDown, { className: `h-3 w-3 transition-transform ${expanded ? "rotate-180" : ""}` })
1778
+ ]
1779
+ }
1780
+ )
1781
+ ] }),
1782
+ expanded && /* @__PURE__ */ jsx9("div", { className: "border-t border-warning/20 px-4 py-3 text-xs", children: custom ?? /* @__PURE__ */ jsx9(DefaultToolDetail, { call }) })
1783
+ ] });
1784
+ }
1785
+ function FollowupCard({ call }) {
1786
+ const a = call.args ?? {};
1787
+ const when = typeof a.when === "string" ? a.when : typeof a.at === "string" ? a.at : typeof a.schedule === "string" ? a.schedule : null;
1788
+ return /* @__PURE__ */ jsxs8("div", { className: "w-fit min-w-[260px] max-w-full rounded-lg border border-border/60 border-l-2 border-l-primary/60 bg-muted/20 px-3 py-2 text-sm", children: [
1789
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1790
+ /* @__PURE__ */ jsx9(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-primary/80" }),
1791
+ /* @__PURE__ */ jsx9("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: friendlyToolTitle(call) })
1792
+ ] }),
1793
+ when && /* @__PURE__ */ jsx9("p", { className: "mt-0.5 pl-[22px] text-[12px] text-muted-foreground", children: when })
1794
+ ] });
1795
+ }
1796
+ function ToolCallCard({
1797
+ call,
1798
+ message,
1799
+ approval,
1800
+ onOpenRun,
1801
+ renderers
1802
+ }) {
1803
+ const [expanded, setExpanded] = useState7(false);
1804
+ const pending = call.status === "done" ? pendingApprovalOf(call) : null;
1805
+ const kind = blockKindOf(call);
1806
+ const failed = call.status === "error" || toolOutcomeOf(call)?.ok === false;
1807
+ const custom = renderers?.[call.name]?.(call, message);
1808
+ if (pending) {
1809
+ return /* @__PURE__ */ jsx9(
1810
+ ProposalCard,
1811
+ {
1812
+ call,
1813
+ message,
1814
+ pending,
1815
+ approval,
1816
+ renderers
1817
+ }
1818
+ );
1819
+ }
1820
+ if (kind === "followup" && !failed) {
1821
+ return /* @__PURE__ */ jsx9(FollowupCard, { call });
1822
+ }
1823
+ const isCommand = kind === "command";
1824
+ return /* @__PURE__ */ jsxs8(
1825
+ "div",
1826
+ {
1827
+ className: `w-fit min-w-[280px] max-w-full rounded-lg border text-xs transition ${failed ? "border-destructive/40 bg-destructive/5" : "border-border/60 bg-muted/20"}`,
1828
+ children: [
1829
+ /* @__PURE__ */ jsxs8("div", { className: "flex w-full items-center gap-2 px-3 py-2", children: [
1830
+ /* @__PURE__ */ jsxs8(
1831
+ "button",
1832
+ {
1833
+ type: "button",
1834
+ onClick: () => setExpanded((v) => !v),
1835
+ "aria-expanded": expanded,
1836
+ 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",
1837
+ children: [
1838
+ /* @__PURE__ */ jsx9(
1839
+ "span",
1840
+ {
1841
+ className: `h-2 w-2 shrink-0 rounded-full ${call.status === "running" ? "animate-pulse bg-warning" : failed ? "bg-destructive" : "bg-success"}`
1842
+ }
1843
+ ),
1844
+ /* @__PURE__ */ jsx9(ToolGlyph, { name: call.name, className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
1845
+ /* @__PURE__ */ jsx9(
1846
+ "span",
1847
+ {
1848
+ className: `min-w-0 flex-1 truncate ${isCommand ? "font-mono text-[12px] tracking-tight text-foreground/90" : "font-medium"}`,
1849
+ children: friendlyToolTitle(call)
1850
+ }
1851
+ )
1852
+ ]
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ jsx9("span", { className: "shrink-0 text-[11px] text-muted-foreground", children: call.status === "running" ? "running\u2026" : failed ? "failed" : "done" }),
1856
+ /* @__PURE__ */ jsx9(
1857
+ "button",
1858
+ {
1859
+ type: "button",
1860
+ onClick: () => setExpanded((v) => !v),
1861
+ "aria-label": expanded ? "Collapse details" : "Expand details",
1862
+ "aria-expanded": expanded,
1863
+ className: "shrink-0 rounded p-0.5 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1864
+ children: /* @__PURE__ */ jsx9(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${expanded ? "rotate-180" : ""}` })
1865
+ }
1866
+ )
1867
+ ] }),
1868
+ expanded && /* @__PURE__ */ jsxs8("div", { className: "border-t border-border/40 px-3 py-2.5", children: [
1869
+ custom ?? (call.name === "sandbox_run_command" ? /* @__PURE__ */ jsx9(ShellDetail, { call }) : /* @__PURE__ */ jsx9(DefaultToolDetail, { call })),
1870
+ onOpenRun && call.name.startsWith("sandbox_") && /* @__PURE__ */ jsx9(
1871
+ "button",
1872
+ {
1873
+ type: "button",
1874
+ onClick: () => onOpenRun(call, message),
1875
+ className: "mt-2 rounded border border-border bg-card px-2 py-1 text-[11px] font-medium transition hover:bg-accent/30",
1876
+ children: "Open full transcript \u2192"
1877
+ }
1878
+ )
1879
+ ] })
1880
+ ]
1881
+ }
1882
+ );
1883
+ }
1884
+ function StreamingCaret() {
1885
+ return /* @__PURE__ */ jsx9(
1886
+ "span",
1887
+ {
1888
+ className: "ml-0.5 inline-block h-[1.1em] w-[3px] translate-y-[2px] animate-pulse rounded-sm bg-foreground/70",
1889
+ "aria-hidden": true
1890
+ }
1891
+ );
1892
+ }
1893
+ function SegmentText({
1894
+ content,
1895
+ streaming,
1896
+ showCaret,
1897
+ renderBody
1898
+ }) {
1899
+ const text = useSmoothText(content, streaming);
1900
+ const body = useMemo3(() => renderBody(text), [renderBody, text]);
1901
+ if (!content.trim() && !showCaret) return null;
1902
+ return /* @__PURE__ */ jsxs8("div", { className: "text-base leading-[1.75]", children: [
1903
+ body,
1904
+ showCaret && /* @__PURE__ */ jsx9(StreamingCaret, {})
1905
+ ] });
1906
+ }
1907
+ function SegmentedBody({
1908
+ segments,
1909
+ msg,
1910
+ streaming,
1911
+ renderBody,
1912
+ approval,
1913
+ onToolCallClick,
1914
+ toolRenderers
1915
+ }) {
1916
+ const lastIndex = segments.length - 1;
1917
+ const segmentToolIds = new Set(
1918
+ segments.flatMap((s) => s.kind === "tool" ? [s.call.id] : [])
1919
+ );
1920
+ const leftoverToolCalls = (msg.toolCalls ?? []).filter(
1921
+ (tc) => !segmentToolIds.has(tc.id)
1922
+ );
1923
+ const renderToolCard = (call) => /* @__PURE__ */ jsx9(
1924
+ ToolCallCard,
1925
+ {
1926
+ call,
1927
+ message: msg,
1928
+ approval,
1929
+ onOpenRun: onToolCallClick,
1930
+ renderers: toolRenderers
1931
+ },
1932
+ `tool-${call.id}`
1933
+ );
1934
+ return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
1935
+ segments.map(
1936
+ (seg, i) => seg.kind === "text" ? /* @__PURE__ */ jsx9(
1937
+ SegmentText,
1938
+ {
1939
+ content: seg.content,
1940
+ streaming: streaming && i === lastIndex,
1941
+ showCaret: streaming && i === lastIndex,
1942
+ renderBody
1943
+ },
1944
+ `text-${i}`
1945
+ ) : renderToolCard(seg.call)
1946
+ ),
1947
+ leftoverToolCalls.map(renderToolCard),
1948
+ streaming && segments[lastIndex]?.kind === "tool" && /* @__PURE__ */ jsx9(StreamingCaret, {})
1949
+ ] });
1950
+ }
1951
+ function AssistantMessageImpl({
1952
+ msg,
1953
+ streaming,
1954
+ models,
1955
+ agentLabel,
1956
+ renderBody,
1957
+ approval,
1958
+ onToolCallClick,
1959
+ toolRenderers,
1960
+ renderExtras
1961
+ }) {
1962
+ const content = useSmoothText(msg.content, streaming);
1963
+ const reasoning = useSmoothText(msg.reasoning ?? "", streaming);
1964
+ const body = useMemo3(() => renderBody(content), [renderBody, content]);
1965
+ const segments = msg.segments;
1966
+ const hasAnswerText = content !== "" || (segments?.some((s) => s.kind === "text" && s.content.trim() !== "") ?? false);
1967
+ const reasoningScrollRef = useRef5(null);
1968
+ const thinkStartRef = useRef5(null);
1969
+ const thinkMsRef = useRef5(null);
1970
+ if (streaming && reasoning && !hasAnswerText && thinkStartRef.current === null) {
1971
+ thinkStartRef.current = performance.now();
1972
+ }
1973
+ if (hasAnswerText && thinkStartRef.current !== null && thinkMsRef.current === null) {
1974
+ thinkMsRef.current = performance.now() - thinkStartRef.current;
1975
+ }
1976
+ useEffect6(() => {
1977
+ const el = reasoningScrollRef.current;
1978
+ if (el && streaming && !hasAnswerText) el.scrollTop = el.scrollHeight;
1979
+ }, [reasoning, streaming, hasAnswerText]);
1980
+ const thinkingSeconds = useThinkingSeconds(
1981
+ streaming && !!reasoning && !hasAnswerText
1982
+ );
1983
+ return /* @__PURE__ */ jsxs8("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
1984
+ /* @__PURE__ */ jsxs8("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground", children: [
1985
+ /* @__PURE__ */ jsx9("span", { className: "font-semibold uppercase", children: agentLabel }),
1986
+ msg.modelUsed && /* @__PURE__ */ jsx9("span", { className: "font-mono normal-case", children: msg.modelUsed }),
1987
+ formatTokensPerSecond(msg) && /* @__PURE__ */ jsx9("span", { children: formatTokensPerSecond(msg) }),
1988
+ formatModelCost(msg, models) && /* @__PURE__ */ jsx9("span", { children: formatModelCost(msg, models) })
1989
+ ] }),
1990
+ reasoning && /* @__PURE__ */ jsxs8("details", { className: "mb-2 rounded-lg border-l-2 border-border/70 bg-muted/20 px-3 py-2", open: !hasAnswerText, children: [
1991
+ /* @__PURE__ */ jsx9("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: !hasAnswerText ? /* @__PURE__ */ jsxs8("span", { className: "animate-pulse", children: [
1992
+ "Thinking",
1993
+ thinkingSeconds >= 3 ? ` \xB7 ${thinkingSeconds}s` : "\u2026"
1994
+ ] }) : thinkMsRef.current != null ? `Thought for ${Math.max(1, Math.round(thinkMsRef.current / 1e3))}s` : "Thought process" }),
1995
+ /* @__PURE__ */ jsx9("div", { ref: reasoningScrollRef, className: "mt-2 max-h-48 overflow-y-auto whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground", children: reasoning })
1996
+ ] }),
1997
+ segments && segments.length > 0 ? /* @__PURE__ */ jsx9(
1998
+ SegmentedBody,
1999
+ {
2000
+ segments,
2001
+ msg,
2002
+ streaming,
2003
+ renderBody,
2004
+ approval,
2005
+ onToolCallClick,
2006
+ toolRenderers
2007
+ }
2008
+ ) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
2009
+ /* @__PURE__ */ jsxs8("div", { className: "text-base leading-[1.75]", children: [
2010
+ body,
2011
+ streaming && content && !msg.toolCalls?.length && /* @__PURE__ */ jsx9(StreamingCaret, {})
2012
+ ] }),
2013
+ msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx9("div", { className: "mt-2 flex flex-col gap-1.5", children: msg.toolCalls.map((tc) => /* @__PURE__ */ jsx9(
2014
+ ToolCallCard,
2015
+ {
2016
+ call: tc,
2017
+ message: msg,
2018
+ approval,
2019
+ onOpenRun: onToolCallClick,
2020
+ renderers: toolRenderers
2021
+ },
2022
+ tc.id
2023
+ )) })
2024
+ ] }),
2025
+ renderExtras?.(msg)
2026
+ ] });
2027
+ }
2028
+ var AssistantMessage = memo(AssistantMessageImpl);
2029
+ function useThinkingSeconds(active) {
2030
+ const [seconds, setSeconds] = useState7(0);
2031
+ useEffect6(() => {
2032
+ if (!active) return;
2033
+ setSeconds(0);
2034
+ const id = setInterval(() => setSeconds((s) => s + 1), 1e3);
2035
+ return () => clearInterval(id);
2036
+ }, [active]);
2037
+ return seconds;
2038
+ }
2039
+ function ThinkingRow({ agentLabel }) {
2040
+ const seconds = useThinkingSeconds(true);
2041
+ return /* @__PURE__ */ jsxs8("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
2042
+ /* @__PURE__ */ jsx9("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: agentLabel }),
2043
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
2044
+ /* @__PURE__ */ jsx9("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx9("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
2045
+ "Thinking",
2046
+ seconds >= 3 ? ` \xB7 ${seconds}s` : "..."
2047
+ ] })
2048
+ ] });
2049
+ }
2050
+ function StreamErrorRow({ message, onRetry }) {
2051
+ return /* @__PURE__ */ jsx9("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs8("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: [
2052
+ /* @__PURE__ */ jsxs8("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: [
2053
+ /* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "9" }),
2054
+ /* @__PURE__ */ jsx9("path", { d: "M12 8v4m0 4h.01" })
2055
+ ] }),
2056
+ /* @__PURE__ */ jsx9("span", { className: "min-w-0 flex-1 break-words", children: message }),
2057
+ onRetry && /* @__PURE__ */ jsx9(
2058
+ "button",
2059
+ {
2060
+ type: "button",
2061
+ onClick: onRetry,
2062
+ 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}`,
2063
+ children: "Retry"
2064
+ }
2065
+ )
2066
+ ] }) });
2067
+ }
2068
+ function ChatMessages({
2069
+ messages,
2070
+ models = [],
2071
+ renderMarkdown,
2072
+ renderExtras,
2073
+ userLabel = "User",
2074
+ agentLabel = "Agent",
2075
+ loading,
2076
+ approval,
2077
+ onToolCallClick,
2078
+ toolRenderers,
2079
+ error,
2080
+ onRetry,
2081
+ renderEmpty,
2082
+ emptyState,
2083
+ header
2084
+ }) {
2085
+ const renderBody = useMemo3(
2086
+ () => renderMarkdown ?? ((content) => /* @__PURE__ */ jsx9("p", { className: "whitespace-pre-wrap", children: content })),
2087
+ [renderMarkdown]
2088
+ );
2089
+ const lastIsUser = messages[messages.length - 1]?.role === "user";
2090
+ if (messages.length === 0 && !loading && !error) {
2091
+ const empty = renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx9(ChatEmptyState, { ...emptyState });
2092
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
2093
+ header,
2094
+ empty
2095
+ ] });
2096
+ }
2097
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
2098
+ header,
2099
+ messages.map(
2100
+ (msg) => msg.role === "user" ? /* @__PURE__ */ jsx9("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs8("div", { className: "ml-auto w-fit max-w-[85%]", children: [
2101
+ /* @__PURE__ */ jsx9("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground", children: userLabel }),
2102
+ /* @__PURE__ */ jsx9("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx9("p", { className: "whitespace-pre-wrap", children: msg.content }) })
2103
+ ] }) }, msg.id) : /* @__PURE__ */ jsx9(
2104
+ AssistantMessage,
2105
+ {
2106
+ msg,
2107
+ streaming: !!loading && msg.id === messages[messages.length - 1]?.id,
2108
+ models,
2109
+ agentLabel,
2110
+ renderBody,
2111
+ approval,
2112
+ onToolCallClick,
2113
+ toolRenderers,
2114
+ renderExtras
2115
+ },
2116
+ msg.id
2117
+ )
2118
+ ),
2119
+ loading && lastIsUser && /* @__PURE__ */ jsx9(ThinkingRow, { agentLabel }),
2120
+ error && !loading && /* @__PURE__ */ jsx9(StreamErrorRow, { message: error, onRetry })
2121
+ ] });
2122
+ }
2123
+
2124
+ export {
2125
+ nextRevealCount,
2126
+ useSmoothText,
2127
+ ProviderLogo,
2128
+ usePopover,
2129
+ usePending,
2130
+ ModelPicker,
2131
+ DEFAULT_EFFORT_LEVELS,
2132
+ EffortPicker,
2133
+ dispatchChatStreamLine,
2134
+ consumeChatStream,
2135
+ streamChatTurn,
2136
+ ChatComposer,
2137
+ activityTone,
2138
+ formatActivityCost,
2139
+ formatActivityDuration,
2140
+ mergeActivityPages,
2141
+ waterfallLayout,
2142
+ FlowWaterfall,
2143
+ MissionActivityLane,
2144
+ AgentActivityPanel,
2145
+ useSandboxTerminalConnection,
2146
+ tabTerminalConnectionId,
2147
+ WorkspaceTerminalPanel,
2148
+ SeatPaywall,
2149
+ AgentSessionControls,
2150
+ formatModelCost,
2151
+ formatTokensPerSecond,
2152
+ RunDrillIn,
2153
+ pendingApprovalOf,
2154
+ ChatEmptyState,
2155
+ useThinkingSeconds,
2156
+ ChatMessages
2157
+ };
2158
+ //# sourceMappingURL=chunk-WV2U5UIB.js.map