@stigmer/react 3.0.6 → 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-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
- package/agent-instance/AgentInstanceDetailPanel.js +2 -9
- package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
- package/agent-instance/AgentInstanceList.d.ts.map +1 -1
- package/agent-instance/AgentInstanceList.js +2 -9
- 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 +1 -1
- package/index.d.ts.map +1 -1
- package/index.js.map +1 -1
- package/library/InstanceVisibilitySelector.d.ts +23 -9
- package/library/InstanceVisibilitySelector.d.ts.map +1 -1
- package/library/InstanceVisibilitySelector.js +14 -9
- package/library/InstanceVisibilitySelector.js.map +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 +47 -24
- package/library/VisibilitySelector.d.ts.map +1 -1
- package/library/VisibilitySelector.js +137 -115
- package/library/VisibilitySelector.js.map +1 -1
- package/library/visibilityLevels.d.ts +25 -3
- package/library/visibilityLevels.d.ts.map +1 -1
- package/library/visibilityLevels.js +8 -2
- package/library/visibilityLevels.js.map +1 -1
- package/package.json +4 -4
- 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/src/agent-instance/AgentInstanceDetailPanel.tsx +7 -27
- package/src/agent-instance/AgentInstanceList.tsx +7 -27
- 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 +2 -0
- package/src/library/InstanceVisibilitySelector.tsx +27 -9
- package/src/library/VisibilityOptionRow.tsx +244 -0
- package/src/library/VisibilitySelector.tsx +303 -260
- package/src/library/__tests__/VisibilitySelector.test.tsx +256 -0
- package/src/library/visibilityLevels.ts +35 -5
- 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/workflow/instance/CreateWorkflowInstanceDialog.tsx +1 -0
- package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +7 -27
- package/src/workflow/instance/WorkflowInstanceList.tsx +7 -27
- package/styles.css +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 -9
- package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
- package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceList.js +2 -9
- package/workflow/instance/WorkflowInstanceList.js.map +1 -1
|
@@ -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
|
+
});
|
|
@@ -14,11 +14,33 @@ export interface VisibilityLevelOption {
|
|
|
14
14
|
/** One-line explanation shown under the selector for the current level. */
|
|
15
15
|
readonly description: string;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* level. Omitted for the
|
|
19
|
-
* confirms — revoking access is
|
|
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}.
|
|
20
25
|
*/
|
|
21
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
|
+
};
|
|
22
44
|
/** Color treatment for the selected segment and the confirmation prompt. */
|
|
23
45
|
readonly tone: "private" | "org" | "platform" | "public";
|
|
24
46
|
}
|
|
@@ -42,7 +64,11 @@ const PLATFORM_OPTION: VisibilityLevelOption = {
|
|
|
42
64
|
value: ApiResourceVisibility.visibility_platform,
|
|
43
65
|
label: "Platform",
|
|
44
66
|
description: "All organizations managed by your platform",
|
|
45
|
-
|
|
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
|
+
},
|
|
46
72
|
tone: "platform",
|
|
47
73
|
};
|
|
48
74
|
|
|
@@ -50,7 +76,11 @@ const PUBLIC_OPTION: VisibilityLevelOption = {
|
|
|
50
76
|
value: ApiResourceVisibility.visibility_public,
|
|
51
77
|
label: "Public",
|
|
52
78
|
description: "Anyone on Stigmer",
|
|
53
|
-
|
|
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
|
+
},
|
|
54
84
|
tone: "public",
|
|
55
85
|
};
|
|
56
86
|
|
|
@@ -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}
|
|
@@ -17,6 +17,8 @@ import { SessionComposer } from "../composer";
|
|
|
17
17
|
import { SecretFlowErrorGuide, isSecretFlowError } from "../error";
|
|
18
18
|
import { useSessionPageFlow } from "./useSessionPageFlow";
|
|
19
19
|
import { SessionInspector } from "./inspector/SessionInspector";
|
|
20
|
+
import type { RuntimeEnvProvider } from "./runtime-env";
|
|
21
|
+
import type { SessionAudience } from "./audience";
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Message submitted when the user implements a plan. References the published
|
|
@@ -67,6 +69,27 @@ export interface SessionViewerProps {
|
|
|
67
69
|
* expandable file tree. (DD-004 capability injection, DD-011 opt-in.)
|
|
68
70
|
*/
|
|
69
71
|
readonly workspaceFileLister?: WorkspaceFileLister;
|
|
72
|
+
/**
|
|
73
|
+
* Supplies host-app environment variables for every follow-up
|
|
74
|
+
* execution (e.g. short-lived credentials for MCP tools, minted as
|
|
75
|
+
* the signed-in user). Evaluated fresh at each send; host values win
|
|
76
|
+
* over composer-collected env on key collisions. If the provider
|
|
77
|
+
* throws, the send is blocked and an error banner is shown — see
|
|
78
|
+
* {@link RuntimeEnvProvider}.
|
|
79
|
+
*/
|
|
80
|
+
readonly getRuntimeEnv?: RuntimeEnvProvider;
|
|
81
|
+
/**
|
|
82
|
+
* Presentation audience for the viewer. `"endUser"` locks the
|
|
83
|
+
* session's agent and hides the MCP server, skill, and
|
|
84
|
+
* session-variable configuration in both the composer and the
|
|
85
|
+
* inspector's Setup tab — for product-embedded chat where the agent
|
|
86
|
+
* is configured upstream by the platform. The model selector,
|
|
87
|
+
* interaction mode, attachments, and workspace picker remain. See
|
|
88
|
+
* {@link SessionAudience}.
|
|
89
|
+
*
|
|
90
|
+
* @default "integrator"
|
|
91
|
+
*/
|
|
92
|
+
readonly audience?: SessionAudience;
|
|
70
93
|
/**
|
|
71
94
|
* Slot for host-injected header actions (e.g., Share button with
|
|
72
95
|
* PermissionGate). Rendered in the top-right corner of the viewer.
|
|
@@ -126,12 +149,15 @@ export function SessionViewer({
|
|
|
126
149
|
enableLocal = false,
|
|
127
150
|
onBrowseLocalFolder,
|
|
128
151
|
workspaceFileLister,
|
|
152
|
+
getRuntimeEnv,
|
|
153
|
+
audience = "integrator",
|
|
129
154
|
headerActions,
|
|
130
155
|
onApplied,
|
|
131
156
|
className,
|
|
132
157
|
}: SessionViewerProps) {
|
|
133
|
-
const flow = useSessionPageFlow({ sessionId, org });
|
|
158
|
+
const flow = useSessionPageFlow({ sessionId, org, getRuntimeEnv });
|
|
134
159
|
const { conv } = flow;
|
|
160
|
+
const isEndUser = audience === "endUser";
|
|
135
161
|
|
|
136
162
|
const [modelId, setModelId] = flow.model;
|
|
137
163
|
const [interactionMode, setInteractionMode] = flow.interactionMode;
|
|
@@ -202,6 +228,7 @@ export function SessionViewer({
|
|
|
202
228
|
enableLocal={enableLocal}
|
|
203
229
|
onBrowseLocalFolder={onBrowseLocalFolder}
|
|
204
230
|
onBuildFromPlan={handleBuildFromPlan}
|
|
231
|
+
isEndUser={isEndUser}
|
|
205
232
|
/>
|
|
206
233
|
}
|
|
207
234
|
secondary={
|
|
@@ -216,6 +243,7 @@ export function SessionViewer({
|
|
|
216
243
|
gitHubConnection={gitHubConnection}
|
|
217
244
|
onBrowseLocalFolder={onBrowseLocalFolder}
|
|
218
245
|
workspaceFileLister={workspaceFileLister}
|
|
246
|
+
isEndUser={isEndUser}
|
|
219
247
|
/>
|
|
220
248
|
}
|
|
221
249
|
/>
|
|
@@ -241,6 +269,7 @@ interface ConversationColumnProps {
|
|
|
241
269
|
readonly enableLocal: boolean;
|
|
242
270
|
readonly onBrowseLocalFolder?: () => Promise<string | null>;
|
|
243
271
|
readonly onBuildFromPlan: () => void;
|
|
272
|
+
readonly isEndUser: boolean;
|
|
244
273
|
}
|
|
245
274
|
|
|
246
275
|
function ConversationColumn({
|
|
@@ -256,8 +285,10 @@ function ConversationColumn({
|
|
|
256
285
|
enableLocal,
|
|
257
286
|
onBrowseLocalFolder,
|
|
258
287
|
onBuildFromPlan,
|
|
288
|
+
isEndUser,
|
|
259
289
|
}: ConversationColumnProps) {
|
|
260
290
|
const { conv } = flow;
|
|
291
|
+
const sendError = flow.submitError ?? conv.sendError ?? conv.approvalError;
|
|
261
292
|
|
|
262
293
|
return (
|
|
263
294
|
<div className="flex h-full min-w-0 flex-col">
|
|
@@ -282,9 +313,7 @@ function ConversationColumn({
|
|
|
282
313
|
onReconnect={conv.reconnectStream}
|
|
283
314
|
/>
|
|
284
315
|
)}
|
|
285
|
-
{
|
|
286
|
-
<SendErrorBanner error={(conv.sendError ?? conv.approvalError)!} />
|
|
287
|
-
)}
|
|
316
|
+
{sendError && <SendErrorBanner error={sendError} />}
|
|
288
317
|
{flow.autoApproveAll && (
|
|
289
318
|
<AutoApproveIndicator onTurnOff={() => flow.setAutoApproveAll(false)} />
|
|
290
319
|
)}
|
|
@@ -309,11 +338,12 @@ function ConversationColumn({
|
|
|
309
338
|
onAgentRefChange={flow.setAgentRef}
|
|
310
339
|
onAgentResolutionChange={flow.setResolution}
|
|
311
340
|
isDefaultAgent={flow.isDefaultAgent}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
341
|
+
lockAgent={isEndUser}
|
|
342
|
+
mcpServerUsages={isEndUser ? undefined : flow.mcpServerUsages}
|
|
343
|
+
onMcpServerUsagesChange={isEndUser ? undefined : flow.setMcpServerUsages}
|
|
344
|
+
skillRefs={isEndUser ? undefined : flow.skillRefs}
|
|
345
|
+
onSkillRefsChange={isEndUser ? undefined : flow.setSkillRefs}
|
|
346
|
+
sessionVariables={isEndUser ? undefined : flow.sessionVariables}
|
|
317
347
|
className="px-4 py-3"
|
|
318
348
|
/>
|
|
319
349
|
</div>
|
|
@@ -337,6 +367,7 @@ interface InspectorPanelProps {
|
|
|
337
367
|
readonly gitHubConnection?: UseGitHubConnectionReturn;
|
|
338
368
|
readonly onBrowseLocalFolder?: () => Promise<string | null>;
|
|
339
369
|
readonly workspaceFileLister?: WorkspaceFileLister;
|
|
370
|
+
readonly isEndUser: boolean;
|
|
340
371
|
}
|
|
341
372
|
|
|
342
373
|
function InspectorPanel({
|
|
@@ -350,6 +381,7 @@ function InspectorPanel({
|
|
|
350
381
|
gitHubConnection,
|
|
351
382
|
onBrowseLocalFolder,
|
|
352
383
|
workspaceFileLister,
|
|
384
|
+
isEndUser,
|
|
353
385
|
}: InspectorPanelProps) {
|
|
354
386
|
const selectedItem = useSelectedThreadItem();
|
|
355
387
|
|
|
@@ -390,16 +422,20 @@ function InspectorPanel({
|
|
|
390
422
|
harness: flow.harness,
|
|
391
423
|
executionTarget: flow.executionTarget,
|
|
392
424
|
modelId: flow.model[0],
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
425
|
+
// End users see the configuration but cannot strip it — the Setup
|
|
426
|
+
// tab renders read-only without mutation callbacks (DD-011).
|
|
427
|
+
mutations: isEndUser
|
|
428
|
+
? undefined
|
|
429
|
+
: {
|
|
430
|
+
onRemoveAgent: flow.isDefaultAgent ? undefined : handleRemoveAgent,
|
|
431
|
+
onRemoveMcp: handleRemoveMcp,
|
|
432
|
+
onRemoveSkill: handleRemoveSkill,
|
|
433
|
+
},
|
|
398
434
|
}),
|
|
399
435
|
[
|
|
400
436
|
flow.agentRef, flow.isDefaultAgent, flow.mcpServerUsages, flow.skillRefs,
|
|
401
437
|
flow.sessionVariables, flow.harness, flow.executionTarget, flow.model,
|
|
402
|
-
handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
438
|
+
isEndUser, handleRemoveAgent, handleRemoveMcp, handleRemoveSkill,
|
|
403
439
|
],
|
|
404
440
|
);
|
|
405
441
|
|