@stigmer/react 3.0.5 → 3.0.7-dev.20260611143057
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/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +1 -1
- package/agent/AgentDetailView.js.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.js +2 -13
- package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
- package/agent-instance/AgentInstanceList.d.ts.map +1 -1
- package/agent-instance/AgentInstanceList.js +2 -13
- package/agent-instance/AgentInstanceList.js.map +1 -1
- package/agent-instance/CreateAgentInstanceDialog.d.ts.map +1 -1
- package/agent-instance/CreateAgentInstanceDialog.js +1 -1
- package/agent-instance/CreateAgentInstanceDialog.js.map +1 -1
- package/composer/SessionComposer.d.ts +14 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +15 -9
- package/composer/SessionComposer.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/library/InstanceVisibilitySelector.d.ts +30 -23
- package/library/InstanceVisibilitySelector.d.ts.map +1 -1
- package/library/InstanceVisibilitySelector.js +22 -145
- package/library/InstanceVisibilitySelector.js.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +23 -6
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +38 -9
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/ScopeToggle.d.ts +1 -1
- package/library/ScopeToggle.js +1 -1
- package/library/VisibilityOptionRow.d.ts +52 -0
- package/library/VisibilityOptionRow.d.ts.map +1 -0
- package/library/VisibilityOptionRow.js +92 -0
- package/library/VisibilityOptionRow.js.map +1 -0
- package/library/VisibilitySelector.d.ts +98 -0
- package/library/VisibilitySelector.d.ts.map +1 -0
- package/library/VisibilitySelector.js +193 -0
- package/library/VisibilitySelector.js.map +1 -0
- package/library/index.d.ts +4 -2
- package/library/index.d.ts.map +1 -1
- package/library/index.js +2 -1
- package/library/index.js.map +1 -1
- package/library/useUpdateVisibility.d.ts +5 -4
- package/library/useUpdateVisibility.d.ts.map +1 -1
- package/library/useUpdateVisibility.js +5 -4
- package/library/useUpdateVisibility.js.map +1 -1
- package/library/visibilityLevels.d.ts +96 -0
- package/library/visibilityLevels.d.ts.map +1 -0
- package/library/visibilityLevels.js +97 -0
- package/library/visibilityLevels.js.map +1 -0
- package/mcp-server/McpServerDetailView.d.ts +1 -11
- package/mcp-server/McpServerDetailView.d.ts.map +1 -1
- package/mcp-server/McpServerDetailView.js +3 -6
- package/mcp-server/McpServerDetailView.js.map +1 -1
- package/package.json +4 -4
- package/resource-detail/types.d.ts +1 -1
- package/session/NewSessionViewer.d.ts +32 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +20 -9
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +24 -1
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +18 -12
- package/session/SessionViewer.js.map +1 -1
- package/session/audience.d.ts +21 -0
- package/session/audience.d.ts.map +1 -0
- package/session/audience.js +2 -0
- package/session/audience.js.map +1 -0
- package/session/index.d.ts +2 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js.map +1 -1
- package/session/runtime-env.d.ts +47 -0
- package/session/runtime-env.d.ts.map +1 -0
- package/session/runtime-env.js +20 -0
- package/session/runtime-env.js.map +1 -0
- package/session/useNewSessionFlow.d.ts +25 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +20 -8
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +27 -2
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +34 -13
- package/session/useSessionPageFlow.js.map +1 -1
- package/skill/SkillDetailView.d.ts.map +1 -1
- package/skill/SkillDetailView.js +1 -1
- package/skill/SkillDetailView.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +1 -0
- package/src/agent-instance/AgentInstanceDetailPanel.tsx +7 -32
- package/src/agent-instance/AgentInstanceList.tsx +7 -32
- package/src/agent-instance/CreateAgentInstanceDialog.tsx +1 -0
- package/src/composer/SessionComposer.tsx +30 -8
- package/src/composer/__tests__/SessionComposer-lockAgent.test.tsx +150 -0
- package/src/index.ts +10 -2
- package/src/library/InstanceVisibilitySelector.tsx +44 -283
- package/src/library/ResourceVisibilityControl.tsx +54 -8
- package/src/library/ScopeToggle.tsx +1 -1
- package/src/library/VisibilityOptionRow.tsx +244 -0
- package/src/library/VisibilitySelector.tsx +436 -0
- package/src/library/__tests__/VisibilitySelector.test.tsx +256 -0
- package/src/library/index.ts +13 -2
- package/src/library/useUpdateVisibility.ts +5 -4
- package/src/library/visibilityLevels.ts +174 -0
- package/src/mcp-server/McpServerDetailView.tsx +10 -35
- package/src/resource-detail/types.ts +1 -1
- package/src/session/NewSessionViewer.tsx +61 -12
- package/src/session/SessionViewer.tsx +51 -15
- package/src/session/__tests__/audienceWiring.test.tsx +274 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +122 -0
- package/src/session/__tests__/useSessionPageFlow.runtimeEnv.test.tsx +170 -0
- package/src/session/audience.ts +20 -0
- package/src/session/index.ts +3 -0
- package/src/session/runtime-env.ts +57 -0
- package/src/session/useNewSessionFlow.ts +44 -9
- package/src/session/useSessionPageFlow.ts +65 -17
- package/src/skill/SkillDetailView.tsx +1 -0
- package/src/workflow/WorkflowDetailView.tsx +1 -0
- package/src/workflow/instance/CreateWorkflowInstanceDialog.tsx +1 -0
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +7 -32
- package/src/workflow/instance/WorkflowInstanceList.tsx +7 -32
- package/styles.css +1 -1
- package/workflow/WorkflowDetailView.d.ts.map +1 -1
- package/workflow/WorkflowDetailView.js +1 -1
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.d.ts.map +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.js +1 -1
- package/workflow/instance/CreateWorkflowInstanceDialog.js.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceDetailPanel.js +2 -13
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceList.js +2 -13
- package/workflow/instance/WorkflowInstanceList.js.map +1 -1
- package/library/VisibilityToggle.d.ts +0 -53
- package/library/VisibilityToggle.d.ts.map +0 -1
- package/library/VisibilityToggle.js +0 -100
- package/library/VisibilityToggle.js.map +0 -1
- package/src/library/VisibilityToggle.tsx +0 -280
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
render,
|
|
4
|
+
screen,
|
|
5
|
+
within,
|
|
6
|
+
fireEvent,
|
|
7
|
+
waitFor,
|
|
8
|
+
cleanup,
|
|
9
|
+
} from "@testing-library/react";
|
|
10
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
11
|
+
import {
|
|
12
|
+
VisibilitySelector,
|
|
13
|
+
VisibilityBadge,
|
|
14
|
+
} from "../VisibilitySelector";
|
|
15
|
+
import {
|
|
16
|
+
INSTANCE_VISIBILITY_LEVELS,
|
|
17
|
+
blueprintVisibilityLevels,
|
|
18
|
+
} from "../visibilityLevels";
|
|
19
|
+
|
|
20
|
+
// Without a StigmerProvider the portal container is null, and Base UI's
|
|
21
|
+
// Portal renders nothing — pin it to document.body so the popover mounts.
|
|
22
|
+
vi.mock("../../portal-container", () => ({
|
|
23
|
+
useStigmerPortalContainer: () => document.body,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Base UI's Popover positioner observes its anchor; happy-dom lacks
|
|
27
|
+
// ResizeObserver, so provide a no-op shim.
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
if (!("ResizeObserver" in globalThis)) {
|
|
30
|
+
(globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
|
|
31
|
+
class {
|
|
32
|
+
observe() {}
|
|
33
|
+
unobserve() {}
|
|
34
|
+
disconnect() {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(cleanup);
|
|
40
|
+
|
|
41
|
+
const BLUEPRINT_LEVELS = blueprintVisibilityLevels({
|
|
42
|
+
deploymentMode: "cloud",
|
|
43
|
+
hasIdentityProvider: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Option accessible names are "<label> <description>"; anchor on the label so
|
|
47
|
+
// e.g. /^Organization/ does not also match Platform's "All organizations …".
|
|
48
|
+
const optionByLabel = (label: string) =>
|
|
49
|
+
screen.getByRole("option", { name: new RegExp(`^${label}`, "i") });
|
|
50
|
+
|
|
51
|
+
/** Opens the manage-mode popover, resolving once its option rows are mounted. */
|
|
52
|
+
async function openPopover() {
|
|
53
|
+
fireEvent.click(screen.getByRole("button", { name: /Resource visibility:/i }));
|
|
54
|
+
await screen.findByRole("option", { name: /^Private/i });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("VisibilitySelector — create mode (inline list)", () => {
|
|
58
|
+
it("renders every offered level with its label and description", () => {
|
|
59
|
+
render(
|
|
60
|
+
<VisibilitySelector
|
|
61
|
+
mode="create"
|
|
62
|
+
visibility={ApiResourceVisibility.visibility_private}
|
|
63
|
+
options={INSTANCE_VISIBILITY_LEVELS}
|
|
64
|
+
onVisibilityChange={() => {}}
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const group = screen.getByRole("radiogroup", { name: "Resource visibility" });
|
|
69
|
+
const radios = within(group).getAllByRole("radio");
|
|
70
|
+
expect(radios).toHaveLength(3);
|
|
71
|
+
expect(within(group).getByText("Private")).toBeTruthy();
|
|
72
|
+
expect(within(group).getByText("Organization")).toBeTruthy();
|
|
73
|
+
expect(within(group).getByText("Public")).toBeTruthy();
|
|
74
|
+
expect(within(group).getByText("Only you can access")).toBeTruthy();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("applies any selection immediately, with no confirmation", () => {
|
|
78
|
+
const onChange = vi.fn();
|
|
79
|
+
render(
|
|
80
|
+
<VisibilitySelector
|
|
81
|
+
mode="create"
|
|
82
|
+
visibility={ApiResourceVisibility.visibility_private}
|
|
83
|
+
options={INSTANCE_VISIBILITY_LEVELS}
|
|
84
|
+
onVisibilityChange={onChange}
|
|
85
|
+
/>,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Public is an escalation, but in create mode there is nothing to escalate.
|
|
89
|
+
fireEvent.click(screen.getByRole("radio", { name: /Public/i }));
|
|
90
|
+
expect(onChange).toHaveBeenCalledWith(ApiResourceVisibility.visibility_public);
|
|
91
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("renders the current level even when it is not offerable", () => {
|
|
95
|
+
render(
|
|
96
|
+
<VisibilitySelector
|
|
97
|
+
mode="create"
|
|
98
|
+
visibility={ApiResourceVisibility.visibility_platform}
|
|
99
|
+
options={INSTANCE_VISIBILITY_LEVELS}
|
|
100
|
+
onVisibilityChange={() => {}}
|
|
101
|
+
/>,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const group = screen.getByRole("radiogroup", { name: "Resource visibility" });
|
|
105
|
+
const platformRow = within(group).getByText("Platform").closest("button");
|
|
106
|
+
expect(platformRow).not.toBeNull();
|
|
107
|
+
expect(platformRow?.getAttribute("aria-checked")).toBe("true");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("disables interaction when disabled", () => {
|
|
111
|
+
const onChange = vi.fn();
|
|
112
|
+
render(
|
|
113
|
+
<VisibilitySelector
|
|
114
|
+
mode="create"
|
|
115
|
+
disabled
|
|
116
|
+
visibility={ApiResourceVisibility.visibility_private}
|
|
117
|
+
options={INSTANCE_VISIBILITY_LEVELS}
|
|
118
|
+
onVisibilityChange={onChange}
|
|
119
|
+
/>,
|
|
120
|
+
);
|
|
121
|
+
for (const radio of screen.getAllByRole("radio")) {
|
|
122
|
+
expect((radio as HTMLButtonElement).disabled).toBe(true);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("VisibilitySelector — manage mode (popover + confirmation)", () => {
|
|
128
|
+
it("shows the current level on the trigger and lists levels on open", async () => {
|
|
129
|
+
render(
|
|
130
|
+
<VisibilitySelector
|
|
131
|
+
visibility={ApiResourceVisibility.visibility_org}
|
|
132
|
+
options={BLUEPRINT_LEVELS}
|
|
133
|
+
onVisibilityChange={() => {}}
|
|
134
|
+
/>,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(
|
|
138
|
+
screen.getByRole("button", { name: "Resource visibility: Organization" }),
|
|
139
|
+
).toBeTruthy();
|
|
140
|
+
|
|
141
|
+
await openPopover();
|
|
142
|
+
expect(screen.getAllByRole("option")).toHaveLength(4);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("applies a de-escalation immediately, without confirmation", async () => {
|
|
146
|
+
const onChange = vi.fn();
|
|
147
|
+
render(
|
|
148
|
+
<VisibilitySelector
|
|
149
|
+
visibility={ApiResourceVisibility.visibility_public}
|
|
150
|
+
options={BLUEPRINT_LEVELS}
|
|
151
|
+
onVisibilityChange={onChange}
|
|
152
|
+
/>,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
await openPopover();
|
|
156
|
+
fireEvent.click(optionByLabel("Private"));
|
|
157
|
+
expect(onChange).toHaveBeenCalledWith(ApiResourceVisibility.visibility_private);
|
|
158
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("requires an inline confirm before escalating to Organization", async () => {
|
|
162
|
+
const onChange = vi.fn();
|
|
163
|
+
render(
|
|
164
|
+
<VisibilitySelector
|
|
165
|
+
visibility={ApiResourceVisibility.visibility_private}
|
|
166
|
+
options={BLUEPRINT_LEVELS}
|
|
167
|
+
onVisibilityChange={onChange}
|
|
168
|
+
/>,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
await openPopover();
|
|
172
|
+
fireEvent.click(optionByLabel("Organization"));
|
|
173
|
+
|
|
174
|
+
// Not applied yet — an inline prompt appears first.
|
|
175
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
176
|
+
const alert = await screen.findByRole("alert");
|
|
177
|
+
expect(alert).toBeTruthy();
|
|
178
|
+
|
|
179
|
+
fireEvent.click(within(alert).getByRole("button", { name: "Confirm" }));
|
|
180
|
+
expect(onChange).toHaveBeenCalledWith(ApiResourceVisibility.visibility_org);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("requires the confirm dialog before escalating to Public", async () => {
|
|
184
|
+
const onChange = vi.fn();
|
|
185
|
+
render(
|
|
186
|
+
<VisibilitySelector
|
|
187
|
+
visibility={ApiResourceVisibility.visibility_org}
|
|
188
|
+
options={BLUEPRINT_LEVELS}
|
|
189
|
+
onVisibilityChange={onChange}
|
|
190
|
+
/>,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
await openPopover();
|
|
194
|
+
fireEvent.click(optionByLabel("Public"));
|
|
195
|
+
|
|
196
|
+
// Not applied until the modal is confirmed.
|
|
197
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
198
|
+
expect(await screen.findByText("Make this public?")).toBeTruthy();
|
|
199
|
+
|
|
200
|
+
fireEvent.click(screen.getByRole("button", { name: "Make Public" }));
|
|
201
|
+
// The confirm resolves on a microtask, so the apply is asynchronous.
|
|
202
|
+
await waitFor(() =>
|
|
203
|
+
expect(onChange).toHaveBeenCalledWith(ApiResourceVisibility.visibility_public),
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("moves focus between options with the arrow keys", async () => {
|
|
208
|
+
render(
|
|
209
|
+
<VisibilitySelector
|
|
210
|
+
visibility={ApiResourceVisibility.visibility_org}
|
|
211
|
+
options={BLUEPRINT_LEVELS}
|
|
212
|
+
onVisibilityChange={() => {}}
|
|
213
|
+
/>,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
await openPopover();
|
|
217
|
+
// Opening focuses the current level.
|
|
218
|
+
await waitFor(() =>
|
|
219
|
+
expect(document.activeElement).toBe(optionByLabel("Organization")),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
fireEvent.keyDown(optionByLabel("Organization"), { key: "ArrowDown" });
|
|
223
|
+
expect(document.activeElement).toBe(optionByLabel("Platform"));
|
|
224
|
+
|
|
225
|
+
fireEvent.keyDown(optionByLabel("Platform"), { key: "ArrowUp" });
|
|
226
|
+
expect(document.activeElement).toBe(optionByLabel("Organization"));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("does not apply when the confirm dialog is cancelled", async () => {
|
|
230
|
+
const onChange = vi.fn();
|
|
231
|
+
render(
|
|
232
|
+
<VisibilitySelector
|
|
233
|
+
visibility={ApiResourceVisibility.visibility_org}
|
|
234
|
+
options={BLUEPRINT_LEVELS}
|
|
235
|
+
onVisibilityChange={onChange}
|
|
236
|
+
/>,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
await openPopover();
|
|
240
|
+
fireEvent.click(optionByLabel("Public"));
|
|
241
|
+
await screen.findByText("Make this public?");
|
|
242
|
+
|
|
243
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
244
|
+
await waitFor(() =>
|
|
245
|
+
expect(screen.queryByText("Make this public?")).toBeNull(),
|
|
246
|
+
);
|
|
247
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("VisibilityBadge", () => {
|
|
252
|
+
it("renders the human label for a visibility value", () => {
|
|
253
|
+
render(<VisibilityBadge visibility={ApiResourceVisibility.visibility_platform} />);
|
|
254
|
+
expect(screen.getByText("Platform")).toBeTruthy();
|
|
255
|
+
});
|
|
256
|
+
});
|
package/src/library/index.ts
CHANGED
|
@@ -68,8 +68,19 @@ export type {
|
|
|
68
68
|
export { ImportResourceDialog } from "./ImportResourceDialog";
|
|
69
69
|
export type { ImportResourceDialogProps } from "./ImportResourceDialog";
|
|
70
70
|
|
|
71
|
-
export {
|
|
72
|
-
export type {
|
|
71
|
+
export { VisibilitySelector, VisibilityBadge } from "./VisibilitySelector";
|
|
72
|
+
export type { VisibilitySelectorProps } from "./VisibilitySelector";
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
blueprintVisibilityLevels,
|
|
76
|
+
INSTANCE_VISIBILITY_LEVELS,
|
|
77
|
+
visibilityLabel,
|
|
78
|
+
visibilityOption,
|
|
79
|
+
} from "./visibilityLevels";
|
|
80
|
+
export type {
|
|
81
|
+
BlueprintVisibilityLevelsContext,
|
|
82
|
+
VisibilityLevelOption,
|
|
83
|
+
} from "./visibilityLevels";
|
|
73
84
|
|
|
74
85
|
export { ResourceVisibilityControl } from "./ResourceVisibilityControl";
|
|
75
86
|
export type { ResourceVisibilityControlProps } from "./ResourceVisibilityControl";
|
|
@@ -34,9 +34,9 @@ export interface UseUpdateVisibilityReturn {
|
|
|
34
34
|
/**
|
|
35
35
|
* Behavior hook that updates the visibility of a resource.
|
|
36
36
|
*
|
|
37
|
-
* Supports blueprints (Agent, Workflow, Skill, MCP Server) with
|
|
38
|
-
* private/public
|
|
39
|
-
* WorkflowInstance) with
|
|
37
|
+
* Supports blueprints (Agent, Workflow, Skill, MCP Server) with the
|
|
38
|
+
* full private/org/public/platform spectrum, and instances
|
|
39
|
+
* (AgentInstance, WorkflowInstance) with private/org/public.
|
|
40
40
|
*
|
|
41
41
|
* Wraps the generated `stigmer.{kind}.updateVisibility()` SDK method
|
|
42
42
|
* with loading and error state management. The hook is stateless with
|
|
@@ -51,8 +51,9 @@ export interface UseUpdateVisibilityReturn {
|
|
|
51
51
|
* ```tsx
|
|
52
52
|
* const { updateVisibility, isPending } = useUpdateVisibility("workflow", workflow.metadata.id);
|
|
53
53
|
*
|
|
54
|
-
* <
|
|
54
|
+
* <VisibilitySelector
|
|
55
55
|
* visibility={workflow.metadata.visibility}
|
|
56
|
+
* options={options}
|
|
56
57
|
* onVisibilityChange={updateVisibility}
|
|
57
58
|
* isPending={isPending}
|
|
58
59
|
* />
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A selectable visibility level: label, explanation, and escalation copy.
|
|
5
|
+
*
|
|
6
|
+
* Options are always declared in escalation order (least to most exposed:
|
|
7
|
+
* private < org < platform < public); {@link VisibilitySelector} derives
|
|
8
|
+
* "is this an escalation?" from array position, and shows
|
|
9
|
+
* {@link confirmPrompt} before applying one.
|
|
10
|
+
*/
|
|
11
|
+
export interface VisibilityLevelOption {
|
|
12
|
+
readonly value: ApiResourceVisibility;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
/** One-line explanation shown under the selector for the current level. */
|
|
15
|
+
readonly description: string;
|
|
16
|
+
/**
|
|
17
|
+
* Light inline confirmation question shown inside the selector when the
|
|
18
|
+
* user escalates TO this level (e.g. private → org). Omitted for the
|
|
19
|
+
* least-exposed level (de-escalation never confirms — revoking access is
|
|
20
|
+
* always safe).
|
|
21
|
+
*
|
|
22
|
+
* Levels that expand access far beyond the owning org carry a
|
|
23
|
+
* {@link confirmDialog} instead; see the severity ladder on
|
|
24
|
+
* {@link VisibilityLevelOption}.
|
|
25
|
+
*/
|
|
26
|
+
readonly confirmPrompt?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Heavy confirmation shown as a modal {@link ConfirmDialog} when the user
|
|
29
|
+
* escalates TO this level. Reserved for levels that expose the resource
|
|
30
|
+
* beyond the owning organization (platform, public), where a blocking,
|
|
31
|
+
* audience-naming confirmation is warranted.
|
|
32
|
+
*
|
|
33
|
+
* The selector derives escalation severity purely from this data:
|
|
34
|
+
* `confirmDialog` present → modal; else `confirmPrompt` present → inline;
|
|
35
|
+
* else apply immediately. There is no per-level branching in the
|
|
36
|
+
* component.
|
|
37
|
+
*/
|
|
38
|
+
readonly confirmDialog?: {
|
|
39
|
+
/** Modal title, phrased as a question (e.g. "Make this public?"). */
|
|
40
|
+
readonly title: string;
|
|
41
|
+
/** Body copy that names the exact audience and the consequence. */
|
|
42
|
+
readonly description: string;
|
|
43
|
+
};
|
|
44
|
+
/** Color treatment for the selected segment and the confirmation prompt. */
|
|
45
|
+
readonly tone: "private" | "org" | "platform" | "public";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PRIVATE_OPTION: VisibilityLevelOption = {
|
|
49
|
+
value: ApiResourceVisibility.visibility_private,
|
|
50
|
+
label: "Private",
|
|
51
|
+
description: "Only you can access",
|
|
52
|
+
tone: "private",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ORG_OPTION: VisibilityLevelOption = {
|
|
56
|
+
value: ApiResourceVisibility.visibility_org,
|
|
57
|
+
label: "Organization",
|
|
58
|
+
description: "All members of your organization",
|
|
59
|
+
confirmPrompt: "Make visible to all org members?",
|
|
60
|
+
tone: "org",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const PLATFORM_OPTION: VisibilityLevelOption = {
|
|
64
|
+
value: ApiResourceVisibility.visibility_platform,
|
|
65
|
+
label: "Platform",
|
|
66
|
+
description: "All organizations managed by your platform",
|
|
67
|
+
confirmDialog: {
|
|
68
|
+
title: "Share with your whole platform?",
|
|
69
|
+
description:
|
|
70
|
+
"Every organization managed by your platform will be able to view and use this resource. You can return it to a narrower visibility at any time.",
|
|
71
|
+
},
|
|
72
|
+
tone: "platform",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const PUBLIC_OPTION: VisibilityLevelOption = {
|
|
76
|
+
value: ApiResourceVisibility.visibility_public,
|
|
77
|
+
label: "Public",
|
|
78
|
+
description: "Anyone on Stigmer",
|
|
79
|
+
confirmDialog: {
|
|
80
|
+
title: "Make this public?",
|
|
81
|
+
description:
|
|
82
|
+
"Anyone signed in to Stigmer will be able to view and use this resource. You can return it to a narrower visibility at any time.",
|
|
83
|
+
},
|
|
84
|
+
tone: "public",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Inputs that gate which levels a blueprint selector offers.
|
|
89
|
+
*
|
|
90
|
+
* Mirrors the backend's per-kind `VisibilityConfig` plus runtime context the
|
|
91
|
+
* proto cannot know:
|
|
92
|
+
*
|
|
93
|
+
* - `deploymentMode`: the OSS Go backend (`local`) is single-user and
|
|
94
|
+
* performs no org/platform visibility gating, so only Private/Public are
|
|
95
|
+
* meaningful there.
|
|
96
|
+
* - `hasIdentityProvider`: `visibility_platform` requires the owning org to
|
|
97
|
+
* operate an IdentityProvider — the backend rejects it otherwise
|
|
98
|
+
* (`ValidateVisibilityStep`), so the option only renders when the signal
|
|
99
|
+
* is present (use `useSsoProvider`, the permission-free lookup).
|
|
100
|
+
*/
|
|
101
|
+
export interface BlueprintVisibilityLevelsContext {
|
|
102
|
+
readonly deploymentMode: "cloud" | "local";
|
|
103
|
+
readonly hasIdentityProvider: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The levels a blueprint (agent, skill, workflow, mcp_server) selector
|
|
108
|
+
* offers, in escalation order.
|
|
109
|
+
*
|
|
110
|
+
* Cloud: Private / Organization [/ Platform] / Public — Organization is the
|
|
111
|
+
* creation default (blueprints are shared org assets; Private is an explicit
|
|
112
|
+
* opt-in). Local: Private / Public.
|
|
113
|
+
*/
|
|
114
|
+
export function blueprintVisibilityLevels(
|
|
115
|
+
context: BlueprintVisibilityLevelsContext,
|
|
116
|
+
): readonly VisibilityLevelOption[] {
|
|
117
|
+
if (context.deploymentMode === "local") {
|
|
118
|
+
return [PRIVATE_OPTION, PUBLIC_OPTION];
|
|
119
|
+
}
|
|
120
|
+
return context.hasIdentityProvider
|
|
121
|
+
? [PRIVATE_OPTION, ORG_OPTION, PLATFORM_OPTION, PUBLIC_OPTION]
|
|
122
|
+
: [PRIVATE_OPTION, ORG_OPTION, PUBLIC_OPTION];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The levels an instance (agent_instance, workflow_instance) selector
|
|
127
|
+
* offers, in escalation order: Private / Organization / Public.
|
|
128
|
+
*
|
|
129
|
+
* Platform is deliberately absent — instances are tenant-isolated by
|
|
130
|
+
* design (each managed org instantiates shared blueprints inside its own
|
|
131
|
+
* boundary). Descriptions are execution-oriented because org visibility on
|
|
132
|
+
* an instance is about who can run it and see its executions.
|
|
133
|
+
*/
|
|
134
|
+
export const INSTANCE_VISIBILITY_LEVELS: readonly VisibilityLevelOption[] = [
|
|
135
|
+
PRIVATE_OPTION,
|
|
136
|
+
{
|
|
137
|
+
...ORG_OPTION,
|
|
138
|
+
description: "All org members can view executions",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
...PUBLIC_OPTION,
|
|
142
|
+
description: "All authenticated users can view",
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Canonical option for a visibility value, independent of any kind's offered
|
|
148
|
+
* list. Used to render the current level even when it is not offerable in
|
|
149
|
+
* the current context (e.g. a platform-shared blueprint whose org no longer
|
|
150
|
+
* operates an IdentityProvider) — the state must stay legible.
|
|
151
|
+
*/
|
|
152
|
+
export function visibilityOption(
|
|
153
|
+
visibility: ApiResourceVisibility,
|
|
154
|
+
): VisibilityLevelOption {
|
|
155
|
+
switch (visibility) {
|
|
156
|
+
case ApiResourceVisibility.visibility_org:
|
|
157
|
+
return ORG_OPTION;
|
|
158
|
+
case ApiResourceVisibility.visibility_platform:
|
|
159
|
+
return PLATFORM_OPTION;
|
|
160
|
+
case ApiResourceVisibility.visibility_public:
|
|
161
|
+
return PUBLIC_OPTION;
|
|
162
|
+
default:
|
|
163
|
+
return PRIVATE_OPTION;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Human label for a visibility value — the one place list rows, badges, and
|
|
169
|
+
* detail panels resolve enum-to-text, so no surface ever falls through to
|
|
170
|
+
* "Private" (or "unknown") for org/platform.
|
|
171
|
+
*/
|
|
172
|
+
export function visibilityLabel(visibility: ApiResourceVisibility): string {
|
|
173
|
+
return visibilityOption(visibility).label;
|
|
174
|
+
}
|
|
@@ -13,7 +13,6 @@ import type {
|
|
|
13
13
|
import type { ToolApprovalPolicy, McpServerSpec } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
14
14
|
import { ValidationState } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/status_pb";
|
|
15
15
|
import type { EnvVarDeclaration } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/spec_pb";
|
|
16
|
-
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
17
16
|
import { useMcpServer } from "./useMcpServer";
|
|
18
17
|
import { useUpdateMcpServer } from "./useUpdateMcpServer";
|
|
19
18
|
import { mcpServerToInput } from "./internal/mcpServerToInput";
|
|
@@ -27,8 +26,7 @@ import { OAuthAppForm } from "./OAuthAppForm";
|
|
|
27
26
|
import { ErrorMessage } from "../error/ErrorMessage";
|
|
28
27
|
import { EnvVarForm } from "../environment/EnvVarForm";
|
|
29
28
|
import type { EnvVarFormVariable } from "../environment/EnvVarForm";
|
|
30
|
-
import {
|
|
31
|
-
import { PermissionGate } from "../iam-policy/PermissionGate";
|
|
29
|
+
import { ResourceVisibilityControl } from "../library/ResourceVisibilityControl";
|
|
32
30
|
import { Tabs, type TabItem } from "../tabs/Tabs";
|
|
33
31
|
import { ResourceDetailShell } from "../resource-detail/ResourceDetailShell";
|
|
34
32
|
import { Section } from "../resource-detail/Section";
|
|
@@ -58,15 +56,6 @@ export interface McpServerDetailViewProps {
|
|
|
58
56
|
* Not called on error or not-found states.
|
|
59
57
|
*/
|
|
60
58
|
readonly onResourceLoad?: (meta: { name: string; id: string }) => void;
|
|
61
|
-
/**
|
|
62
|
-
* Called when the user toggles visibility via the inline control.
|
|
63
|
-
* When provided, the header renders an interactive
|
|
64
|
-
* {@link VisibilityToggle} instead of a read-only badge.
|
|
65
|
-
* When omitted, visibility is displayed as a static "Public" pill.
|
|
66
|
-
*/
|
|
67
|
-
readonly onVisibilityChange?: (v: ApiResourceVisibility) => void;
|
|
68
|
-
/** `true` while a visibility update RPC is in flight. */
|
|
69
|
-
readonly isVisibilityPending?: boolean;
|
|
70
59
|
/**
|
|
71
60
|
* Initial active capability tab. Defaults to `"tools"`.
|
|
72
61
|
* Useful for deep-linking or demo scenarios that need to start on
|
|
@@ -147,8 +136,6 @@ export function McpServerDetailView({
|
|
|
147
136
|
org,
|
|
148
137
|
slug,
|
|
149
138
|
onResourceLoad,
|
|
150
|
-
onVisibilityChange,
|
|
151
|
-
isVisibilityPending,
|
|
152
139
|
defaultCapabilityTab = "tools",
|
|
153
140
|
defaultShowCredentialForm = false,
|
|
154
141
|
credentialPoolValues,
|
|
@@ -393,27 +380,15 @@ export function McpServerDetailView({
|
|
|
393
380
|
updatedAt: specAudit?.updatedAt ? timestampDate(specAudit.updatedAt) : null,
|
|
394
381
|
};
|
|
395
382
|
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
<PermissionGate
|
|
406
|
-
resource={{ kind: "mcp_server", id: meta.id }}
|
|
407
|
-
relation="can_edit"
|
|
408
|
-
fallback={visibilityBadge}
|
|
409
|
-
>
|
|
410
|
-
<VisibilityToggle
|
|
411
|
-
visibility={meta.visibility}
|
|
412
|
-
onVisibilityChange={onVisibilityChange}
|
|
413
|
-
isPending={isVisibilityPending}
|
|
414
|
-
/>
|
|
415
|
-
</PermissionGate>
|
|
416
|
-
) : visibilityBadge;
|
|
383
|
+
const visibilityControl = meta ? (
|
|
384
|
+
<ResourceVisibilityControl
|
|
385
|
+
kind="mcpServer"
|
|
386
|
+
resourceId={meta.id}
|
|
387
|
+
visibility={meta.visibility}
|
|
388
|
+
org={meta.org || org}
|
|
389
|
+
onChanged={refetch}
|
|
390
|
+
/>
|
|
391
|
+
) : undefined;
|
|
417
392
|
|
|
418
393
|
const headerMetaExtra = (
|
|
419
394
|
<>
|
|
@@ -151,7 +151,7 @@ export interface ResourceDetailShellProps {
|
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
153
|
* Visibility control rendered in the header.
|
|
154
|
-
* Typically a `<
|
|
154
|
+
* Typically a `<ResourceVisibilityControl />` from the library module.
|
|
155
155
|
* Rendered inline after the resource name.
|
|
156
156
|
*/
|
|
157
157
|
readonly visibilityControl?: ReactNode;
|
|
@@ -7,10 +7,13 @@ import type { UseGitHubConnectionReturn } from "../github/useGitHubConnection";
|
|
|
7
7
|
import type { WorkspaceFileLister } from "../workspace/WorkspaceFileLister";
|
|
8
8
|
import type { InteractionModeOption } from "../composer";
|
|
9
9
|
import { SessionComposer } from "../composer";
|
|
10
|
+
import type { HarnessOption } from "../models/harness";
|
|
10
11
|
import { ResizableSplit } from "../internal/ResizableSplit";
|
|
11
12
|
import { SessionInspector } from "./inspector/SessionInspector";
|
|
12
13
|
import type { SetupTabProps } from "./inspector/SetupTab";
|
|
13
14
|
import { useNewSessionFlow } from "./useNewSessionFlow";
|
|
15
|
+
import type { RuntimeEnvProvider } from "./runtime-env";
|
|
16
|
+
import type { SessionAudience } from "./audience";
|
|
14
17
|
|
|
15
18
|
/** Props for {@link NewSessionViewer}. */
|
|
16
19
|
export interface NewSessionViewerProps {
|
|
@@ -44,6 +47,37 @@ export interface NewSessionViewerProps {
|
|
|
44
47
|
*/
|
|
45
48
|
readonly workspaceFileLister?: WorkspaceFileLister;
|
|
46
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Supplies host-app environment variables for the session's first
|
|
52
|
+
* execution (e.g. short-lived credentials for MCP tools, minted as
|
|
53
|
+
* the signed-in user). Evaluated at submit time, before the session
|
|
54
|
+
* is created; host values win over composer-collected env on key
|
|
55
|
+
* collisions. If the provider throws, the submission fails with an
|
|
56
|
+
* error surfaced via `onError` — see {@link RuntimeEnvProvider}.
|
|
57
|
+
*/
|
|
58
|
+
readonly getRuntimeEnv?: RuntimeEnvProvider;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Presentation audience for the launcher. `"endUser"` locks the
|
|
62
|
+
* pinned agent (when `initialAgentRef` is set) and hides the MCP
|
|
63
|
+
* server, skill, and session-variable pickers — for product-embedded
|
|
64
|
+
* chat where the agent is configured upstream by the platform. The
|
|
65
|
+
* model selector, interaction mode, harness selector, attachments,
|
|
66
|
+
* and workspace picker remain. See {@link SessionAudience}.
|
|
67
|
+
*
|
|
68
|
+
* @default "integrator"
|
|
69
|
+
*/
|
|
70
|
+
readonly audience?: SessionAudience;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Harness pre-selected for new sessions when the user has not made
|
|
74
|
+
* an explicit choice yet. The user can still switch before starting
|
|
75
|
+
* the session, and their explicit choice wins on subsequent visits.
|
|
76
|
+
*
|
|
77
|
+
* @default "native"
|
|
78
|
+
*/
|
|
79
|
+
readonly defaultHarness?: HarnessOption;
|
|
80
|
+
|
|
47
81
|
/** Agent to auto-select on mount (used for draft flows). */
|
|
48
82
|
readonly initialAgentRef?: ResourceRef;
|
|
49
83
|
/**
|
|
@@ -122,6 +156,9 @@ export function NewSessionViewer({
|
|
|
122
156
|
enableLocal = false,
|
|
123
157
|
onBrowseLocalFolder,
|
|
124
158
|
workspaceFileLister,
|
|
159
|
+
getRuntimeEnv,
|
|
160
|
+
audience = "integrator",
|
|
161
|
+
defaultHarness,
|
|
125
162
|
initialAgentRef,
|
|
126
163
|
initialInstanceId,
|
|
127
164
|
initialAttachments,
|
|
@@ -132,8 +169,15 @@ export function NewSessionViewer({
|
|
|
132
169
|
footerContent,
|
|
133
170
|
className,
|
|
134
171
|
}: NewSessionViewerProps) {
|
|
135
|
-
const flow = useNewSessionFlow({
|
|
172
|
+
const flow = useNewSessionFlow({
|
|
173
|
+
org,
|
|
174
|
+
onSessionCreated,
|
|
175
|
+
onError,
|
|
176
|
+
getRuntimeEnv,
|
|
177
|
+
defaultHarness,
|
|
178
|
+
});
|
|
136
179
|
const [interactionMode, setInteractionMode] = useState<InteractionModeOption>("agent");
|
|
180
|
+
const isEndUser = audience === "endUser";
|
|
137
181
|
|
|
138
182
|
const hasContext =
|
|
139
183
|
flow.workspace.hasEntries ||
|
|
@@ -179,16 +223,20 @@ export function NewSessionViewer({
|
|
|
179
223
|
harness: flow.harness,
|
|
180
224
|
executionTarget: undefined,
|
|
181
225
|
modelId: flow.modelId,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
226
|
+
// End users see the configuration but cannot strip it — the Setup
|
|
227
|
+
// tab renders read-only without mutation callbacks (DD-011).
|
|
228
|
+
mutations: isEndUser
|
|
229
|
+
? undefined
|
|
230
|
+
: {
|
|
231
|
+
onRemoveAgent: flow.agentRef ? handleRemoveAgent : undefined,
|
|
232
|
+
onRemoveMcp: handleRemoveMcp,
|
|
233
|
+
onRemoveSkill: handleRemoveSkill,
|
|
234
|
+
},
|
|
187
235
|
}),
|
|
188
236
|
[
|
|
189
237
|
flow.agentRef, flow.mcpServerUsages, flow.skillRefs,
|
|
190
238
|
flow.sessionVariables, flow.harness, flow.modelId,
|
|
191
|
-
handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
239
|
+
isEndUser, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
192
240
|
],
|
|
193
241
|
);
|
|
194
242
|
|
|
@@ -231,11 +279,12 @@ export function NewSessionViewer({
|
|
|
231
279
|
initialAgentRef={initialAgentRef}
|
|
232
280
|
initialInstanceId={initialInstanceId}
|
|
233
281
|
initialAttachments={initialAttachments}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
282
|
+
lockAgent={isEndUser && initialAgentRef != null}
|
|
283
|
+
mcpServerUsages={isEndUser ? undefined : flow.mcpServerUsages}
|
|
284
|
+
onMcpServerUsagesChange={isEndUser ? undefined : flow.setMcpServerUsages}
|
|
285
|
+
skillRefs={isEndUser ? undefined : flow.skillRefs}
|
|
286
|
+
onSkillRefsChange={isEndUser ? undefined : flow.setSkillRefs}
|
|
287
|
+
sessionVariables={isEndUser ? undefined : flow.sessionVariables}
|
|
239
288
|
showHarnessSelector
|
|
240
289
|
harness={flow.harness}
|
|
241
290
|
onHarnessChange={flow.setHarness}
|