@stigmer/react 3.0.6 → 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/agent-instance/AgentInstanceDetailPanel.d.ts.map +1 -1
  2. package/agent-instance/AgentInstanceDetailPanel.js +2 -9
  3. package/agent-instance/AgentInstanceDetailPanel.js.map +1 -1
  4. package/agent-instance/AgentInstanceList.d.ts.map +1 -1
  5. package/agent-instance/AgentInstanceList.js +2 -9
  6. package/agent-instance/AgentInstanceList.js.map +1 -1
  7. package/agent-instance/CreateAgentInstanceDialog.d.ts.map +1 -1
  8. package/agent-instance/CreateAgentInstanceDialog.js +1 -1
  9. package/agent-instance/CreateAgentInstanceDialog.js.map +1 -1
  10. package/composer/SessionComposer.d.ts +14 -0
  11. package/composer/SessionComposer.d.ts.map +1 -1
  12. package/composer/SessionComposer.js +15 -9
  13. package/composer/SessionComposer.js.map +1 -1
  14. package/index.d.ts +1 -1
  15. package/index.d.ts.map +1 -1
  16. package/index.js.map +1 -1
  17. package/library/InstanceVisibilitySelector.d.ts +23 -9
  18. package/library/InstanceVisibilitySelector.d.ts.map +1 -1
  19. package/library/InstanceVisibilitySelector.js +14 -9
  20. package/library/InstanceVisibilitySelector.js.map +1 -1
  21. package/library/VisibilityOptionRow.d.ts +52 -0
  22. package/library/VisibilityOptionRow.d.ts.map +1 -0
  23. package/library/VisibilityOptionRow.js +92 -0
  24. package/library/VisibilityOptionRow.js.map +1 -0
  25. package/library/VisibilitySelector.d.ts +47 -24
  26. package/library/VisibilitySelector.d.ts.map +1 -1
  27. package/library/VisibilitySelector.js +137 -115
  28. package/library/VisibilitySelector.js.map +1 -1
  29. package/library/visibilityLevels.d.ts +25 -3
  30. package/library/visibilityLevels.d.ts.map +1 -1
  31. package/library/visibilityLevels.js +8 -2
  32. package/library/visibilityLevels.js.map +1 -1
  33. package/package.json +4 -4
  34. package/session/NewSessionViewer.d.ts +32 -1
  35. package/session/NewSessionViewer.d.ts.map +1 -1
  36. package/session/NewSessionViewer.js +20 -9
  37. package/session/NewSessionViewer.js.map +1 -1
  38. package/session/SessionViewer.d.ts +24 -1
  39. package/session/SessionViewer.d.ts.map +1 -1
  40. package/session/SessionViewer.js +18 -12
  41. package/session/SessionViewer.js.map +1 -1
  42. package/session/audience.d.ts +21 -0
  43. package/session/audience.d.ts.map +1 -0
  44. package/session/audience.js +2 -0
  45. package/session/audience.js.map +1 -0
  46. package/session/index.d.ts +2 -0
  47. package/session/index.d.ts.map +1 -1
  48. package/session/index.js.map +1 -1
  49. package/session/runtime-env.d.ts +47 -0
  50. package/session/runtime-env.d.ts.map +1 -0
  51. package/session/runtime-env.js +20 -0
  52. package/session/runtime-env.js.map +1 -0
  53. package/session/useNewSessionFlow.d.ts +25 -0
  54. package/session/useNewSessionFlow.d.ts.map +1 -1
  55. package/session/useNewSessionFlow.js +20 -8
  56. package/session/useNewSessionFlow.js.map +1 -1
  57. package/session/useSessionPageFlow.d.ts +27 -2
  58. package/session/useSessionPageFlow.d.ts.map +1 -1
  59. package/session/useSessionPageFlow.js +34 -13
  60. package/session/useSessionPageFlow.js.map +1 -1
  61. package/src/agent-instance/AgentInstanceDetailPanel.tsx +7 -27
  62. package/src/agent-instance/AgentInstanceList.tsx +7 -27
  63. package/src/agent-instance/CreateAgentInstanceDialog.tsx +1 -0
  64. package/src/composer/SessionComposer.tsx +30 -8
  65. package/src/composer/__tests__/SessionComposer-lockAgent.test.tsx +150 -0
  66. package/src/index.ts +2 -0
  67. package/src/library/InstanceVisibilitySelector.tsx +27 -9
  68. package/src/library/VisibilityOptionRow.tsx +244 -0
  69. package/src/library/VisibilitySelector.tsx +303 -260
  70. package/src/library/__tests__/VisibilitySelector.test.tsx +256 -0
  71. package/src/library/visibilityLevels.ts +35 -5
  72. package/src/session/NewSessionViewer.tsx +61 -12
  73. package/src/session/SessionViewer.tsx +51 -15
  74. package/src/session/__tests__/audienceWiring.test.tsx +274 -0
  75. package/src/session/__tests__/useNewSessionFlow.test.tsx +122 -0
  76. package/src/session/__tests__/useSessionPageFlow.runtimeEnv.test.tsx +170 -0
  77. package/src/session/audience.ts +20 -0
  78. package/src/session/index.ts +3 -0
  79. package/src/session/runtime-env.ts +57 -0
  80. package/src/session/useNewSessionFlow.ts +44 -9
  81. package/src/session/useSessionPageFlow.ts +65 -17
  82. package/src/workflow/instance/CreateWorkflowInstanceDialog.tsx +1 -0
  83. package/src/workflow/instance/WorkflowInstanceDetailPanel.tsx +7 -27
  84. package/src/workflow/instance/WorkflowInstanceList.tsx +7 -27
  85. package/styles.css +1 -1
  86. package/workflow/instance/CreateWorkflowInstanceDialog.d.ts.map +1 -1
  87. package/workflow/instance/CreateWorkflowInstanceDialog.js +1 -1
  88. package/workflow/instance/CreateWorkflowInstanceDialog.js.map +1 -1
  89. package/workflow/instance/WorkflowInstanceDetailPanel.d.ts.map +1 -1
  90. package/workflow/instance/WorkflowInstanceDetailPanel.js +2 -9
  91. package/workflow/instance/WorkflowInstanceDetailPanel.js.map +1 -1
  92. package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
  93. package/workflow/instance/WorkflowInstanceList.js +2 -9
  94. package/workflow/instance/WorkflowInstanceList.js.map +1 -1
@@ -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
- return stored === "cursor" ? "cursor" : DEFAULT_HARNESS;
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: context?.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) and delegates to
132
- * `conv.sendFollowUp` with all managed state.
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
- let agentInstanceIdOverride: string | undefined;
361
+ setSubmitError(null);
334
362
 
335
- if (resolution) {
336
- if (
337
- resolution.mode === "saved" &&
338
- resolution.instanceId !== sessionInstanceId
339
- ) {
340
- agentInstanceIdOverride = resolution.instanceId;
341
- } else if (resolution.mode === "direct" && agentRef) {
342
- const agent = await stigmer.agent.getByReference(agentRef);
343
- const defaultId = agent.status?.defaultInstanceId;
344
- if (defaultId && defaultId !== sessionInstanceId) {
345
- agentInstanceIdOverride = defaultId;
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: context?.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,
@@ -203,6 +203,7 @@ export function CreateWorkflowInstanceDialog({
203
203
  <InstanceVisibilitySelector
204
204
  visibility={visibility}
205
205
  onVisibilityChange={setVisibility}
206
+ mode="create"
206
207
  disabled={isCreating}
207
208
  />
208
209
  </div>
@@ -10,9 +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 { useUpdateVisibility } from "../../library/useUpdateVisibility";
14
- import { InstanceVisibilitySelector } from "../../library/InstanceVisibilitySelector";
15
- import { visibilityLabel } from "../../library/visibilityLevels";
13
+ import { ResourceVisibilityControl } from "../../library/ResourceVisibilityControl";
16
14
  import { PermissionGate } from "../../iam-policy/PermissionGate";
17
15
  import { SharePanel } from "../../iam-policy/SharePanel";
18
16
  import { EnvironmentPicker } from "../../environment/EnvironmentPicker";
@@ -56,7 +54,6 @@ export function WorkflowInstanceDetailPanel({
56
54
 
57
55
  const { update, isUpdating } = useUpdateWorkflowInstance();
58
56
  const { deleteInstance, isDeleting } = useDeleteWorkflowInstance();
59
- const { updateVisibility, isPending: isVisibilityPending } = useUpdateVisibility("workflowInstance", id || null);
60
57
  const { environments } = useEnvironmentList(org);
61
58
 
62
59
  const [isEditingEnvs, setIsEditingEnvs] = useState(false);
@@ -65,14 +62,6 @@ export function WorkflowInstanceDetailPanel({
65
62
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
66
63
  const [deleteError, setDeleteError] = useState<Error | null>(null);
67
64
 
68
- const handleVisibilityChange = useCallback(
69
- async (v: ApiResourceVisibility) => {
70
- await updateVisibility(v);
71
- onUpdated?.();
72
- },
73
- [updateVisibility, onUpdated],
74
- );
75
-
76
65
  const handleStartEditEnvs = useCallback(() => {
77
66
  const currentRefs: ResourceRef[] = (spec?.environmentRefs ?? []).map((ref) => ({
78
67
  org: ref.org || org,
@@ -255,21 +244,12 @@ export function WorkflowInstanceDetailPanel({
255
244
  {/* Visibility */}
256
245
  <div className="px-4 py-3">
257
246
  <h4 className="text-xs font-medium text-muted-foreground mb-2">Visibility</h4>
258
- <PermissionGate
259
- resource={{ kind: "workflow_instance", id }}
260
- relation="can_edit"
261
- fallback={
262
- <span className="text-sm text-foreground">
263
- {visibilityLabel(meta?.visibility ?? ApiResourceVisibility.visibility_private)}
264
- </span>
265
- }
266
- >
267
- <InstanceVisibilitySelector
268
- visibility={meta?.visibility ?? ApiResourceVisibility.visibility_private}
269
- onVisibilityChange={handleVisibilityChange}
270
- isPending={isVisibilityPending}
271
- />
272
- </PermissionGate>
247
+ <ResourceVisibilityControl
248
+ kind="workflowInstance"
249
+ resourceId={id}
250
+ visibility={meta?.visibility ?? ApiResourceVisibility.visibility_private}
251
+ onChanged={onUpdated}
252
+ />
273
253
  </div>
274
254
 
275
255
  {/* Share Panel */}
@@ -6,9 +6,7 @@ import type { WorkflowInstance } from "@stigmer/protos/ai/stigmer/agentic/workfl
6
6
  import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
7
7
  import { useWorkflowInstances } from "../useWorkflowInstances";
8
8
  import { useEnvironmentList } from "../../environment/useEnvironmentList";
9
- import { useUpdateVisibility } from "../../library/useUpdateVisibility";
10
- import { InstanceVisibilitySelector } from "../../library/InstanceVisibilitySelector";
11
- import { visibilityLabel } from "../../library/visibilityLevels";
9
+ import { ResourceVisibilityControl } from "../../library/ResourceVisibilityControl";
12
10
  import { PermissionGate } from "../../iam-policy/PermissionGate";
13
11
  import { WorkflowInstanceEmptyState } from "./WorkflowInstanceEmptyState";
14
12
 
@@ -167,18 +165,9 @@ function InstanceRow({
167
165
  }: InstanceRowProps) {
168
166
  const meta = instance.metadata;
169
167
  const id = meta?.id ?? "";
170
- const { updateVisibility, isPending } = useUpdateVisibility("workflowInstance", id || null);
171
168
 
172
169
  const envRefs = instance.spec?.environmentRefs ?? [];
173
170
 
174
- const handleVisibilityChange = useCallback(
175
- async (v: ApiResourceVisibility) => {
176
- await updateVisibility(v);
177
- refetch();
178
- },
179
- [updateVisibility, refetch],
180
- );
181
-
182
171
  return (
183
172
  <tr
184
173
  className={cn(
@@ -224,21 +213,12 @@ function InstanceRow({
224
213
 
225
214
  <td className="px-4 py-2.5" onClick={(e) => e.stopPropagation()}>
226
215
  {id ? (
227
- <PermissionGate
228
- resource={{ kind: "workflow_instance", id }}
229
- relation="can_edit"
230
- fallback={
231
- <span className="text-xs text-muted-foreground">
232
- {visibilityLabel(meta?.visibility ?? ApiResourceVisibility.visibility_private)}
233
- </span>
234
- }
235
- >
236
- <InstanceVisibilitySelector
237
- visibility={meta?.visibility ?? ApiResourceVisibility.visibility_private}
238
- onVisibilityChange={handleVisibilityChange}
239
- isPending={isPending}
240
- />
241
- </PermissionGate>
216
+ <ResourceVisibilityControl
217
+ kind="workflowInstance"
218
+ resourceId={id}
219
+ visibility={meta?.visibility ?? ApiResourceVisibility.visibility_private}
220
+ onChanged={refetch}
221
+ />
242
222
  ) : (
243
223
  <span className="text-xs text-muted-foreground">—</span>
244
224
  )}