@stigmer/react 0.0.46 → 0.0.48

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 (38) hide show
  1. package/agent/AgentPicker.d.ts +12 -2
  2. package/agent/AgentPicker.d.ts.map +1 -1
  3. package/agent/AgentPicker.js +2 -2
  4. package/agent/AgentPicker.js.map +1 -1
  5. package/agent/useAgentSearch.d.ts +9 -1
  6. package/agent/useAgentSearch.d.ts.map +1 -1
  7. package/agent/useAgentSearch.js +9 -1
  8. package/agent/useAgentSearch.js.map +1 -1
  9. package/composer/ContextChip.d.ts +3 -3
  10. package/composer/ContextChip.d.ts.map +1 -1
  11. package/composer/SessionComposer.d.ts.map +1 -1
  12. package/composer/SessionComposer.js +84 -11
  13. package/composer/SessionComposer.js.map +1 -1
  14. package/mcp-server/McpServerPicker.d.ts +12 -2
  15. package/mcp-server/McpServerPicker.d.ts.map +1 -1
  16. package/mcp-server/McpServerPicker.js +2 -2
  17. package/mcp-server/McpServerPicker.js.map +1 -1
  18. package/mcp-server/useTriggerApprovalPolicySession.d.ts +6 -0
  19. package/mcp-server/useTriggerApprovalPolicySession.d.ts.map +1 -1
  20. package/mcp-server/useTriggerApprovalPolicySession.js +76 -5
  21. package/mcp-server/useTriggerApprovalPolicySession.js.map +1 -1
  22. package/package.json +4 -4
  23. package/search/useResourceSearch.d.ts +10 -0
  24. package/search/useResourceSearch.d.ts.map +1 -1
  25. package/search/useResourceSearch.js +4 -2
  26. package/search/useResourceSearch.js.map +1 -1
  27. package/skill/SkillPicker.d.ts +12 -2
  28. package/skill/SkillPicker.d.ts.map +1 -1
  29. package/skill/SkillPicker.js +2 -2
  30. package/skill/SkillPicker.js.map +1 -1
  31. package/src/agent/AgentPicker.tsx +13 -2
  32. package/src/agent/useAgentSearch.ts +9 -1
  33. package/src/composer/ContextChip.tsx +3 -3
  34. package/src/composer/SessionComposer.tsx +117 -7
  35. package/src/mcp-server/McpServerPicker.tsx +13 -2
  36. package/src/mcp-server/useTriggerApprovalPolicySession.ts +100 -5
  37. package/src/search/useResourceSearch.ts +14 -2
  38. package/src/skill/SkillPicker.tsx +13 -2
@@ -568,7 +568,11 @@ export function SessionComposer({
568
568
  setConfigOpen(open);
569
569
  if (!open) {
570
570
  configMcpInitialServerKeyRef.current = undefined;
571
- if (configActivePanel === "agent") {
571
+ if (
572
+ configActivePanel === "agent" &&
573
+ agentSetup.state.status !== "needsEnvVars" &&
574
+ agentSetup.state.status !== "submitting"
575
+ ) {
572
576
  agentSetup.reset();
573
577
  }
574
578
  }
@@ -578,7 +582,12 @@ export function SessionComposer({
578
582
 
579
583
  const handleConfigActivePanelChange = useCallback(
580
584
  (panel: string | null) => {
581
- if (configActivePanel === "agent" && panel !== "agent") {
585
+ if (
586
+ configActivePanel === "agent" &&
587
+ panel !== "agent" &&
588
+ agentSetup.state.status !== "needsEnvVars" &&
589
+ agentSetup.state.status !== "submitting"
590
+ ) {
582
591
  agentSetup.reset();
583
592
  }
584
593
  setConfigActivePanel(panel);
@@ -651,6 +660,12 @@ export function SessionComposer({
651
660
  onAgentResolutionChange?.(null);
652
661
  }, [onAgentRefChange, onAgentResolutionChange]);
653
662
 
663
+ const handlePendingAgentChipRemove = useCallback(() => {
664
+ agentSetup.reset();
665
+ onAgentRefChange?.(null);
666
+ onAgentResolutionChange?.(null);
667
+ }, [agentSetup, onAgentRefChange, onAgentResolutionChange]);
668
+
654
669
  // ---------------------------------------------------------------------------
655
670
  // Initial agent: auto-resolve on mount when initialAgentRef is provided
656
671
  // ---------------------------------------------------------------------------
@@ -661,12 +676,40 @@ export function SessionComposer({
661
676
  const initialAgentHandled = useRef(false);
662
677
 
663
678
  useEffect(() => {
664
- if (initialAgentRef && showAgent && org && !initialAgentHandled.current) {
665
- initialAgentHandled.current = true;
666
- handleAgentSelectRef.current(initialAgentRef);
679
+ if (!initialAgentRef || !showAgent || !org || initialAgentHandled.current) {
680
+ return;
667
681
  }
682
+
683
+ let cancelled = false;
684
+ initialAgentHandled.current = true;
685
+
686
+ handleAgentSelectRef.current(initialAgentRef).catch(() => {
687
+ if (!cancelled) {
688
+ initialAgentHandled.current = false;
689
+ }
690
+ });
691
+
692
+ return () => {
693
+ cancelled = true;
694
+ };
668
695
  }, [initialAgentRef, showAgent, org]);
669
696
 
697
+ // ---------------------------------------------------------------------------
698
+ // Initial agent: auto-open Configure > Agent when env vars are needed
699
+ // ---------------------------------------------------------------------------
700
+
701
+ const initialAgentConfigAutoOpened = useRef(false);
702
+
703
+ useEffect(() => {
704
+ if (initialAgentConfigAutoOpened.current) return;
705
+ if (!initialAgentRef) return;
706
+ if (agentSetup.state.status !== "needsEnvVars") return;
707
+
708
+ initialAgentConfigAutoOpened.current = true;
709
+ setConfigOpen(true);
710
+ setConfigActivePanel("agent");
711
+ }, [initialAgentRef, agentSetup.state.status]);
712
+
670
713
  // ---------------------------------------------------------------------------
671
714
  // Initial attachments: upload files on mount when provided
672
715
  // ---------------------------------------------------------------------------
@@ -727,6 +770,38 @@ export function SessionComposer({
727
770
  type: "agent",
728
771
  onRemove: handleAgentChipRemove,
729
772
  });
773
+ } else if (
774
+ agentSetup.state.status === "needsEnvVars" ||
775
+ agentSetup.state.status === "resolving" ||
776
+ agentSetup.state.status === "submitting"
777
+ ) {
778
+ const st = agentSetup.state;
779
+ const ref = st.agentRef;
780
+ const refStr = `${ref.org}/${ref.slug}`;
781
+ const name =
782
+ st.status !== "resolving"
783
+ ? st.agentName
784
+ : (displayNames.get(refStr) ?? ref.slug);
785
+
786
+ items.push({
787
+ key: `agent:${refStr}`,
788
+ label: name,
789
+ type: "agent",
790
+ onRemove: handlePendingAgentChipRemove,
791
+ status:
792
+ st.status === "resolving"
793
+ ? "loading"
794
+ : st.status === "submitting"
795
+ ? "submitting"
796
+ : "needsSetup",
797
+ onClick:
798
+ st.status === "needsEnvVars"
799
+ ? () => {
800
+ setConfigOpen(true);
801
+ setConfigActivePanel("agent");
802
+ }
803
+ : undefined,
804
+ });
730
805
  }
731
806
 
732
807
  if (workspace) {
@@ -808,7 +883,9 @@ export function SessionComposer({
808
883
  return items;
809
884
  }, [
810
885
  agentRef,
886
+ agentSetup.state,
811
887
  handleAgentChipRemove,
888
+ handlePendingAgentChipRemove,
812
889
  workspace,
813
890
  showMcp,
814
891
  mcpSetup.entries,
@@ -858,11 +935,17 @@ export function SessionComposer({
858
935
  const configureItems = useMemo((): ConfigureMenuItem[] => {
859
936
  const items: ConfigureMenuItem[] = [];
860
937
  if (showAgent) {
938
+ const agentPending =
939
+ !agentRef &&
940
+ (agentSetup.state.status === "needsEnvVars" ||
941
+ agentSetup.state.status === "resolving" ||
942
+ agentSetup.state.status === "submitting");
861
943
  items.push({
862
944
  id: "agent",
863
945
  icon: <AgentIcon />,
864
946
  label: "Agent",
865
- count: agentRef ? 1 : 0,
947
+ count: agentRef || agentPending ? 1 : 0,
948
+ hasWarning: agentPending && agentSetup.state.status === "needsEnvVars",
866
949
  });
867
950
  }
868
951
  if (showMcp) {
@@ -891,7 +974,7 @@ export function SessionComposer({
891
974
  });
892
975
  }
893
976
  return items;
894
- }, [showAgent, agentRef, showMcp, mcpCount, mcpSetup.needsSetupCount, showSkills, skillCount, showSessionVars, sessionVarCount]);
977
+ }, [showAgent, agentRef, agentSetup.state, showMcp, mcpCount, mcpSetup.needsSetupCount, showSkills, skillCount, showSessionVars, sessionVarCount]);
895
978
 
896
979
  const renderConfigPanel = useCallback(
897
980
  (panelId: string): React.ReactNode => {
@@ -924,6 +1007,7 @@ export function SessionComposer({
924
1007
  <div className="relative">
925
1008
  <AgentPicker
926
1009
  org={org!}
1010
+ scope="all"
927
1011
  value={agentRef ?? null}
928
1012
  onChange={handleAgentSelect}
929
1013
  onDisplayNameResolved={handleDisplayNameResolved}
@@ -944,6 +1028,7 @@ export function SessionComposer({
944
1028
  return (
945
1029
  <McpServerPicker
946
1030
  org={org!}
1031
+ scope="all"
947
1032
  setup={{
948
1033
  entries: mcpSetup.entries,
949
1034
  onServerAdded: (ref) => mcpSetup.addServer(ref),
@@ -966,6 +1051,7 @@ export function SessionComposer({
966
1051
  return (
967
1052
  <SkillPicker
968
1053
  org={org!}
1054
+ scope="all"
969
1055
  value={skillRefs ?? []}
970
1056
  onChange={onSkillRefsChange!}
971
1057
  onDisplayNameResolved={handleDisplayNameResolved}
@@ -1087,6 +1173,30 @@ export function SessionComposer({
1087
1173
  />
1088
1174
  )}
1089
1175
 
1176
+ {/* Zone 2.7: Agent setup warning */}
1177
+ {showAgent &&
1178
+ agentSetup.state.status === "needsEnvVars" &&
1179
+ !agentRef && (
1180
+ <div
1181
+ role="status"
1182
+ className="mx-3 mb-2 flex items-center gap-2 rounded-md bg-warning/10 px-2.5 py-1.5 text-xs text-warning"
1183
+ >
1184
+ <AlertTriangleIcon />
1185
+ <span>Agent needs configuration before use</span>
1186
+ <button
1187
+ type="button"
1188
+ onClick={() => {
1189
+ setConfigOpen(true);
1190
+ setConfigActivePanel("agent");
1191
+ }}
1192
+ disabled={isDisabled}
1193
+ className="ml-auto shrink-0 rounded px-1.5 py-0.5 text-[0.6rem] font-medium hover:bg-warning/20 disabled:pointer-events-none disabled:opacity-50"
1194
+ >
1195
+ Configure
1196
+ </button>
1197
+ </div>
1198
+ )}
1199
+
1090
1200
  {/* Zone 2.75: MCP setup warning */}
1091
1201
  {showMcp && mcpSetup.needsSetupCount > 0 && (
1092
1202
  <div
@@ -69,8 +69,18 @@ export interface McpServerSetupIntegration {
69
69
  // ---------------------------------------------------------------------------
70
70
 
71
71
  export interface McpServerPickerProps {
72
- /** Organization slug to scope the search. */
72
+ /** Organization slug used as the default search scope. */
73
73
  readonly org: string;
74
+ /**
75
+ * Controls search scope.
76
+ *
77
+ * - `"org"` — search only within the provided organization.
78
+ * - `"all"` — search all organizations the caller can access,
79
+ * including public/platform MCP servers from other orgs.
80
+ *
81
+ * @default "org"
82
+ */
83
+ readonly scope?: "org" | "all";
74
84
  /**
75
85
  * Currently selected MCP server usages.
76
86
  *
@@ -227,6 +237,7 @@ function slugFromServerKey(key: string): string {
227
237
  */
228
238
  export function McpServerPicker({
229
239
  org,
240
+ scope,
230
241
  value,
231
242
  onChange,
232
243
  onDisplayNameResolved,
@@ -240,7 +251,7 @@ export function McpServerPicker({
240
251
  const listId = `${instanceId}-list`;
241
252
 
242
253
  const { results, isLoading, error, query, setQuery } =
243
- useMcpServerSearch(org);
254
+ useMcpServerSearch(org, { scope });
244
255
 
245
256
  const [focusIndex, setFocusIndex] = useState(-1);
246
257
  const [view, setView] = useState<PickerView>(() =>
@@ -3,9 +3,14 @@
3
3
  import { useCallback, useState } from "react";
4
4
  import { create } from "@bufbuild/protobuf";
5
5
  import { UploadAttachmentRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
6
+ import { ListAgentInstancesRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/io_pb";
7
+ import { ListEnvironmentsRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/io_pb";
8
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
6
9
  import { PENDING_SUBJECT } from "@stigmer/sdk";
10
+ import type { Stigmer } from "@stigmer/sdk";
7
11
  import { useStigmer } from "../hooks";
8
12
  import { toError } from "../internal/toError";
13
+ import { buildPersonalInstanceInput } from "../agent-instance/buildPersonalInstanceInput";
9
14
 
10
15
  export interface TriggerApprovalPolicyResult {
11
16
  readonly sessionId: string;
@@ -36,6 +41,56 @@ export interface UseTriggerApprovalPolicySessionReturn {
36
41
  const MCP_SERVER_CREATOR_SLUG = "mcp-server-creator";
37
42
  const MCP_SERVER_CREATOR_ORG = "stigmer";
38
43
 
44
+ const PERSONAL_LABEL = "stigmer.ai/personal";
45
+ const FOR_AGENT_LABEL = "stigmer.ai/for-agent";
46
+
47
+ /**
48
+ * Finds an existing personal agent instance for the given agent, or
49
+ * creates one linked to the caller's personal environment.
50
+ *
51
+ * Re-checks immediately before creation to narrow the race window
52
+ * when multiple tabs trigger simultaneously (same guard used by
53
+ * `useAgentSetup.findOrCreatePersonalInstance`).
54
+ */
55
+ async function findOrCreatePersonalInstance(
56
+ stigmer: Stigmer,
57
+ params: {
58
+ org: string;
59
+ agentId: string;
60
+ agentSlug: string;
61
+ personalEnvSlug: string;
62
+ },
63
+ ) {
64
+ const agentLabel = `${MCP_SERVER_CREATOR_ORG}/${params.agentSlug}`;
65
+
66
+ const existing = await stigmer.agentInstance.list(
67
+ create(ListAgentInstancesRequestSchema, {
68
+ org: params.org,
69
+ labels: {
70
+ [PERSONAL_LABEL]: "true",
71
+ [FOR_AGENT_LABEL]: agentLabel,
72
+ },
73
+ }),
74
+ );
75
+
76
+ if (existing.items.length > 0) {
77
+ return existing.items[0];
78
+ }
79
+
80
+ return stigmer.agentInstance.create(
81
+ buildPersonalInstanceInput({
82
+ org: params.org,
83
+ agentId: params.agentId,
84
+ agentSlug: params.agentSlug,
85
+ environmentRef: {
86
+ org: params.org,
87
+ slug: params.personalEnvSlug,
88
+ kind: ApiResourceKind.environment,
89
+ },
90
+ }),
91
+ );
92
+ }
93
+
39
94
  /**
40
95
  * Orchestration hook that auto-creates a session with the `mcp-server-creator`
41
96
  * agent and starts an execution with a pre-filled prompt and YAML attachment.
@@ -45,6 +100,12 @@ const MCP_SERVER_CREATOR_ORG = "stigmer";
45
100
  * and generate `default_tool_approvals` entries with appropriate approval
46
101
  * messages. The agent applies the result using the `apply_mcp_server` tool.
47
102
  *
103
+ * The session is created with a **personal** agent instance whose
104
+ * `environment_refs` include the caller's personal environment. This
105
+ * ensures MCP server credentials (e.g. `STIGMER_API_KEY`) are available
106
+ * through the standard environment merge chain, consistent with how
107
+ * `SessionComposer` / `useAgentSetup` provisions sessions.
108
+ *
48
109
  * @example
49
110
  * ```tsx
50
111
  * const { trigger, isTriggering, result } = useTriggerApprovalPolicySession();
@@ -79,19 +140,53 @@ export function useTriggerApprovalPolicySession(): UseTriggerApprovalPolicySessi
79
140
  slug: MCP_SERVER_CREATOR_SLUG,
80
141
  });
81
142
 
82
- const defaultInstanceId = agent.status?.defaultInstanceId;
83
- if (!defaultInstanceId) {
143
+ const agentId = agent.metadata?.id;
144
+ if (!agentId) {
84
145
  throw new Error(
85
- `Agent "${MCP_SERVER_CREATOR_ORG}/${MCP_SERVER_CREATOR_SLUG}" does not have a ` +
86
- "default instance. Ensure the mcp-server-creator agent is properly set up.",
146
+ `Agent "${MCP_SERVER_CREATOR_ORG}/${MCP_SERVER_CREATOR_SLUG}" not found. ` +
147
+ "Ensure the mcp-server-creator agent is properly set up.",
87
148
  );
88
149
  }
89
150
 
151
+ // Resolve the agent instance to use for the session. Prefer a
152
+ // personal instance linked to the caller's personal environment
153
+ // so MCP server credentials flow through environment_refs. Fall
154
+ // back to the default instance when no personal environment exists.
155
+ let instanceId: string;
156
+
157
+ const envList = await stigmer.environment.list(
158
+ create(ListEnvironmentsRequestSchema, {
159
+ org,
160
+ labels: { [PERSONAL_LABEL]: "true" },
161
+ }),
162
+ );
163
+
164
+ if (envList.items.length > 0) {
165
+ const personalEnvSlug = envList.items[0].metadata!.slug;
166
+ const instance = await findOrCreatePersonalInstance(stigmer, {
167
+ org,
168
+ agentId,
169
+ agentSlug: MCP_SERVER_CREATOR_SLUG,
170
+ personalEnvSlug,
171
+ });
172
+ instanceId = instance.metadata!.id;
173
+ } else {
174
+ const defaultInstanceId = agent.status?.defaultInstanceId;
175
+ if (!defaultInstanceId) {
176
+ throw new Error(
177
+ `Agent "${MCP_SERVER_CREATOR_ORG}/${MCP_SERVER_CREATOR_SLUG}" does not have a ` +
178
+ "default instance and no personal environment exists. " +
179
+ "Add credentials to your personal environment in Settings first.",
180
+ );
181
+ }
182
+ instanceId = defaultInstanceId;
183
+ }
184
+
90
185
  const session = await stigmer.session.create({
91
186
  name: `approval-policy-${mcpServerSlug}-${Date.now()}`,
92
187
  org,
93
188
  subject: PENDING_SUBJECT,
94
- agentInstanceId: defaultInstanceId,
189
+ agentInstanceId: instanceId,
95
190
  });
96
191
 
97
192
  const sessionId = session.metadata!.id;
@@ -9,6 +9,16 @@ export interface UseResourceSearchOptions {
9
9
  readonly pageSize?: number;
10
10
  /** Debounce delay for query changes in milliseconds. @default 300 */
11
11
  readonly debounceMs?: number;
12
+ /**
13
+ * Controls search scope.
14
+ *
15
+ * - `"org"` — search only within the provided organization.
16
+ * - `"all"` — search all organizations the caller can access,
17
+ * including public/platform resources from other orgs.
18
+ *
19
+ * @default "org"
20
+ */
21
+ readonly scope?: "org" | "all";
12
22
  }
13
23
 
14
24
  export interface UseResourceSearchReturn {
@@ -46,6 +56,7 @@ export function useResourceSearch(
46
56
 
47
57
  const pageSize = options?.pageSize ?? DEFAULT_PAGE_SIZE;
48
58
  const debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
59
+ const scope = options?.scope ?? "org";
49
60
 
50
61
  // Debounce query changes
51
62
  useEffect(() => {
@@ -61,8 +72,9 @@ export function useResourceSearch(
61
72
  setError(null);
62
73
 
63
74
  const params: ListParams = {
64
- org,
75
+ org: scope === "all" ? "" : org,
65
76
  query: debouncedQuery || undefined,
77
+ excludePublic: false,
66
78
  page: { num: 1, size: pageSize },
67
79
  };
68
80
 
@@ -84,7 +96,7 @@ export function useResourceSearch(
84
96
  return () => {
85
97
  cancelled.current = true;
86
98
  };
87
- }, [listFn, org, debouncedQuery, pageSize, fetchKey]);
99
+ }, [listFn, org, debouncedQuery, pageSize, scope, fetchKey]);
88
100
 
89
101
  return { results, isLoading, error, query, setQuery, refetch };
90
102
  }
@@ -17,8 +17,18 @@ import { useScrollShadows } from "../internal/useScrollShadows";
17
17
  import { ScrollFade } from "../internal/ScrollFade";
18
18
 
19
19
  export interface SkillPickerProps {
20
- /** Organization slug to scope the search. */
20
+ /** Organization slug used as the default search scope. */
21
21
  readonly org: string;
22
+ /**
23
+ * Controls search scope.
24
+ *
25
+ * - `"org"` — search only within the provided organization.
26
+ * - `"all"` — search all organizations the caller can access,
27
+ * including public/platform skills from other orgs.
28
+ *
29
+ * @default "org"
30
+ */
31
+ readonly scope?: "org" | "all";
22
32
  /** Currently selected skill references. */
23
33
  readonly value: ResourceRef[];
24
34
  /** Called when the selection changes. */
@@ -48,13 +58,14 @@ const LIST_ID = "stgm-skill-list";
48
58
  */
49
59
  export function SkillPicker({
50
60
  org,
61
+ scope,
51
62
  value,
52
63
  onChange,
53
64
  onDisplayNameResolved,
54
65
  disabled,
55
66
  className,
56
67
  }: SkillPickerProps) {
57
- const { results, isLoading, error, query, setQuery } = useSkillSearch(org);
68
+ const { results, isLoading, error, query, setQuery } = useSkillSearch(org, { scope });
58
69
 
59
70
  const [focusIndex, setFocusIndex] = useState(-1);
60
71
  const searchRef = useRef<HTMLInputElement>(null);