@tangle-network/sandbox-ui 0.61.0 → 0.63.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/{WorkflowGraph-VMG2ZEAN.js → WorkflowGraph-M5ZOY43R.js} +2 -4
- package/dist/chat.d.ts +32 -4
- package/dist/chat.js +1 -1
- package/dist/{chunk-V5JZOO3W.js → chunk-2E3FJM56.js} +221 -59
- package/dist/{chunk-WEYFEMIN.js → chunk-34F66QTV.js} +15 -3
- package/dist/{chunk-J36F6LD2.js → chunk-6OJUOEF4.js} +1 -1
- package/dist/{chunk-OQZQFSWM.js → chunk-DZA4UBXU.js} +11 -9
- package/dist/dashboard.d.ts +8 -1
- package/dist/dashboard.js +1 -1
- package/dist/globals.css +38 -84
- package/dist/index.js +22 -22
- package/dist/styles.css +38 -84
- package/dist/workbench.js +3 -3
- package/dist/workflows.js +2 -3
- package/package.json +5 -9
- package/dist/assistant.d.ts +0 -560
- package/dist/assistant.js +0 -2610
- package/dist/chunk-7JLB7UZ3.js +0 -18
|
@@ -2,11 +2,9 @@ import {
|
|
|
2
2
|
ProviderIcon
|
|
3
3
|
} from "./chunk-GTJ5PSK5.js";
|
|
4
4
|
import {
|
|
5
|
-
buildWorkflowGraph
|
|
6
|
-
} from "./chunk-WEYFEMIN.js";
|
|
7
|
-
import {
|
|
5
|
+
buildWorkflowGraph,
|
|
8
6
|
providerLabel
|
|
9
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-34F66QTV.js";
|
|
10
8
|
import "./chunk-U67V476Y.js";
|
|
11
9
|
|
|
12
10
|
// src/workflows/WorkflowGraph.tsx
|
package/dist/chat.d.ts
CHANGED
|
@@ -124,8 +124,21 @@ interface AgentSessionHarnessControl {
|
|
|
124
124
|
* switch harness.
|
|
125
125
|
*/
|
|
126
126
|
locked?: boolean;
|
|
127
|
-
/** Tooltip shown on the locked trigger. */
|
|
127
|
+
/** Tooltip shown on the locked trigger when no `onNewChat` is provided. */
|
|
128
128
|
lockReason?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Fork action for a locked harness. When provided, the locked trigger turns
|
|
131
|
+
* into an informative chip: hover or tap opens a small popover explaining the
|
|
132
|
+
* lock and offering a button that calls this to start a fresh session. When
|
|
133
|
+
* omitted, the locked trigger stays a plain inert button with `lockReason`.
|
|
134
|
+
*/
|
|
135
|
+
onNewChat?: () => void;
|
|
136
|
+
/** Popover heading. Defaults to `Agent fixed to {harness} for this conversation.` */
|
|
137
|
+
lockTitle?: React.ReactNode;
|
|
138
|
+
/** Popover sub-line. Defaults to "Conversations stay on one agent." */
|
|
139
|
+
lockBody?: React.ReactNode;
|
|
140
|
+
/** Fork button label. Defaults to "New chat to switch agent". */
|
|
141
|
+
newChatLabel?: string;
|
|
129
142
|
}
|
|
130
143
|
interface AgentSessionModelControl {
|
|
131
144
|
/** Canonical model id (provider-prefixed, e.g. "anthropic/claude-opus-4-8"). */
|
|
@@ -180,6 +193,16 @@ interface AgentSessionControlsProps {
|
|
|
180
193
|
model?: AgentSessionModelControl;
|
|
181
194
|
/** Thinking-effort level applied to subsequent turns. */
|
|
182
195
|
reasoning?: AgentSessionReasoningControl;
|
|
196
|
+
/**
|
|
197
|
+
* Restrict the model catalog to what the selected harness can actually run,
|
|
198
|
+
* the same way `harness.locked` does — but WITHOUT locking the harness
|
|
199
|
+
* dropdown. Off by default: the strip shows every model and picking an
|
|
200
|
+
* incompatible one snaps the harness to the model's native backend. Turn it
|
|
201
|
+
* ON for a harness-first surface (e.g. a workspace-wide, sandbox-bound
|
|
202
|
+
* harness) where that silent backend swap is undesirable — an incompatible
|
|
203
|
+
* model is then simply not offered.
|
|
204
|
+
*/
|
|
205
|
+
filterModelsToHarness?: boolean;
|
|
183
206
|
/** Right-aligned extra content (token meter, cost, status). */
|
|
184
207
|
trailing?: React.ReactNode;
|
|
185
208
|
className?: string;
|
|
@@ -218,12 +241,17 @@ interface AgentSessionControlsProps {
|
|
|
218
241
|
* coherent automatically (see harness-model-compat): picking a harness
|
|
219
242
|
* snaps an incompatible model to that harness's best catalog option;
|
|
220
243
|
* picking a model the current harness can't run switches to the model's
|
|
221
|
-
* native harness — unless the
|
|
222
|
-
*
|
|
244
|
+
* native harness — unless the catalog is restricted to the harness (when
|
|
245
|
+
* `locked`, or the consumer sets `filterModelsToHarness`), in which case the
|
|
246
|
+
* incompatible model is never offered and no swap happens.
|
|
247
|
+
*
|
|
248
|
+
* When a reasoning control is also present, the effort is re-clamped into the
|
|
249
|
+
* set the new (harness, model) pair supports on every harness/model change, so
|
|
250
|
+
* the effort picker's label never disagrees with the value actually in effect.
|
|
223
251
|
*
|
|
224
252
|
* Designed to slot into `SandboxWorkbench`'s `session.composerControls`.
|
|
225
253
|
*/
|
|
226
|
-
declare function AgentSessionControls({ harness, profile, model, reasoning, trailing, className, context, layout, menuPlacement, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
|
|
254
|
+
declare function AgentSessionControls({ harness, profile, model, reasoning, filterModelsToHarness, trailing, className, context, layout, menuPlacement, }: AgentSessionControlsProps): react_jsx_runtime.JSX.Element | null;
|
|
227
255
|
|
|
228
256
|
/** A staged attachment shown as a chip above the input. */
|
|
229
257
|
interface ComposerFile {
|
package/dist/chat.js
CHANGED
|
@@ -28,6 +28,12 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
|
28
28
|
import { Brain, ChevronDown, Sparkles } from "lucide-react";
|
|
29
29
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
30
30
|
var REASONING_LADDER = reasoningLadder;
|
|
31
|
+
function clampReasoningLevel(value, available) {
|
|
32
|
+
if (value === "auto" || !available || available.includes(value)) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
return available.length ? available[available.length - 1] : "auto";
|
|
36
|
+
}
|
|
31
37
|
function ReasoningGlyph({ level }) {
|
|
32
38
|
if (level === "auto") {
|
|
33
39
|
return /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-muted-foreground" });
|
|
@@ -144,9 +150,30 @@ function ReasoningLevelPicker({
|
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
// src/chat/agent-session-controls.tsx
|
|
147
|
-
import {
|
|
153
|
+
import {
|
|
154
|
+
harnessHonorsEffort,
|
|
155
|
+
harnessHonorsModel,
|
|
156
|
+
reasoningEffortsFor
|
|
157
|
+
} from "@tangle-network/agent-interface";
|
|
148
158
|
import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
|
|
149
|
-
import { ChevronDown as ChevronDown3, Lock, SlidersHorizontal } from "lucide-react";
|
|
159
|
+
import { ChevronDown as ChevronDown3, Lock, Plus as Plus2, SlidersHorizontal } from "lucide-react";
|
|
160
|
+
import * as React3 from "react";
|
|
161
|
+
|
|
162
|
+
// src/lib/use-click-outside.ts
|
|
163
|
+
import * as React from "react";
|
|
164
|
+
function useClickOutside(onOutside) {
|
|
165
|
+
const ref = React.useRef(null);
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
function handler(event) {
|
|
168
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
169
|
+
onOutside();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
document.addEventListener("mousedown", handler);
|
|
173
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
174
|
+
}, [onOutside]);
|
|
175
|
+
return ref;
|
|
176
|
+
}
|
|
150
177
|
|
|
151
178
|
// src/chat/harness-model-compat.ts
|
|
152
179
|
import {
|
|
@@ -163,21 +190,8 @@ function snapModelToHarness(harness, modelId, models) {
|
|
|
163
190
|
|
|
164
191
|
// src/chat/agent-profile-picker.tsx
|
|
165
192
|
import { Bot, Check, ChevronDown as ChevronDown2, Pencil, Plus, Trash2 } from "lucide-react";
|
|
166
|
-
import * as
|
|
193
|
+
import * as React2 from "react";
|
|
167
194
|
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
195
|
function profileSubtitle(profile) {
|
|
182
196
|
if (profile.description) return profile.description;
|
|
183
197
|
const count = profile.capabilities?.length ?? 0;
|
|
@@ -195,8 +209,8 @@ function AgentProfilePicker({
|
|
|
195
209
|
className,
|
|
196
210
|
side = "top"
|
|
197
211
|
}) {
|
|
198
|
-
const [open, setOpen] =
|
|
199
|
-
const [composing, setComposing] =
|
|
212
|
+
const [open, setOpen] = React2.useState(false);
|
|
213
|
+
const [composing, setComposing] = React2.useState(
|
|
200
214
|
null
|
|
201
215
|
);
|
|
202
216
|
const ref = useClickOutside(() => {
|
|
@@ -363,11 +377,11 @@ function ProfileForm({
|
|
|
363
377
|
onCancel,
|
|
364
378
|
onSave
|
|
365
379
|
}) {
|
|
366
|
-
const [name, setName] =
|
|
367
|
-
const [caps, setCaps] =
|
|
380
|
+
const [name, setName] = React2.useState(initial?.name ?? "");
|
|
381
|
+
const [caps, setCaps] = React2.useState(
|
|
368
382
|
initial?.capabilities ? [...initial.capabilities] : capabilities.map((c) => c.id)
|
|
369
383
|
);
|
|
370
|
-
const [instructions, setInstructions] =
|
|
384
|
+
const [instructions, setInstructions] = React2.useState(initial?.instructions ?? "");
|
|
371
385
|
const toggle = (id) => setCaps(
|
|
372
386
|
(prev) => prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]
|
|
373
387
|
);
|
|
@@ -448,6 +462,110 @@ function ProfileForm({
|
|
|
448
462
|
|
|
449
463
|
// src/chat/agent-session-controls.tsx
|
|
450
464
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
465
|
+
function selectorIgnoreNote(harness) {
|
|
466
|
+
const ignoresModel = !harnessHonorsModel(harness);
|
|
467
|
+
const ignoresEffort = !harnessHonorsEffort(harness);
|
|
468
|
+
if (ignoresModel && ignoresEffort) return "Ignores model & effort";
|
|
469
|
+
if (ignoresModel) return "Ignores model selection";
|
|
470
|
+
if (ignoresEffort) return "Ignores reasoning effort";
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function InformativeLock({
|
|
474
|
+
label,
|
|
475
|
+
lockTitle,
|
|
476
|
+
lockBody,
|
|
477
|
+
newChatLabel,
|
|
478
|
+
onNewChat
|
|
479
|
+
}) {
|
|
480
|
+
const [open, setOpen] = React3.useState(false);
|
|
481
|
+
const closeTimer = React3.useRef(null);
|
|
482
|
+
const ref = useClickOutside(() => setOpen(false));
|
|
483
|
+
const cancelClose = React3.useCallback(() => {
|
|
484
|
+
if (closeTimer.current) {
|
|
485
|
+
clearTimeout(closeTimer.current);
|
|
486
|
+
closeTimer.current = null;
|
|
487
|
+
}
|
|
488
|
+
}, []);
|
|
489
|
+
const scheduleClose = React3.useCallback(() => {
|
|
490
|
+
cancelClose();
|
|
491
|
+
closeTimer.current = setTimeout(() => setOpen(false), 150);
|
|
492
|
+
}, [cancelClose]);
|
|
493
|
+
React3.useEffect(() => cancelClose, [cancelClose]);
|
|
494
|
+
return /* @__PURE__ */ jsxs3(
|
|
495
|
+
"div",
|
|
496
|
+
{
|
|
497
|
+
ref,
|
|
498
|
+
className: "relative inline-flex",
|
|
499
|
+
onMouseEnter: () => {
|
|
500
|
+
cancelClose();
|
|
501
|
+
setOpen(true);
|
|
502
|
+
},
|
|
503
|
+
onMouseLeave: scheduleClose,
|
|
504
|
+
onKeyDown: (event) => {
|
|
505
|
+
if (event.key === "Escape") setOpen(false);
|
|
506
|
+
},
|
|
507
|
+
children: [
|
|
508
|
+
/* @__PURE__ */ jsxs3(
|
|
509
|
+
"button",
|
|
510
|
+
{
|
|
511
|
+
type: "button",
|
|
512
|
+
onClick: () => setOpen(true),
|
|
513
|
+
"data-state": open ? "open" : "closed",
|
|
514
|
+
"aria-label": "Agent harness (locked)",
|
|
515
|
+
"aria-haspopup": "dialog",
|
|
516
|
+
"aria-expanded": open,
|
|
517
|
+
className: cn(
|
|
518
|
+
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
|
|
519
|
+
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
520
|
+
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
521
|
+
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high"
|
|
522
|
+
),
|
|
523
|
+
children: [
|
|
524
|
+
/* @__PURE__ */ jsx3(Lock, { className: "h-3 w-3 text-muted-foreground" }),
|
|
525
|
+
/* @__PURE__ */ jsx3("span", { children: label })
|
|
526
|
+
]
|
|
527
|
+
}
|
|
528
|
+
),
|
|
529
|
+
open && /* @__PURE__ */ jsxs3(
|
|
530
|
+
"div",
|
|
531
|
+
{
|
|
532
|
+
role: "dialog",
|
|
533
|
+
className: cn(
|
|
534
|
+
"absolute bottom-full left-0 z-50 mb-2 w-72 rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-3",
|
|
535
|
+
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]"
|
|
536
|
+
),
|
|
537
|
+
children: [
|
|
538
|
+
/* @__PURE__ */ jsxs3("p", { className: "flex items-start gap-2 text-sm font-medium text-foreground", children: [
|
|
539
|
+
/* @__PURE__ */ jsx3(Lock, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
540
|
+
/* @__PURE__ */ jsx3("span", { children: lockTitle ?? `Agent fixed to ${label} for this conversation.` })
|
|
541
|
+
] }),
|
|
542
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-1.5 text-xs text-muted-foreground", children: lockBody ?? "Conversations stay on one agent." }),
|
|
543
|
+
/* @__PURE__ */ jsxs3(
|
|
544
|
+
"button",
|
|
545
|
+
{
|
|
546
|
+
type: "button",
|
|
547
|
+
onClick: () => {
|
|
548
|
+
setOpen(false);
|
|
549
|
+
onNewChat();
|
|
550
|
+
},
|
|
551
|
+
className: cn(
|
|
552
|
+
"mt-3 flex w-full items-center justify-center gap-1.5 rounded-md px-2.5 py-2 text-sm font-medium text-foreground",
|
|
553
|
+
"bg-primary/10 ring-1 ring-inset ring-primary/25 transition-colors",
|
|
554
|
+
"hover:bg-primary/15 focus:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
555
|
+
),
|
|
556
|
+
children: [
|
|
557
|
+
/* @__PURE__ */ jsx3(Plus2, { className: "h-4 w-4" }),
|
|
558
|
+
newChatLabel ?? "New chat to switch agent"
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
)
|
|
562
|
+
]
|
|
563
|
+
}
|
|
564
|
+
)
|
|
565
|
+
]
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
}
|
|
451
569
|
function HarnessDropdown({
|
|
452
570
|
value,
|
|
453
571
|
onChange,
|
|
@@ -455,6 +573,10 @@ function HarnessDropdown({
|
|
|
455
573
|
disabled,
|
|
456
574
|
locked,
|
|
457
575
|
lockReason,
|
|
576
|
+
onNewChat,
|
|
577
|
+
lockTitle,
|
|
578
|
+
lockBody,
|
|
579
|
+
newChatLabel,
|
|
458
580
|
side = "bottom",
|
|
459
581
|
align = "start",
|
|
460
582
|
avoidCollisions
|
|
@@ -464,6 +586,18 @@ function HarnessDropdown({
|
|
|
464
586
|
);
|
|
465
587
|
const options = HARNESS_OPTIONS.filter((h) => allowed.has(h.type));
|
|
466
588
|
const selected = options.find((option) => option.type === value);
|
|
589
|
+
if (locked && onNewChat) {
|
|
590
|
+
return /* @__PURE__ */ jsx3(
|
|
591
|
+
InformativeLock,
|
|
592
|
+
{
|
|
593
|
+
label: selected?.label ?? value,
|
|
594
|
+
lockTitle,
|
|
595
|
+
lockBody,
|
|
596
|
+
newChatLabel,
|
|
597
|
+
onNewChat
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
}
|
|
467
601
|
return /* @__PURE__ */ jsxs3(DropdownMenu2.Root, { children: [
|
|
468
602
|
/* @__PURE__ */ jsx3(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
|
|
469
603
|
"button",
|
|
@@ -520,7 +654,8 @@ function HarnessDropdown({
|
|
|
520
654
|
/* @__PURE__ */ jsx3(HarnessLogo, { type: option.type, size: 20, className: "mt-0.5" }),
|
|
521
655
|
/* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
522
656
|
/* @__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 })
|
|
657
|
+
option.description && /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", children: option.description }),
|
|
658
|
+
selectorIgnoreNote(option.type) && /* @__PURE__ */ jsx3("span", { className: "mt-0.5 inline-flex w-fit items-center rounded border border-[var(--md3-outline-variant)] px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: selectorIgnoreNote(option.type) })
|
|
524
659
|
] })
|
|
525
660
|
]
|
|
526
661
|
},
|
|
@@ -530,11 +665,20 @@ function HarnessDropdown({
|
|
|
530
665
|
) })
|
|
531
666
|
] });
|
|
532
667
|
}
|
|
668
|
+
function modelReasoningCapability(models, canonicalId) {
|
|
669
|
+
if (!models || !canonicalId) return null;
|
|
670
|
+
const entry = models.find((m) => canonicalModelId(m) === canonicalId);
|
|
671
|
+
return entry ? {
|
|
672
|
+
supportsReasoning: entry.supportsReasoning,
|
|
673
|
+
maxEffort: entry.maxReasoningEffort
|
|
674
|
+
} : null;
|
|
675
|
+
}
|
|
533
676
|
function AgentSessionControls({
|
|
534
677
|
harness,
|
|
535
678
|
profile,
|
|
536
679
|
model,
|
|
537
680
|
reasoning,
|
|
681
|
+
filterModelsToHarness,
|
|
538
682
|
trailing,
|
|
539
683
|
className,
|
|
540
684
|
context = "chat",
|
|
@@ -542,21 +686,45 @@ function AgentSessionControls({
|
|
|
542
686
|
menuPlacement = "auto"
|
|
543
687
|
}) {
|
|
544
688
|
if (!harness && !profile && !model && !reasoning && !trailing) return null;
|
|
689
|
+
const restrictModelsToHarness = Boolean(
|
|
690
|
+
harness && (harness.locked || filterModelsToHarness)
|
|
691
|
+
);
|
|
692
|
+
const selectorHonorsModel = !harness || harnessHonorsModel(harness.value);
|
|
693
|
+
const selectorHonorsEffort = !harness || harnessHonorsEffort(harness.value);
|
|
694
|
+
const clampEffort = (harnessValue, modelId) => {
|
|
695
|
+
if (!reasoning) return;
|
|
696
|
+
const available = reasoningEffortsFor(
|
|
697
|
+
harnessValue,
|
|
698
|
+
modelReasoningCapability(model?.models, modelId)
|
|
699
|
+
);
|
|
700
|
+
const clamped = clampReasoningLevel(reasoning.value, available);
|
|
701
|
+
if (clamped !== reasoning.value) reasoning.onChange(clamped);
|
|
702
|
+
};
|
|
545
703
|
const handleHarnessChange = (next) => {
|
|
546
704
|
harness?.onChange(next);
|
|
705
|
+
let nextModelId = model?.value;
|
|
547
706
|
if (model) {
|
|
548
707
|
const snapped = snapModelToHarness(next, model.value, model.models);
|
|
549
|
-
if (snapped !== model.value)
|
|
708
|
+
if (snapped !== model.value) {
|
|
709
|
+
model.onChange(snapped);
|
|
710
|
+
nextModelId = snapped;
|
|
711
|
+
}
|
|
550
712
|
}
|
|
713
|
+
clampEffort(next, nextModelId);
|
|
551
714
|
};
|
|
552
715
|
const handleModelChange = (nextModelId) => {
|
|
553
716
|
model?.onChange(nextModelId);
|
|
554
|
-
|
|
717
|
+
let nextHarness = harness?.value;
|
|
718
|
+
if (harness && !restrictModelsToHarness) {
|
|
555
719
|
const snapped = snapHarnessToModel(harness.value, nextModelId);
|
|
556
|
-
if (snapped !== harness.value)
|
|
720
|
+
if (snapped !== harness.value) {
|
|
721
|
+
harness.onChange(snapped);
|
|
722
|
+
nextHarness = snapped;
|
|
723
|
+
}
|
|
557
724
|
}
|
|
725
|
+
if (nextHarness) clampEffort(nextHarness, nextModelId);
|
|
558
726
|
};
|
|
559
|
-
const visibleModels = model && harness
|
|
727
|
+
const visibleModels = model && restrictModelsToHarness && harness ? model.models.filter(
|
|
560
728
|
(entry) => isModelCompatibleWithHarness(
|
|
561
729
|
harness.value,
|
|
562
730
|
canonicalModelId(entry)
|
|
@@ -603,25 +771,19 @@ function AgentSessionControls({
|
|
|
603
771
|
loading: model.loading,
|
|
604
772
|
popular: model.popular,
|
|
605
773
|
recents: model.recents,
|
|
606
|
-
disabled: model.disabled || (visibleModels ?? []).length === 0,
|
|
774
|
+
disabled: model.disabled || (visibleModels ?? []).length === 0 || !selectorHonorsModel,
|
|
607
775
|
side: layout === "gear" ? void 0 : "bottom",
|
|
608
776
|
avoidCollisions
|
|
609
777
|
}
|
|
610
778
|
);
|
|
611
|
-
const
|
|
612
|
-
(entry) => canonicalModelId(entry) === model.value
|
|
613
|
-
);
|
|
614
|
-
const modelReasoning = selectedModel ? {
|
|
615
|
-
supportsReasoning: selectedModel.supportsReasoning,
|
|
616
|
-
maxEffort: selectedModel.maxReasoningEffort
|
|
617
|
-
} : null;
|
|
779
|
+
const modelReasoning = modelReasoningCapability(model?.models, model?.value);
|
|
618
780
|
const reasoningNode = reasoning && /* @__PURE__ */ jsx3(
|
|
619
781
|
ReasoningLevelPicker,
|
|
620
782
|
{
|
|
621
783
|
value: reasoning.value,
|
|
622
784
|
onChange: reasoning.onChange,
|
|
623
785
|
options: reasoning.options,
|
|
624
|
-
disabled: reasoning.disabled,
|
|
786
|
+
disabled: reasoning.disabled || !selectorHonorsEffort,
|
|
625
787
|
available: harness ? reasoningEffortsFor(harness.value, modelReasoning) : reasoning.available,
|
|
626
788
|
side: layout === "gear" ? void 0 : "bottom",
|
|
627
789
|
avoidCollisions
|
|
@@ -739,7 +901,7 @@ import {
|
|
|
739
901
|
Upload,
|
|
740
902
|
X
|
|
741
903
|
} from "lucide-react";
|
|
742
|
-
import * as
|
|
904
|
+
import * as React4 from "react";
|
|
743
905
|
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
744
906
|
function AgentComposer({
|
|
745
907
|
value,
|
|
@@ -770,22 +932,22 @@ function AgentComposer({
|
|
|
770
932
|
sendLabel = "Send",
|
|
771
933
|
autoFocus
|
|
772
934
|
}) {
|
|
773
|
-
const textareaRef =
|
|
774
|
-
const fileInputRef =
|
|
775
|
-
const folderInputRef =
|
|
776
|
-
const [dragOver, setDragOver] =
|
|
777
|
-
const dragDepth =
|
|
778
|
-
const resize =
|
|
935
|
+
const textareaRef = React4.useRef(null);
|
|
936
|
+
const fileInputRef = React4.useRef(null);
|
|
937
|
+
const folderInputRef = React4.useRef(null);
|
|
938
|
+
const [dragOver, setDragOver] = React4.useState(false);
|
|
939
|
+
const dragDepth = React4.useRef(0);
|
|
940
|
+
const resize = React4.useCallback(
|
|
779
941
|
(el) => {
|
|
780
942
|
el.style.height = "auto";
|
|
781
943
|
el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
|
|
782
944
|
},
|
|
783
945
|
[maxHeight]
|
|
784
946
|
);
|
|
785
|
-
|
|
947
|
+
React4.useEffect(() => {
|
|
786
948
|
if (textareaRef.current) resize(textareaRef.current);
|
|
787
949
|
}, [value, resize]);
|
|
788
|
-
|
|
950
|
+
React4.useEffect(() => {
|
|
789
951
|
if (!focusShortcut || disabled) return;
|
|
790
952
|
function onKeyDown(event) {
|
|
791
953
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "l") {
|
|
@@ -1024,7 +1186,7 @@ function AgentComposer({
|
|
|
1024
1186
|
}
|
|
1025
1187
|
|
|
1026
1188
|
// src/chat/artifact-agent-dock.tsx
|
|
1027
|
-
import * as
|
|
1189
|
+
import * as React5 from "react";
|
|
1028
1190
|
import { Sparkles as Sparkles2, Send, Square as Square2, X as X2 } from "lucide-react";
|
|
1029
1191
|
import { ChatMessage } from "@tangle-network/ui/chat";
|
|
1030
1192
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
@@ -1073,23 +1235,23 @@ function ArtifactAgentDock({
|
|
|
1073
1235
|
onError,
|
|
1074
1236
|
className
|
|
1075
1237
|
}) {
|
|
1076
|
-
const [threadId, setThreadId] =
|
|
1077
|
-
const [messages, setMessages] =
|
|
1078
|
-
const [composer, setComposer] =
|
|
1079
|
-
const [streamContent, setStreamContent] =
|
|
1080
|
-
const [sending, setSending] =
|
|
1081
|
-
const [loading, setLoading] =
|
|
1082
|
-
const abortRef =
|
|
1083
|
-
const scrollRef =
|
|
1084
|
-
const scopeTokenRef =
|
|
1085
|
-
const reportError =
|
|
1238
|
+
const [threadId, setThreadId] = React5.useState(null);
|
|
1239
|
+
const [messages, setMessages] = React5.useState([]);
|
|
1240
|
+
const [composer, setComposer] = React5.useState(defaultPrompt ?? "");
|
|
1241
|
+
const [streamContent, setStreamContent] = React5.useState("");
|
|
1242
|
+
const [sending, setSending] = React5.useState(false);
|
|
1243
|
+
const [loading, setLoading] = React5.useState(false);
|
|
1244
|
+
const abortRef = React5.useRef(null);
|
|
1245
|
+
const scrollRef = React5.useRef(null);
|
|
1246
|
+
const scopeTokenRef = React5.useRef(0);
|
|
1247
|
+
const reportError = React5.useCallback(
|
|
1086
1248
|
(message) => {
|
|
1087
1249
|
if (onError) onError(message);
|
|
1088
1250
|
else if (typeof console !== "undefined") console.error("[ArtifactAgentDock]", message);
|
|
1089
1251
|
},
|
|
1090
1252
|
[onError]
|
|
1091
1253
|
);
|
|
1092
|
-
|
|
1254
|
+
React5.useEffect(() => {
|
|
1093
1255
|
if (!open) return;
|
|
1094
1256
|
scopeTokenRef.current += 1;
|
|
1095
1257
|
abortRef.current?.abort();
|
|
@@ -1125,11 +1287,11 @@ function ArtifactAgentDock({
|
|
|
1125
1287
|
cancelled = true;
|
|
1126
1288
|
};
|
|
1127
1289
|
}, [open, scope.kind, scope.key, transport, reportError]);
|
|
1128
|
-
|
|
1290
|
+
React5.useEffect(() => {
|
|
1129
1291
|
if (!scrollRef.current) return;
|
|
1130
1292
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
1131
1293
|
}, [messages.length, streamContent]);
|
|
1132
|
-
const send =
|
|
1294
|
+
const send = React5.useCallback(async () => {
|
|
1133
1295
|
const text = composer.trim();
|
|
1134
1296
|
if (!text || !threadId || sending) return;
|
|
1135
1297
|
const token = scopeTokenRef.current;
|
|
@@ -1179,7 +1341,7 @@ function ArtifactAgentDock({
|
|
|
1179
1341
|
if (abortRef.current === controller) abortRef.current = null;
|
|
1180
1342
|
}
|
|
1181
1343
|
}, [composer, threadId, sending, transport, reportError]);
|
|
1182
|
-
const cancel =
|
|
1344
|
+
const cancel = React5.useCallback(() => {
|
|
1183
1345
|
if (threadId && transport.cancel) {
|
|
1184
1346
|
try {
|
|
1185
1347
|
void Promise.resolve(transport.cancel({ threadId })).catch(() => {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/workflows/provider-label.ts
|
|
2
|
+
var PROVIDER_LABELS = {
|
|
3
|
+
github: "GitHub",
|
|
4
|
+
gitlab: "GitLab",
|
|
5
|
+
slack: "Slack",
|
|
6
|
+
stripe: "Stripe",
|
|
7
|
+
notion: "Notion",
|
|
8
|
+
linear: "Linear",
|
|
9
|
+
discord: "Discord"
|
|
10
|
+
};
|
|
11
|
+
function providerLabel(provider) {
|
|
12
|
+
const key = provider.toLowerCase();
|
|
13
|
+
return PROVIDER_LABELS[key] ?? provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
14
|
+
}
|
|
4
15
|
|
|
5
16
|
// src/workflows/model.ts
|
|
6
17
|
import { parse as parseYaml } from "yaml";
|
|
@@ -293,5 +304,6 @@ function buildWorkflowGraph(yaml, options) {
|
|
|
293
304
|
}
|
|
294
305
|
|
|
295
306
|
export {
|
|
307
|
+
providerLabel,
|
|
296
308
|
buildWorkflowGraph
|
|
297
309
|
};
|
|
@@ -564,13 +564,17 @@ function RailExpandable({
|
|
|
564
564
|
loadSubItems,
|
|
565
565
|
activeSubIds,
|
|
566
566
|
emptyLabel = "Nothing here yet",
|
|
567
|
+
defaultOpen,
|
|
567
568
|
onNavigate,
|
|
568
569
|
LinkComponent,
|
|
569
570
|
className
|
|
570
571
|
}) {
|
|
571
572
|
const Link = LinkComponent ?? DefaultLink;
|
|
572
|
-
const [open, setOpen] = React3.useState(false);
|
|
573
|
+
const [open, setOpen] = React3.useState(defaultOpen ?? false);
|
|
573
574
|
const { items, loading, ensureLoaded } = useExpandableItems(subItems, loadSubItems);
|
|
575
|
+
React3.useEffect(() => {
|
|
576
|
+
if (defaultOpen) ensureLoaded();
|
|
577
|
+
}, [defaultOpen, ensureLoaded]);
|
|
574
578
|
const activeSet = React3.useMemo(() => new Set(activeSubIds ?? []), [activeSubIds]);
|
|
575
579
|
const triggerRef = React3.useRef(null);
|
|
576
580
|
const closeTimer = React3.useRef(null);
|
|
@@ -589,8 +593,8 @@ function RailExpandable({
|
|
|
589
593
|
onClick: onNavigate,
|
|
590
594
|
...item.prefetch !== void 0 ? { prefetch: item.prefetch } : {},
|
|
591
595
|
className: cn(
|
|
592
|
-
"flex h-
|
|
593
|
-
active ? "bg-[var(--accent-surface-strong)] font-medium text-[var(--accent-text)]" : "text-muted-foreground hover:bg-[var(--accent-surface-soft)] hover:text-foreground"
|
|
596
|
+
"flex h-7 min-w-0 items-center gap-2 rounded-md px-2.5 text-[12px] transition-colors",
|
|
597
|
+
active ? "bg-[var(--accent-surface-strong)] font-medium text-[var(--accent-text)]" : item.emphasis ? "font-semibold text-[var(--accent-text)] hover:bg-[var(--accent-surface-soft)]" : "text-muted-foreground hover:bg-[var(--accent-surface-soft)] hover:text-foreground"
|
|
594
598
|
),
|
|
595
599
|
children: [
|
|
596
600
|
FIcon ? /* @__PURE__ */ jsx3(FIcon, { className: "h-4 w-4 shrink-0" }) : null,
|
|
@@ -719,18 +723,15 @@ function RailExpandable({
|
|
|
719
723
|
/* @__PURE__ */ jsx3(
|
|
720
724
|
Icon,
|
|
721
725
|
{
|
|
722
|
-
className:
|
|
723
|
-
"h-[17px] w-[17px] shrink-0 transition-opacity duration-150",
|
|
724
|
-
open ? "opacity-0" : "opacity-100 group-hover/exp:opacity-0"
|
|
725
|
-
)
|
|
726
|
+
className: "h-[17px] w-[17px] shrink-0 opacity-100 transition-opacity duration-150 group-hover/exp:opacity-0"
|
|
726
727
|
}
|
|
727
728
|
),
|
|
728
729
|
/* @__PURE__ */ jsx3(
|
|
729
730
|
ChevronRightIcon,
|
|
730
731
|
{
|
|
731
732
|
className: cn(
|
|
732
|
-
"absolute h-4 w-4 transition-all duration-150",
|
|
733
|
-
open
|
|
733
|
+
"absolute h-4 w-4 opacity-0 transition-all duration-150 group-hover/exp:opacity-100",
|
|
734
|
+
open && "rotate-90"
|
|
734
735
|
)
|
|
735
736
|
}
|
|
736
737
|
)
|
|
@@ -2368,6 +2369,7 @@ function SidebarLayoutInner({
|
|
|
2368
2369
|
loadSubItems: item.loadSubItems,
|
|
2369
2370
|
activeSubIds: item.subActiveIds,
|
|
2370
2371
|
emptyLabel: item.emptyLabel,
|
|
2372
|
+
defaultOpen: item.defaultOpen,
|
|
2371
2373
|
onNavigate: handleNavClick,
|
|
2372
2374
|
LinkComponent
|
|
2373
2375
|
},
|
package/dist/dashboard.d.ts
CHANGED
|
@@ -185,6 +185,9 @@ interface RailExpandableSubItem {
|
|
|
185
185
|
prefetch?: "none" | "intent" | "render" | "viewport";
|
|
186
186
|
/** Show a live working indicator on the row while the item is busy. */
|
|
187
187
|
isLoading?: boolean;
|
|
188
|
+
/** Render the row emphasized (bold + accent) so it stands out — e.g. a
|
|
189
|
+
* trailing "view all" action below a capped list. */
|
|
190
|
+
emphasis?: boolean;
|
|
188
191
|
}
|
|
189
192
|
interface RailExpandableProps {
|
|
190
193
|
icon: React.ComponentType<{
|
|
@@ -201,6 +204,8 @@ interface RailExpandableProps {
|
|
|
201
204
|
subItems?: RailExpandableSubItem[];
|
|
202
205
|
/** Lazy sub-items — called once on first open; the result is cached. */
|
|
203
206
|
loadSubItems?: () => Promise<RailExpandableSubItem[]>;
|
|
207
|
+
/** Start expanded on mount (labeled rail). Defaults to collapsed. */
|
|
208
|
+
defaultOpen?: boolean;
|
|
204
209
|
/** Ids of sub-items whose route is active (drives sub-item highlight). */
|
|
205
210
|
activeSubIds?: string[];
|
|
206
211
|
/** Shown when the resolved sub-item list is empty. */
|
|
@@ -218,7 +223,7 @@ interface RailExpandableProps {
|
|
|
218
223
|
* Sub-items can be fixed (`subItems`) or lazy (`loadSubItems`, with a loading
|
|
219
224
|
* skeleton). Used for e.g. a "History" entry fronting recent sessions.
|
|
220
225
|
*/
|
|
221
|
-
declare function RailExpandable({ icon: Icon, label, href, isActive, showLabel, subItems, loadSubItems, activeSubIds, emptyLabel, onNavigate, LinkComponent, className, }: RailExpandableProps): react_jsx_runtime.JSX.Element;
|
|
226
|
+
declare function RailExpandable({ icon: Icon, label, href, isActive, showLabel, subItems, loadSubItems, activeSubIds, emptyLabel, defaultOpen, onNavigate, LinkComponent, className, }: RailExpandableProps): react_jsx_runtime.JSX.Element;
|
|
222
227
|
interface RailModeButtonProps {
|
|
223
228
|
mode: string;
|
|
224
229
|
icon: React.ComponentType<{
|
|
@@ -913,6 +918,8 @@ interface SidebarLayoutNavItem {
|
|
|
913
918
|
subActiveIds?: string[];
|
|
914
919
|
/** Empty-state text for an `expandable` item that resolves to no sub-items. */
|
|
915
920
|
emptyLabel?: string;
|
|
921
|
+
/** Start an `expandable` item expanded on mount (labeled rail). */
|
|
922
|
+
defaultOpen?: boolean;
|
|
916
923
|
badge?: number;
|
|
917
924
|
/**
|
|
918
925
|
* React Router prefetch behavior for this link, forwarded to the underlying
|