@tangle-network/sandbox-ui 0.26.0 → 0.27.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
@@ -2,8 +2,8 @@ import { MessageRole } from '@tangle-network/ui/chat';
2
2
  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';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- import { M as ModelInfo } from './model-picker-DUfMTQo5.js';
6
- import { e as HarnessType } from './harness-picker-ppDe7ap-.js';
5
+ import { M as ModelInfo } from './model-picker-Cmisf9Y8.js';
6
+ import { e as HarnessType } from './harness-picker-lyeEncN4.js';
7
7
 
8
8
  /**
9
9
  * Thinking-effort ladder — the superset of the per-harness reasoning scales so
@@ -77,6 +77,22 @@ interface AgentSessionControlsProps {
77
77
  /** Right-aligned extra content (token meter, cost, status). */
78
78
  trailing?: React.ReactNode;
79
79
  className?: string;
80
+ /**
81
+ * Which surface these controls live on. `"chat"` (default) restricts the
82
+ * harness list to chat-capable backends — shell-only `cli-base` is hidden
83
+ * because it has no conversational agent. `"all"` keeps every harness for
84
+ * scheduled / non-chat surfaces. An explicit `harness.available` list still
85
+ * wins; this only trims the default-everything set.
86
+ */
87
+ context?: "chat" | "all";
88
+ /**
89
+ * Trigger layout. `"inline"` (default) lays the pickers out in a row — the
90
+ * existing behavior. `"gear"` collapses them behind a single compact gear
91
+ * button whose menu opens up-and-left, for right-anchored copilots where
92
+ * the composer is tight. Nested model/harness/effort menus render adjacent
93
+ * on the left of the gear menu.
94
+ */
95
+ layout?: "inline" | "gear";
80
96
  }
81
97
  /**
82
98
  * Compact control strip for an agent chat composer: harness, model, and
@@ -92,7 +108,7 @@ interface AgentSessionControlsProps {
92
108
  *
93
109
  * Designed to slot into `SandboxWorkbench`'s `session.composerControls`.
94
110
  */
95
- declare function AgentSessionControls({ harness, model, reasoning, trailing, className, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
111
+ declare function AgentSessionControls({ harness, model, reasoning, trailing, className, context, layout, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
96
112
 
97
113
  /**
98
114
  * Harness ↔ model compatibility policy.
package/dist/chat.js CHANGED
@@ -16,9 +16,9 @@ import {
16
16
  modelProvider,
17
17
  snapHarnessToModel,
18
18
  snapModelToHarness
19
- } from "./chunk-KANKBACI.js";
20
- import "./chunk-HXIYUQN2.js";
21
- import "./chunk-JDMX4HHN.js";
19
+ } from "./chunk-BJFWPP5T.js";
20
+ import "./chunk-ELGN7DPS.js";
21
+ import "./chunk-6NQBODYV.js";
22
22
  import "./chunk-EI44GEQ5.js";
23
23
  export {
24
24
  AgentSessionControls,
@@ -145,6 +145,38 @@ function canonicalModelId(model) {
145
145
  const provider = model._provider ?? model.provider;
146
146
  return provider ? `${provider}/${id}` : id;
147
147
  }
148
+ function modelDedupKey(model) {
149
+ const canonical = canonicalModelId(model).toLowerCase();
150
+ const segments = canonical.split("/").filter(Boolean);
151
+ const modelId = segments[segments.length - 1] ?? canonical;
152
+ const lab = resolveModelBrandIdentity(model).lab.key;
153
+ return lab === "unknown" ? canonical : `${lab}/${modelId}`;
154
+ }
155
+ function dedupeModels(models) {
156
+ const byKey = /* @__PURE__ */ new Map();
157
+ const result = [];
158
+ for (const model of models) {
159
+ const key = modelDedupKey(model);
160
+ const existingIndex = byKey.get(key);
161
+ if (existingIndex === void 0) {
162
+ byKey.set(key, result.length);
163
+ result.push(model);
164
+ continue;
165
+ }
166
+ const existing = result[existingIndex];
167
+ if (!existing.featured && model.featured) {
168
+ result[existingIndex] = model;
169
+ }
170
+ }
171
+ return result;
172
+ }
173
+ var DEFAULT_FEATURED_MODEL_IDS = [
174
+ "anthropic/claude-opus-4-8",
175
+ "anthropic/claude-sonnet-4-6",
176
+ "openai/gpt-5.4",
177
+ "google/gemini-2.5-pro",
178
+ "deepseek/deepseek-v3"
179
+ ];
148
180
  function formatPricing(pricing) {
149
181
  const prompt = Number(pricing?.prompt ?? 0);
150
182
  const completion = Number(pricing?.completion ?? 0);
@@ -229,11 +261,12 @@ function ModelPicker({
229
261
  });
230
262
  return () => cancelAnimationFrame(frame);
231
263
  }, [open]);
264
+ const deduped = React.useMemo(() => dedupeModels(models), [models]);
232
265
  const filtered = React.useMemo(() => {
233
266
  const excluded = new Set((excludeProviders ?? []).map((p) => p.toLowerCase()));
234
267
  const allowedModalities = modalities ? new Set(modalities) : null;
235
268
  const q = query.trim().toLowerCase();
236
- return models.filter((m) => {
269
+ return deduped.filter((m) => {
237
270
  const provider = (m._provider ?? m.provider ?? "").toLowerCase();
238
271
  if (excluded.has(provider)) return false;
239
272
  if (allowedModalities && m.architecture?.modality && !allowedModalities.has(m.architecture.modality)) return false;
@@ -243,7 +276,7 @@ function ModelPicker({
243
276
  const identity = resolveModelBrandIdentity(m);
244
277
  return id.includes(q) || name.includes(q) || provider.includes(q) || identity.host.label.toLowerCase().includes(q) || identity.lab.label.toLowerCase().includes(q);
245
278
  });
246
- }, [models, query, modalities, excludeProviders]);
279
+ }, [deduped, query, modalities, excludeProviders]);
247
280
  const grouped = React.useMemo(() => {
248
281
  const groups = /* @__PURE__ */ new Map();
249
282
  for (const m of filtered) {
@@ -270,6 +303,18 @@ function ModelPicker({
270
303
  const lookup = new Map(models.map((m) => [canonicalModelId(m), m]));
271
304
  return popular.map((id) => lookup.get(id)).filter((m) => Boolean(m));
272
305
  }, [popular, models]);
306
+ const featuredModels = React.useMemo(() => {
307
+ const flagged = deduped.filter((m) => m.featured);
308
+ if (flagged.length > 0) return flagged;
309
+ const seedKeys = new Set(
310
+ DEFAULT_FEATURED_MODEL_IDS.map((id) => modelDedupKey({ id }))
311
+ );
312
+ const seeded = deduped.filter((m) => seedKeys.has(modelDedupKey(m)));
313
+ const order = DEFAULT_FEATURED_MODEL_IDS.map((id) => modelDedupKey({ id }));
314
+ return seeded.sort(
315
+ (a, b) => order.indexOf(modelDedupKey(a)) - order.indexOf(modelDedupKey(b))
316
+ );
317
+ }, [deduped]);
273
318
  const handleSelect = (id) => {
274
319
  onChange(id);
275
320
  setOpen(false);
@@ -363,6 +408,16 @@ function ModelPicker({
363
408
  loading && /* @__PURE__ */ jsx2(Loader2, { className: "h-3.5 w-3.5 animate-spin text-muted-foreground" })
364
409
  ] }),
365
410
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 overflow-y-auto", children: [
411
+ !query && featuredModels.length > 0 && /* @__PURE__ */ jsx2(Section, { label: "Recommended", tone: "featured", children: featuredModels.map((m) => /* @__PURE__ */ jsx2(
412
+ ModelRow,
413
+ {
414
+ model: m,
415
+ active: canonicalModelId(m) === value,
416
+ onSelect: handleSelect,
417
+ featured: true
418
+ },
419
+ `featured-${canonicalModelId(m)}`
420
+ )) }),
366
421
  !query && popularModels.length > 0 && /* @__PURE__ */ jsx2(Section, { label: "Top models", tone: "featured", children: popularModels.map((m) => /* @__PURE__ */ jsx2(
367
422
  ModelRow,
368
423
  {
@@ -395,9 +450,9 @@ function ModelPicker({
395
450
  /* @__PURE__ */ jsxs2("div", { className: "border-t border-border px-3 py-1.5 text-[10px] text-muted-foreground", children: [
396
451
  filtered.length,
397
452
  " of ",
398
- models.length,
453
+ deduped.length,
399
454
  " model",
400
- models.length === 1 ? "" : "s"
455
+ deduped.length === 1 ? "" : "s"
401
456
  ] })
402
457
  ]
403
458
  }
@@ -438,11 +493,11 @@ function PickerItem({
438
493
  onSelect();
439
494
  },
440
495
  className: cn(
441
- "flex cursor-pointer items-start gap-2 px-3 py-2 outline-none",
496
+ "flex cursor-pointer items-start gap-2 rounded-md px-3 py-2 outline-none",
442
497
  "transition-colors duration-[var(--transition-fast)]",
443
- "hover:bg-accent/40 focus:bg-accent/40",
444
- featured && "mx-1 rounded-lg border border-primary/10 bg-primary/[0.035] px-2.5",
445
- active && "bg-[var(--accent-surface-soft)] text-[var(--accent-text)]"
498
+ "hover:bg-accent/50 focus:bg-accent/50",
499
+ featured && "mx-1 border border-primary/10 bg-primary/[0.04] px-2.5",
500
+ active && "bg-primary/10 font-medium text-foreground ring-1 ring-inset ring-primary/25 hover:bg-primary/15 focus:bg-primary/15"
446
501
  ),
447
502
  children
448
503
  }
@@ -715,6 +770,9 @@ var BRAND_INFO = {
715
770
  export {
716
771
  moonshot_default,
717
772
  canonicalModelId,
773
+ modelDedupKey,
774
+ dedupeModels,
775
+ DEFAULT_FEATURED_MODEL_IDS,
718
776
  formatPricing,
719
777
  formatContext,
720
778
  ModelPicker
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  HARNESS_OPTIONS,
3
- HarnessLogo
4
- } from "./chunk-HXIYUQN2.js";
3
+ HarnessLogo,
4
+ chatCapableHarnesses
5
+ } from "./chunk-ELGN7DPS.js";
5
6
  import {
6
7
  ModelPicker,
7
8
  canonicalModelId
8
- } from "./chunk-JDMX4HHN.js";
9
+ } from "./chunk-6NQBODYV.js";
9
10
  import {
10
11
  cn
11
12
  } from "./chunk-EI44GEQ5.js";
@@ -145,7 +146,7 @@ function ReasoningLevelPicker({
145
146
 
146
147
  // src/chat/agent-session-controls.tsx
147
148
  import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
148
- import { ChevronDown as ChevronDown2, Lock } from "lucide-react";
149
+ import { ChevronDown as ChevronDown2, Lock, SlidersHorizontal } from "lucide-react";
149
150
 
150
151
  // src/chat/harness-model-compat.ts
151
152
  var HARNESS_MODEL_POLICIES = {
@@ -222,7 +223,9 @@ function HarnessDropdown({
222
223
  available,
223
224
  disabled,
224
225
  locked,
225
- lockReason
226
+ lockReason,
227
+ side = "bottom",
228
+ align = "start"
226
229
  }) {
227
230
  const allowed = new Set(
228
231
  available ?? HARNESS_OPTIONS.map((h) => h.type)
@@ -239,8 +242,8 @@ function HarnessDropdown({
239
242
  className: cn(
240
243
  "inline-flex h-8 items-center gap-1.5 rounded-lg border border-border bg-card px-2.5",
241
244
  "text-xs font-medium text-foreground shadow-sm transition-colors",
242
- "hover:border-primary/30 hover:bg-accent/30 focus:outline-none focus:border-primary/40",
243
- "data-[state=open]:border-primary/40 data-[state=open]:bg-accent/30",
245
+ "hover:border-primary/30 hover:bg-accent/40 focus:outline-none focus:border-primary/40",
246
+ "data-[state=open]:border-primary/40 data-[state=open]:bg-accent/40",
244
247
  "disabled:cursor-not-allowed disabled:opacity-60"
245
248
  ),
246
249
  "aria-label": "Agent harness",
@@ -254,7 +257,8 @@ function HarnessDropdown({
254
257
  /* @__PURE__ */ jsx2(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx2(
255
258
  DropdownMenu2.Content,
256
259
  {
257
- align: "start",
260
+ side,
261
+ align,
258
262
  sideOffset: 6,
259
263
  className: cn(
260
264
  "z-50 w-72 overflow-hidden rounded-[var(--radius-md)] border border-border bg-card p-1",
@@ -272,8 +276,8 @@ function HarnessDropdown({
272
276
  },
273
277
  className: cn(
274
278
  "flex cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-2 outline-none",
275
- "transition-colors hover:bg-accent/40 focus:bg-accent/40",
276
- option.type === value && "bg-[var(--accent-surface-soft)] text-[var(--accent-text)]"
279
+ "transition-colors hover:bg-accent/50 focus:bg-accent/50",
280
+ 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"
277
281
  ),
278
282
  children: [
279
283
  /* @__PURE__ */ jsx2(HarnessLogo, { type: option.type, size: 20, className: "mt-0.5" }),
@@ -294,7 +298,9 @@ function AgentSessionControls({
294
298
  model,
295
299
  reasoning,
296
300
  trailing,
297
- className
301
+ className,
302
+ context = "chat",
303
+ layout = "inline"
298
304
  }) {
299
305
  if (!harness && !model && !reasoning && !trailing) return null;
300
306
  const handleHarnessChange = (next) => {
@@ -317,41 +323,139 @@ function AgentSessionControls({
317
323
  canonicalModelId(entry)
318
324
  )
319
325
  ) : model?.models;
326
+ const harnessAvailable = harness?.available ?? (context === "chat" ? chatCapableHarnesses : void 0);
327
+ const menuSide = layout === "gear" ? "left" : "bottom";
328
+ const menuAlign = layout === "gear" ? "end" : "start";
329
+ const harnessNode = harness && /* @__PURE__ */ jsx2(
330
+ HarnessDropdown,
331
+ {
332
+ ...harness,
333
+ available: harnessAvailable,
334
+ onChange: handleHarnessChange,
335
+ side: menuSide,
336
+ align: menuAlign
337
+ }
338
+ );
339
+ const modelNode = model && /* @__PURE__ */ jsx2(
340
+ ModelPicker,
341
+ {
342
+ variant: "pill",
343
+ label: "",
344
+ value: model.value,
345
+ onChange: handleModelChange,
346
+ models: visibleModels ?? [],
347
+ loading: model.loading,
348
+ popular: model.popular,
349
+ recents: model.recents,
350
+ disabled: model.disabled || (visibleModels ?? []).length === 0
351
+ }
352
+ );
353
+ const reasoningNode = reasoning && /* @__PURE__ */ jsx2(
354
+ ReasoningLevelPicker,
355
+ {
356
+ value: reasoning.value,
357
+ onChange: reasoning.onChange,
358
+ options: reasoning.options,
359
+ disabled: reasoning.disabled
360
+ }
361
+ );
362
+ if (layout === "gear") {
363
+ return /* @__PURE__ */ jsx2(
364
+ GearControls,
365
+ {
366
+ className,
367
+ harnessNode,
368
+ modelNode,
369
+ reasoningNode,
370
+ trailing
371
+ }
372
+ );
373
+ }
320
374
  return /* @__PURE__ */ jsxs2(
321
375
  "div",
322
376
  {
323
377
  className: cn("flex flex-wrap items-center gap-2", className),
324
378
  "data-testid": "agent-session-controls",
325
379
  children: [
326
- harness && /* @__PURE__ */ jsx2(HarnessDropdown, { ...harness, onChange: handleHarnessChange }),
327
- model && /* @__PURE__ */ jsx2(
328
- ModelPicker,
329
- {
330
- variant: "pill",
331
- label: "",
332
- value: model.value,
333
- onChange: handleModelChange,
334
- models: visibleModels ?? [],
335
- loading: model.loading,
336
- popular: model.popular,
337
- recents: model.recents,
338
- disabled: model.disabled || (visibleModels ?? []).length === 0
339
- }
340
- ),
341
- reasoning && /* @__PURE__ */ jsx2(
342
- ReasoningLevelPicker,
343
- {
344
- value: reasoning.value,
345
- onChange: reasoning.onChange,
346
- options: reasoning.options,
347
- disabled: reasoning.disabled
348
- }
349
- ),
380
+ harnessNode,
381
+ modelNode,
382
+ reasoningNode,
350
383
  trailing && /* @__PURE__ */ jsx2("div", { className: "ml-auto flex items-center gap-2", children: trailing })
351
384
  ]
352
385
  }
353
386
  );
354
387
  }
388
+ function GearControls({
389
+ className,
390
+ harnessNode,
391
+ modelNode,
392
+ reasoningNode,
393
+ trailing
394
+ }) {
395
+ return /* @__PURE__ */ jsxs2(
396
+ "div",
397
+ {
398
+ className: cn("flex items-center gap-2", className),
399
+ "data-testid": "agent-session-controls",
400
+ children: [
401
+ trailing && /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2", children: trailing }),
402
+ /* @__PURE__ */ jsxs2(DropdownMenu2.Root, { children: [
403
+ /* @__PURE__ */ jsx2(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx2(
404
+ "button",
405
+ {
406
+ type: "button",
407
+ "aria-label": "Session controls",
408
+ className: cn(
409
+ "inline-flex h-8 w-8 items-center justify-center rounded-lg border border-border bg-card",
410
+ "text-foreground shadow-sm transition-colors",
411
+ "hover:border-primary/30 hover:bg-accent/40 focus:outline-none focus:border-primary/40",
412
+ "data-[state=open]:border-primary/40 data-[state=open]:bg-accent/40"
413
+ ),
414
+ children: /* @__PURE__ */ jsx2(SlidersHorizontal, { className: "h-4 w-4 text-muted-foreground" })
415
+ }
416
+ ) }),
417
+ /* @__PURE__ */ jsx2(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs2(
418
+ DropdownMenu2.Content,
419
+ {
420
+ side: "top",
421
+ align: "end",
422
+ sideOffset: 8,
423
+ onInteractOutside: (event) => {
424
+ const target = event.target;
425
+ if (target?.closest(
426
+ "[data-radix-popper-content-wrapper],[data-radix-menu-content],[role=menu],[role=listbox]"
427
+ )) {
428
+ event.preventDefault();
429
+ }
430
+ },
431
+ className: cn(
432
+ "z-50 flex w-56 flex-col gap-2 rounded-[var(--radius-md)] border border-border bg-card p-2",
433
+ "shadow-[var(--shadow-dropdown)]",
434
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
435
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
436
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
437
+ ),
438
+ children: [
439
+ harnessNode && /* @__PURE__ */ jsx2(GearSection, { label: "Harness", children: harnessNode }),
440
+ modelNode && /* @__PURE__ */ jsx2(GearSection, { label: "Model", children: modelNode }),
441
+ reasoningNode && /* @__PURE__ */ jsx2(GearSection, { label: "Effort", children: reasoningNode })
442
+ ]
443
+ }
444
+ ) })
445
+ ] })
446
+ ]
447
+ }
448
+ );
449
+ }
450
+ function GearSection({
451
+ label,
452
+ children
453
+ }) {
454
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
455
+ /* @__PURE__ */ jsx2("span", { className: "px-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/70", children: label }),
456
+ children
457
+ ] });
458
+ }
355
459
 
356
460
  // src/chat/artifact-agent-dock.tsx
357
461
  import * as React from "react";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  moonshot_default
3
- } from "./chunk-JDMX4HHN.js";
3
+ } from "./chunk-6NQBODYV.js";
4
4
  import {
5
5
  cn
6
6
  } from "./chunk-EI44GEQ5.js";
@@ -166,54 +166,65 @@ var HARNESS_OPTIONS = [
166
166
  {
167
167
  type: "opencode",
168
168
  label: "OpenCode",
169
- description: "Default agent \u2014 broad model support, deterministic streaming"
169
+ description: "Default agent \u2014 broad model support, deterministic streaming",
170
+ chatCapable: true
170
171
  },
171
172
  {
172
173
  type: "claude-code",
173
174
  label: "Claude Code",
174
- description: "Native Claude skills and tools (requires ANTHROPIC_API_KEY)"
175
+ description: "Native Claude skills and tools (requires ANTHROPIC_API_KEY)",
176
+ chatCapable: true
175
177
  },
176
178
  {
177
179
  type: "codex",
178
180
  label: "Codex",
179
- description: "OpenAI Codex CLI (requires OPENAI_API_KEY)"
181
+ description: "OpenAI Codex CLI (requires OPENAI_API_KEY)",
182
+ chatCapable: true
180
183
  },
181
184
  {
182
185
  type: "amp",
183
186
  label: "AMP",
184
- description: "Sourcegraph AMP agent"
187
+ description: "Sourcegraph AMP agent",
188
+ chatCapable: true
185
189
  },
186
190
  {
187
191
  type: "factory-droids",
188
192
  label: "Factory Droids",
189
- description: "Factory Droid agent"
193
+ description: "Factory Droid agent",
194
+ chatCapable: true
190
195
  },
191
196
  {
192
197
  type: "kimi-code",
193
198
  label: "Kimi Code",
194
- description: "Moonshot Kimi Code CLI (Kimi subscription OAuth or MOONSHOT_API_KEY)"
199
+ description: "Moonshot Kimi Code CLI (Kimi subscription OAuth or MOONSHOT_API_KEY)",
200
+ chatCapable: true
195
201
  },
196
202
  {
197
203
  type: "openclaw",
198
204
  label: "OpenClaw",
199
- description: "Dispatcher routing to Claude / Codex / Gemini CLIs"
205
+ description: "Dispatcher routing to Claude / Codex / Gemini CLIs",
206
+ chatCapable: true
200
207
  },
201
208
  {
202
209
  type: "nanoclaw",
203
210
  label: "NanoClaw",
204
- description: "Local socket-bridge agent backend"
211
+ description: "Local socket-bridge agent backend",
212
+ chatCapable: true
205
213
  },
206
214
  {
207
215
  type: "hermes",
208
216
  label: "Hermes",
209
- description: "Hermes inference-router agent"
217
+ description: "Hermes inference-router agent",
218
+ chatCapable: true
210
219
  },
211
220
  {
212
221
  type: "cli-base",
213
222
  label: "CLI base (no agent)",
214
- description: "Shell tools only \u2014 for non-AI scheduled tasks"
223
+ description: "Shell tools only \u2014 for non-AI scheduled tasks",
224
+ chatCapable: false
215
225
  }
216
226
  ];
227
+ var chatCapableHarnesses = HARNESS_OPTIONS.filter((h) => h.chatCapable).map((h) => h.type);
217
228
  function HarnessPicker({
218
229
  value,
219
230
  onChange,
@@ -249,5 +260,6 @@ export {
249
260
  HARNESS_BRAND,
250
261
  HarnessLogo,
251
262
  HARNESS_OPTIONS,
263
+ chatCapableHarnesses,
252
264
  HarnessPicker
253
265
  };
@@ -975,31 +975,31 @@ function SessionSidebar({
975
975
  className: cn("relative flex shrink-0 flex-col border-r border-border bg-card", className),
976
976
  style: { width: resizable ? resize.width : defaultWidth },
977
977
  children: [
978
- /* @__PURE__ */ jsxs7("div", { className: "border-b border-border px-3 py-2.5", children: [
979
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-2", children: [
980
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 min-w-0", children: [
981
- /* @__PURE__ */ jsx7("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary", children: /* @__PURE__ */ jsx7(MessageSquareText, { className: "h-3 w-3" }) }),
982
- /* @__PURE__ */ jsxs7("div", { className: "min-w-0", children: [
983
- /* @__PURE__ */ jsx7("div", { className: "truncate text-xs font-semibold text-foreground", children: title }),
984
- subtitle && /* @__PURE__ */ jsx7("div", { className: "truncate text-[10px] text-muted-foreground", children: subtitle })
985
- ] })
986
- ] }),
987
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5 shrink-0", children: [
988
- runningCount > 0 && /* @__PURE__ */ jsx7("span", { className: "rounded-full border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] px-1.5 py-px text-[10px] font-medium text-primary", children: runningCount }),
989
- onCreate && /* @__PURE__ */ jsx7(
990
- "button",
991
- {
992
- type: "button",
993
- onClick: onCreate,
994
- title: createLabel,
995
- className: "flex h-6 w-6 items-center justify-center rounded-[var(--radius-sm)] border border-border text-muted-foreground transition-colors hover:bg-[var(--accent-surface-soft)] hover:text-foreground",
996
- children: /* @__PURE__ */ jsx7(Plus, { className: "h-3 w-3" })
997
- }
998
- )
978
+ /* @__PURE__ */ jsx7("div", { className: "flex h-14 shrink-0 items-center border-b border-border px-3", children: /* @__PURE__ */ jsxs7("div", { className: "flex w-full items-center justify-between gap-2", children: [
979
+ /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 items-center gap-2", children: [
980
+ /* @__PURE__ */ jsx7("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary", children: /* @__PURE__ */ jsx7(MessageSquareText, { className: "h-3.5 w-3.5" }) }),
981
+ /* @__PURE__ */ jsxs7("div", { className: "min-w-0", children: [
982
+ /* @__PURE__ */ jsx7("div", { className: "truncate text-sm font-semibold text-foreground", children: title }),
983
+ subtitle && /* @__PURE__ */ jsx7("div", { className: "truncate text-xs text-muted-foreground", children: subtitle })
999
984
  ] })
1000
985
  ] }),
1001
- enableSearch && items.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "relative mt-2", children: [
1002
- /* @__PURE__ */ jsx7(Search2, { className: "pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" }),
986
+ /* @__PURE__ */ jsxs7("div", { className: "flex shrink-0 items-center gap-1.5", children: [
987
+ runningCount > 0 && /* @__PURE__ */ jsx7("span", { className: "rounded-full border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] px-1.5 py-px text-xs font-medium text-primary", children: runningCount }),
988
+ onCreate && /* @__PURE__ */ jsx7(
989
+ "button",
990
+ {
991
+ type: "button",
992
+ onClick: onCreate,
993
+ title: createLabel,
994
+ className: "flex h-7 w-7 items-center justify-center rounded-[var(--radius-sm)] border border-border text-muted-foreground transition-colors hover:bg-[var(--accent-surface-soft)] hover:text-foreground",
995
+ children: /* @__PURE__ */ jsx7(Plus, { className: "h-3.5 w-3.5" })
996
+ }
997
+ )
998
+ ] })
999
+ ] }) }),
1000
+ (enableSearch && items.length > 0 || filters.length > 0) && /* @__PURE__ */ jsxs7("div", { className: "shrink-0 border-b border-border px-3 py-2.5", children: [
1001
+ enableSearch && items.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
1002
+ /* @__PURE__ */ jsx7(Search2, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
1003
1003
  /* @__PURE__ */ jsx7(
1004
1004
  "input",
1005
1005
  {
@@ -1007,11 +1007,11 @@ function SessionSidebar({
1007
1007
  onChange: (event) => setQuery(event.target.value),
1008
1008
  placeholder: searchPlaceholder,
1009
1009
  "aria-label": searchPlaceholder,
1010
- className: "h-7 w-full rounded-[var(--radius-sm)] border border-border bg-muted pl-7 pr-2 text-xs text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-accent)]"
1010
+ className: "h-8 w-full rounded-[var(--radius-sm)] border border-border bg-muted pl-7 pr-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-accent)]"
1011
1011
  }
1012
1012
  )
1013
1013
  ] }),
1014
- filters.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mt-1.5 flex flex-wrap gap-1", children: filters.map((filter) => {
1014
+ filters.length > 0 && /* @__PURE__ */ jsx7("div", { className: cn("flex flex-wrap gap-1", enableSearch && items.length > 0 && "mt-2"), children: filters.map((filter) => {
1015
1015
  const isSelected = activeFilterId === filter.id;
1016
1016
  return /* @__PURE__ */ jsxs7(
1017
1017
  "button",
@@ -1019,19 +1019,19 @@ function SessionSidebar({
1019
1019
  type: "button",
1020
1020
  onClick: () => setActiveFilterId(filter.id),
1021
1021
  className: cn(
1022
- "inline-flex items-center gap-1 rounded-full border px-2 py-px text-[10px] font-medium transition-colors",
1022
+ "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors",
1023
1023
  isSelected ? "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary" : "border-border text-muted-foreground hover:text-foreground"
1024
1024
  ),
1025
1025
  children: [
1026
1026
  /* @__PURE__ */ jsx7("span", { children: filter.label }),
1027
- /* @__PURE__ */ jsx7("span", { className: "text-[9px] opacity-60", children: filterCounts[filter.id] ?? 0 })
1027
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] opacity-60", children: filterCounts[filter.id] ?? 0 })
1028
1028
  ]
1029
1029
  },
1030
1030
  filter.id
1031
1031
  );
1032
1032
  }) })
1033
1033
  ] }),
1034
- /* @__PURE__ */ jsx7("nav", { "aria-label": "Sessions", className: "flex-1 overflow-y-auto px-1.5 py-1.5", children: visibleItems.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "rounded-[var(--radius-lg)] border border-dashed border-border px-3 py-3 text-xs text-muted-foreground", children: query.trim() ? `No sessions match "${query.trim()}".` : emptyMessage }) : /* @__PURE__ */ jsx7("ul", { className: "space-y-px", children: visibleItems.map((item) => {
1034
+ /* @__PURE__ */ jsx7("nav", { "aria-label": "Sessions", className: "flex-1 overflow-y-auto px-1.5 py-1.5", children: visibleItems.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "rounded-[var(--radius-lg)] border border-dashed border-border px-3 py-3 text-sm text-muted-foreground", children: query.trim() ? `No sessions match "${query.trim()}".` : emptyMessage }) : /* @__PURE__ */ jsx7("ul", { className: "space-y-px", children: visibleItems.map((item) => {
1035
1035
  const session = sessionsById.get(item.id) ?? null;
1036
1036
  const isActive = currentItemId === item.id;
1037
1037
  const status = session?.status ?? item.status;
@@ -1043,8 +1043,8 @@ function SessionSidebar({
1043
1043
  "div",
1044
1044
  {
1045
1045
  className: cn(
1046
- "group relative flex items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5 transition-colors",
1047
- isActive ? "bg-[var(--accent-surface-soft)] text-foreground shadow-[inset_2px_0_0_hsl(var(--primary))]" : "text-muted-foreground hover:bg-muted"
1046
+ "group relative flex items-center gap-2 rounded-[var(--radius-sm)] px-3 py-2 transition-colors",
1047
+ isActive ? "bg-primary/10 font-medium text-foreground" : "text-muted-foreground hover:bg-accent/30"
1048
1048
  ),
1049
1049
  children: [
1050
1050
  /* @__PURE__ */ jsxs7(
@@ -1064,10 +1064,10 @@ function SessionSidebar({
1064
1064
  /* @__PURE__ */ jsx7("span", { className: cn("h-1.5 w-1.5 shrink-0 rounded-full", statusDot(status)) }),
1065
1065
  /* @__PURE__ */ jsxs7("div", { className: "min-w-0 flex-1", children: [
1066
1066
  /* @__PURE__ */ jsx7("div", { className: cn(
1067
- "truncate text-xs",
1068
- isActive ? "font-semibold text-foreground" : "font-medium"
1067
+ "truncate text-sm font-medium",
1068
+ isActive && "text-foreground"
1069
1069
  ), children: item.title }),
1070
- item.subtitle && /* @__PURE__ */ jsx7("div", { className: "truncate text-[10px] leading-tight text-muted-foreground", children: item.subtitle })
1070
+ item.subtitle && /* @__PURE__ */ jsx7("div", { className: "truncate text-xs leading-tight text-muted-foreground", children: item.subtitle })
1071
1071
  ] })
1072
1072
  ]
1073
1073
  }
@@ -1112,9 +1112,9 @@ function SessionSidebar({
1112
1112
  }
1113
1113
  navigateToHref(link.href);
1114
1114
  },
1115
- className: "flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1.5 text-left text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
1115
+ className: "flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-3 py-2 text-left text-sm text-muted-foreground transition-colors hover:bg-accent/30 hover:text-foreground",
1116
1116
  children: [
1117
- /* @__PURE__ */ jsx7(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
1117
+ /* @__PURE__ */ jsx7(Icon, { className: "h-4 w-4 shrink-0" }),
1118
1118
  /* @__PURE__ */ jsx7("span", { className: "truncate", children: link.label })
1119
1119
  ]
1120
1120
  },