@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.
- package/lib/api/mcp/observability.d.ts +1 -1
- package/lib/api/mcp/observability.js +2 -0
- package/lib/api/mcp/operations.d.ts +28 -10
- package/lib/api/mcp/tasks.d.ts +9 -1
- package/lib/api/otel/firewall.d.ts +74 -0
- package/lib/api/otel/firewall.js +42 -0
- package/lib/api/otel/index.d.ts +1 -0
- package/lib/api/otel/index.js +1 -0
- package/lib/cli-login.js +2 -2
- package/lib/components/anonymous/AnonymousKeyExpired.js +15 -3
- package/lib/components/principal/PrincipalSwitcherMenu.js +4 -4
- package/lib/components/sharing/ShareAccessComponent.js +33 -8
- package/lib/components/toast/ToastPanel.js +18 -7
- package/lib/components/toast/toastQueue.d.ts +2 -0
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useCache.d.ts +4 -2
- package/lib/hooks/useCache.js +13 -4
- package/lib/hooks/useKeptScroll.d.ts +6 -0
- package/lib/hooks/useKeptScroll.js +54 -0
- package/lib/hooks/useMcp.d.ts +2 -2
- package/lib/hooks/useMcp.js +5 -2
- package/lib/hooks/useNavigate.d.ts +16 -2
- package/lib/hooks/useNavigate.js +65 -5
- package/lib/models/Usage.d.ts +13 -0
- package/lib/models/Usage.js +5 -0
- package/lib/otel/client/OtelClient.d.ts +56 -0
- package/lib/otel/client/OtelClient.js +25 -0
- package/lib/otel/client/index.d.ts +1 -1
- package/lib/otel/index.d.ts +1 -1
- package/lib/otel/views/OtelLive.js +1 -1
- package/lib/state/substates/LayoutState.js +24 -3
- package/lib/state/substates/NavigationState.d.ts +11 -3
- package/lib/state/substates/NavigationState.js +48 -5
- package/lib/views/mcp/McpObservability.d.ts +22 -3
- package/lib/views/mcp/McpObservability.js +65 -17
- package/lib/views/mcp/RunDetail.js +2 -2
- package/package.json +1 -1
package/lib/hooks/useMcp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type McpTaskAnswered, type McpActivity, type McpActivityFilters, type McpAuditFilters, type McpBindingListFilters, type McpGatewayVersion, type McpMetricsFilters, type McpMetricsSnapshot, type McpOrganizationOverview, type McpOrganizationUsage, type McpOrganizationOverviewFilters, type McpPolicyFilters, type McpRunLogs, type McpRunTrace, type McpTaskEvent, type McpTaskListFilters, type McpWorkerList, type McpWorkflowsHealth } from '../api/mcp';
|
|
1
|
+
import { type McpTaskAnswered, type McpTaskCancelled, type McpActivity, type McpActivityFilters, type McpAuditFilters, type McpBindingListFilters, type McpGatewayVersion, type McpMetricsFilters, type McpMetricsSnapshot, type McpOrganizationOverview, type McpOrganizationUsage, type McpOrganizationOverviewFilters, type McpPolicyFilters, type McpRunLogs, type McpRunTrace, type McpTaskEvent, type McpTaskListFilters, type McpWorkerList, type McpWorkflowsHealth } from '../api/mcp';
|
|
2
2
|
import { type ConnectedAgent } from '../api/iam/connectedAgents';
|
|
3
3
|
import { type OrganizationTeam } from '../api/iam/organizationTeams';
|
|
4
4
|
import { setAuditSettings, type McpAuditSettings } from '../api/iam/mcpAuditSettings';
|
|
@@ -28,7 +28,7 @@ export declare const useTaskEvents: (taskUid?: string, options?: {
|
|
|
28
28
|
enabled?: boolean;
|
|
29
29
|
onEvent?: (event: McpTaskEvent) => void;
|
|
30
30
|
}) => void;
|
|
31
|
-
export declare const useCancelTask: () => import("@tanstack/react-query").UseMutationResult<
|
|
31
|
+
export declare const useCancelTask: () => import("@tanstack/react-query").UseMutationResult<McpTaskCancelled, Error, string, unknown>;
|
|
32
32
|
export declare const useAnswerTask: () => import("@tanstack/react-query").UseMutationResult<McpTaskAnswered, Error, {
|
|
33
33
|
taskUid: string;
|
|
34
34
|
input: Record<string, unknown>;
|
package/lib/hooks/useMcp.js
CHANGED
|
@@ -124,8 +124,11 @@ export const useCancelTask = () => {
|
|
|
124
124
|
const mcpUrl = useMcpServerUrl();
|
|
125
125
|
return useMutation({
|
|
126
126
|
mutationFn: taskUid => cancelTask(token ?? '', taskUid, mcpUrl),
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
// The gateway answers `{uid, cancelled, status}`, not the task: read the
|
|
128
|
+
// task again rather than caching that reply in its place (the run page
|
|
129
|
+
// lost its tool and output on a cancel, as it did on an answer).
|
|
130
|
+
onSuccess: (_cancelled, taskUid) => {
|
|
131
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.mcp.task(taskUid) });
|
|
129
132
|
queryClient.invalidateQueries({ queryKey: queryKeys.mcp.tasks() });
|
|
130
133
|
queryClient.invalidateQueries({ queryKey: queryKeys.mcp.activity() });
|
|
131
134
|
},
|
|
@@ -5,8 +5,15 @@ export interface NavigateOptions {
|
|
|
5
5
|
state?: unknown;
|
|
6
6
|
/** Open the destination in a new tab and leave this one where it is. */
|
|
7
7
|
newTab?: boolean;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Where the reader lands: the top of the page when unsaid or `true`; an
|
|
10
|
+
* element, by its id (with or without its `#`), scrolled into view once
|
|
11
|
+
* the page has it; `false` to leave the scroll where it is. The page and
|
|
12
|
+
* every scroll panel marked `data-scroll-root` are scrolled, so a layout
|
|
13
|
+
* that scrolls inside its own panel lands at the top too (the user,
|
|
14
|
+
* 6 November 2026: the Trust page opened scrolled down).
|
|
15
|
+
*/
|
|
16
|
+
scroll?: boolean | string;
|
|
10
17
|
/**
|
|
11
18
|
* Close the layout's side portals on the way; on by default. Only the
|
|
12
19
|
* history API host has them.
|
|
@@ -40,5 +47,12 @@ export type Navigate = (to: NavigateTarget, optionsOrEvent?: NavigateOptions | N
|
|
|
40
47
|
export declare function isNewTabClick(event: NavigateEvent): boolean;
|
|
41
48
|
/** Open `to` in a new tab, without handing it this window. */
|
|
42
49
|
export declare function openInNewTab(to: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Scroll on arrival: to the top (`undefined` or `true`), to an element by
|
|
52
|
+
* its id (a string, `#` or not), or nowhere (`false`). Done now and again
|
|
53
|
+
* on the next frames, since the page navigated to renders after the call
|
|
54
|
+
* and a layout's panel keeps its scroll across pages.
|
|
55
|
+
*/
|
|
56
|
+
export declare function scrollOnArrival(target: boolean | string | undefined): void;
|
|
43
57
|
export declare const useNavigate: () => Navigate;
|
|
44
58
|
export default useNavigate;
|
package/lib/hooks/useNavigate.js
CHANGED
|
@@ -62,6 +62,68 @@ function isLinkTo(event, to) {
|
|
|
62
62
|
return false;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The scroll panels a layout marks, beside the page itself: `data-scroll-root`,
|
|
67
|
+
* or the `dla-scroll-root` class for a component that passes a class name but
|
|
68
|
+
* no data attribute (Primer's `PageLayout.Content`, whose own element is the
|
|
69
|
+
* one that scrolls; 9 November 2026, a card on /agentspecs/identities opened
|
|
70
|
+
* its page scrolled down).
|
|
71
|
+
*/
|
|
72
|
+
const scrollRoots = () => Array.from(document.querySelectorAll('[data-scroll-root], .dla-scroll-root'));
|
|
73
|
+
const nextFrame = (run) => {
|
|
74
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
75
|
+
requestAnimationFrame(run);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
setTimeout(run, 16);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** How many frames an element is waited for: a page loaded lazily takes a few. */
|
|
82
|
+
const ARRIVAL_FRAMES = 60;
|
|
83
|
+
/**
|
|
84
|
+
* Scroll on arrival: to the top (`undefined` or `true`), to an element by
|
|
85
|
+
* its id (a string, `#` or not), or nowhere (`false`). Done now and again
|
|
86
|
+
* on the next frames, since the page navigated to renders after the call
|
|
87
|
+
* and a layout's panel keeps its scroll across pages.
|
|
88
|
+
*/
|
|
89
|
+
export function scrollOnArrival(target) {
|
|
90
|
+
if (target === false || typeof document === 'undefined') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const id = typeof target === 'string' ? target.replace(/^#/, '') : '';
|
|
94
|
+
if (!id) {
|
|
95
|
+
const toTop = () => {
|
|
96
|
+
window.scrollTo(0, 0);
|
|
97
|
+
document.body.scrollTop = 0;
|
|
98
|
+
document.documentElement.scrollTop = 0;
|
|
99
|
+
for (const root of scrollRoots()) {
|
|
100
|
+
root.scrollTop = 0;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
toTop();
|
|
104
|
+
let frames = 0;
|
|
105
|
+
const again = () => {
|
|
106
|
+
toTop();
|
|
107
|
+
if (++frames < 2) {
|
|
108
|
+
nextFrame(again);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
nextFrame(again);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
let tries = 0;
|
|
115
|
+
const attempt = () => {
|
|
116
|
+
const element = document.getElementById(id);
|
|
117
|
+
if (element) {
|
|
118
|
+
element.scrollIntoView({ block: 'start' });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (++tries < ARRIVAL_FRAMES) {
|
|
122
|
+
nextFrame(attempt);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
attempt();
|
|
126
|
+
}
|
|
65
127
|
const isEvent = (value) => !!value && typeof value.preventDefault === 'function';
|
|
66
128
|
function parseCall(optionsOrEvent, third, fourth) {
|
|
67
129
|
if (isEvent(optionsOrEvent)) {
|
|
@@ -129,10 +191,7 @@ export const useNavigate = () => {
|
|
|
129
191
|
return;
|
|
130
192
|
}
|
|
131
193
|
event?.preventDefault();
|
|
132
|
-
|
|
133
|
-
window.scrollTo(0, 0);
|
|
134
|
-
document.body.scrollTop = 0;
|
|
135
|
-
}
|
|
194
|
+
scrollOnArrival(options.scroll);
|
|
136
195
|
// Where the reader is now, for the shell they are in. A new tab and a
|
|
137
196
|
// step back through history are both already past by here. Only for
|
|
138
197
|
// somebody signed in: the shells are theirs, and a page a visitor read
|
|
@@ -152,7 +211,8 @@ export const useNavigate = () => {
|
|
|
152
211
|
nativeNavigate(to, {
|
|
153
212
|
replace: options.replace,
|
|
154
213
|
state: options.state,
|
|
155
|
-
|
|
214
|
+
// Scrolled above, after the page has rendered.
|
|
215
|
+
scroll: false,
|
|
156
216
|
});
|
|
157
217
|
}, [rrNavigate, nativeNavigate, layoutStore]);
|
|
158
218
|
};
|
package/lib/models/Usage.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ interface IResources {
|
|
|
8
8
|
memory: string;
|
|
9
9
|
'nvidia.com/gpu'?: string;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* What ran and hosted the agent a usage record is for (`runtime_engine`):
|
|
13
|
+
* a Datalayer runtime, kagent, or AWS AgentCore in an organization's AWS
|
|
14
|
+
* account. IAM records it from the launch; a record from before, or of a
|
|
15
|
+
* call with no agent runtime behind it, has none.
|
|
16
|
+
*/
|
|
17
|
+
export type UsageRuntimeEngine = 'datalayer' | 'kagent' | 'agentcore';
|
|
18
|
+
/** The engine a raw record says, or `undefined` when it says none (or one not known). */
|
|
19
|
+
export declare const usageRuntimeEngineOf: (value: unknown) => UsageRuntimeEngine | undefined;
|
|
11
20
|
export interface IRawUsage {
|
|
12
21
|
account_uid: string;
|
|
13
22
|
resource_uid: string;
|
|
@@ -26,6 +35,7 @@ export interface IRawUsage {
|
|
|
26
35
|
usage_period_label?: string;
|
|
27
36
|
usage_period_start?: string;
|
|
28
37
|
usage_period_end?: string;
|
|
38
|
+
runtime_engine?: UsageRuntimeEngine | null;
|
|
29
39
|
metadata: Map<string, string>;
|
|
30
40
|
}
|
|
31
41
|
export declare const asUsage: (u: any) => {
|
|
@@ -46,6 +56,7 @@ export declare const asUsage: (u: any) => {
|
|
|
46
56
|
usagePeriodLabel: any;
|
|
47
57
|
usagePeriodStart: Date | undefined;
|
|
48
58
|
usagePeriodEnd: Date | undefined;
|
|
59
|
+
runtimeEngine: UsageRuntimeEngine | undefined;
|
|
49
60
|
metadata: Map<string, string>;
|
|
50
61
|
};
|
|
51
62
|
export interface IUsage {
|
|
@@ -66,6 +77,8 @@ export interface IUsage {
|
|
|
66
77
|
usagePeriodLabel?: string;
|
|
67
78
|
usagePeriodStart?: Date;
|
|
68
79
|
usagePeriodEnd?: Date;
|
|
80
|
+
/** What ran and hosted its agent; absent on a record that says none. */
|
|
81
|
+
runtimeEngine?: UsageRuntimeEngine;
|
|
69
82
|
metadata: Map<string, string>;
|
|
70
83
|
}
|
|
71
84
|
export {};
|
package/lib/models/Usage.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
/** The engine a raw record says, or `undefined` when it says none (or one not known). */
|
|
6
|
+
export const usageRuntimeEngineOf = (value) => value === 'datalayer' || value === 'kagent' || value === 'agentcore'
|
|
7
|
+
? value
|
|
8
|
+
: undefined;
|
|
5
9
|
export const asUsage = (u) => {
|
|
6
10
|
const usagePeriodKey = u.usage_period_key;
|
|
7
11
|
const usagePeriodLabel = u.usage_period_label;
|
|
@@ -25,6 +29,7 @@ export const asUsage = (u) => {
|
|
|
25
29
|
usagePeriodLabel,
|
|
26
30
|
usagePeriodStart: usagePeriodStart ? new Date(usagePeriodStart) : undefined,
|
|
27
31
|
usagePeriodEnd: usagePeriodEnd ? new Date(usagePeriodEnd) : undefined,
|
|
32
|
+
runtimeEngine: usageRuntimeEngineOf(u.runtime_engine),
|
|
28
33
|
metadata: new Map(Object.entries(u.metadata ?? {})),
|
|
29
34
|
};
|
|
30
35
|
};
|
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
import type { OtelSpan, OtelLog, OtelMetric, OtelQueryResult } from '../types';
|
|
2
2
|
import type { OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable } from '../hooks';
|
|
3
|
+
export interface FetchTokenUsageOptions {
|
|
4
|
+
/** The window, in hours back from now (24 by default). */
|
|
5
|
+
hours?: number;
|
|
6
|
+
/** Only these services (runtimes); every one by default. */
|
|
7
|
+
serviceNames?: string[];
|
|
8
|
+
accountUid?: string;
|
|
9
|
+
}
|
|
10
|
+
/** What `GET /api/otel/v1/metrics/tokens` answers. */
|
|
11
|
+
export interface OtelTokenUsage {
|
|
12
|
+
total: number;
|
|
13
|
+
prompt: number;
|
|
14
|
+
completion: number;
|
|
15
|
+
by_service: Array<{
|
|
16
|
+
service_name: string;
|
|
17
|
+
total: number;
|
|
18
|
+
prompt: number;
|
|
19
|
+
completion: number;
|
|
20
|
+
}>;
|
|
21
|
+
window?: {
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
hours: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* When telemetry last reached the store, whatever the account: `at` in
|
|
28
|
+
* unix nanoseconds, from rows ingested since the service started
|
|
29
|
+
* (`ingest`) or from its newest stored file (`files`); `null` with the
|
|
30
|
+
* `reason` when nothing was ever stored or the time could not be read.
|
|
31
|
+
* Absent from a service that does not say it.
|
|
32
|
+
*/
|
|
33
|
+
last_received?: OtelLastReceived;
|
|
34
|
+
/**
|
|
35
|
+
* The same for the asked account, known only from what arrived since the
|
|
36
|
+
* service started (`since`, unix nanoseconds): `at` is `null`, with a
|
|
37
|
+
* `reason`, until rows of the account arrive.
|
|
38
|
+
*/
|
|
39
|
+
account_last_received?: OtelAccountLastReceived;
|
|
40
|
+
}
|
|
41
|
+
export interface OtelLastReceived {
|
|
42
|
+
at: number | null;
|
|
43
|
+
source: 'ingest' | 'files' | null;
|
|
44
|
+
reason: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface OtelAccountLastReceived {
|
|
47
|
+
at: number | null;
|
|
48
|
+
since: number;
|
|
49
|
+
reason: string | null;
|
|
50
|
+
}
|
|
3
51
|
export interface OtelClientOptions {
|
|
4
52
|
/**
|
|
5
53
|
* Base URL of the OTEL service.
|
|
@@ -65,6 +113,14 @@ export declare class OtelClient {
|
|
|
65
113
|
data: OtelMetric[];
|
|
66
114
|
count: number;
|
|
67
115
|
}>;
|
|
116
|
+
/**
|
|
117
|
+
* The tokens agents counted over the last `hours` (24 by default): total,
|
|
118
|
+
* prompt and completion, overall and per service, in one request — the
|
|
119
|
+
* service reads each counter's increase over the window in one scan. The
|
|
120
|
+
* answer also says when telemetry last reached the store (`last_received`),
|
|
121
|
+
* so a total of 0 can be told from a store that stopped receiving.
|
|
122
|
+
*/
|
|
123
|
+
fetchTokenUsage(options?: FetchTokenUsageOptions): Promise<OtelTokenUsage>;
|
|
68
124
|
/** Sum all values for one metric name, with optional service filter. */
|
|
69
125
|
fetchMetricTotal(metricName: string, options?: FetchMetricTotalOptions): Promise<number>;
|
|
70
126
|
/** List known service names observed in traces. */
|
|
@@ -177,6 +177,31 @@ export class OtelClient {
|
|
|
177
177
|
: [];
|
|
178
178
|
return { data: normalized, count: normalized.length };
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* The tokens agents counted over the last `hours` (24 by default): total,
|
|
182
|
+
* prompt and completion, overall and per service, in one request — the
|
|
183
|
+
* service reads each counter's increase over the window in one scan. The
|
|
184
|
+
* answer also says when telemetry last reached the store (`last_received`),
|
|
185
|
+
* so a total of 0 can be told from a store that stopped receiving.
|
|
186
|
+
*/
|
|
187
|
+
async fetchTokenUsage(options = {}) {
|
|
188
|
+
const params = new URLSearchParams({ hours: String(options.hours ?? 24) });
|
|
189
|
+
for (const name of options.serviceNames ?? []) {
|
|
190
|
+
params.append('service_name', name);
|
|
191
|
+
}
|
|
192
|
+
if (options.accountUid)
|
|
193
|
+
params.set('account_uid', options.accountUid);
|
|
194
|
+
const resp = await otelFetch(`${this.baseUrl}/api/otel/v1/metrics/tokens?${params}`, this.token, this.userUid);
|
|
195
|
+
return {
|
|
196
|
+
total: Number(resp.total ?? 0),
|
|
197
|
+
prompt: Number(resp.prompt ?? 0),
|
|
198
|
+
completion: Number(resp.completion ?? 0),
|
|
199
|
+
by_service: Array.isArray(resp.by_service) ? resp.by_service : [],
|
|
200
|
+
window: resp.window,
|
|
201
|
+
last_received: resp.last_received,
|
|
202
|
+
account_last_received: resp.account_last_received,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
180
205
|
/** Sum all values for one metric name, with optional service filter. */
|
|
181
206
|
async fetchMetricTotal(metricName, options = {}) {
|
|
182
207
|
const { serviceName, limit = 500, fallbackWithoutService = true, accountUid, } = options;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { OtelClient, createOtelClient } from './OtelClient';
|
|
2
|
-
export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './OtelClient';
|
|
2
|
+
export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, FetchTokenUsageOptions, OtelTokenUsage, OtelLastReceived, OtelAccountLastReceived, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './OtelClient';
|
package/lib/otel/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type { ResolvedOtelAuth } from './auth';
|
|
|
20
20
|
export { useOtelTraces, useOtelTrace, useOtelLogs, useOtelMetrics, useOtelServices, useOtelStats, useOtelQuery, useOtelSystem, useOtelWebSocket, setOtelOnUnauthorized, } from './hooks';
|
|
21
21
|
export type { OtelWsMessage, OtelWsCallbacks, OtelSystemData, OtelSystemProcess, OtelSystemDisk, OtelSystemTable, } from './hooks';
|
|
22
22
|
export { OtelClient, createOtelClient } from './client';
|
|
23
|
-
export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, } from './client';
|
|
23
|
+
export type { OtelClientOptions, FetchTracesOptions, FetchLogsOptions, FetchMetricsOptions, FetchMetricTotalOptions, FetchTokenUsageOptions, OtelTokenUsage, OtelLastReceived, OtelAccountLastReceived, } from './client';
|
|
24
24
|
export { OtelLive } from './views';
|
|
25
25
|
export { OtelTracesList } from './views';
|
|
26
26
|
export { OtelSpanDetail } from './views';
|
|
@@ -292,7 +292,7 @@ export const OtelLive = ({ baseUrl = getOtelConsumeUrl(), wsBaseUrl, token, auto
|
|
|
292
292
|
? tracesLoading
|
|
293
293
|
: signal === 'logs'
|
|
294
294
|
? logsLoading
|
|
295
|
-
: metricsLoading }) }), _jsx(Box, { pr: 2, flexShrink: 0, children: _jsx(Label, { variant:
|
|
295
|
+
: metricsLoading }) }), wsConnected ? null : (_jsx(Box, { pr: 2, flexShrink: 0, children: _jsx(Label, { variant: "secondary", size: "small", children: "\u25CB Polling" }) }))] }), timelineBounds && rangeStart && rangeEnd && (_jsx(Box, { px: 3, pt: 2, pb: 1, borderBottom: "1px solid", borderColor: "border.default", bg: "canvas.subtle", flexShrink: 0, position: "relative", zIndex: 0, children: _jsx(OtelTimelineRangeSlider, { timelineStart: timelineBounds.start, timelineEnd: timelineBounds.end, selectedStart: rangeStart, selectedEnd: rangeEnd, onRangeChange: handleRangeChange, histogram: histogram, height: 48, tickCount: 6 }) })), _jsxs(Box, { display: "flex", flex: 1, minHeight: 0, overflow: "hidden", children: [_jsxs(Box, { display: "flex", flexDirection: "column", flex: hasDetail ? '0 0 55%' : '1 1 100%', minHeight: 0, overflow: "hidden", children: [signal === 'traces' && (_jsx(OtelTracesList, { spans: filteredTraces, loading: tracesLoading, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), signal === 'logs' && (_jsx(OtelLogsList, { logs: filteredLogs, loading: logsLoading, selectedLogIndex: selectedLogIdx, onSelectLog: handleLogSelect })), signal === 'metrics' && (_jsx(OtelMetricsList, { metrics: filteredMetrics, loading: metricsLoading }))] }), hasDetail && (_jsxs(Box, { flex: "0 0 45%", minHeight: 0, overflow: "auto", borderLeft: "1px solid", borderColor: "border.default", children: [signal === 'traces' && selectedSpan && (_jsx(OtelSpanDetail, { span: selectedSpan, traceSpans: traceSpans ?? undefined, onClose: handleCloseDetail })), signal === 'logs' && selectedLogIdx !== null && logs && (_jsxs(Box, { p: 3, children: [_jsxs(Box, { display: "flex", justifyContent: "space-between", mb: 3, children: [_jsx(Text, { sx: { fontWeight: 'bold', fontSize: 2 }, children: "Log Detail" }), _jsx(Button, { size: "small", variant: "invisible", onClick: handleCloseDetail, children: "\u2715" })] }), _jsx(Box, { as: "pre", m: 0, fontSize: 1, fontFamily: "mono", whiteSpace: "pre-wrap", wordBreak: "break-word", children: JSON.stringify(logs[selectedLogIdx], null, 2) })] }))] }))] }), signal === 'traces' && selectedSpan && spanTree.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Box, { display: "flex", gap: 1, px: 3, py: 1, bg: "canvas.subtle", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [_jsx(Button, { size: "small", variant: bottomPane === 'timeline' ? 'primary' : 'invisible', leadingVisual: ClockIcon, onClick: () => toggleBottomPane('timeline'), children: "Timeline" }), _jsx(Button, { size: "small", variant: bottomPane === 'tree' ? 'primary' : 'invisible', leadingVisual: GitBranchIcon, onClick: () => toggleBottomPane('tree'), children: "Span Tree" })] }), bottomPane && (_jsxs(Box, { height: 260, overflow: "auto", borderTop: "1px solid", borderColor: "border.default", flexShrink: 0, children: [bottomPane === 'timeline' && (_jsx(OtelTimeline, { spans: traceSpans ?? [], selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect })), bottomPane === 'tree' && (_jsx(OtelSpanTree, { spans: spanTree, selectedSpanId: selectedSpan?.span_id, onSelectSpan: handleSpanSelect, defaultExpandDepth: 3 }))] }))] }))] }));
|
|
296
296
|
};
|
|
297
297
|
// ── Client-side filter helpers ──────────────────────────────────────
|
|
298
298
|
function filterSpans(spans, q) {
|
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { useStore } from 'zustand';
|
|
7
7
|
import { registerSessionState } from '../sessionEnd.js';
|
|
8
|
+
/**
|
|
9
|
+
* Whether an update is about the entity the layout holds. An update naming
|
|
10
|
+
* another space or organization (by `uid`, else `id`) replaces the one held:
|
|
11
|
+
* merged over it, the fields the new one does not carry (an organization's
|
|
12
|
+
* handle, a team) stayed from the one left. An update naming none, or one
|
|
13
|
+
* naming the same, is merged onto what is held.
|
|
14
|
+
*/
|
|
15
|
+
const entityUid = (entity) => {
|
|
16
|
+
const record = entity;
|
|
17
|
+
const uid = record?.uid ?? record?.id;
|
|
18
|
+
return typeof uid === 'string' && uid.trim() ? uid.trim() : undefined;
|
|
19
|
+
};
|
|
20
|
+
const sameEntity = (held, update) => {
|
|
21
|
+
const heldUid = entityUid(held);
|
|
22
|
+
const updateUid = entityUid(update);
|
|
23
|
+
return !heldUid || !updateUid || heldUid === updateUid;
|
|
24
|
+
};
|
|
8
25
|
/**
|
|
9
26
|
* Name of the cookie holding the space last selected by the user.
|
|
10
27
|
*
|
|
@@ -25,7 +42,9 @@ const readSpaceContextCookie = () => {
|
|
|
25
42
|
}
|
|
26
43
|
try {
|
|
27
44
|
const parsed = JSON.parse(decodeURIComponent(match[1]));
|
|
28
|
-
if (!parsed ||
|
|
45
|
+
if (!parsed ||
|
|
46
|
+
typeof parsed !== 'object' ||
|
|
47
|
+
typeof parsed.id !== 'string') {
|
|
29
48
|
return undefined;
|
|
30
49
|
}
|
|
31
50
|
return {
|
|
@@ -102,7 +121,9 @@ export const layoutStore = createStore((set, get) => ({
|
|
|
102
121
|
return {
|
|
103
122
|
organization: organization
|
|
104
123
|
? {
|
|
105
|
-
...state.organization,
|
|
124
|
+
...(sameEntity(state.organization, organization)
|
|
125
|
+
? state.organization
|
|
126
|
+
: undefined),
|
|
106
127
|
...organization,
|
|
107
128
|
}
|
|
108
129
|
: undefined,
|
|
@@ -121,7 +142,7 @@ export const layoutStore = createStore((set, get) => ({
|
|
|
121
142
|
updateLayoutSpace: (space) => set((state) => {
|
|
122
143
|
const next = space
|
|
123
144
|
? {
|
|
124
|
-
...state.space,
|
|
145
|
+
...(sameEntity(state.space, space) ? state.space : undefined),
|
|
125
146
|
...space,
|
|
126
147
|
}
|
|
127
148
|
: undefined;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `datalayer-research` organization, at `/research` (the public papers, read
|
|
16
16
|
* signed in, at `/research/papers/…` beside them).
|
|
17
17
|
*/
|
|
18
|
-
export type NavigationView = 'studio' | 'home' | 'agentify' | 'research' | 'admin' | 'apps';
|
|
18
|
+
export type NavigationView = 'studio' | 'home' | 'spaces' | 'agentify' | 'research' | 'admin' | 'apps';
|
|
19
19
|
/** The shells, and where each one opens when it has not been visited. */
|
|
20
20
|
export declare const NAVIGATION_VIEW_HOMES: Record<NavigationView, string>;
|
|
21
21
|
/** Well-known view identifiers for {@link NavigationState.tabsByView}. */
|
|
@@ -77,13 +77,21 @@ export declare function isRememberableRoute(route: string): boolean;
|
|
|
77
77
|
*/
|
|
78
78
|
export declare function isHostedAppRoute(route: string): boolean;
|
|
79
79
|
/**
|
|
80
|
-
* Whether a page belongs to no shell: the settings, the documentation
|
|
81
|
-
* the
|
|
80
|
+
* Whether a page belongs to no shell: the settings, the documentation, the
|
|
81
|
+
* presentations and the agentspecs catalogue (since 8 November 2026: reached
|
|
82
|
+
* from an agent's picker on /agents/new, its env var's page swapped the
|
|
83
|
+
* Sessions sidebar for the Studio's).
|
|
82
84
|
*
|
|
83
85
|
* They are read from whichever shell the reader is in — its sidebar stays
|
|
84
86
|
* beside them and the header's selector does not move — so they are never a
|
|
85
87
|
* shell's own page: opening one does not change the shell, and no shell
|
|
86
88
|
* returns to one.
|
|
89
|
+
*
|
|
90
|
+
* The presentations are `/decks/<collection>/<slug>` and their list,
|
|
91
|
+
* `/presentations`. Bare `/decks` is not one of them: it is the selected
|
|
92
|
+
* space's decks table, a page of the Spaces shell (the user, 12 November
|
|
93
|
+
* 2026). Neutral, it took the current shell whenever one was given, and the
|
|
94
|
+
* Sessions sidebar stayed beside it.
|
|
87
95
|
*/
|
|
88
96
|
export declare function isShellNeutralRoute(route: string): boolean;
|
|
89
97
|
/**
|
|
@@ -38,6 +38,9 @@ export const NAVIGATION_VIEW_HOMES = {
|
|
|
38
38
|
// of its own to be sent to. It was `/power` until 2026-10-09, when the
|
|
39
39
|
// shell took the name it is called by everywhere else.
|
|
40
40
|
home: '/platform',
|
|
41
|
+
// The spaces and what they hold: the Workspace of the Sessions shell,
|
|
42
|
+
// a shell of its own since 9 November 2026.
|
|
43
|
+
spaces: '/spaces',
|
|
41
44
|
agentify: '/agentify',
|
|
42
45
|
// The research areas and their spaces.
|
|
43
46
|
research: '/research',
|
|
@@ -60,6 +63,7 @@ const FORMER_TABS_KEY = 'datalayer_view_tabs';
|
|
|
60
63
|
const COOKIE_MAX_AGE_DAYS = 365;
|
|
61
64
|
const isView = (value) => value === 'studio' ||
|
|
62
65
|
value === 'home' ||
|
|
66
|
+
value === 'spaces' ||
|
|
63
67
|
value === 'agentify' ||
|
|
64
68
|
value === 'research' ||
|
|
65
69
|
value === 'admin' ||
|
|
@@ -243,17 +247,26 @@ export function isHostedAppRoute(route) {
|
|
|
243
247
|
(segments.length === 4 && segments[2] === 'preview')));
|
|
244
248
|
}
|
|
245
249
|
/**
|
|
246
|
-
* Whether a page belongs to no shell: the settings, the documentation
|
|
247
|
-
* the
|
|
250
|
+
* Whether a page belongs to no shell: the settings, the documentation, the
|
|
251
|
+
* presentations and the agentspecs catalogue (since 8 November 2026: reached
|
|
252
|
+
* from an agent's picker on /agents/new, its env var's page swapped the
|
|
253
|
+
* Sessions sidebar for the Studio's).
|
|
248
254
|
*
|
|
249
255
|
* They are read from whichever shell the reader is in — its sidebar stays
|
|
250
256
|
* beside them and the header's selector does not move — so they are never a
|
|
251
257
|
* shell's own page: opening one does not change the shell, and no shell
|
|
252
258
|
* returns to one.
|
|
259
|
+
*
|
|
260
|
+
* The presentations are `/decks/<collection>/<slug>` and their list,
|
|
261
|
+
* `/presentations`. Bare `/decks` is not one of them: it is the selected
|
|
262
|
+
* space's decks table, a page of the Spaces shell (the user, 12 November
|
|
263
|
+
* 2026). Neutral, it took the current shell whenever one was given, and the
|
|
264
|
+
* Sessions sidebar stayed beside it.
|
|
253
265
|
*/
|
|
254
266
|
export function isShellNeutralRoute(route) {
|
|
255
267
|
const path = pathOf(route);
|
|
256
|
-
return
|
|
268
|
+
return (path.startsWith('/decks/') ||
|
|
269
|
+
['/settings', '/docs', '/presentations', '/agentspecs'].some(prefix => path === prefix || path.startsWith(`${prefix}/`)));
|
|
257
270
|
}
|
|
258
271
|
/**
|
|
259
272
|
* The shell a route belongs to, read off its first segment.
|
|
@@ -275,8 +288,8 @@ export function viewForRoute(route, current) {
|
|
|
275
288
|
return current;
|
|
276
289
|
}
|
|
277
290
|
// The root is the Studio: what a signed-in reader opens on. The settings
|
|
278
|
-
// are read from it too, with its sidebar
|
|
279
|
-
//
|
|
291
|
+
// and the agentspecs catalogue are read from it too, with its sidebar
|
|
292
|
+
// beside them, when no shell is current.
|
|
280
293
|
if (path === '/' ||
|
|
281
294
|
path === '/studio' ||
|
|
282
295
|
path.startsWith('/studio/') ||
|
|
@@ -286,6 +299,10 @@ export function viewForRoute(route, current) {
|
|
|
286
299
|
path.startsWith('/agentspecs/')) {
|
|
287
300
|
return 'studio';
|
|
288
301
|
}
|
|
302
|
+
if (SPACES_SHELL_ROUTES.some(prefix => path === prefix || path.startsWith(`${prefix}/`)) ||
|
|
303
|
+
SPACES_SHELL_EXACT_ROUTES.includes(path)) {
|
|
304
|
+
return 'spaces';
|
|
305
|
+
}
|
|
289
306
|
if (path === '/agentify' || path.startsWith('/agentify/')) {
|
|
290
307
|
return 'agentify';
|
|
291
308
|
}
|
|
@@ -315,6 +332,32 @@ export function viewForRoute(route, current) {
|
|
|
315
332
|
}
|
|
316
333
|
return 'home';
|
|
317
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* The Spaces shell's pages: the spaces, then the work they hold — what was
|
|
337
|
+
* the Workspace of the Sessions sidebar until 9 November 2026, when it became
|
|
338
|
+
* a shell of its own.
|
|
339
|
+
*/
|
|
340
|
+
const SPACES_SHELL_ROUTES = [
|
|
341
|
+
'/spaces',
|
|
342
|
+
'/items',
|
|
343
|
+
// The Library, public and private, beside the Spaces sidebar (the user,
|
|
344
|
+
// 9 November 2026).
|
|
345
|
+
'/library',
|
|
346
|
+
'/shared',
|
|
347
|
+
'/notebooks',
|
|
348
|
+
'/documents',
|
|
349
|
+
'/cells',
|
|
350
|
+
// The selected space's spreadsheets (the user, 12 November 2026).
|
|
351
|
+
'/spreadsheets',
|
|
352
|
+
'/datasets',
|
|
353
|
+
'/contents',
|
|
354
|
+
];
|
|
355
|
+
/**
|
|
356
|
+
* The selected space's decks, `/decks` itself only (the user, 12 November
|
|
357
|
+
* 2026): `/decks/<collection>/<slug>` are presentations, drawn by their own
|
|
358
|
+
* page, not the Spaces shell's.
|
|
359
|
+
*/
|
|
360
|
+
const SPACES_SHELL_EXACT_ROUTES = ['/decks'];
|
|
318
361
|
/**
|
|
319
362
|
* `/<account>/<space>/<kind>[/…]` for the kinds a space shows beside the
|
|
320
363
|
* Agentify sidebar — decks, spreadsheets (Cells), courses and what a course
|
|
@@ -24,6 +24,8 @@ import type { JSX } from 'react';
|
|
|
24
24
|
import type { McpWorkflowsHealth } from '../../api/mcp/operations';
|
|
25
25
|
import { type McpErrorStateFn, type McpRoutes } from './types';
|
|
26
26
|
export type McpObservabilityPane = 'runs' | 'live' | 'metrics';
|
|
27
|
+
/** Every pane, in the order the switcher shows them. */
|
|
28
|
+
export declare const ALL_PANES: readonly McpObservabilityPane[];
|
|
27
29
|
export interface McpObservabilityProps {
|
|
28
30
|
/** The application's words for a failed request. */
|
|
29
31
|
errorState: McpErrorStateFn;
|
|
@@ -32,6 +34,13 @@ export interface McpObservabilityProps {
|
|
|
32
34
|
/** Which pane is open; the address owns it. */
|
|
33
35
|
pane: McpObservabilityPane;
|
|
34
36
|
onPaneChange: (pane: McpObservabilityPane) => void;
|
|
37
|
+
/**
|
|
38
|
+
* The panes offered, in this order; all three when unsaid. The Live pane
|
|
39
|
+
* reads the platform's own engine and is a platform administrator's
|
|
40
|
+
* (the user, 6 November 2026): an application shows an account its runs
|
|
41
|
+
* and metrics, and its admin page the live engine.
|
|
42
|
+
*/
|
|
43
|
+
panes?: readonly McpObservabilityPane[];
|
|
35
44
|
/** The run to open, when the address names one. */
|
|
36
45
|
taskUid?: string;
|
|
37
46
|
/** The trace to open — what a synchronous call leaves behind. */
|
|
@@ -67,13 +76,23 @@ export declare const stageOf: (name: string) => string;
|
|
|
67
76
|
* second is usually a problem with the network between them, and sending
|
|
68
77
|
* somebody to the wrong one costs an hour.
|
|
69
78
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
79
|
+
* An engine taking work is healthy; a queue it was told to run that nobody
|
|
80
|
+
* serves is drawn as a warning beside it — work sent there is accepted and
|
|
81
|
+
* never done. The engine reports no backlog of its own (DBOS's pending and
|
|
82
|
+
* enqueued are both `running`), so what is running is counted among the
|
|
83
|
+
* newest runs it lists, and said as such.
|
|
72
84
|
*/
|
|
73
85
|
export declare const engineLook: (health: McpWorkflowsHealth | undefined, failed: boolean) => {
|
|
74
86
|
label: string;
|
|
75
87
|
variant: "success" | "attention" | "danger" | "secondary";
|
|
76
88
|
note: string;
|
|
77
89
|
};
|
|
78
|
-
|
|
90
|
+
/** Each queue the engine polls, with how many of its newest runs are running there and the oldest of them. */
|
|
91
|
+
export declare const engineQueues: (health: McpWorkflowsHealth | undefined, now?: number) => {
|
|
92
|
+
name: string;
|
|
93
|
+
running: number;
|
|
94
|
+
oldestSeconds?: number;
|
|
95
|
+
served: boolean;
|
|
96
|
+
}[];
|
|
97
|
+
export declare const McpObservability: ({ errorState, routes, pane, onPaneChange, panes, taskUid, traceId, onSelect, agent, org, showTitle, }: McpObservabilityProps) => JSX.Element;
|
|
79
98
|
export default McpObservability;
|