@datalayer/core 1.2.28 → 1.2.29

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.
Files changed (38) hide show
  1. package/lib/api/mcp/observability.d.ts +1 -1
  2. package/lib/api/mcp/observability.js +2 -0
  3. package/lib/api/mcp/operations.d.ts +28 -10
  4. package/lib/api/mcp/tasks.d.ts +9 -1
  5. package/lib/api/otel/firewall.d.ts +74 -0
  6. package/lib/api/otel/firewall.js +42 -0
  7. package/lib/api/otel/index.d.ts +1 -0
  8. package/lib/api/otel/index.js +1 -0
  9. package/lib/cli-login.js +2 -2
  10. package/lib/components/anonymous/AnonymousKeyExpired.js +15 -3
  11. package/lib/components/principal/PrincipalSwitcherMenu.js +4 -4
  12. package/lib/components/sharing/ShareAccessComponent.js +33 -8
  13. package/lib/components/toast/ToastPanel.js +18 -7
  14. package/lib/components/toast/toastQueue.d.ts +2 -0
  15. package/lib/hooks/index.d.ts +1 -0
  16. package/lib/hooks/index.js +1 -0
  17. package/lib/hooks/useCache.d.ts +4 -2
  18. package/lib/hooks/useCache.js +13 -4
  19. package/lib/hooks/useKeptScroll.d.ts +6 -0
  20. package/lib/hooks/useKeptScroll.js +54 -0
  21. package/lib/hooks/useMcp.d.ts +2 -2
  22. package/lib/hooks/useMcp.js +5 -2
  23. package/lib/hooks/useNavigate.d.ts +16 -2
  24. package/lib/hooks/useNavigate.js +65 -5
  25. package/lib/models/Usage.d.ts +13 -0
  26. package/lib/models/Usage.js +5 -0
  27. package/lib/otel/client/OtelClient.d.ts +56 -0
  28. package/lib/otel/client/OtelClient.js +25 -0
  29. package/lib/otel/client/index.d.ts +1 -1
  30. package/lib/otel/index.d.ts +1 -1
  31. package/lib/otel/views/OtelLive.js +1 -1
  32. package/lib/state/substates/LayoutState.js +24 -3
  33. package/lib/state/substates/NavigationState.d.ts +11 -3
  34. package/lib/state/substates/NavigationState.js +48 -5
  35. package/lib/views/mcp/McpObservability.d.ts +22 -3
  36. package/lib/views/mcp/McpObservability.js +65 -17
  37. package/lib/views/mcp/RunDetail.js +2 -2
  38. package/package.json +1 -1
@@ -10,6 +10,17 @@ import { TraceTimeline } from './TraceTimeline.js';
10
10
  import { useNavigate } from '../../hooks/index.js';
11
11
  import { MCP_METRIC_CATALOG } from '../../api/mcp/index.js';
12
12
  import { durationLabel, timeAgo } from './format.js';
13
+ /** Every pane, in the order the switcher shows them. */
14
+ export const ALL_PANES = [
15
+ 'runs',
16
+ 'live',
17
+ 'metrics',
18
+ ];
19
+ const PANE_LABELS = {
20
+ runs: 'Runs',
21
+ live: 'Live',
22
+ metrics: 'Metrics',
23
+ };
13
24
  /**
14
25
  * What part of the platform a span belongs to, from its name.
15
26
  *
@@ -71,8 +82,11 @@ const Sli = ({ label, value, note, }) => (_jsxs(Card, { border: true, shadow: "n
71
82
  * second is usually a problem with the network between them, and sending
72
83
  * somebody to the wrong one costs an hour.
73
84
  *
74
- * A healthy engine with a backlog is still healthy, and says so with the
75
- * backlog beside it: a queue is a queue, not a fault.
85
+ * An engine taking work is healthy; a queue it was told to run that nobody
86
+ * serves is drawn as a warning beside it — work sent there is accepted and
87
+ * never done. The engine reports no backlog of its own (DBOS's pending and
88
+ * enqueued are both `running`), so what is running is counted among the
89
+ * newest runs it lists, and said as such.
76
90
  */
77
91
  export const engineLook = (health, failed) => {
78
92
  if (failed) {
@@ -85,26 +99,58 @@ export const engineLook = (health, failed) => {
85
99
  if (!health) {
86
100
  return { label: 'Asking…', variant: 'secondary', note: '' };
87
101
  }
88
- const backlog = (health.queues ?? []).reduce((total, queue) => total + (queue.backlog ?? 0), 0);
89
- if (!health.healthy) {
102
+ if (!health.durable) {
90
103
  return {
91
104
  label: 'Unhealthy',
92
105
  variant: 'danger',
93
- note: health.detail || 'The engine reports itself unhealthy.',
106
+ note: health.detail || 'The engine reports it is not taking work.',
94
107
  };
95
108
  }
109
+ const unserved = health.queuesNobodyServes ?? [];
110
+ if (unserved.length > 0) {
111
+ return {
112
+ label: 'Healthy',
113
+ variant: 'attention',
114
+ note: `Nobody serves ${unserved.join(', ')}: work sent there is accepted and never done.`,
115
+ };
116
+ }
117
+ const running = (health.runs ?? []).filter(run => run.status === 'running').length;
96
118
  return {
97
119
  label: 'Healthy',
98
- variant: backlog > 0 ? 'attention' : 'success',
99
- note: backlog > 0
100
- ? `${backlog} ${backlog === 1 ? 'run is' : 'runs are'} waiting to be picked up.`
101
- : 'Nothing is waiting to be picked up.',
120
+ variant: 'success',
121
+ note: running > 0
122
+ ? `${running} of its newest runs ${running === 1 ? 'is' : 'are'} running.`
123
+ : 'None of its newest runs is running.',
102
124
  };
103
125
  };
126
+ /** Each queue the engine polls, with how many of its newest runs are running there and the oldest of them. */
127
+ export const engineQueues = (health, now = Date.now()) => {
128
+ if (!health)
129
+ return [];
130
+ const unserved = health.queuesNobodyServes ?? [];
131
+ const names = [
132
+ ...(health.queues ?? []),
133
+ ...unserved.filter(name => !(health.queues ?? []).includes(name)),
134
+ ];
135
+ return names.map(name => {
136
+ const running = (health.runs ?? []).filter(run => run.queue === name && run.status === 'running');
137
+ const started = running
138
+ .map(run => run.startedAt)
139
+ .filter((at) => typeof at === 'number');
140
+ return {
141
+ name,
142
+ running: running.length,
143
+ oldestSeconds: started.length
144
+ ? Math.max(0, Math.round(now / 1000 - Math.min(...started)))
145
+ : undefined,
146
+ served: !unserved.includes(name),
147
+ };
148
+ });
149
+ };
104
150
  const percentLabel = (value) => value === null || value === undefined
105
151
  ? 'Not measured'
106
152
  : `${(value * 100).toFixed(1)}%`;
107
- export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskUid, traceId, onSelect, agent, org, showTitle = true, }) => {
153
+ export const McpObservability = ({ errorState, routes, pane, onPaneChange, panes = ALL_PANES, taskUid, traceId, onSelect, agent, org, showTitle = true, }) => {
108
154
  const navigate = useNavigate();
109
155
  const [find, setFind] = useState(taskUid ?? traceId ?? '');
110
156
  // One of the two, never both: a task names its own trace, and a trace
@@ -120,7 +166,7 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
120
166
  const engine = useMcpWorkflowsHealth({ enabled: pane === 'live' });
121
167
  const live = useTasks({ status: 'working' }, { enabled: pane === 'live' });
122
168
  const telemetryNotice = (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.subtle' }, children: ["This is telemetry: spans and metrics are sampled, kept for a short time and meant for understanding how a call ran. It is not the record of what was allowed \u2014", ' ', _jsx(Text, { as: "span", sx: { color: 'accent.fg', cursor: 'pointer' }, onClick: () => navigate(routes.audit), children: "the audit log" }), ' ', "is, and it is kept per plan and never changed."] }));
123
- return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Observability" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "How the calls of your agents ran: where the time went, and how the service is behaving." })] })), _jsxs(SegmentedControl, { "aria-label": "Observability panes", size: "small", children: [_jsx(SegmentedControl.Button, { selected: pane === 'runs', onClick: () => onPaneChange('runs'), children: "Runs" }), _jsx(SegmentedControl.Button, { selected: pane === 'live', onClick: () => onPaneChange('live'), children: "Live" }), _jsx(SegmentedControl.Button, { selected: pane === 'metrics', onClick: () => onPaneChange('metrics'), children: "Metrics" })] }), telemetryNotice, pane === 'runs' && (_jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(TextInput, { size: "small", leadingVisual: SearchIcon, "aria-label": "Task or trace id", placeholder: "Task uid or trace id", value: find, sx: { minWidth: 320 }, onChange: event => setFind(event.currentTarget.value) }), _jsx(Button, { size: "small", onClick: () => {
169
+ return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [showTitle && (_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Observability" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: "How the calls of your agents ran: where the time went, and how the service is behaving." })] })), panes.length > 1 && (_jsx(SegmentedControl, { "aria-label": "Observability panes", size: "small", children: panes.map(each => (_jsx(SegmentedControl.Button, { selected: pane === each, onClick: () => onPaneChange(each), children: PANE_LABELS[each] }, each))) })), telemetryNotice, pane === 'runs' && (_jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(TextInput, { size: "small", leadingVisual: SearchIcon, "aria-label": "Task or trace id", placeholder: "Task uid or trace id", value: find, sx: { minWidth: 320 }, onChange: event => setFind(event.currentTarget.value) }), _jsx(Button, { size: "small", onClick: () => {
124
170
  const value = find.trim();
125
171
  if (!value) {
126
172
  onSelect({});
@@ -131,16 +177,18 @@ export const McpObservability = ({ errorState, routes, pane, onPaneChange, taskU
131
177
  onSelect(/^[0-9a-f]{32}$/i.test(value)
132
178
  ? { trace: value }
133
179
  : { task: value });
134
- }, children: "Show the run" })] }), !taskUid && !traceId ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Pick a run" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Open a call from the audit log or the dashboard, or paste its task uid or trace id here, and its spans across the gateway, the policy check and the worker appear with the time each took." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.audit), children: "Open the audit log" })] })) : run.isError ? (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'This run'), onRetry: () => run.refetch() })) : run.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (run.data?.spans.length ?? 0) === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No span for this call" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Spans reach the collector a few seconds after the call and are kept for a short time. A call older than the retention window has an audit row but no trace." }) })] })) : (_jsxs(Box, { display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 3, alignItems: "center", flexWrap: "wrap", children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: run.data?.traceId }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [run.data?.spans.length, " spans"] }), _jsx(Button, { size: "small", onClick: () => navigate(`${routes.audit}?trace=${encodeURIComponent(run.data?.traceId ?? '')}`), children: "The audit row" })] }), _jsx(TraceTimeline, { tree: run.data?.tree ?? [] }), _jsx(Card, { border: true, shadow: "none", display: "block", overflowX: "auto", children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) })] }))] })), pane === 'live' && (_jsxs(Box, { display: "grid", gap: 3, children: [(() => {
135
- const look = engineLook(engine.data, engine.isError);
136
- const queues = engine.data?.queues ?? [];
137
- return (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "The durable engine" }), _jsx(Label, { size: "small", variant: look.variant, children: look.label }), engine.data?.engine && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: engine.data.engine }))] }), look.note && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: look.note })), queues.length > 0 && (_jsx(Box, { display: "grid", gap: 1, children: queues.map(queue => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", fontSize: 0, children: [_jsx(Text, { sx: { fontFamily: 'mono' }, children: queue.name }), _jsxs(Text, { sx: { color: 'fg.muted' }, children: [queue.backlog, " waiting"] }), typeof queue.oldestWaitSeconds === 'number' && (_jsxs(Text, { sx: { color: 'fg.muted' }, children: ["oldest", ' ', durationLabel(queue.oldestWaitSeconds * 1000)] }))] }, queue.name))) }))] }));
138
- })(), _jsxs(Box, { display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "Running now" }), live.isError ? (_jsx(McpErrorBlankslate, { state: errorState(live.error, 'The running calls'), onRetry: () => live.refetch() })) : live.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(Spinner, {}) })) : (live.data?.items.length ?? 0) === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Nothing of yours is running. A call that finishes inside its own request never appears here \u2014 it is over before this pane could draw it \u2014 and is found by its trace in Runs." })) : ((live.data?.items ?? []).map(task => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Label, { size: "small", variant: "success", children: "Working" }), _jsx(Text, { sx: { fontSize: 0 }, children: task.tool || 'Run' }), _jsx(Text, { sx: {
180
+ }, children: "Show the run" })] }), !taskUid && !traceId ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "Pick a run" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Open a call from the audit log or the dashboard, or paste its task uid or trace id here, and its spans across the gateway, the policy check and the worker appear with the time each took." }) }), _jsx(Button, { size: "small", onClick: () => navigate(routes.audit), children: "Open the audit log" })] })) : run.isError ? (_jsx(McpErrorBlankslate, { state: errorState(run.error, 'This run'), onRetry: () => run.refetch() })) : run.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (run.data?.spans.length ?? 0) === 0 ? (_jsxs(Blankslate, { border: true, spacious: true, children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: "medium" }) }), _jsx(Blankslate.Heading, { children: "No span for this call" }), _jsx(Blankslate.Description, { children: _jsx(Text, { sx: { textAlign: 'center' }, children: "Spans reach the collector a few seconds after the call and are kept for a short time. A call older than the retention window has an audit row but no trace." }) })] })) : (_jsxs(Box, { display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 3, alignItems: "center", flexWrap: "wrap", children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: run.data?.traceId }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [run.data?.spans.length, " spans"] }), _jsx(Button, { size: "small", onClick: () => navigate(`${routes.audit}?trace=${encodeURIComponent(run.data?.traceId ?? '')}`), children: "The audit row" })] }), _jsx(TraceTimeline, { tree: run.data?.tree ?? [] }), _jsx(Card, { border: true, shadow: "none", display: "block", overflowX: "auto", children: (run.data?.tree ?? []).map(node => (_jsx(SpanRow, { node: node, depth: 0 }, node.span.span_id))) })] }))] })), pane === 'live' && (_jsxs(Box, { display: "grid", gap: 3, children: [_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "Running now" }), live.isError ? (_jsx(McpErrorBlankslate, { state: errorState(live.error, 'The running calls'), onRetry: () => live.refetch() })) : live.isPending ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(Spinner, {}) })) : (live.data?.items.length ?? 0) === 0 ? (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Nothing of yours is running. A call that finishes inside its own request never appears here \u2014 it is over before this pane could draw it \u2014 and is found by its trace in Runs." })) : ((live.data?.items ?? []).map(task => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", py: 1, borderBottom: "1px solid", borderColor: "border.muted", children: [_jsx(Label, { size: "small", variant: "success", children: "Working" }), _jsx(Text, { sx: { fontSize: 0 }, children: task.tool || 'Run' }), _jsx(Text, { sx: {
139
181
  fontSize: 0,
140
182
  fontFamily: 'mono',
141
183
  color: 'accent.fg',
142
184
  cursor: 'pointer',
143
- }, onClick: () => onSelect({ task: task.uid }), children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["started ", timeAgo(task.createdAt)] })] }, task.uid))))] })] })), pane === 'metrics' && (_jsx(Box, { display: "grid", gap: 3, children: metrics.isError ? (_jsx(McpErrorBlankslate, { state: errorState(metrics.error, 'Metrics'), onRetry: () => metrics.refetch() })) : metrics.isPending && !metrics.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: [
185
+ }, onClick: () => onSelect({ task: task.uid }), children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["started ", timeAgo(task.createdAt)] })] }, task.uid))))] }), (() => {
186
+ const look = engineLook(engine.data, engine.isError);
187
+ const queues = engineQueues(engine.data);
188
+ return (_jsxs(Card, { border: true, shadow: "none", p: 3, display: "grid", gap: 2, children: [_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "The durable engine" }), _jsx(Label, { size: "small", variant: look.variant, children: look.label }), engine.data?.engine && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: engine.data.engine }))] }), look.note && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: look.note })), queues.length > 0 && (_jsx(Box, { display: "grid", gap: 1, children: queues.map(queue => (_jsxs(Box, { display: "flex", gap: 2, alignItems: "center", fontSize: 0, children: [_jsx(Text, { sx: { fontFamily: 'mono' }, children: queue.name }), queue.served ? (_jsx(Text, { sx: { color: 'fg.muted' }, children: queue.running > 0
189
+ ? `${queue.running} running`
190
+ : 'idle' })) : (_jsx(Label, { size: "small", variant: "attention", children: "nobody serves it" })), typeof queue.oldestSeconds === 'number' && (_jsxs(Text, { sx: { color: 'fg.muted' }, children: ["oldest ", durationLabel(queue.oldestSeconds * 1000)] }))] }, queue.name))) }))] }));
191
+ })()] })), pane === 'metrics' && (_jsx(Box, { display: "grid", gap: 3, children: metrics.isError ? (_jsx(McpErrorBlankslate, { state: errorState(metrics.error, 'Metrics'), onRetry: () => metrics.refetch() })) : metrics.isPending && !metrics.data ? (_jsx(Box, { display: "flex", justifyContent: "center", py: 5, children: _jsx(Spinner, {}) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "grid", gap: 3, gridTemplateColumns: [
144
192
  '1fr',
145
193
  'repeat(2, 1fr)',
146
194
  'repeat(4, 1fr)',
@@ -160,8 +160,8 @@ export const RunDetail = ({ taskUid, errorState, routes, mock = false, }) => {
160
160
  return (_jsxs(Box, { display: "grid", gap: 3, minWidth: 0, children: [_jsxs(Box, { children: [_jsx(Link, { href: routes.runs, onClick: event => {
161
161
  event.preventDefault();
162
162
  navigate(routes.runs);
163
- }, sx: { fontSize: 0 }, children: "All runs" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, mt: 1, mb: 1 }, children: task.tool || 'Run' }), _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["updated ", _jsx(RelativeTime, { datetime: task.lastUpdatedAt })] })] }), task.statusMessage && (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2 }, children: task.statusMessage }))] }), task.status === 'input_required' && (_jsxs(Box, { borderWidth: 1, borderStyle: "solid", borderColor: "attention.emphasis", borderRadius: 2, p: 3, display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "This run is waiting on you" }), _jsx(Textarea, { "aria-label": "Your answer", value: reply, onChange: event => setReply(event.target.value), placeholder: "Your answer", rows: 3 }), _jsx(Box, { children: _jsx(Button, { variant: "primary", disabled: !reply.trim() || answer.isPending, onClick: () => answer.mutate({ taskUid: task.uid, input: { answer: reply } }, { onSuccess: () => setReply('') }), children: answer.isPending ? 'Answering…' : 'Answer' }) })] })), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Output" }), output ? (_jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 0, whiteSpace: "pre-wrap", overflowX: "auto", bg: "canvas.subtle", borderRadius: 2, p: 3, m: 0, children: output })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: over
163
+ }, sx: { fontSize: 0 }, children: "All runs" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, mt: 1, mb: 1 }, children: task.tool || 'Run' }), _jsxs(Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap", children: [_jsx(Label, { size: "small", variant: look.variant, children: look.label }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'fg.muted' }, children: task.uid }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["updated ", _jsx(RelativeTime, { datetime: task.lastUpdatedAt })] })] }), task.statusMessage && (_jsx(Text, { as: "p", sx: { fontSize: 1, color: 'fg.muted', mt: 2 }, children: task.statusMessage }))] }), task.status === 'input_required' && (_jsxs(Box, { borderWidth: 1, borderStyle: "solid", borderColor: "attention.emphasis", borderRadius: 2, p: 3, display: "grid", gap: 2, children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1 }, children: "This run is waiting on you" }), _jsx(Textarea, { "aria-label": "Your answer", value: reply, onChange: event => setReply(event.target.value), placeholder: "Your answer", rows: 3 }), _jsxs(Box, { children: [_jsx(Button, { variant: "primary", disabled: !reply.trim() || answer.isPending, onClick: () => answer.mutate({ taskUid: task.uid, input: { answer: reply } }, { onSuccess: () => setReply('') }), children: answer.isPending ? 'Answering…' : 'Answer' }), answer.error && (_jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'danger.fg', mt: 2 }, children: ["The answer did not reach the run: ", answer.error.message] }))] })] })), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Output" }), output ? (_jsx(Box, { as: "pre", fontFamily: "mono", fontSize: 0, whiteSpace: "pre-wrap", overflowX: "auto", bg: "canvas.subtle", borderRadius: 2, p: 3, m: 0, children: output })) : (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: over
164
164
  ? 'This run printed nothing.'
165
- : 'Nothing printed yet. Output appears here as it arrives.' }))] }), failure && (_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Why it failed" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'danger.fg' }, children: failure })] })), _jsx(TraceTimelineSection, { tree: mock ? SAMPLE_TRACE : (trace.data?.tree ?? []), traceId: mock ? SAMPLE_TRACE[0].span.trace_id : trace.data?.traceId, live: !over }), !over && (_jsxs(Box, { children: [_jsx(Button, { variant: "danger", disabled: cancel.isPending, onClick: () => cancel.mutate(task.uid), children: cancel.isPending ? 'Cancelling…' : 'Cancel this run' }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 2 }, children: "Cancelling interrupts the work itself, so the sandbox stops being billed for it. What it has already printed is kept." })] }))] }));
165
+ : 'Nothing printed yet. Output appears here as it arrives.' }))] }), failure && (_jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 2 }, children: "Why it failed" }), _jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0, color: 'danger.fg' }, children: failure })] })), _jsx(TraceTimelineSection, { tree: mock ? SAMPLE_TRACE : (trace.data?.tree ?? []), traceId: mock ? SAMPLE_TRACE[0].span.trace_id : trace.data?.traceId, live: !over }), !over && (_jsxs(Box, { children: [_jsx(Button, { variant: "danger", disabled: cancel.isPending, onClick: () => cancel.mutate(task.uid), children: cancel.isPending ? 'Cancelling…' : 'Cancel this run' }), cancel.error && (_jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'danger.fg', mt: 2 }, children: ["The run was not cancelled: ", cancel.error.message] })), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mt: 2 }, children: "Cancelling interrupts the work itself, so the sandbox stops being billed for it. What it has already printed is kept." })] }))] }));
166
166
  };
167
167
  export default RunDetail;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/core",
3
- "version": "1.2.28",
3
+ "version": "1.2.29",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",