@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,170 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { renderHook, act } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Mocks — useSessionPageFlow composes many hooks; we stub them to isolate the
|
|
6
|
+
// host runtime-env behavior (per-follow-up evaluation, host-wins merge, and
|
|
7
|
+
// fail-fast into submitError before any optimistic UI).
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
const mockSendFollowUp = vi.fn();
|
|
11
|
+
|
|
12
|
+
const mockConv = {
|
|
13
|
+
session: { spec: {} },
|
|
14
|
+
isLoading: false,
|
|
15
|
+
completedExecutions: [] as unknown[],
|
|
16
|
+
activeStreamExecution: null,
|
|
17
|
+
workspaceEntries: [] as unknown[],
|
|
18
|
+
submitApproval: vi.fn(),
|
|
19
|
+
sendFollowUp: mockSendFollowUp,
|
|
20
|
+
};
|
|
21
|
+
vi.mock("../useSessionConversation", () => ({
|
|
22
|
+
useSessionConversation: () => mockConv,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock("../../hooks", () => ({
|
|
26
|
+
useStigmer: () => ({ agent: { getByReference: vi.fn() } }),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock("../../agent", () => ({
|
|
30
|
+
useDefaultAgent: () => ({ agent: null, isLoading: false, error: null }),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const mockWorkspace = {
|
|
34
|
+
entries: [],
|
|
35
|
+
hasEntries: false,
|
|
36
|
+
toInput: vi.fn().mockReturnValue([]),
|
|
37
|
+
addGitRepo: vi.fn(),
|
|
38
|
+
addLocalPath: vi.fn(),
|
|
39
|
+
removeEntry: vi.fn(),
|
|
40
|
+
clear: vi.fn(),
|
|
41
|
+
};
|
|
42
|
+
vi.mock("../../workspace", () => ({
|
|
43
|
+
useWorkspaceEntries: () => mockWorkspace,
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const mockSessionVariables = {
|
|
47
|
+
variables: [],
|
|
48
|
+
isEmpty: true,
|
|
49
|
+
clear: vi.fn(),
|
|
50
|
+
};
|
|
51
|
+
vi.mock("../../execution/useSessionVariables", () => ({
|
|
52
|
+
useSessionVariables: () => mockSessionVariables,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vi.mock("../usePersistedModel", () => ({
|
|
56
|
+
usePersistedModel: () => ["model-x", vi.fn()] as const,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
vi.mock("../useAgentRefFromSession", () => ({
|
|
60
|
+
useAgentRefFromSession: () => ({ agentRef: null }),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
import { useSessionPageFlow } from "../useSessionPageFlow";
|
|
64
|
+
|
|
65
|
+
const OPTS = { sessionId: "ses_1", org: "acme" };
|
|
66
|
+
|
|
67
|
+
describe("useSessionPageFlow — host runtime env (getRuntimeEnv)", () => {
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("merges host env into follow-ups, host wins on collisions", async () => {
|
|
73
|
+
const getRuntimeEnv = vi.fn().mockResolvedValue({
|
|
74
|
+
PLATFORM_TOKEN: { value: "fresh-token", isSecret: true },
|
|
75
|
+
});
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useSessionPageFlow({ ...OPTS, getRuntimeEnv }),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
await act(async () => {
|
|
81
|
+
await result.current.handleSubmit("follow up", undefined, {
|
|
82
|
+
runtimeEnv: {
|
|
83
|
+
PLATFORM_TOKEN: { value: "stale-token", isSecret: true },
|
|
84
|
+
USER_VAR: { value: "kept" },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(mockSendFollowUp).toHaveBeenCalledTimes(1);
|
|
90
|
+
expect(mockSendFollowUp.mock.calls[0][1].runtimeEnv).toEqual({
|
|
91
|
+
PLATFORM_TOKEN: { value: "fresh-token", isSecret: true },
|
|
92
|
+
USER_VAR: { value: "kept" },
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("evaluates the provider fresh on every follow-up", async () => {
|
|
97
|
+
let mint = 0;
|
|
98
|
+
const getRuntimeEnv = vi.fn(() => ({ TOKEN: { value: `token-${++mint}` } }));
|
|
99
|
+
const { result } = renderHook(() =>
|
|
100
|
+
useSessionPageFlow({ ...OPTS, getRuntimeEnv }),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await act(async () => {
|
|
104
|
+
await result.current.handleSubmit("first");
|
|
105
|
+
});
|
|
106
|
+
await act(async () => {
|
|
107
|
+
await result.current.handleSubmit("second");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(getRuntimeEnv).toHaveBeenCalledTimes(2);
|
|
111
|
+
expect(mockSendFollowUp.mock.calls[0][1].runtimeEnv).toEqual({
|
|
112
|
+
TOKEN: { value: "token-1" },
|
|
113
|
+
});
|
|
114
|
+
expect(mockSendFollowUp.mock.calls[1][1].runtimeEnv).toEqual({
|
|
115
|
+
TOKEN: { value: "token-2" },
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("blocks the send and sets submitError when the provider throws", async () => {
|
|
120
|
+
const getRuntimeEnv = vi.fn().mockRejectedValue(new Error("token mint failed"));
|
|
121
|
+
const { result } = renderHook(() =>
|
|
122
|
+
useSessionPageFlow({ ...OPTS, getRuntimeEnv }),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await act(async () => {
|
|
126
|
+
await result.current.handleSubmit("follow up");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Nothing was sent: no optimistic message, no session-variable clear.
|
|
130
|
+
expect(mockSendFollowUp).not.toHaveBeenCalled();
|
|
131
|
+
expect(mockSessionVariables.clear).not.toHaveBeenCalled();
|
|
132
|
+
expect(result.current.submitError).toBeInstanceOf(Error);
|
|
133
|
+
expect(result.current.submitError?.message).toBe("token mint failed");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("clears submitError at the start of the next submission", async () => {
|
|
137
|
+
const getRuntimeEnv = vi
|
|
138
|
+
.fn()
|
|
139
|
+
.mockRejectedValueOnce(new Error("transient failure"))
|
|
140
|
+
.mockResolvedValue({ TOKEN: { value: "ok" } });
|
|
141
|
+
const { result } = renderHook(() =>
|
|
142
|
+
useSessionPageFlow({ ...OPTS, getRuntimeEnv }),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
await act(async () => {
|
|
146
|
+
await result.current.handleSubmit("fails");
|
|
147
|
+
});
|
|
148
|
+
expect(result.current.submitError).not.toBeNull();
|
|
149
|
+
|
|
150
|
+
await act(async () => {
|
|
151
|
+
await result.current.handleSubmit("succeeds");
|
|
152
|
+
});
|
|
153
|
+
expect(result.current.submitError).toBeNull();
|
|
154
|
+
expect(mockSendFollowUp).toHaveBeenCalledTimes(1);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("passes composer env through untouched when no provider is configured", async () => {
|
|
158
|
+
const { result } = renderHook(() => useSessionPageFlow(OPTS));
|
|
159
|
+
|
|
160
|
+
await act(async () => {
|
|
161
|
+
await result.current.handleSubmit("follow up", undefined, {
|
|
162
|
+
runtimeEnv: { USER_VAR: { value: "composer-only" } },
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(mockSendFollowUp.mock.calls[0][1].runtimeEnv).toEqual({
|
|
167
|
+
USER_VAR: { value: "composer-only" },
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who the session organisms (`SessionViewer` / `NewSessionViewer`) are
|
|
3
|
+
* presented to.
|
|
4
|
+
*
|
|
5
|
+
* - `"integrator"` (default) — the full configuration surface: agent
|
|
6
|
+
* picker, MCP servers, skills, and session variables. The presentation
|
|
7
|
+
* used by the Stigmer Console, where the person at the keyboard is
|
|
8
|
+
* composing the session's configuration.
|
|
9
|
+
* - `"endUser"` — a curated, product-embedded chat. The agent (and its
|
|
10
|
+
* MCP servers, skills, and identity) is configured upstream by the
|
|
11
|
+
* embedding platform; the end user chats, picks a model, toggles
|
|
12
|
+
* Agent/Plan mode, and attaches workspaces, but never reconfigures
|
|
13
|
+
* the agent. The organisms lock the agent and hide the integrator
|
|
14
|
+
* pickers, in both the composer and the inspector's Setup tab.
|
|
15
|
+
*
|
|
16
|
+
* A preset rather than individual flags: "end user" is a product intent,
|
|
17
|
+
* and keeping it in one place lets the SDK evolve what that presentation
|
|
18
|
+
* means without breaking embedders.
|
|
19
|
+
*/
|
|
20
|
+
export type SessionAudience = "integrator" | "endUser";
|
package/src/session/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { toProtoExecutionTarget, fromProtoExecutionTarget } from "./execution-target";
|
|
2
2
|
export type { ExecutionTargetOption } from "./execution-target";
|
|
3
3
|
|
|
4
|
+
export type { RuntimeEnvProvider } from "./runtime-env";
|
|
5
|
+
export type { SessionAudience } from "./audience";
|
|
6
|
+
|
|
4
7
|
export { useCreateSession } from "./useCreateSession";
|
|
5
8
|
export type {
|
|
6
9
|
SharedSessionFields,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { EnvVarInput } from "@stigmer/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host-app callback that supplies environment variables for a single
|
|
5
|
+
* execution.
|
|
6
|
+
*
|
|
7
|
+
* Invoked once per execution, at submit time — never cached — so
|
|
8
|
+
* short-lived credentials (e.g. a freshly minted platform token scoped
|
|
9
|
+
* to the signed-in user) are always current when the execution starts.
|
|
10
|
+
* May return the variables synchronously or via a promise.
|
|
11
|
+
*
|
|
12
|
+
* Host-provided values take precedence over composer-collected env on
|
|
13
|
+
* key collisions: the host injects identity material, and a stale or
|
|
14
|
+
* user-supplied value must never shadow it.
|
|
15
|
+
*
|
|
16
|
+
* If the provider throws (or rejects), the submission is aborted and
|
|
17
|
+
* the error surfaces through the owning flow's error channel — an
|
|
18
|
+
* execution never runs with missing or stale credentials.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <SessionViewer
|
|
23
|
+
* sessionId={id}
|
|
24
|
+
* org={org}
|
|
25
|
+
* getRuntimeEnv={async () => ({
|
|
26
|
+
* PLATFORM_TOKEN: { value: await mintShortLivedToken(), isSecret: true },
|
|
27
|
+
* PLATFORM_ORG: { value: activeOrg },
|
|
28
|
+
* })}
|
|
29
|
+
* />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type RuntimeEnvProvider = () =>
|
|
33
|
+
| Promise<Record<string, EnvVarInput>>
|
|
34
|
+
| Record<string, EnvVarInput>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolves the effective `runtimeEnv` for one execution by merging
|
|
38
|
+
* composer-collected env with host-provided env from a
|
|
39
|
+
* {@link RuntimeEnvProvider}.
|
|
40
|
+
*
|
|
41
|
+
* Host values win on key collisions (see {@link RuntimeEnvProvider}).
|
|
42
|
+
* Returns `undefined` when neither source contributes any variables,
|
|
43
|
+
* so callers can pass the result straight to execution creation.
|
|
44
|
+
*
|
|
45
|
+
* Provider errors are intentionally not caught here: callers must
|
|
46
|
+
* treat a failure as fatal for the submission. Callers without a
|
|
47
|
+
* provider should pass the composer env through directly rather than
|
|
48
|
+
* paying this function's await.
|
|
49
|
+
*/
|
|
50
|
+
export async function resolveExecutionRuntimeEnv(
|
|
51
|
+
getRuntimeEnv: RuntimeEnvProvider,
|
|
52
|
+
composerEnv: Record<string, EnvVarInput> | undefined,
|
|
53
|
+
): Promise<Record<string, EnvVarInput> | undefined> {
|
|
54
|
+
const hostEnv = await getRuntimeEnv();
|
|
55
|
+
const merged = { ...composerEnv, ...hostEnv };
|
|
56
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
57
|
+
}
|
|
@@ -16,6 +16,7 @@ import { useCreateAgentExecution } from "../execution/useCreateAgentExecution";
|
|
|
16
16
|
import type { ExecutionTargetOption } from "./execution-target";
|
|
17
17
|
import { useExecutionTarget } from "../execution-target-context";
|
|
18
18
|
import { useRunnerAdapter } from "../runner-adapter";
|
|
19
|
+
import { resolveExecutionRuntimeEnv, type RuntimeEnvProvider } from "./runtime-env";
|
|
19
20
|
|
|
20
21
|
const DEFAULT_AGENT_TIMEOUT_MS = 10_000;
|
|
21
22
|
|
|
@@ -55,6 +56,30 @@ export interface UseNewSessionFlowOptions {
|
|
|
55
56
|
* server decides based on deployment context.
|
|
56
57
|
*/
|
|
57
58
|
readonly executionTarget?: ExecutionTargetOption;
|
|
59
|
+
/**
|
|
60
|
+
* Supplies host-app environment variables for the session's first
|
|
61
|
+
* execution. Evaluated once per submission, at submit time, so
|
|
62
|
+
* short-lived credentials stay fresh; evaluated **before** the session
|
|
63
|
+
* is created so a credential failure never strands an empty session.
|
|
64
|
+
*
|
|
65
|
+
* Host values win over composer-collected env on key collisions. If
|
|
66
|
+
* the provider throws, the submission fails and the error surfaces
|
|
67
|
+
* via {@link UseNewSessionFlowReturn.submitError} / {@link onError}.
|
|
68
|
+
* See {@link RuntimeEnvProvider}.
|
|
69
|
+
*/
|
|
70
|
+
readonly getRuntimeEnv?: RuntimeEnvProvider;
|
|
71
|
+
/**
|
|
72
|
+
* Harness pre-selected for new sessions when the user has not made an
|
|
73
|
+
* explicit choice yet (e.g. an embedder whose agents primarily run
|
|
74
|
+
* coding tasks defaults to `"cursor"`).
|
|
75
|
+
*
|
|
76
|
+
* Read once on mount. The user's own selection — persisted to
|
|
77
|
+
* localStorage on explicit change — always takes precedence on
|
|
78
|
+
* subsequent visits.
|
|
79
|
+
*
|
|
80
|
+
* @default DEFAULT_HARNESS ("native")
|
|
81
|
+
*/
|
|
82
|
+
readonly defaultHarness?: HarnessOption;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
/** Return value of {@link useNewSessionFlow}. */
|
|
@@ -158,15 +183,18 @@ export interface UseNewSessionFlowReturn {
|
|
|
158
183
|
export function useNewSessionFlow(
|
|
159
184
|
options: UseNewSessionFlowOptions,
|
|
160
185
|
): UseNewSessionFlowReturn {
|
|
161
|
-
const { org, onSessionCreated, onError } = options;
|
|
186
|
+
const { org, onSessionCreated, onError, getRuntimeEnv, defaultHarness } = options;
|
|
162
187
|
const contextTarget = useExecutionTarget();
|
|
163
188
|
const executionTarget = options.executionTarget ?? contextTarget;
|
|
164
189
|
const adapter = useRunnerAdapter();
|
|
165
190
|
|
|
166
191
|
const [harness, setHarnessRaw] = useState<HarnessOption>(() => {
|
|
167
|
-
if (typeof window === "undefined") return DEFAULT_HARNESS;
|
|
192
|
+
if (typeof window === "undefined") return defaultHarness ?? DEFAULT_HARNESS;
|
|
193
|
+
// Only explicit user choices are persisted (see setHarness), so a
|
|
194
|
+
// stored value always outranks the embedder's defaultHarness.
|
|
168
195
|
const stored = localStorage.getItem(STORAGE_KEY_HARNESS);
|
|
169
|
-
|
|
196
|
+
if (stored === "native" || stored === "cursor") return stored;
|
|
197
|
+
return defaultHarness ?? DEFAULT_HARNESS;
|
|
170
198
|
});
|
|
171
199
|
|
|
172
200
|
const { getModel, isLoading: isModelsLoading } = useModelRegistry({ harness });
|
|
@@ -191,14 +219,12 @@ export function useNewSessionFlow(
|
|
|
191
219
|
|
|
192
220
|
const validModelId = modelId && getModel(modelId) ? modelId : undefined;
|
|
193
221
|
|
|
194
|
-
// Persist harness on change
|
|
195
|
-
useEffect(() => {
|
|
196
|
-
localStorage.setItem(STORAGE_KEY_HARNESS, harness);
|
|
197
|
-
}, [harness]);
|
|
198
|
-
|
|
199
222
|
const setHarness = useCallback(
|
|
200
223
|
(h: HarnessOption) => {
|
|
201
224
|
setHarnessRaw(h);
|
|
225
|
+
// Persist only explicit choices — never the seeded value — so the
|
|
226
|
+
// embedder's defaultHarness keeps applying until the user decides.
|
|
227
|
+
localStorage.setItem(STORAGE_KEY_HARNESS, h);
|
|
202
228
|
const storedModel = localStorage.getItem(modelStorageKey(h));
|
|
203
229
|
const plain = storedModel ? (parseModelKey(storedModel)?.modelId ?? storedModel) : undefined;
|
|
204
230
|
setModelId(plain);
|
|
@@ -246,6 +272,14 @@ export function useNewSessionFlow(
|
|
|
246
272
|
setSubmitError(null);
|
|
247
273
|
|
|
248
274
|
try {
|
|
275
|
+
// Host env is evaluated per submission (short-lived credentials)
|
|
276
|
+
// and before session creation, so a credential failure can never
|
|
277
|
+
// strand an empty session. Without a provider the composer env
|
|
278
|
+
// passes through untouched — no extra await on the hot path.
|
|
279
|
+
const runtimeEnv = getRuntimeEnv
|
|
280
|
+
? await resolveExecutionRuntimeEnv(getRuntimeEnv, context?.runtimeEnv)
|
|
281
|
+
: context?.runtimeEnv;
|
|
282
|
+
|
|
249
283
|
const sessionFields = {
|
|
250
284
|
org,
|
|
251
285
|
workspaceEntries: workspace.hasEntries
|
|
@@ -261,7 +295,7 @@ export function useNewSessionFlow(
|
|
|
261
295
|
org,
|
|
262
296
|
message,
|
|
263
297
|
modelName: selectedModel ?? validModelId,
|
|
264
|
-
runtimeEnv
|
|
298
|
+
runtimeEnv,
|
|
265
299
|
attachments: context?.attachments,
|
|
266
300
|
interactionMode: context?.interactionMode,
|
|
267
301
|
workspaceFileRefs: context?.workspaceFileRefs,
|
|
@@ -344,6 +378,7 @@ export function useNewSessionFlow(
|
|
|
344
378
|
harness,
|
|
345
379
|
executionTarget,
|
|
346
380
|
adapter,
|
|
381
|
+
getRuntimeEnv,
|
|
347
382
|
validModelId,
|
|
348
383
|
workspace,
|
|
349
384
|
mcpServerUsages,
|
|
@@ -14,6 +14,7 @@ import { fromProtoHarness, type HarnessOption } from "../models/harness";
|
|
|
14
14
|
import { Harness, ExecutionTarget } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
|
|
15
15
|
import { fromProtoExecutionTarget, type ExecutionTargetOption } from "./execution-target";
|
|
16
16
|
import { useSessionConversation, type UseSessionConversationReturn } from "./useSessionConversation";
|
|
17
|
+
import { resolveExecutionRuntimeEnv, type RuntimeEnvProvider } from "./runtime-env";
|
|
17
18
|
import { useAgentRefFromSession } from "./useAgentRefFromSession";
|
|
18
19
|
import { usePersistedModel, type UsePersistedModelReturn } from "./usePersistedModel";
|
|
19
20
|
import { specMcpUsagesToInput, specSkillRefsToInput } from "./session-spec-converters";
|
|
@@ -30,6 +31,18 @@ export interface UseSessionPageFlowOptions {
|
|
|
30
31
|
readonly sessionId: string;
|
|
31
32
|
/** Organization slug. */
|
|
32
33
|
readonly org: string;
|
|
34
|
+
/**
|
|
35
|
+
* Supplies host-app environment variables for every follow-up
|
|
36
|
+
* execution. Evaluated once per follow-up, at send time, so
|
|
37
|
+
* short-lived credentials stay fresh.
|
|
38
|
+
*
|
|
39
|
+
* Host values win over composer-collected env on key collisions. If
|
|
40
|
+
* the provider throws, the follow-up is aborted before any optimistic
|
|
41
|
+
* UI or session mutation and the error surfaces via
|
|
42
|
+
* {@link UseSessionPageFlowReturn.submitError}. See
|
|
43
|
+
* {@link RuntimeEnvProvider}.
|
|
44
|
+
*/
|
|
45
|
+
readonly getRuntimeEnv?: RuntimeEnvProvider;
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
/** Return value of {@link useSessionPageFlow}. */
|
|
@@ -128,8 +141,10 @@ export interface UseSessionPageFlowReturn {
|
|
|
128
141
|
|
|
129
142
|
/**
|
|
130
143
|
* Submit a follow-up message. Handles agent override resolution
|
|
131
|
-
* (if the user changed the agent mid-session)
|
|
132
|
-
* `conv.sendFollowUp` with
|
|
144
|
+
* (if the user changed the agent mid-session), evaluates the host
|
|
145
|
+
* runtime-env provider, and delegates to `conv.sendFollowUp` with
|
|
146
|
+
* all managed state. Never rejects — pre-send failures land in
|
|
147
|
+
* {@link submitError}.
|
|
133
148
|
*/
|
|
134
149
|
readonly handleSubmit: (
|
|
135
150
|
message: string,
|
|
@@ -137,6 +152,17 @@ export interface UseSessionPageFlowReturn {
|
|
|
137
152
|
context?: SessionComposerSubmitContext,
|
|
138
153
|
) => Promise<void>;
|
|
139
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Error from the most recent follow-up's pre-send work (agent
|
|
157
|
+
* override resolution, host runtime-env evaluation), or `null`.
|
|
158
|
+
*
|
|
159
|
+
* Distinct from `conv.sendError`, which covers the create-execution
|
|
160
|
+
* RPC itself. Kept as the raw `Error` so consumers can render
|
|
161
|
+
* contextual guidance (e.g. secret-flow errors). Cleared at the
|
|
162
|
+
* start of each submission.
|
|
163
|
+
*/
|
|
164
|
+
readonly submitError: Error | null;
|
|
165
|
+
|
|
140
166
|
/**
|
|
141
167
|
* The most relevant execution for sidebar display — the active
|
|
142
168
|
* streaming execution, or the last completed one.
|
|
@@ -199,7 +225,7 @@ export interface UseSessionPageFlowReturn {
|
|
|
199
225
|
export function useSessionPageFlow(
|
|
200
226
|
options: UseSessionPageFlowOptions,
|
|
201
227
|
): UseSessionPageFlowReturn {
|
|
202
|
-
const { sessionId, org } = options;
|
|
228
|
+
const { sessionId, org, getRuntimeEnv } = options;
|
|
203
229
|
|
|
204
230
|
const stigmer = useStigmer();
|
|
205
231
|
const conv = useSessionConversation(sessionId, org);
|
|
@@ -324,27 +350,48 @@ export function useSessionPageFlow(
|
|
|
324
350
|
// Follow-up submission with agent override
|
|
325
351
|
// -------------------------------------------------------------------------
|
|
326
352
|
|
|
353
|
+
const [submitError, setSubmitError] = useState<Error | null>(null);
|
|
354
|
+
|
|
327
355
|
const handleSubmit = useCallback(
|
|
328
356
|
async (
|
|
329
357
|
message: string,
|
|
330
358
|
selectedModel?: string,
|
|
331
359
|
context?: SessionComposerSubmitContext,
|
|
332
360
|
) => {
|
|
333
|
-
|
|
361
|
+
setSubmitError(null);
|
|
334
362
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
363
|
+
// Pre-send work runs before conv.sendFollowUp so a failure here
|
|
364
|
+
// aborts cleanly: no optimistic pending message, no session
|
|
365
|
+
// mutation. The composer fires this handler without awaiting it,
|
|
366
|
+
// so a rejection would otherwise be an unhandled rejection —
|
|
367
|
+
// failures must land in submitError instead.
|
|
368
|
+
let agentInstanceIdOverride: string | undefined;
|
|
369
|
+
let runtimeEnv: SessionComposerSubmitContext["runtimeEnv"];
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
if (resolution) {
|
|
373
|
+
if (
|
|
374
|
+
resolution.mode === "saved" &&
|
|
375
|
+
resolution.instanceId !== sessionInstanceId
|
|
376
|
+
) {
|
|
377
|
+
agentInstanceIdOverride = resolution.instanceId;
|
|
378
|
+
} else if (resolution.mode === "direct" && agentRef) {
|
|
379
|
+
const agent = await stigmer.agent.getByReference(agentRef);
|
|
380
|
+
const defaultId = agent.status?.defaultInstanceId;
|
|
381
|
+
if (defaultId && defaultId !== sessionInstanceId) {
|
|
382
|
+
agentInstanceIdOverride = defaultId;
|
|
383
|
+
}
|
|
346
384
|
}
|
|
347
385
|
}
|
|
386
|
+
|
|
387
|
+
// Evaluated per follow-up so short-lived host credentials are
|
|
388
|
+
// current; host values win over composer-collected env.
|
|
389
|
+
runtimeEnv = getRuntimeEnv
|
|
390
|
+
? await resolveExecutionRuntimeEnv(getRuntimeEnv, context?.runtimeEnv)
|
|
391
|
+
: context?.runtimeEnv;
|
|
392
|
+
} catch (err) {
|
|
393
|
+
setSubmitError(err instanceof Error ? err : new Error(String(err)));
|
|
394
|
+
return;
|
|
348
395
|
}
|
|
349
396
|
|
|
350
397
|
conv.sendFollowUp(message, {
|
|
@@ -355,7 +402,7 @@ export function useSessionPageFlow(
|
|
|
355
402
|
: undefined,
|
|
356
403
|
mcpServerUsages: mcpServerUsages.length > 0 ? mcpServerUsages : undefined,
|
|
357
404
|
skillRefs: skillRefs.length > 0 ? skillRefs : undefined,
|
|
358
|
-
runtimeEnv
|
|
405
|
+
runtimeEnv,
|
|
359
406
|
attachments: context?.attachments,
|
|
360
407
|
interactionMode: context?.interactionMode,
|
|
361
408
|
// Sourced from the session-scoped preference set at the approval gate,
|
|
@@ -366,7 +413,7 @@ export function useSessionPageFlow(
|
|
|
366
413
|
|
|
367
414
|
sessionVariables.clear();
|
|
368
415
|
},
|
|
369
|
-
[conv.sendFollowUp, modelId, workspace, mcpServerUsages, skillRefs, sessionVariables.clear, resolution, agentRef, sessionInstanceId, stigmer, autoApproveAll],
|
|
416
|
+
[conv.sendFollowUp, modelId, workspace, mcpServerUsages, skillRefs, sessionVariables.clear, resolution, agentRef, sessionInstanceId, stigmer, autoApproveAll, getRuntimeEnv],
|
|
370
417
|
);
|
|
371
418
|
|
|
372
419
|
// -------------------------------------------------------------------------
|
|
@@ -416,6 +463,7 @@ export function useSessionPageFlow(
|
|
|
416
463
|
setAutoApproveAll,
|
|
417
464
|
submitApproval,
|
|
418
465
|
handleSubmit,
|
|
466
|
+
submitError,
|
|
419
467
|
displayExecution,
|
|
420
468
|
allExecutions,
|
|
421
469
|
sandboxWorkspaceRoot,
|
|
@@ -10,8 +10,7 @@ import type { ResourceRef } from "@stigmer/sdk";
|
|
|
10
10
|
import { getUserMessage } from "@stigmer/sdk";
|
|
11
11
|
import { useUpdateWorkflowInstance } from "./useUpdateWorkflowInstance";
|
|
12
12
|
import { useDeleteWorkflowInstance } from "./useDeleteWorkflowInstance";
|
|
13
|
-
import {
|
|
14
|
-
import { InstanceVisibilitySelector } from "../../library/InstanceVisibilitySelector";
|
|
13
|
+
import { ResourceVisibilityControl } from "../../library/ResourceVisibilityControl";
|
|
15
14
|
import { PermissionGate } from "../../iam-policy/PermissionGate";
|
|
16
15
|
import { SharePanel } from "../../iam-policy/SharePanel";
|
|
17
16
|
import { EnvironmentPicker } from "../../environment/EnvironmentPicker";
|
|
@@ -55,7 +54,6 @@ export function WorkflowInstanceDetailPanel({
|
|
|
55
54
|
|
|
56
55
|
const { update, isUpdating } = useUpdateWorkflowInstance();
|
|
57
56
|
const { deleteInstance, isDeleting } = useDeleteWorkflowInstance();
|
|
58
|
-
const { updateVisibility, isPending: isVisibilityPending } = useUpdateVisibility("workflowInstance", id || null);
|
|
59
57
|
const { environments } = useEnvironmentList(org);
|
|
60
58
|
|
|
61
59
|
const [isEditingEnvs, setIsEditingEnvs] = useState(false);
|
|
@@ -64,14 +62,6 @@ export function WorkflowInstanceDetailPanel({
|
|
|
64
62
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
65
63
|
const [deleteError, setDeleteError] = useState<Error | null>(null);
|
|
66
64
|
|
|
67
|
-
const handleVisibilityChange = useCallback(
|
|
68
|
-
async (v: ApiResourceVisibility) => {
|
|
69
|
-
await updateVisibility(v);
|
|
70
|
-
onUpdated?.();
|
|
71
|
-
},
|
|
72
|
-
[updateVisibility, onUpdated],
|
|
73
|
-
);
|
|
74
|
-
|
|
75
65
|
const handleStartEditEnvs = useCallback(() => {
|
|
76
66
|
const currentRefs: ResourceRef[] = (spec?.environmentRefs ?? []).map((ref) => ({
|
|
77
67
|
org: ref.org || org,
|
|
@@ -254,21 +244,12 @@ export function WorkflowInstanceDetailPanel({
|
|
|
254
244
|
{/* Visibility */}
|
|
255
245
|
<div className="px-4 py-3">
|
|
256
246
|
<h4 className="text-xs font-medium text-muted-foreground mb-2">Visibility</h4>
|
|
257
|
-
<
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
</span>
|
|
264
|
-
}
|
|
265
|
-
>
|
|
266
|
-
<InstanceVisibilitySelector
|
|
267
|
-
visibility={meta?.visibility ?? ApiResourceVisibility.visibility_private}
|
|
268
|
-
onVisibilityChange={handleVisibilityChange}
|
|
269
|
-
isPending={isVisibilityPending}
|
|
270
|
-
/>
|
|
271
|
-
</PermissionGate>
|
|
247
|
+
<ResourceVisibilityControl
|
|
248
|
+
kind="workflowInstance"
|
|
249
|
+
resourceId={id}
|
|
250
|
+
visibility={meta?.visibility ?? ApiResourceVisibility.visibility_private}
|
|
251
|
+
onChanged={onUpdated}
|
|
252
|
+
/>
|
|
272
253
|
</div>
|
|
273
254
|
|
|
274
255
|
{/* Share Panel */}
|
|
@@ -343,12 +324,6 @@ export function WorkflowInstanceDetailPanel({
|
|
|
343
324
|
);
|
|
344
325
|
}
|
|
345
326
|
|
|
346
|
-
const VISIBILITY_LABELS: Record<number, string> = {
|
|
347
|
-
[ApiResourceVisibility.visibility_private]: "Private",
|
|
348
|
-
[ApiResourceVisibility.visibility_org]: "Organization",
|
|
349
|
-
[ApiResourceVisibility.visibility_public]: "Public",
|
|
350
|
-
};
|
|
351
|
-
|
|
352
327
|
function CloseIcon() {
|
|
353
328
|
return (
|
|
354
329
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|