@tangle-network/sandbox-ui 0.21.0 → 0.21.1

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.
@@ -22,9 +22,13 @@ function useSandboxMetrics({
22
22
  sandboxId,
23
23
  token,
24
24
  enabled = true,
25
- intervalMs = 3e3
25
+ intervalMs = 3e3,
26
+ historyLimit = 120
26
27
  }) {
27
28
  const [metrics, setMetrics] = React.useState(null);
29
+ const [system, setSystem] = React.useState(null);
30
+ const [latency, setLatency] = React.useState(null);
31
+ const [history, setHistory] = React.useState([]);
28
32
  const [loading, setLoading] = React.useState(false);
29
33
  const [error, setError] = React.useState(null);
30
34
  const [lastUpdatedAt, setLastUpdatedAt] = React.useState(null);
@@ -38,6 +42,9 @@ function useSandboxMetrics({
38
42
  sampleRef.current = null;
39
43
  hasLoadedRef.current = false;
40
44
  setMetrics(null);
45
+ setSystem(null);
46
+ setLatency(null);
47
+ setHistory([]);
41
48
  setLastUpdatedAt(null);
42
49
  setError(null);
43
50
  if (sandboxCleared) setLoading(false);
@@ -69,8 +76,8 @@ function useSandboxMetrics({
69
76
  }
70
77
  const data = await res.json();
71
78
  const user = data?.process?.cpuSeconds?.user ?? 0;
72
- const system = data?.process?.cpuSeconds?.system ?? 0;
73
- const cpuSeconds = user + system;
79
+ const system2 = data?.process?.cpuSeconds?.system ?? 0;
80
+ const cpuSeconds = user + system2;
74
81
  const wallMs = Date.now();
75
82
  if (cancelled) return;
76
83
  let cpuPercent = null;
@@ -89,6 +96,23 @@ function useSandboxMetrics({
89
96
  heapUsedBytes: data?.process?.memoryBytes?.heapUsed ?? 0,
90
97
  heapTotalBytes: data?.process?.memoryBytes?.heapTotal ?? 0
91
98
  });
99
+ const sys = data?.system ?? null;
100
+ setSystem(sys);
101
+ setLatency(data?.latency ?? null);
102
+ if (sys) {
103
+ const sample = {
104
+ at: wallMs,
105
+ cpuPercent: sys.cpuPercent,
106
+ memoryUsedBytes: sys.memory?.usedBytes ?? null,
107
+ memoryTotalBytes: sys.memory?.totalBytes ?? null,
108
+ diskUsedBytes: sys.disk?.usedBytes ?? null,
109
+ diskTotalBytes: sys.disk?.totalBytes ?? null
110
+ };
111
+ setHistory((prevHistory) => {
112
+ const next = [...prevHistory, sample];
113
+ return next.length > historyLimit ? next.slice(next.length - historyLimit) : next;
114
+ });
115
+ }
92
116
  setLastUpdatedAt(wallMs);
93
117
  setError(null);
94
118
  hasLoadedRef.current = true;
@@ -113,8 +137,8 @@ function useSandboxMetrics({
113
137
  controller.abort();
114
138
  if (timeoutId !== null) window.clearTimeout(timeoutId);
115
139
  };
116
- }, [apiBaseUrl, sandboxId, token, enabled, intervalMs]);
117
- return { metrics, loading, error, lastUpdatedAt };
140
+ }, [apiBaseUrl, sandboxId, token, enabled, intervalMs, historyLimit]);
141
+ return { metrics, system, latency, history, loading, error, lastUpdatedAt };
118
142
  }
119
143
 
120
144
  // src/hooks/use-session-crud.ts
@@ -2,7 +2,8 @@ import {
2
2
  HARNESS_OPTIONS
3
3
  } from "./chunk-ESRYVGHF.js";
4
4
  import {
5
- ModelPicker
5
+ ModelPicker,
6
+ canonicalModelId
6
7
  } from "./chunk-4KAPMTPU.js";
7
8
  import {
8
9
  cn
@@ -100,13 +101,76 @@ function ReasoningLevelPicker({
100
101
 
101
102
  // src/chat/agent-session-controls.tsx
102
103
  import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
103
- import { Bot, ChevronDown as ChevronDown2 } from "lucide-react";
104
+ import { Bot, ChevronDown as ChevronDown2, Lock } from "lucide-react";
105
+
106
+ // src/chat/harness-model-compat.ts
107
+ var HARNESS_MODEL_POLICIES = {
108
+ opencode: { providers: null, preferred: [] },
109
+ "claude-code": {
110
+ providers: ["anthropic"],
111
+ preferred: [
112
+ /^anthropic\/claude-opus-[\d.-]+$/,
113
+ /^anthropic\/claude-sonnet-[\d.-]+$/,
114
+ /^anthropic\//
115
+ ]
116
+ },
117
+ codex: {
118
+ providers: ["openai"],
119
+ // Standard frontier tier first (gpt-N / gpt-N.N), then any gpt
120
+ // variant (mini/nano), then anything OpenAI.
121
+ preferred: [/^openai\/gpt-\d+(\.\d+)?$/, /^openai\/gpt/, /^openai\//]
122
+ },
123
+ amp: { providers: null, preferred: [] },
124
+ "factory-droids": { providers: null, preferred: [] },
125
+ "cli-base": { providers: null, preferred: [] }
126
+ };
127
+ var PROVIDER_PREFERRED_HARNESS = {
128
+ anthropic: "claude-code",
129
+ openai: "codex"
130
+ };
131
+ function modelProvider(modelId) {
132
+ const slash = modelId.indexOf("/");
133
+ return slash > 0 ? modelId.slice(0, slash) : null;
134
+ }
135
+ function isModelCompatibleWithHarness(harness, modelId) {
136
+ const policy = HARNESS_MODEL_POLICIES[harness];
137
+ if (!policy || policy.providers === null) return true;
138
+ const provider = modelProvider(modelId);
139
+ if (!provider) return true;
140
+ return policy.providers.includes(provider);
141
+ }
142
+ var numericDesc = new Intl.Collator(void 0, {
143
+ numeric: true,
144
+ sensitivity: "base"
145
+ });
146
+ function snapModelToHarness(harness, modelId, models) {
147
+ if (isModelCompatibleWithHarness(harness, modelId)) return modelId;
148
+ const ids = models.map(canonicalModelId);
149
+ const policy = HARNESS_MODEL_POLICIES[harness];
150
+ for (const pattern of policy.preferred) {
151
+ const matches = ids.filter((id) => pattern.test(id)).sort((a, b) => numericDesc.compare(b, a));
152
+ if (matches.length > 0) return matches[0];
153
+ }
154
+ const fallback = ids.find(
155
+ (id) => isModelCompatibleWithHarness(harness, id)
156
+ );
157
+ return fallback ?? modelId;
158
+ }
159
+ function snapHarnessToModel(harness, modelId) {
160
+ if (isModelCompatibleWithHarness(harness, modelId)) return harness;
161
+ const provider = modelProvider(modelId);
162
+ return provider && PROVIDER_PREFERRED_HARNESS[provider] || "opencode";
163
+ }
164
+
165
+ // src/chat/agent-session-controls.tsx
104
166
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
105
167
  function HarnessDropdown({
106
168
  value,
107
169
  onChange,
108
170
  available,
109
- disabled
171
+ disabled,
172
+ locked,
173
+ lockReason
110
174
  }) {
111
175
  const allowed = new Set(
112
176
  available ?? HARNESS_OPTIONS.map((h) => h.type)
@@ -118,19 +182,20 @@ function HarnessDropdown({
118
182
  "button",
119
183
  {
120
184
  type: "button",
121
- disabled,
185
+ disabled: disabled || locked,
186
+ title: locked ? lockReason : void 0,
122
187
  className: cn(
123
188
  "inline-flex h-8 items-center gap-1.5 rounded-lg border border-border bg-card px-2.5",
124
189
  "text-xs font-medium text-foreground shadow-sm transition-colors",
125
190
  "hover:border-primary/30 hover:bg-accent/30 focus:outline-none focus:border-primary/40",
126
191
  "data-[state=open]:border-primary/40 data-[state=open]:bg-accent/30",
127
- "disabled:cursor-not-allowed disabled:opacity-50"
192
+ "disabled:cursor-not-allowed disabled:opacity-60"
128
193
  ),
129
194
  "aria-label": "Agent harness",
130
195
  children: [
131
- /* @__PURE__ */ jsx2(Bot, { className: "h-3.5 w-3.5 text-muted-foreground" }),
196
+ locked ? /* @__PURE__ */ jsx2(Lock, { className: "h-3 w-3 text-muted-foreground" }) : /* @__PURE__ */ jsx2(Bot, { className: "h-3.5 w-3.5 text-muted-foreground" }),
132
197
  /* @__PURE__ */ jsx2("span", { children: selected?.label ?? value }),
133
- /* @__PURE__ */ jsx2(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
198
+ !locked && /* @__PURE__ */ jsx2(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
134
199
  ]
135
200
  }
136
201
  ) }),
@@ -177,25 +242,45 @@ function AgentSessionControls({
177
242
  className
178
243
  }) {
179
244
  if (!harness && !model && !reasoning && !trailing) return null;
245
+ const handleHarnessChange = (next) => {
246
+ harness?.onChange(next);
247
+ if (model) {
248
+ const snapped = snapModelToHarness(next, model.value, model.models);
249
+ if (snapped !== model.value) model.onChange(snapped);
250
+ }
251
+ };
252
+ const handleModelChange = (nextModelId) => {
253
+ model?.onChange(nextModelId);
254
+ if (harness && !harness.locked) {
255
+ const snapped = snapHarnessToModel(harness.value, nextModelId);
256
+ if (snapped !== harness.value) harness.onChange(snapped);
257
+ }
258
+ };
259
+ const visibleModels = model && harness?.locked ? model.models.filter(
260
+ (entry) => isModelCompatibleWithHarness(
261
+ harness.value,
262
+ canonicalModelId(entry)
263
+ )
264
+ ) : model?.models;
180
265
  return /* @__PURE__ */ jsxs2(
181
266
  "div",
182
267
  {
183
268
  className: cn("flex flex-wrap items-center gap-2", className),
184
269
  "data-testid": "agent-session-controls",
185
270
  children: [
186
- harness && /* @__PURE__ */ jsx2(HarnessDropdown, { ...harness }),
271
+ harness && /* @__PURE__ */ jsx2(HarnessDropdown, { ...harness, onChange: handleHarnessChange }),
187
272
  model && /* @__PURE__ */ jsx2(
188
273
  ModelPicker,
189
274
  {
190
275
  variant: "pill",
191
276
  label: "",
192
277
  value: model.value,
193
- onChange: model.onChange,
194
- models: model.models,
278
+ onChange: handleModelChange,
279
+ models: visibleModels ?? [],
195
280
  loading: model.loading,
196
281
  popular: model.popular,
197
282
  recents: model.recents,
198
- disabled: model.disabled || model.models.length === 0
283
+ disabled: model.disabled || (visibleModels ?? []).length === 0
199
284
  }
200
285
  ),
201
286
  reasoning && /* @__PURE__ */ jsx2(
@@ -554,6 +639,11 @@ function createFetchTransport(opts) {
554
639
  export {
555
640
  DEFAULT_REASONING_LEVEL_OPTIONS,
556
641
  ReasoningLevelPicker,
642
+ HARNESS_MODEL_POLICIES,
643
+ modelProvider,
644
+ isModelCompatibleWithHarness,
645
+ snapModelToHarness,
646
+ snapHarnessToModel,
557
647
  AgentSessionControls,
558
648
  ArtifactAgentDock,
559
649
  createFetchTransport,
@@ -451,6 +451,39 @@ interface ProfileComparisonProps {
451
451
  }
452
452
  declare function ProfileComparison({ profiles, className, }: ProfileComparisonProps): react_jsx_runtime.JSX.Element | null;
453
453
 
454
+ interface MetricChartPoint {
455
+ /** Wall-clock ms. */
456
+ at: number;
457
+ /** Null renders a gap in the series, never a zero. */
458
+ value: number | null;
459
+ }
460
+ type MetricChartTone = "primary" | "success" | "warning" | "danger";
461
+ interface MetricAreaChartProps {
462
+ points: MetricChartPoint[];
463
+ label: string;
464
+ /** Formats the current value and y-axis bounds (e.g. percent, bytes). */
465
+ formatValue: (value: number) => string;
466
+ /**
467
+ * Fixed y-axis maximum (100 for percents, memory total for bytes).
468
+ * Omitted = auto-scale to the observed maximum with 10% headroom.
469
+ */
470
+ maxValue?: number;
471
+ /** Secondary line under the current value (e.g. "of 8 GiB"). */
472
+ detail?: React.ReactNode;
473
+ tone?: MetricChartTone;
474
+ /** Plot height in px. Defaults to 96. */
475
+ height?: number;
476
+ /** Shown instead of the plot while no point has a value yet. */
477
+ emptyState?: React.ReactNode;
478
+ className?: string;
479
+ }
480
+ /**
481
+ * Lightweight live time-series panel (Prometheus/Grafana idiom): big
482
+ * current value, area chart of the rolling window, dashed gridlines.
483
+ * Pure SVG — no chart dependency. Null samples render as gaps.
484
+ */
485
+ declare function MetricAreaChart({ points, label, formatValue, maxValue, detail, tone, height, emptyState, className, }: MetricAreaChartProps): react_jsx_runtime.JSX.Element;
486
+
454
487
  type VariantStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
455
488
  type VariantOutcome = "pending_review" | "accepted" | "rejected" | "merged_with_conflicts" | "expired";
456
489
  interface Variant {
@@ -626,4 +659,4 @@ interface InfoPanelProps {
626
659
  }
627
660
  declare function InfoPanel({ label, title, description, className }: InfoPanelProps): react_jsx_runtime.JSX.Element;
628
661
 
629
- export { BackendConfig, type BackendConfigProps, type BackendStatusData, ClusterStatusBar, type ClusterStatusBarProps, type ClusterStatusItem, CreditBalance, type CreditBalanceProps, DashboardLayout, type DashboardLayoutProps, type Profile as DashboardProfile, type SnapshotInfo as DashboardSnapshotInfo, type DashboardUser, type ExposedPort, type GitCommitData, GitPanel, type GitPanelProps, type GitStatusData, INSUFFICIENT_BALANCE_CODE, InfoPanel, type InfoPanelProps, type InsufficientBalance, type Invoice, InvoiceTable, type InvoiceTableProps, type McpServer, type NavItem, NetworkConfig, type NetworkConfigData, type NetworkConfigProps, NewSandboxCard, type NewSandboxCardProps, OutOfCreditsModal, type OutOfCreditsModalProps, type PlanCardData, PlanCards, type PlanCardsProps, type PlanFeature, PortsList, type PortsListProps, type ProcessInfo, ProcessList, type ProcessListProps, type ProductVariant, ProfileAvatar, type ProfileAvatarProps, ProfileComparison, type ProfileComparisonProps, ProfileSelector, type ProfileSelectorProps, PromoBanner, type PromoBannerProps, RailButton, type RailButtonProps, RailModeButton, type RailModeButtonProps, RailSeparator, type RailSeparatorProps, ResourceMeter, type ResourceMeterProps, SIDEBAR_MOBILE_WIDTH, SIDEBAR_PANEL_WIDTH, SIDEBAR_RAIL_WIDTH, SIDEBAR_TOTAL_WIDTH, SandboxCard, type SandboxCardData, type SandboxCardProps, type SandboxStatus, SandboxTable, type SandboxTableProps, Sidebar, SidebarContent, type SidebarContentProps, SidebarPanel, SidebarPanelContent, type SidebarPanelContentProps, SidebarPanelHeader, type SidebarPanelHeaderProps, type SidebarPanelProps, type SidebarProps, SidebarProvider, type SidebarProviderProps, SidebarRail, SidebarRailFooter, type SidebarRailFooterProps, SidebarRailHeader, type SidebarRailHeaderProps, SidebarRailNav, type SidebarRailNavProps, type SidebarRailProps, type SidebarUser, SnapshotList, type SnapshotListProps, SystemLogsViewer, type SystemLogsViewerProps, type TeamRole, UsageSummary, type UsageSummaryData, type UsageSummaryProps, type Variant, VariantList, type VariantListProps, type VariantOutcome, type VariantStatus, canAdminSandbox, parseInsufficientBalance, useSidebar };
662
+ export { BackendConfig, type BackendConfigProps, type BackendStatusData, ClusterStatusBar, type ClusterStatusBarProps, type ClusterStatusItem, CreditBalance, type CreditBalanceProps, DashboardLayout, type DashboardLayoutProps, type Profile as DashboardProfile, type SnapshotInfo as DashboardSnapshotInfo, type DashboardUser, type ExposedPort, type GitCommitData, GitPanel, type GitPanelProps, type GitStatusData, INSUFFICIENT_BALANCE_CODE, InfoPanel, type InfoPanelProps, type InsufficientBalance, type Invoice, InvoiceTable, type InvoiceTableProps, type McpServer, MetricAreaChart, type MetricAreaChartProps, type MetricChartPoint, type MetricChartTone, type NavItem, NetworkConfig, type NetworkConfigData, type NetworkConfigProps, NewSandboxCard, type NewSandboxCardProps, OutOfCreditsModal, type OutOfCreditsModalProps, type PlanCardData, PlanCards, type PlanCardsProps, type PlanFeature, PortsList, type PortsListProps, type ProcessInfo, ProcessList, type ProcessListProps, type ProductVariant, ProfileAvatar, type ProfileAvatarProps, ProfileComparison, type ProfileComparisonProps, ProfileSelector, type ProfileSelectorProps, PromoBanner, type PromoBannerProps, RailButton, type RailButtonProps, RailModeButton, type RailModeButtonProps, RailSeparator, type RailSeparatorProps, ResourceMeter, type ResourceMeterProps, SIDEBAR_MOBILE_WIDTH, SIDEBAR_PANEL_WIDTH, SIDEBAR_RAIL_WIDTH, SIDEBAR_TOTAL_WIDTH, SandboxCard, type SandboxCardData, type SandboxCardProps, type SandboxStatus, SandboxTable, type SandboxTableProps, Sidebar, SidebarContent, type SidebarContentProps, SidebarPanel, SidebarPanelContent, type SidebarPanelContentProps, SidebarPanelHeader, type SidebarPanelHeaderProps, type SidebarPanelProps, type SidebarProps, SidebarProvider, type SidebarProviderProps, SidebarRail, SidebarRailFooter, type SidebarRailFooterProps, SidebarRailHeader, type SidebarRailHeaderProps, SidebarRailNav, type SidebarRailNavProps, type SidebarRailProps, type SidebarUser, SnapshotList, type SnapshotListProps, SystemLogsViewer, type SystemLogsViewerProps, type TeamRole, UsageSummary, type UsageSummaryData, type UsageSummaryProps, type Variant, VariantList, type VariantListProps, type VariantOutcome, type VariantStatus, canAdminSandbox, parseInsufficientBalance, useSidebar };
package/dist/dashboard.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  GitPanel,
7
7
  INSUFFICIENT_BALANCE_CODE,
8
8
  InvoiceTable,
9
+ MetricAreaChart,
9
10
  NetworkConfig,
10
11
  NewSandboxCard,
11
12
  OutOfCreditsModal,
@@ -43,7 +44,7 @@ import {
43
44
  canAdminSandbox,
44
45
  parseInsufficientBalance,
45
46
  useSidebar
46
- } from "./chunk-FLWMBK77.js";
47
+ } from "./chunk-LA5GHELP.js";
47
48
  import {
48
49
  BillingDashboard,
49
50
  InfoPanel,
@@ -78,6 +79,7 @@ export {
78
79
  INSUFFICIENT_BALANCE_CODE,
79
80
  InfoPanel,
80
81
  InvoiceTable,
82
+ MetricAreaChart,
81
83
  ModelPicker,
82
84
  NetworkConfig,
83
85
  NewSandboxCard,