@tangle-network/sandbox-ui 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/chat.d.ts CHANGED
@@ -48,6 +48,69 @@ interface ReasoningLevelPickerProps {
48
48
  declare const DEFAULT_REASONING_LEVEL_OPTIONS: ReadonlyArray<ReasoningLevelOption>;
49
49
  declare function ReasoningLevelPicker({ value, onChange, disabled, className, triggerClassName, options, available, side, avoidCollisions, }: ReasoningLevelPickerProps): react_jsx_runtime.JSX.Element;
50
50
 
51
+ /**
52
+ * An agent profile: a named bundle of toolset + persona layered over the same
53
+ * model and session. `builtin` profiles are the product's shipped modes (e.g.
54
+ * Studio / Assistant); the rest are user-saved custom agents. The concept is
55
+ * backend-agnostic — it applies to a router-backed agent and a sandbox-backed
56
+ * one alike, which is why it lives beside the model control rather than under
57
+ * the harness.
58
+ */
59
+ interface AgentProfileOption {
60
+ id: string;
61
+ name: string;
62
+ /** Builtin: a one-line description. Custom: derived "N tools" if omitted. */
63
+ description?: string;
64
+ /** Capability/tool-group ids this profile enables. */
65
+ capabilities?: ReadonlyArray<string>;
66
+ /** Persona / extra system instructions. */
67
+ instructions?: string;
68
+ /** A shipped mode vs a user-saved custom agent. */
69
+ builtin?: boolean;
70
+ }
71
+ /** A selectable tool group offered by the create/edit form. */
72
+ interface AgentProfileCapability {
73
+ id: string;
74
+ label: string;
75
+ description?: string;
76
+ }
77
+ /** The editable shape the create/edit form emits. */
78
+ interface AgentProfileDraft {
79
+ id?: string;
80
+ name: string;
81
+ capabilities: string[];
82
+ instructions: string;
83
+ }
84
+ interface AgentProfilePickerProps {
85
+ /** Selected profile id. */
86
+ value: string;
87
+ onChange: (id: string) => void;
88
+ profiles: ReadonlyArray<AgentProfileOption>;
89
+ /**
90
+ * Tool catalog for the create/edit form. Provide together with at least one
91
+ * write callback to enable authoring custom agents; omit for a
92
+ * selection-only picker (the New / edit / delete affordances stay hidden).
93
+ */
94
+ capabilities?: ReadonlyArray<AgentProfileCapability>;
95
+ onCreate?: (draft: AgentProfileDraft) => void | Promise<void>;
96
+ onUpdate?: (id: string, draft: AgentProfileDraft) => void | Promise<void>;
97
+ onDelete?: (id: string) => void | Promise<void>;
98
+ disabled?: boolean;
99
+ className?: string;
100
+ /** Side the menu opens toward. Defaults to up (composer-anchored). */
101
+ side?: "top" | "bottom";
102
+ }
103
+ /**
104
+ * Agent / mode switcher styled to match the chat control strip's pills
105
+ * (harness, model, effort). The pill shows the active profile; the menu lists
106
+ * builtin modes and saved custom agents. When a capability catalog and a write
107
+ * callback are supplied it also authors agents inline (name + tool toggles +
108
+ * persona) — a self-contained click-outside popover rather than a Radix menu,
109
+ * because the inline form needs its own focus and typing without the menu
110
+ * stealing keystrokes.
111
+ */
112
+ declare function AgentProfilePicker({ value, onChange, profiles, capabilities, onCreate, onUpdate, onDelete, disabled, className, side, }: AgentProfilePickerProps): react_jsx_runtime.JSX.Element;
113
+
51
114
  interface AgentSessionHarnessControl {
52
115
  value: HarnessType;
53
116
  onChange: (next: HarnessType) => void;
@@ -86,12 +149,33 @@ interface AgentSessionReasoningControl {
86
149
  */
87
150
  available?: ReadonlyArray<ReasoningEffort>;
88
151
  }
152
+ /**
153
+ * Agent-profile (mode / toolset / persona) selection. Backend-agnostic — it
154
+ * sits beside the model in both router-backed and sandbox-backed modes, so the
155
+ * same session can switch the agent's identity without touching the harness.
156
+ */
157
+ interface AgentSessionProfileControl {
158
+ value: string;
159
+ onChange: (id: string) => void;
160
+ profiles: ReadonlyArray<AgentProfileOption>;
161
+ /** Tool catalog for inline authoring; pair with a write callback to enable it. */
162
+ capabilities?: ReadonlyArray<AgentProfileCapability>;
163
+ onCreate?: (draft: AgentProfileDraft) => void | Promise<void>;
164
+ onUpdate?: (id: string, draft: AgentProfileDraft) => void | Promise<void>;
165
+ onDelete?: (id: string) => void | Promise<void>;
166
+ disabled?: boolean;
167
+ }
89
168
  interface AgentSessionControlsProps {
90
169
  /**
91
170
  * Harness (agent backend) selection. Switching harness usually means
92
171
  * re-creating the agent session — the consumer owns that lifecycle.
93
172
  */
94
173
  harness?: AgentSessionHarnessControl;
174
+ /**
175
+ * Agent profile (mode / toolset / persona). Universal — present in both
176
+ * router-backed and sandbox-backed composers, independent of the harness.
177
+ */
178
+ profile?: AgentSessionProfileControl;
95
179
  /** Per-turn model override, fed by the router's model catalog. */
96
180
  model?: AgentSessionModelControl;
97
181
  /** Thinking-effort level applied to subsequent turns. */
@@ -139,7 +223,49 @@ interface AgentSessionControlsProps {
139
223
  *
140
224
  * Designed to slot into `SandboxWorkbench`'s `session.composerControls`.
141
225
  */
142
- declare function AgentSessionControls({ harness, model, reasoning, trailing, className, context, layout, menuPlacement, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
226
+ declare function AgentSessionControls({ harness, profile, model, reasoning, trailing, className, context, layout, menuPlacement, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
227
+
228
+ interface AgentComposerProps {
229
+ /** Composer text (controlled). */
230
+ value: string;
231
+ onChange: (value: string) => void;
232
+ /** Fired on Enter (without Shift) or the send button. */
233
+ onSubmit: () => void;
234
+ placeholder?: string;
235
+ /** Disables typing and sending — e.g. while a turn is streaming. */
236
+ disabled?: boolean;
237
+ /** Spins the send button and blocks submit, without disabling the textarea. */
238
+ busy?: boolean;
239
+ /**
240
+ * Agent backend. Present → sandbox-backed (the harness pill shows and snaps
241
+ * with the model); omitted → router-backed (no harness, just model/effort).
242
+ */
243
+ harness?: AgentSessionHarnessControl;
244
+ /** Agent profile (mode / toolset / persona) — universal across both modes. */
245
+ profile?: AgentSessionProfileControl;
246
+ model?: AgentSessionModelControl;
247
+ reasoning?: AgentSessionReasoningControl;
248
+ /** Forwarded to the control strip (chat hides shell-only harnesses). */
249
+ context?: "chat" | "all";
250
+ /** Extra content left of the send button (token meter, cost, status). */
251
+ trailing?: React.ReactNode;
252
+ /** Minimum textarea rows before it grows. Default 2. */
253
+ minRows?: number;
254
+ /** Max pixel height before the textarea scrolls. Default 200. */
255
+ maxHeight?: number;
256
+ className?: string;
257
+ sendLabel?: string;
258
+ autoFocus?: boolean;
259
+ }
260
+ /**
261
+ * The canonical agent chat input: one rounded surface holding an auto-growing
262
+ * textarea, the embedded control strip (profile · harness · model · effort) at
263
+ * the bottom-left, and the send button at the bottom-right. The strip's pickers
264
+ * snap the harness↔model pair automatically; which controls appear is driven
265
+ * purely by which control objects are passed, so the same component is the
266
+ * router-backed composer (no harness) and the sandbox-backed one (with harness).
267
+ */
268
+ declare function AgentComposer({ value, onChange, onSubmit, placeholder, disabled, busy, harness, profile, model, reasoning, context, trailing, minRows, maxHeight, className, sendLabel, autoFocus, }: AgentComposerProps): react_jsx_runtime.JSX.Element;
143
269
 
144
270
  /**
145
271
  * Whether `harness` can run `modelId`. Router-backed harnesses, and provider-less
@@ -253,4 +379,4 @@ declare function createFetchTransport(opts: {
253
379
  fetchImpl?: typeof fetch;
254
380
  }): ArtifactAgentDockTransport;
255
381
 
256
- export { AgentSessionControls, type AgentSessionControlsProps, type AgentSessionHarnessControl, type AgentSessionModelControl, type AgentSessionReasoningControl, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, DEFAULT_REASONING_LEVEL_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport, isModelCompatibleWithHarness, snapModelToHarness };
382
+ export { AgentComposer, type AgentComposerProps, type AgentProfileCapability, type AgentProfileDraft, type AgentProfileOption, AgentProfilePicker, type AgentProfilePickerProps, AgentSessionControls, type AgentSessionControlsProps, type AgentSessionHarnessControl, type AgentSessionModelControl, type AgentSessionProfileControl, type AgentSessionReasoningControl, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, DEFAULT_REASONING_LEVEL_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport, isModelCompatibleWithHarness, snapModelToHarness };
package/dist/chat.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import {
2
+ AgentComposer,
3
+ AgentProfilePicker,
2
4
  AgentSessionControls,
3
5
  AgentTimeline,
4
6
  ArtifactAgentDock,
@@ -15,12 +17,14 @@ import {
15
17
  modelProvider,
16
18
  snapHarnessToModel,
17
19
  snapModelToHarness
18
- } from "./chunk-WLMLD2CZ.js";
20
+ } from "./chunk-4XOZGMJC.js";
19
21
  import "./chunk-HU4B2M4K.js";
20
22
  import "./chunk-SF6LPMMN.js";
21
23
  import "./chunk-EI44GEQ5.js";
22
24
  import "./chunk-U67V476Y.js";
23
25
  export {
26
+ AgentComposer,
27
+ AgentProfilePicker,
24
28
  AgentSessionControls,
25
29
  AgentTimeline,
26
30
  ArtifactAgentDock,
@@ -146,7 +146,7 @@ function ReasoningLevelPicker({
146
146
  // src/chat/agent-session-controls.tsx
147
147
  import { reasoningEffortsFor } from "@tangle-network/agent-interface";
148
148
  import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
149
- import { ChevronDown as ChevronDown2, Lock, SlidersHorizontal } from "lucide-react";
149
+ import { ChevronDown as ChevronDown3, Lock, SlidersHorizontal } from "lucide-react";
150
150
 
151
151
  // src/chat/harness-model-compat.ts
152
152
  import {
@@ -161,8 +161,293 @@ function snapModelToHarness(harness, modelId, models) {
161
161
  return snapModelIdToHarness(harness, modelId, models.map(canonicalModelId));
162
162
  }
163
163
 
164
- // src/chat/agent-session-controls.tsx
164
+ // src/chat/agent-profile-picker.tsx
165
+ import { Bot, Check, ChevronDown as ChevronDown2, Pencil, Plus, Trash2 } from "lucide-react";
166
+ import * as React from "react";
165
167
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
168
+ function useClickOutside(onOutside) {
169
+ const ref = React.useRef(null);
170
+ React.useEffect(() => {
171
+ function handler(event) {
172
+ if (ref.current && !ref.current.contains(event.target)) {
173
+ onOutside();
174
+ }
175
+ }
176
+ document.addEventListener("mousedown", handler);
177
+ return () => document.removeEventListener("mousedown", handler);
178
+ }, [onOutside]);
179
+ return ref;
180
+ }
181
+ function profileSubtitle(profile) {
182
+ if (profile.description) return profile.description;
183
+ const count = profile.capabilities?.length ?? 0;
184
+ return `${count} ${count === 1 ? "tool group" : "tool groups"}`;
185
+ }
186
+ function AgentProfilePicker({
187
+ value,
188
+ onChange,
189
+ profiles,
190
+ capabilities,
191
+ onCreate,
192
+ onUpdate,
193
+ onDelete,
194
+ disabled,
195
+ className,
196
+ side = "top"
197
+ }) {
198
+ const [open, setOpen] = React.useState(false);
199
+ const [composing, setComposing] = React.useState(
200
+ null
201
+ );
202
+ const ref = useClickOutside(() => {
203
+ setOpen(false);
204
+ setComposing(null);
205
+ });
206
+ const canAuthor = Boolean(capabilities && (onCreate || onUpdate));
207
+ const builtins = profiles.filter((profile) => profile.builtin);
208
+ const custom = profiles.filter((profile) => !profile.builtin);
209
+ const selected = profiles.find((profile) => profile.id === value);
210
+ function select(id) {
211
+ onChange(id);
212
+ setOpen(false);
213
+ }
214
+ async function save(draft) {
215
+ if (draft.id) await onUpdate?.(draft.id, draft);
216
+ else await onCreate?.(draft);
217
+ setComposing(null);
218
+ }
219
+ return /* @__PURE__ */ jsxs2("div", { ref, className: cn("relative inline-flex", className), children: [
220
+ /* @__PURE__ */ jsxs2(
221
+ "button",
222
+ {
223
+ type: "button",
224
+ disabled,
225
+ onClick: () => setOpen((v) => !v),
226
+ className: cn(
227
+ "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
228
+ "text-xs font-medium text-foreground shadow-sm transition-colors",
229
+ "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
230
+ "data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
231
+ "disabled:cursor-not-allowed disabled:opacity-60"
232
+ ),
233
+ "data-state": open ? "open" : "closed",
234
+ "aria-label": "Agent profile",
235
+ children: [
236
+ /* @__PURE__ */ jsx2(Bot, { className: "h-3.5 w-3.5 text-muted-foreground" }),
237
+ /* @__PURE__ */ jsx2("span", { className: "max-w-[140px] truncate", children: selected?.name ?? value }),
238
+ /* @__PURE__ */ jsx2(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
239
+ ]
240
+ }
241
+ ),
242
+ open && /* @__PURE__ */ jsx2(
243
+ "div",
244
+ {
245
+ className: cn(
246
+ "absolute left-0 z-50 w-[320px] overflow-hidden rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-1",
247
+ "shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
248
+ side === "top" ? "bottom-full mb-2" : "top-full mt-2"
249
+ ),
250
+ children: composing ? /* @__PURE__ */ jsx2(
251
+ ProfileForm,
252
+ {
253
+ capabilities: capabilities ?? [],
254
+ initial: composing === "new" ? void 0 : composing,
255
+ onCancel: () => setComposing(null),
256
+ onSave: save
257
+ }
258
+ ) : /* @__PURE__ */ jsxs2("div", { className: "max-h-[min(60vh,420px)] overflow-y-auto", children: [
259
+ /* @__PURE__ */ jsx2(SectionHeader, { children: "Modes" }),
260
+ builtins.map((profile) => /* @__PURE__ */ jsx2(
261
+ ProfileRow,
262
+ {
263
+ active: profile.id === value,
264
+ title: profile.name,
265
+ subtitle: profileSubtitle(profile),
266
+ onSelect: () => select(profile.id)
267
+ },
268
+ profile.id
269
+ )),
270
+ custom.length > 0 && /* @__PURE__ */ jsx2(SectionHeader, { children: "Your agents" }),
271
+ custom.map((profile) => /* @__PURE__ */ jsx2(
272
+ ProfileRow,
273
+ {
274
+ active: profile.id === value,
275
+ title: profile.name,
276
+ subtitle: profileSubtitle(profile),
277
+ onSelect: () => select(profile.id),
278
+ onEdit: canAuthor ? () => setComposing(profile) : void 0,
279
+ onDelete: onDelete ? () => void onDelete(profile.id) : void 0
280
+ },
281
+ profile.id
282
+ )),
283
+ canAuthor && /* @__PURE__ */ jsxs2(
284
+ "button",
285
+ {
286
+ type: "button",
287
+ onClick: () => setComposing("new"),
288
+ className: "mt-1 flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left text-sm text-[var(--accent-text)] transition-colors hover:bg-accent/40",
289
+ children: [
290
+ /* @__PURE__ */ jsx2(Plus, { className: "h-4 w-4" }),
291
+ " New agent\u2026"
292
+ ]
293
+ }
294
+ )
295
+ ] })
296
+ }
297
+ )
298
+ ] });
299
+ }
300
+ function SectionHeader({ children }) {
301
+ return /* @__PURE__ */ jsx2("div", { className: "px-2.5 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70", children });
302
+ }
303
+ function ProfileRow({
304
+ active,
305
+ title,
306
+ subtitle,
307
+ onSelect,
308
+ onEdit,
309
+ onDelete
310
+ }) {
311
+ return /* @__PURE__ */ jsxs2(
312
+ "div",
313
+ {
314
+ className: cn(
315
+ "group flex items-center gap-2 rounded-md px-2.5 py-2 text-sm transition-colors",
316
+ active ? "bg-[var(--accent-surface-soft)] text-[var(--accent-text)]" : "hover:bg-accent/40"
317
+ ),
318
+ children: [
319
+ /* @__PURE__ */ jsxs2(
320
+ "button",
321
+ {
322
+ type: "button",
323
+ onClick: onSelect,
324
+ className: "flex min-w-0 flex-1 items-center gap-2 text-left",
325
+ children: [
326
+ /* @__PURE__ */ jsx2("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: active && /* @__PURE__ */ jsx2(Check, { className: "h-3.5 w-3.5" }) }),
327
+ /* @__PURE__ */ jsxs2("span", { className: "min-w-0", children: [
328
+ /* @__PURE__ */ jsx2("span", { className: "block truncate font-medium", children: title }),
329
+ /* @__PURE__ */ jsx2("span", { className: "block truncate text-xs text-muted-foreground", children: subtitle })
330
+ ] })
331
+ ]
332
+ }
333
+ ),
334
+ (onEdit || onDelete) && /* @__PURE__ */ jsxs2("span", { className: "flex shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100", children: [
335
+ onEdit && /* @__PURE__ */ jsx2(
336
+ "button",
337
+ {
338
+ type: "button",
339
+ onClick: onEdit,
340
+ title: "Edit",
341
+ className: "rounded p-1 text-muted-foreground hover:text-foreground",
342
+ children: /* @__PURE__ */ jsx2(Pencil, { className: "h-3 w-3" })
343
+ }
344
+ ),
345
+ onDelete && /* @__PURE__ */ jsx2(
346
+ "button",
347
+ {
348
+ type: "button",
349
+ onClick: onDelete,
350
+ title: "Delete",
351
+ className: "rounded p-1 text-muted-foreground hover:text-[var(--status-error,#ff4d6d)]",
352
+ children: /* @__PURE__ */ jsx2(Trash2, { className: "h-3 w-3" })
353
+ }
354
+ )
355
+ ] })
356
+ ]
357
+ }
358
+ );
359
+ }
360
+ function ProfileForm({
361
+ capabilities,
362
+ initial,
363
+ onCancel,
364
+ onSave
365
+ }) {
366
+ const [name, setName] = React.useState(initial?.name ?? "");
367
+ const [caps, setCaps] = React.useState(
368
+ initial?.capabilities ? [...initial.capabilities] : capabilities.map((c) => c.id)
369
+ );
370
+ const [instructions, setInstructions] = React.useState(initial?.instructions ?? "");
371
+ const toggle = (id) => setCaps(
372
+ (prev) => prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]
373
+ );
374
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-3 p-2.5", children: [
375
+ /* @__PURE__ */ jsx2(
376
+ "input",
377
+ {
378
+ autoFocus: true,
379
+ value: name,
380
+ onChange: (event) => setName(event.target.value),
381
+ placeholder: "Agent name (e.g. Tax Reviewer)",
382
+ className: "w-full rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
383
+ }
384
+ ),
385
+ capabilities.length > 0 && /* @__PURE__ */ jsxs2("div", { children: [
386
+ /* @__PURE__ */ jsx2("p", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70", children: "Tools" }),
387
+ /* @__PURE__ */ jsx2("div", { className: "space-y-0.5", children: capabilities.map((cap) => /* @__PURE__ */ jsxs2(
388
+ "label",
389
+ {
390
+ className: "flex cursor-pointer items-start gap-2 rounded-md px-2 py-1.5 hover:bg-accent/40",
391
+ children: [
392
+ /* @__PURE__ */ jsx2(
393
+ "input",
394
+ {
395
+ type: "checkbox",
396
+ checked: caps.includes(cap.id),
397
+ onChange: () => toggle(cap.id),
398
+ className: "mt-0.5"
399
+ }
400
+ ),
401
+ /* @__PURE__ */ jsxs2("span", { className: "min-w-0", children: [
402
+ /* @__PURE__ */ jsx2("span", { className: "block text-sm font-medium", children: cap.label }),
403
+ cap.description && /* @__PURE__ */ jsx2("span", { className: "block text-xs text-muted-foreground", children: cap.description })
404
+ ] })
405
+ ]
406
+ },
407
+ cap.id
408
+ )) })
409
+ ] }),
410
+ /* @__PURE__ */ jsx2(
411
+ "textarea",
412
+ {
413
+ value: instructions,
414
+ onChange: (event) => setInstructions(event.target.value),
415
+ rows: 2,
416
+ placeholder: "Instructions (optional) \u2014 how this agent should behave",
417
+ className: "w-full resize-none rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
418
+ }
419
+ ),
420
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-end gap-2", children: [
421
+ /* @__PURE__ */ jsx2(
422
+ "button",
423
+ {
424
+ type: "button",
425
+ onClick: onCancel,
426
+ className: "rounded-lg px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent/40",
427
+ children: "Cancel"
428
+ }
429
+ ),
430
+ /* @__PURE__ */ jsx2(
431
+ "button",
432
+ {
433
+ type: "button",
434
+ disabled: !name.trim(),
435
+ onClick: () => onSave({
436
+ id: initial?.id,
437
+ name: name.trim(),
438
+ capabilities: caps,
439
+ instructions: instructions.trim()
440
+ }),
441
+ className: "rounded-lg bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-40",
442
+ children: "Save agent"
443
+ }
444
+ )
445
+ ] })
446
+ ] });
447
+ }
448
+
449
+ // src/chat/agent-session-controls.tsx
450
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
166
451
  function HarnessDropdown({
167
452
  value,
168
453
  onChange,
@@ -179,8 +464,8 @@ function HarnessDropdown({
179
464
  );
180
465
  const options = HARNESS_OPTIONS.filter((h) => allowed.has(h.type));
181
466
  const selected = options.find((option) => option.type === value);
182
- return /* @__PURE__ */ jsxs2(DropdownMenu2.Root, { children: [
183
- /* @__PURE__ */ jsx2(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs2(
467
+ return /* @__PURE__ */ jsxs3(DropdownMenu2.Root, { children: [
468
+ /* @__PURE__ */ jsx3(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
184
469
  "button",
185
470
  {
186
471
  type: "button",
@@ -195,13 +480,13 @@ function HarnessDropdown({
195
480
  ),
196
481
  "aria-label": "Agent harness",
197
482
  children: [
198
- locked ? /* @__PURE__ */ jsx2(Lock, { className: "h-3 w-3 text-muted-foreground" }) : /* @__PURE__ */ jsx2(HarnessLogo, { type: value, size: 16 }),
199
- /* @__PURE__ */ jsx2("span", { children: selected?.label ?? value }),
200
- !locked && /* @__PURE__ */ jsx2(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
483
+ locked ? /* @__PURE__ */ jsx3(Lock, { className: "h-3 w-3 text-muted-foreground" }) : /* @__PURE__ */ jsx3(HarnessLogo, { type: value, size: 16 }),
484
+ /* @__PURE__ */ jsx3("span", { children: selected?.label ?? value }),
485
+ !locked && /* @__PURE__ */ jsx3(ChevronDown3, { className: "h-3.5 w-3.5 text-muted-foreground" })
201
486
  ]
202
487
  }
203
488
  ) }),
204
- /* @__PURE__ */ jsx2(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx2(
489
+ /* @__PURE__ */ jsx3(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx3(
205
490
  DropdownMenu2.Content,
206
491
  {
207
492
  side,
@@ -219,7 +504,7 @@ function HarnessDropdown({
219
504
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
220
505
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
221
506
  ),
222
- children: options.map((option) => /* @__PURE__ */ jsxs2(
507
+ children: options.map((option) => /* @__PURE__ */ jsxs3(
223
508
  DropdownMenu2.Item,
224
509
  {
225
510
  onSelect: (event) => {
@@ -232,10 +517,10 @@ function HarnessDropdown({
232
517
  option.type === value && "bg-primary/10 font-medium text-foreground ring-1 ring-inset ring-primary/25 hover:bg-primary/15 focus:bg-primary/15"
233
518
  ),
234
519
  children: [
235
- /* @__PURE__ */ jsx2(HarnessLogo, { type: option.type, size: 20, className: "mt-0.5" }),
236
- /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
237
- /* @__PURE__ */ jsx2("span", { className: "text-sm font-medium", children: option.label }),
238
- option.description && /* @__PURE__ */ jsx2("span", { className: "text-xs text-muted-foreground", children: option.description })
520
+ /* @__PURE__ */ jsx3(HarnessLogo, { type: option.type, size: 20, className: "mt-0.5" }),
521
+ /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
522
+ /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: option.label }),
523
+ option.description && /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", children: option.description })
239
524
  ] })
240
525
  ]
241
526
  },
@@ -247,6 +532,7 @@ function HarnessDropdown({
247
532
  }
248
533
  function AgentSessionControls({
249
534
  harness,
535
+ profile,
250
536
  model,
251
537
  reasoning,
252
538
  trailing,
@@ -255,7 +541,7 @@ function AgentSessionControls({
255
541
  layout = "inline",
256
542
  menuPlacement = "auto"
257
543
  }) {
258
- if (!harness && !model && !reasoning && !trailing) return null;
544
+ if (!harness && !profile && !model && !reasoning && !trailing) return null;
259
545
  const handleHarnessChange = (next) => {
260
546
  harness?.onChange(next);
261
547
  if (model) {
@@ -281,7 +567,7 @@ function AgentSessionControls({
281
567
  const menuAlign = layout === "gear" ? "end" : "start";
282
568
  const inlineForceDown = menuPlacement === "down" && layout !== "gear";
283
569
  const avoidCollisions = inlineForceDown ? false : void 0;
284
- const harnessNode = harness && /* @__PURE__ */ jsx2(
570
+ const harnessNode = harness && /* @__PURE__ */ jsx3(
285
571
  HarnessDropdown,
286
572
  {
287
573
  ...harness,
@@ -292,7 +578,21 @@ function AgentSessionControls({
292
578
  avoidCollisions
293
579
  }
294
580
  );
295
- const modelNode = model && /* @__PURE__ */ jsx2(
581
+ const profileNode = profile && /* @__PURE__ */ jsx3(
582
+ AgentProfilePicker,
583
+ {
584
+ value: profile.value,
585
+ onChange: profile.onChange,
586
+ profiles: profile.profiles,
587
+ capabilities: profile.capabilities,
588
+ onCreate: profile.onCreate,
589
+ onUpdate: profile.onUpdate,
590
+ onDelete: profile.onDelete,
591
+ disabled: profile.disabled,
592
+ side: inlineForceDown || layout === "gear" ? "bottom" : "top"
593
+ }
594
+ );
595
+ const modelNode = model && /* @__PURE__ */ jsx3(
296
596
  ModelPicker,
297
597
  {
298
598
  variant: "pill",
@@ -315,7 +615,7 @@ function AgentSessionControls({
315
615
  supportsReasoning: selectedModel.supportsReasoning,
316
616
  maxEffort: selectedModel.maxReasoningEffort
317
617
  } : null;
318
- const reasoningNode = reasoning && /* @__PURE__ */ jsx2(
618
+ const reasoningNode = reasoning && /* @__PURE__ */ jsx3(
319
619
  ReasoningLevelPicker,
320
620
  {
321
621
  value: reasoning.value,
@@ -328,10 +628,11 @@ function AgentSessionControls({
328
628
  }
329
629
  );
330
630
  if (layout === "gear") {
331
- return /* @__PURE__ */ jsx2(
631
+ return /* @__PURE__ */ jsx3(
332
632
  GearControls,
333
633
  {
334
634
  className,
635
+ profileNode,
335
636
  harnessNode,
336
637
  modelNode,
337
638
  reasoningNode,
@@ -339,36 +640,38 @@ function AgentSessionControls({
339
640
  }
340
641
  );
341
642
  }
342
- return /* @__PURE__ */ jsxs2(
643
+ return /* @__PURE__ */ jsxs3(
343
644
  "div",
344
645
  {
345
646
  className: cn("flex flex-wrap items-center gap-2", className),
346
647
  "data-testid": "agent-session-controls",
347
648
  children: [
649
+ profileNode,
348
650
  harnessNode,
349
651
  modelNode,
350
652
  reasoningNode,
351
- trailing && /* @__PURE__ */ jsx2("div", { className: "ml-auto flex items-center gap-2", children: trailing })
653
+ trailing && /* @__PURE__ */ jsx3("div", { className: "ml-auto flex items-center gap-2", children: trailing })
352
654
  ]
353
655
  }
354
656
  );
355
657
  }
356
658
  function GearControls({
357
659
  className,
660
+ profileNode,
358
661
  harnessNode,
359
662
  modelNode,
360
663
  reasoningNode,
361
664
  trailing
362
665
  }) {
363
- return /* @__PURE__ */ jsxs2(
666
+ return /* @__PURE__ */ jsxs3(
364
667
  "div",
365
668
  {
366
669
  className: cn("flex items-center gap-2", className),
367
670
  "data-testid": "agent-session-controls",
368
671
  children: [
369
- trailing && /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2", children: trailing }),
370
- /* @__PURE__ */ jsxs2(DropdownMenu2.Root, { children: [
371
- /* @__PURE__ */ jsx2(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx2(
672
+ trailing && /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-2", children: trailing }),
673
+ /* @__PURE__ */ jsxs3(DropdownMenu2.Root, { children: [
674
+ /* @__PURE__ */ jsx3(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx3(
372
675
  "button",
373
676
  {
374
677
  type: "button",
@@ -379,10 +682,10 @@ function GearControls({
379
682
  "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
380
683
  "data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high"
381
684
  ),
382
- children: /* @__PURE__ */ jsx2(SlidersHorizontal, { className: "h-4 w-4 text-muted-foreground" })
685
+ children: /* @__PURE__ */ jsx3(SlidersHorizontal, { className: "h-4 w-4 text-muted-foreground" })
383
686
  }
384
687
  ) }),
385
- /* @__PURE__ */ jsx2(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs2(
688
+ /* @__PURE__ */ jsx3(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs3(
386
689
  DropdownMenu2.Content,
387
690
  {
388
691
  side: "top",
@@ -404,9 +707,10 @@ function GearControls({
404
707
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
405
708
  ),
406
709
  children: [
407
- harnessNode && /* @__PURE__ */ jsx2(GearSection, { label: "Harness", children: harnessNode }),
408
- modelNode && /* @__PURE__ */ jsx2(GearSection, { label: "Model", children: modelNode }),
409
- reasoningNode && /* @__PURE__ */ jsx2(GearSection, { label: "Effort", children: reasoningNode })
710
+ profileNode && /* @__PURE__ */ jsx3(GearSection, { label: "Agent", children: profileNode }),
711
+ harnessNode && /* @__PURE__ */ jsx3(GearSection, { label: "Harness", children: harnessNode }),
712
+ modelNode && /* @__PURE__ */ jsx3(GearSection, { label: "Model", children: modelNode }),
713
+ reasoningNode && /* @__PURE__ */ jsx3(GearSection, { label: "Effort", children: reasoningNode })
410
714
  ]
411
715
  }
412
716
  ) })
@@ -419,17 +723,123 @@ function GearSection({
419
723
  label,
420
724
  children
421
725
  }) {
422
- return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
423
- /* @__PURE__ */ jsx2("span", { className: "px-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/70", children: label }),
726
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-1", children: [
727
+ /* @__PURE__ */ jsx3("span", { className: "px-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/70", children: label }),
424
728
  children
425
729
  ] });
426
730
  }
427
731
 
732
+ // src/chat/agent-composer.tsx
733
+ import { ArrowUp, Loader2 } from "lucide-react";
734
+ import * as React2 from "react";
735
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
736
+ function AgentComposer({
737
+ value,
738
+ onChange,
739
+ onSubmit,
740
+ placeholder = "Send a message\u2026",
741
+ disabled,
742
+ busy,
743
+ harness,
744
+ profile,
745
+ model,
746
+ reasoning,
747
+ context = "chat",
748
+ trailing,
749
+ minRows = 2,
750
+ maxHeight = 200,
751
+ className,
752
+ sendLabel = "Send",
753
+ autoFocus
754
+ }) {
755
+ const textareaRef = React2.useRef(null);
756
+ const resize = React2.useCallback(
757
+ (el) => {
758
+ el.style.height = "auto";
759
+ el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
760
+ },
761
+ [maxHeight]
762
+ );
763
+ React2.useEffect(() => {
764
+ if (textareaRef.current) resize(textareaRef.current);
765
+ }, [value, resize]);
766
+ const canSend = !disabled && !busy && value.trim().length > 0;
767
+ const submit = () => {
768
+ if (!canSend) return;
769
+ onSubmit();
770
+ };
771
+ return /* @__PURE__ */ jsxs4(
772
+ "div",
773
+ {
774
+ "data-testid": "agent-composer",
775
+ className: cn(
776
+ "flex flex-col gap-2 rounded-2xl border border-[var(--md3-outline-variant)] bg-surface-container-high p-2.5 shadow-sm transition-colors",
777
+ "focus-within:border-primary/60",
778
+ disabled && "opacity-60",
779
+ className
780
+ ),
781
+ children: [
782
+ /* @__PURE__ */ jsx4(
783
+ "textarea",
784
+ {
785
+ ref: textareaRef,
786
+ rows: minRows,
787
+ value,
788
+ disabled,
789
+ autoFocus,
790
+ onChange: (event) => onChange(event.target.value),
791
+ onInput: (event) => resize(event.currentTarget),
792
+ onKeyDown: (event) => {
793
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing && event.keyCode !== 229) {
794
+ event.preventDefault();
795
+ submit();
796
+ }
797
+ },
798
+ placeholder,
799
+ className: cn(
800
+ "w-full resize-none bg-transparent px-2 py-1 text-sm leading-relaxed text-foreground outline-none",
801
+ "placeholder:text-muted-foreground"
802
+ )
803
+ }
804
+ ),
805
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
806
+ /* @__PURE__ */ jsx4(
807
+ AgentSessionControls,
808
+ {
809
+ className: "min-w-0 flex-1",
810
+ context,
811
+ harness,
812
+ profile,
813
+ model,
814
+ reasoning
815
+ }
816
+ ),
817
+ trailing && /* @__PURE__ */ jsx4("div", { className: "flex shrink-0 items-center gap-2", children: trailing }),
818
+ /* @__PURE__ */ jsx4(
819
+ "button",
820
+ {
821
+ type: "button",
822
+ onClick: submit,
823
+ disabled: !canSend,
824
+ "aria-label": sendLabel,
825
+ className: cn(
826
+ "ml-auto flex size-9 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground",
827
+ "transition-opacity hover:opacity-90 disabled:opacity-40"
828
+ ),
829
+ children: busy ? /* @__PURE__ */ jsx4(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx4(ArrowUp, { className: "size-4" })
830
+ }
831
+ )
832
+ ] })
833
+ ]
834
+ }
835
+ );
836
+ }
837
+
428
838
  // src/chat/artifact-agent-dock.tsx
429
- import * as React from "react";
839
+ import * as React3 from "react";
430
840
  import { Sparkles as Sparkles2, Send, Square, X } from "lucide-react";
431
841
  import { ChatMessage } from "@tangle-network/ui/chat";
432
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
842
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
433
843
  function defaultScopeLabel(scope) {
434
844
  if (scope.label) return scope.label;
435
845
  switch (scope.kind) {
@@ -475,23 +885,23 @@ function ArtifactAgentDock({
475
885
  onError,
476
886
  className
477
887
  }) {
478
- const [threadId, setThreadId] = React.useState(null);
479
- const [messages, setMessages] = React.useState([]);
480
- const [composer, setComposer] = React.useState(defaultPrompt ?? "");
481
- const [streamContent, setStreamContent] = React.useState("");
482
- const [sending, setSending] = React.useState(false);
483
- const [loading, setLoading] = React.useState(false);
484
- const abortRef = React.useRef(null);
485
- const scrollRef = React.useRef(null);
486
- const scopeTokenRef = React.useRef(0);
487
- const reportError = React.useCallback(
888
+ const [threadId, setThreadId] = React3.useState(null);
889
+ const [messages, setMessages] = React3.useState([]);
890
+ const [composer, setComposer] = React3.useState(defaultPrompt ?? "");
891
+ const [streamContent, setStreamContent] = React3.useState("");
892
+ const [sending, setSending] = React3.useState(false);
893
+ const [loading, setLoading] = React3.useState(false);
894
+ const abortRef = React3.useRef(null);
895
+ const scrollRef = React3.useRef(null);
896
+ const scopeTokenRef = React3.useRef(0);
897
+ const reportError = React3.useCallback(
488
898
  (message) => {
489
899
  if (onError) onError(message);
490
900
  else if (typeof console !== "undefined") console.error("[ArtifactAgentDock]", message);
491
901
  },
492
902
  [onError]
493
903
  );
494
- React.useEffect(() => {
904
+ React3.useEffect(() => {
495
905
  if (!open) return;
496
906
  scopeTokenRef.current += 1;
497
907
  abortRef.current?.abort();
@@ -527,11 +937,11 @@ function ArtifactAgentDock({
527
937
  cancelled = true;
528
938
  };
529
939
  }, [open, scope.kind, scope.key, transport, reportError]);
530
- React.useEffect(() => {
940
+ React3.useEffect(() => {
531
941
  if (!scrollRef.current) return;
532
942
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
533
943
  }, [messages.length, streamContent]);
534
- const send = React.useCallback(async () => {
944
+ const send = React3.useCallback(async () => {
535
945
  const text = composer.trim();
536
946
  if (!text || !threadId || sending) return;
537
947
  const token = scopeTokenRef.current;
@@ -581,7 +991,7 @@ function ArtifactAgentDock({
581
991
  if (abortRef.current === controller) abortRef.current = null;
582
992
  }
583
993
  }, [composer, threadId, sending, transport, reportError]);
584
- const cancel = React.useCallback(() => {
994
+ const cancel = React3.useCallback(() => {
585
995
  if (threadId && transport.cancel) {
586
996
  try {
587
997
  void Promise.resolve(transport.cancel({ threadId })).catch(() => {
@@ -595,7 +1005,7 @@ function ArtifactAgentDock({
595
1005
  }, [threadId, transport]);
596
1006
  if (!open) return null;
597
1007
  const heading = defaultScopeLabel(scope);
598
- return /* @__PURE__ */ jsxs3(
1008
+ return /* @__PURE__ */ jsxs5(
599
1009
  "aside",
600
1010
  {
601
1011
  className: cn(
@@ -604,38 +1014,38 @@ function ArtifactAgentDock({
604
1014
  ),
605
1015
  "aria-label": `Agent chat about ${heading}`,
606
1016
  children: [
607
- /* @__PURE__ */ jsxs3("header", { className: "flex items-center justify-between border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-4 py-3", children: [
608
- /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
609
- /* @__PURE__ */ jsx3(Sparkles2, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
610
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
611
- /* @__PURE__ */ jsx3("div", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: "Discussing" }),
612
- /* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-semibold text-foreground", children: heading })
1017
+ /* @__PURE__ */ jsxs5("header", { className: "flex items-center justify-between border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-4 py-3", children: [
1018
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
1019
+ /* @__PURE__ */ jsx5(Sparkles2, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
1020
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
1021
+ /* @__PURE__ */ jsx5("div", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: "Discussing" }),
1022
+ /* @__PURE__ */ jsx5("div", { className: "truncate text-sm font-semibold text-foreground", children: heading })
613
1023
  ] })
614
1024
  ] }),
615
- /* @__PURE__ */ jsx3(
1025
+ /* @__PURE__ */ jsx5(
616
1026
  "button",
617
1027
  {
618
1028
  type: "button",
619
1029
  onClick: () => onOpenChange(false),
620
1030
  "aria-label": "Close artifact chat",
621
1031
  className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
622
- children: /* @__PURE__ */ jsx3(X, { className: "h-4 w-4" })
1032
+ children: /* @__PURE__ */ jsx5(X, { className: "h-4 w-4" })
623
1033
  }
624
1034
  )
625
1035
  ] }),
626
- /* @__PURE__ */ jsx3("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-3 py-4", children: loading ? /* @__PURE__ */ jsx3("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "Loading conversation\u2026" }) : messages.length === 0 && !streamContent ? /* @__PURE__ */ jsxs3("div", { className: "px-3 py-12 text-center", children: [
627
- /* @__PURE__ */ jsx3(Sparkles2, { className: "mx-auto mb-3 h-5 w-5 text-muted-foreground/40" }),
628
- /* @__PURE__ */ jsxs3("p", { className: "text-sm text-foreground", children: [
1036
+ /* @__PURE__ */ jsx5("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-3 py-4", children: loading ? /* @__PURE__ */ jsx5("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "Loading conversation\u2026" }) : messages.length === 0 && !streamContent ? /* @__PURE__ */ jsxs5("div", { className: "px-3 py-12 text-center", children: [
1037
+ /* @__PURE__ */ jsx5(Sparkles2, { className: "mx-auto mb-3 h-5 w-5 text-muted-foreground/40" }),
1038
+ /* @__PURE__ */ jsxs5("p", { className: "text-sm text-foreground", children: [
629
1039
  "Ask the agent about this ",
630
1040
  scope.kind === "vault-file" ? "document" : scope.kind,
631
1041
  "."
632
1042
  ] }),
633
- /* @__PURE__ */ jsx3("p", { className: "mt-1 text-xs text-muted-foreground", children: "It can read the current contents and propose edits, rewrites, or follow-ups." })
634
- ] }) : /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
635
- messages.map((m) => /* @__PURE__ */ jsx3(ChatMessage, { role: m.role, content: m.content, timestamp: m.createdAt }, m.id)),
636
- streamContent && /* @__PURE__ */ jsx3(ChatMessage, { role: "assistant", content: streamContent, isStreaming: true })
1043
+ /* @__PURE__ */ jsx5("p", { className: "mt-1 text-xs text-muted-foreground", children: "It can read the current contents and propose edits, rewrites, or follow-ups." })
1044
+ ] }) : /* @__PURE__ */ jsxs5("div", { className: "space-y-3", children: [
1045
+ messages.map((m) => /* @__PURE__ */ jsx5(ChatMessage, { role: m.role, content: m.content, timestamp: m.createdAt }, m.id)),
1046
+ streamContent && /* @__PURE__ */ jsx5(ChatMessage, { role: "assistant", content: streamContent, isStreaming: true })
637
1047
  ] }) }),
638
- /* @__PURE__ */ jsx3(
1048
+ /* @__PURE__ */ jsx5(
639
1049
  "form",
640
1050
  {
641
1051
  className: "border-t border-[var(--md3-outline-variant)] bg-surface-container-low p-3",
@@ -643,8 +1053,8 @@ function ArtifactAgentDock({
643
1053
  e.preventDefault();
644
1054
  void send();
645
1055
  },
646
- children: /* @__PURE__ */ jsxs3("div", { className: "flex items-end gap-2", children: [
647
- /* @__PURE__ */ jsx3(
1056
+ children: /* @__PURE__ */ jsxs5("div", { className: "flex items-end gap-2", children: [
1057
+ /* @__PURE__ */ jsx5(
648
1058
  "textarea",
649
1059
  {
650
1060
  value: composer,
@@ -661,23 +1071,23 @@ function ArtifactAgentDock({
661
1071
  className: "min-h-[44px] flex-1 resize-none rounded-[20px] border border-[var(--md3-outline-variant)] bg-surface-dim px-3.5 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:outline-none focus:border-[var(--border-accent)] disabled:opacity-50"
662
1072
  }
663
1073
  ),
664
- sending ? /* @__PURE__ */ jsx3(
1074
+ sending ? /* @__PURE__ */ jsx5(
665
1075
  "button",
666
1076
  {
667
1077
  type: "button",
668
1078
  onClick: cancel,
669
1079
  "aria-label": "Cancel",
670
1080
  className: "inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
671
- children: /* @__PURE__ */ jsx3(Square, { className: "h-4 w-4" })
1081
+ children: /* @__PURE__ */ jsx5(Square, { className: "h-4 w-4" })
672
1082
  }
673
- ) : /* @__PURE__ */ jsx3(
1083
+ ) : /* @__PURE__ */ jsx5(
674
1084
  "button",
675
1085
  {
676
1086
  type: "submit",
677
1087
  disabled: !composer.trim() || !threadId,
678
1088
  "aria-label": "Send",
679
1089
  className: "inline-flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50",
680
- children: /* @__PURE__ */ jsx3(Send, { className: "h-4 w-4" })
1090
+ children: /* @__PURE__ */ jsx5(Send, { className: "h-4 w-4" })
681
1091
  }
682
1092
  )
683
1093
  ] })
@@ -770,7 +1180,9 @@ export {
770
1180
  snapModelToHarness,
771
1181
  modelProvider,
772
1182
  snapHarnessToModel,
1183
+ AgentProfilePicker,
773
1184
  AgentSessionControls,
1185
+ AgentComposer,
774
1186
  ArtifactAgentDock,
775
1187
  createFetchTransport,
776
1188
  AgentTimeline,
package/dist/globals.css CHANGED
@@ -404,6 +404,9 @@
404
404
  .bottom-\[-0\.75rem\] {
405
405
  bottom: -0.75rem;
406
406
  }
407
+ .bottom-full {
408
+ bottom: 100%;
409
+ }
407
410
  .left-0 {
408
411
  left: calc(var(--spacing) * 0);
409
412
  }
@@ -660,6 +663,14 @@
660
663
  width: calc(var(--spacing) * 3);
661
664
  height: calc(var(--spacing) * 3);
662
665
  }
666
+ .size-4 {
667
+ width: calc(var(--spacing) * 4);
668
+ height: calc(var(--spacing) * 4);
669
+ }
670
+ .size-9 {
671
+ width: calc(var(--spacing) * 9);
672
+ height: calc(var(--spacing) * 9);
673
+ }
663
674
  .\!h-2 {
664
675
  height: calc(var(--spacing) * 2) !important;
665
676
  }
@@ -825,6 +836,9 @@
825
836
  .max-h-\[160px\] {
826
837
  max-height: 160px;
827
838
  }
839
+ .max-h-\[min\(60vh\,420px\)\] {
840
+ max-height: min(60vh, 420px);
841
+ }
828
842
  .max-h-\[min\(440px\,var\(--radix-dropdown-menu-content-available-height\)\)\] {
829
843
  max-height: min(440px, var(--radix-dropdown-menu-content-available-height));
830
844
  }
@@ -885,6 +899,9 @@
885
899
  .min-h-\[300px\] {
886
900
  min-height: 300px;
887
901
  }
902
+ .min-h-\[640px\] {
903
+ min-height: 640px;
904
+ }
888
905
  .min-h-full {
889
906
  min-height: 100%;
890
907
  }
@@ -1050,6 +1067,9 @@
1050
1067
  .w-\[300px\] {
1051
1068
  width: 300px;
1052
1069
  }
1070
+ .w-\[320px\] {
1071
+ width: 320px;
1072
+ }
1053
1073
  .w-\[360px\] {
1054
1074
  width: 360px;
1055
1075
  }
@@ -3375,6 +3395,9 @@
3375
3395
  .accent-primary {
3376
3396
  accent-color: var(--color-primary);
3377
3397
  }
3398
+ .\[color-scheme\:dark\] {
3399
+ color-scheme: dark;
3400
+ }
3378
3401
  .opacity-0 {
3379
3402
  opacity: 0%;
3380
3403
  }
@@ -3839,6 +3862,14 @@
3839
3862
  border-color: var(--color-border);
3840
3863
  }
3841
3864
  }
3865
+ .focus-within\:border-primary\/60 {
3866
+ &:focus-within {
3867
+ border-color: color-mix(in srgb, hsl(var(--primary)) 60%, transparent);
3868
+ @supports (color: color-mix(in lab, red, red)) {
3869
+ border-color: color-mix(in oklab, var(--color-primary) 60%, transparent);
3870
+ }
3871
+ }
3872
+ }
3842
3873
  .focus-within\:shadow-\[var\(--chat-input-focus-shadow\,0_10px_30px_rgba\(15\,23\,42\,0\.08\)\)\] {
3843
3874
  &:focus-within {
3844
3875
  --tw-shadow: var(--chat-input-focus-shadow,0 10px 30px rgba(15,23,42,0.08));
@@ -4355,6 +4386,13 @@
4355
4386
  }
4356
4387
  }
4357
4388
  }
4389
+ .hover\:text-\[var\(--status-error\,\#ff4d6d\)\] {
4390
+ &:hover {
4391
+ @media (hover: hover) {
4392
+ color: var(--status-error,#ff4d6d);
4393
+ }
4394
+ }
4395
+ }
4358
4396
  .hover\:text-\[var\(--surface-danger-text\)\] {
4359
4397
  &:hover {
4360
4398
  @media (hover: hover) {
@@ -4475,6 +4513,11 @@
4475
4513
  border-color: var(--border-accent-hover);
4476
4514
  }
4477
4515
  }
4516
+ .focus\:border-primary {
4517
+ &:focus {
4518
+ border-color: var(--color-primary);
4519
+ }
4520
+ }
4478
4521
  .focus\:bg-accent {
4479
4522
  &:focus {
4480
4523
  background-color: var(--color-accent);
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { AgentWorkbench, ApprovalConfidenceStat, ApprovalItem, ApprovalQueue, Ap
5
5
  export { ArtifactView, BRAND_PRISM_THEME, CodeSurface, CodeSurfaceProps, CodeView, CodeViewProps, DiffStats, DiffView, DiffViewProps, FileBreadcrumb, FileBreadcrumbProps, GitStatusBadge, PanelHeader, PanelHeaderProps, PillTabItem, PillTabs, PillTabsProps, PreviewView, PreviewViewProps, SandboxArtifact, SandboxArtifactPane, SandboxArtifactPaneProps, SandboxArtifactTerminal, buildUnifiedPatch, computeDiffStats, resolveLanguage } from './workbench.js';
6
6
  export { OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifactRendererProps, OpenUIBadgeNode, OpenUICardNode, OpenUICodeNode, OpenUIComponentNode, OpenUIGridNode, OpenUIHeadingNode, OpenUIKeyValueNode, OpenUIMarkdownNode, OpenUIPrimitive, OpenUISeparatorNode, OpenUIStackNode, OpenUIStatNode, OpenUITableNode, OpenUITextNode } from '@tangle-network/ui/openui';
7
7
  export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatInput, ChatInputProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, PendingFile, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from '@tangle-network/ui/chat';
8
- export { AgentSessionControls, AgentSessionControlsProps, AgentSessionHarnessControl, AgentSessionModelControl, AgentSessionReasoningControl, ArtifactAgentDock, ArtifactAgentDockProps, ArtifactAgentDockTransport, ArtifactDockMessage, ArtifactDockStreamEvent, ArtifactKind, ArtifactScope, DEFAULT_REASONING_LEVEL_OPTIONS, ReasoningLevel, ReasoningLevelOption, ReasoningLevelPicker, ReasoningLevelPickerProps, createFetchTransport, isModelCompatibleWithHarness, snapModelToHarness } from './chat.js';
8
+ export { AgentComposer, AgentComposerProps, AgentProfileCapability, AgentProfileDraft, AgentProfileOption, AgentProfilePicker, AgentProfilePickerProps, AgentSessionControls, AgentSessionControlsProps, AgentSessionHarnessControl, AgentSessionModelControl, AgentSessionProfileControl, AgentSessionReasoningControl, ArtifactAgentDock, ArtifactAgentDockProps, ArtifactAgentDockTransport, ArtifactDockMessage, ArtifactDockStreamEvent, ArtifactKind, ArtifactScope, DEFAULT_REASONING_LEVEL_OPTIONS, ReasoningLevel, ReasoningLevelOption, ReasoningLevelPicker, ReasoningLevelPickerProps, createFetchTransport, isModelCompatibleWithHarness, snapModelToHarness } from './chat.js';
9
9
  export { ExpandedToolDetail, ExpandedToolDetailProps, InlineThinkingItem, InlineThinkingItemProps, InlineToolItem, InlineToolItemProps, LiveDuration, RunGroup, RunGroupProps } from '@tangle-network/ui/run';
10
10
  export { CommandPreview, DiffPreview, GlobResultsPreview, GrepResultsPreview, QuestionPreview, WebSearchPreview, WriteFilePreview } from '@tangle-network/ui/tool-previews';
11
11
  export { FileArtifactPane, FileArtifactPaneProps, FileNode, FilePreview, FilePreviewProps, FileTabData, FileTabs, FileTabsProps, FileTree, FileTreeProps, FileTreeVisibilityOptions, RichFileTree, RichFileTreeGitEntry, RichFileTreeGitStatus, RichFileTreeProps, RichFileTreeThemeVars, filterFileTree } from '@tangle-network/ui/files';
package/dist/index.js CHANGED
@@ -74,6 +74,8 @@ import {
74
74
  UserMenu
75
75
  } from "./chunk-IOB2PW5Z.js";
76
76
  import {
77
+ AgentComposer,
78
+ AgentProfilePicker,
77
79
  AgentSessionControls,
78
80
  AgentTimeline,
79
81
  ArtifactAgentDock,
@@ -90,7 +92,7 @@ import {
90
92
  modelProvider,
91
93
  snapHarnessToModel,
92
94
  snapModelToHarness
93
- } from "./chunk-WLMLD2CZ.js";
95
+ } from "./chunk-4XOZGMJC.js";
94
96
  import {
95
97
  TOOL_CATEGORY_ICONS,
96
98
  cn,
@@ -351,6 +353,8 @@ import {
351
353
  import { Markdown } from "@tangle-network/ui/markdown";
352
354
  export {
353
355
  ActivityFeed,
356
+ AgentComposer,
357
+ AgentProfilePicker,
354
358
  AgentSessionControls,
355
359
  AgentTimeline,
356
360
  AgentWorkbench,
package/dist/styles.css CHANGED
@@ -404,6 +404,9 @@
404
404
  .bottom-\[-0\.75rem\] {
405
405
  bottom: -0.75rem;
406
406
  }
407
+ .bottom-full {
408
+ bottom: 100%;
409
+ }
407
410
  .left-0 {
408
411
  left: calc(var(--spacing) * 0);
409
412
  }
@@ -660,6 +663,14 @@
660
663
  width: calc(var(--spacing) * 3);
661
664
  height: calc(var(--spacing) * 3);
662
665
  }
666
+ .size-4 {
667
+ width: calc(var(--spacing) * 4);
668
+ height: calc(var(--spacing) * 4);
669
+ }
670
+ .size-9 {
671
+ width: calc(var(--spacing) * 9);
672
+ height: calc(var(--spacing) * 9);
673
+ }
663
674
  .\!h-2 {
664
675
  height: calc(var(--spacing) * 2) !important;
665
676
  }
@@ -825,6 +836,9 @@
825
836
  .max-h-\[160px\] {
826
837
  max-height: 160px;
827
838
  }
839
+ .max-h-\[min\(60vh\,420px\)\] {
840
+ max-height: min(60vh, 420px);
841
+ }
828
842
  .max-h-\[min\(440px\,var\(--radix-dropdown-menu-content-available-height\)\)\] {
829
843
  max-height: min(440px, var(--radix-dropdown-menu-content-available-height));
830
844
  }
@@ -885,6 +899,9 @@
885
899
  .min-h-\[300px\] {
886
900
  min-height: 300px;
887
901
  }
902
+ .min-h-\[640px\] {
903
+ min-height: 640px;
904
+ }
888
905
  .min-h-full {
889
906
  min-height: 100%;
890
907
  }
@@ -1050,6 +1067,9 @@
1050
1067
  .w-\[300px\] {
1051
1068
  width: 300px;
1052
1069
  }
1070
+ .w-\[320px\] {
1071
+ width: 320px;
1072
+ }
1053
1073
  .w-\[360px\] {
1054
1074
  width: 360px;
1055
1075
  }
@@ -3375,6 +3395,9 @@
3375
3395
  .accent-primary {
3376
3396
  accent-color: var(--color-primary);
3377
3397
  }
3398
+ .\[color-scheme\:dark\] {
3399
+ color-scheme: dark;
3400
+ }
3378
3401
  .opacity-0 {
3379
3402
  opacity: 0%;
3380
3403
  }
@@ -3839,6 +3862,14 @@
3839
3862
  border-color: var(--color-border);
3840
3863
  }
3841
3864
  }
3865
+ .focus-within\:border-primary\/60 {
3866
+ &:focus-within {
3867
+ border-color: color-mix(in srgb, hsl(var(--primary)) 60%, transparent);
3868
+ @supports (color: color-mix(in lab, red, red)) {
3869
+ border-color: color-mix(in oklab, var(--color-primary) 60%, transparent);
3870
+ }
3871
+ }
3872
+ }
3842
3873
  .focus-within\:shadow-\[var\(--chat-input-focus-shadow\,0_10px_30px_rgba\(15\,23\,42\,0\.08\)\)\] {
3843
3874
  &:focus-within {
3844
3875
  --tw-shadow: var(--chat-input-focus-shadow,0 10px 30px rgba(15,23,42,0.08));
@@ -4355,6 +4386,13 @@
4355
4386
  }
4356
4387
  }
4357
4388
  }
4389
+ .hover\:text-\[var\(--status-error\,\#ff4d6d\)\] {
4390
+ &:hover {
4391
+ @media (hover: hover) {
4392
+ color: var(--status-error,#ff4d6d);
4393
+ }
4394
+ }
4395
+ }
4358
4396
  .hover\:text-\[var\(--surface-danger-text\)\] {
4359
4397
  &:hover {
4360
4398
  @media (hover: hover) {
@@ -4475,6 +4513,11 @@
4475
4513
  border-color: var(--border-accent-hover);
4476
4514
  }
4477
4515
  }
4516
+ .focus\:border-primary {
4517
+ &:focus {
4518
+ border-color: var(--color-primary);
4519
+ }
4520
+ }
4478
4521
  .focus\:bg-accent {
4479
4522
  &:focus {
4480
4523
  background-color: var(--color-accent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",