@stigmer/react 3.1.13 → 3.1.15

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 (139) hide show
  1. package/agent/AgentDetailView.d.ts +9 -9
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +23 -31
  4. package/agent/AgentDetailView.js.map +1 -1
  5. package/agent-instance/AgentInstanceEmptyState.d.ts +4 -1
  6. package/agent-instance/AgentInstanceEmptyState.d.ts.map +1 -1
  7. package/agent-instance/AgentInstanceEmptyState.js +12 -5
  8. package/agent-instance/AgentInstanceEmptyState.js.map +1 -1
  9. package/agent-instance/AgentInstanceList.d.ts +13 -2
  10. package/agent-instance/AgentInstanceList.d.ts.map +1 -1
  11. package/agent-instance/AgentInstanceList.js +8 -4
  12. package/agent-instance/AgentInstanceList.js.map +1 -1
  13. package/agent-instance/useAgentInstances.d.ts +10 -3
  14. package/agent-instance/useAgentInstances.d.ts.map +1 -1
  15. package/agent-instance/useAgentInstances.js +12 -4
  16. package/agent-instance/useAgentInstances.js.map +1 -1
  17. package/button/Button.d.ts +54 -0
  18. package/button/Button.d.ts.map +1 -0
  19. package/button/Button.js +42 -0
  20. package/button/Button.js.map +1 -0
  21. package/button/index.d.ts +3 -0
  22. package/button/index.d.ts.map +1 -0
  23. package/button/index.js +2 -0
  24. package/button/index.js.map +1 -0
  25. package/empty-state/EmptyState.d.ts.map +1 -1
  26. package/empty-state/EmptyState.js +8 -3
  27. package/empty-state/EmptyState.js.map +1 -1
  28. package/empty-state/types.d.ts +2 -0
  29. package/empty-state/types.d.ts.map +1 -1
  30. package/execution/index.d.ts +1 -1
  31. package/execution/index.d.ts.map +1 -1
  32. package/execution/index.js.map +1 -1
  33. package/execution/useCreateAgentExecution.d.ts +81 -9
  34. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  35. package/execution/useCreateAgentExecution.js +42 -4
  36. package/execution/useCreateAgentExecution.js.map +1 -1
  37. package/index.d.ts +5 -3
  38. package/index.d.ts.map +1 -1
  39. package/index.js +4 -2
  40. package/index.js.map +1 -1
  41. package/package.json +4 -4
  42. package/session/useNewSessionFlow.d.ts +6 -3
  43. package/session/useNewSessionFlow.d.ts.map +1 -1
  44. package/session/useNewSessionFlow.js +34 -35
  45. package/session/useNewSessionFlow.js.map +1 -1
  46. package/sharing/AgentShareList.d.ts +46 -0
  47. package/sharing/AgentShareList.d.ts.map +1 -0
  48. package/sharing/AgentShareList.js +159 -0
  49. package/sharing/AgentShareList.js.map +1 -0
  50. package/sharing/ShareAgentDialog.d.ts +38 -25
  51. package/sharing/ShareAgentDialog.d.ts.map +1 -1
  52. package/sharing/ShareAgentDialog.js +131 -62
  53. package/sharing/ShareAgentDialog.js.map +1 -1
  54. package/sharing/index.d.ts +10 -8
  55. package/sharing/index.d.ts.map +1 -1
  56. package/sharing/index.js +5 -4
  57. package/sharing/index.js.map +1 -1
  58. package/sharing/useAgentShares.d.ts +48 -0
  59. package/sharing/useAgentShares.d.ts.map +1 -0
  60. package/sharing/useAgentShares.js +44 -0
  61. package/sharing/useAgentShares.js.map +1 -0
  62. package/sharing/useCanCreateAgentShare.d.ts +43 -0
  63. package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
  64. package/sharing/useCanCreateAgentShare.js +44 -0
  65. package/sharing/useCanCreateAgentShare.js.map +1 -0
  66. package/sharing/useDeleteAgentShare.d.ts +42 -0
  67. package/sharing/useDeleteAgentShare.d.ts.map +1 -0
  68. package/sharing/useDeleteAgentShare.js +45 -0
  69. package/sharing/useDeleteAgentShare.js.map +1 -0
  70. package/sharing/useSaveAgentShare.d.ts +43 -18
  71. package/sharing/useSaveAgentShare.d.ts.map +1 -1
  72. package/sharing/useSaveAgentShare.js +55 -22
  73. package/sharing/useSaveAgentShare.js.map +1 -1
  74. package/src/agent/AgentDetailView.tsx +37 -43
  75. package/src/agent-instance/AgentInstanceEmptyState.tsx +24 -43
  76. package/src/agent-instance/AgentInstanceList.tsx +29 -16
  77. package/src/agent-instance/__tests__/agent-instance-hooks.test.tsx +19 -0
  78. package/src/agent-instance/useAgentInstances.ts +12 -3
  79. package/src/button/Button.tsx +104 -0
  80. package/src/button/__tests__/Button.test.tsx +82 -0
  81. package/src/button/index.ts +2 -0
  82. package/src/empty-state/EmptyState.tsx +8 -10
  83. package/src/empty-state/types.ts +2 -0
  84. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +92 -0
  85. package/src/execution/index.ts +2 -0
  86. package/src/execution/useCreateAgentExecution.ts +116 -10
  87. package/src/index.ts +17 -9
  88. package/src/session/__tests__/useNewSessionFlow.test.tsx +121 -52
  89. package/src/session/useNewSessionFlow.ts +38 -37
  90. package/src/sharing/AgentShareList.tsx +502 -0
  91. package/src/sharing/ShareAgentDialog.tsx +310 -112
  92. package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
  93. package/src/sharing/__tests__/ShareAgentDialog.test.tsx +311 -312
  94. package/src/sharing/__tests__/useAgentShares.test.tsx +186 -0
  95. package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
  96. package/src/sharing/index.ts +10 -12
  97. package/src/sharing/useAgentShares.ts +74 -0
  98. package/src/sharing/useCanCreateAgentShare.ts +74 -0
  99. package/src/sharing/useDeleteAgentShare.ts +73 -0
  100. package/src/sharing/useSaveAgentShare.ts +73 -23
  101. package/src/workflow/WorkflowDetailView.tsx +9 -0
  102. package/src/workflow/instance/WorkflowInstanceEmptyState.tsx +24 -43
  103. package/src/workflow/instance/WorkflowInstanceList.tsx +29 -16
  104. package/src/workflow/useWorkflowInstances.ts +10 -3
  105. package/styles.css +1 -1
  106. package/workflow/WorkflowDetailView.d.ts +8 -1
  107. package/workflow/WorkflowDetailView.d.ts.map +1 -1
  108. package/workflow/WorkflowDetailView.js +2 -2
  109. package/workflow/WorkflowDetailView.js.map +1 -1
  110. package/workflow/instance/WorkflowInstanceEmptyState.d.ts +4 -1
  111. package/workflow/instance/WorkflowInstanceEmptyState.d.ts.map +1 -1
  112. package/workflow/instance/WorkflowInstanceEmptyState.js +12 -5
  113. package/workflow/instance/WorkflowInstanceEmptyState.js.map +1 -1
  114. package/workflow/instance/WorkflowInstanceList.d.ts +13 -2
  115. package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
  116. package/workflow/instance/WorkflowInstanceList.js +8 -4
  117. package/workflow/instance/WorkflowInstanceList.js.map +1 -1
  118. package/workflow/useWorkflowInstances.d.ts +8 -3
  119. package/workflow/useWorkflowInstances.d.ts.map +1 -1
  120. package/workflow/useWorkflowInstances.js +10 -4
  121. package/workflow/useWorkflowInstances.js.map +1 -1
  122. package/sharing/useAgentShare.d.ts +0 -49
  123. package/sharing/useAgentShare.d.ts.map +0 -1
  124. package/sharing/useAgentShare.js +0 -64
  125. package/sharing/useAgentShare.js.map +0 -1
  126. package/sharing/useCreateExternalShareLink.d.ts +0 -90
  127. package/sharing/useCreateExternalShareLink.d.ts.map +0 -1
  128. package/sharing/useCreateExternalShareLink.js +0 -65
  129. package/sharing/useCreateExternalShareLink.js.map +0 -1
  130. package/sharing/useShareAgent.d.ts +0 -69
  131. package/sharing/useShareAgent.d.ts.map +0 -1
  132. package/sharing/useShareAgent.js +0 -42
  133. package/sharing/useShareAgent.js.map +0 -1
  134. package/src/sharing/__tests__/useAgentShare.test.tsx +0 -210
  135. package/src/sharing/__tests__/useCreateExternalShareLink.test.tsx +0 -194
  136. package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
  137. package/src/sharing/useAgentShare.ts +0 -105
  138. package/src/sharing/useCreateExternalShareLink.tsx +0 -141
  139. package/src/sharing/useShareAgent.tsx +0 -107
@@ -17,36 +17,65 @@ function sharingAudienceToProto(audience) {
17
17
  : AgentShareAudience.public;
18
18
  }
19
19
  /**
20
- * Behavior hook that persists an agent's canonical share configuration.
20
+ * Projects a share into the editable {@link AgentShareDraft} — the
21
+ * single constructor for the full-spec drafts every save requires.
22
+ * `null` (no share yet) seeds the same defaults the server would apply
23
+ * on first create.
24
+ *
25
+ * Shared by the Share dialog (draft seeding) and the share list's
26
+ * pause/resume toggle, so a toggle rebuilds the complete spec with only
27
+ * `enabled` flipped and can never wipe origins, messages, or credential
28
+ * bindings — the fails-closed posture {@link AgentShareDraft} exists to
29
+ * enforce.
30
+ */
31
+ export function draftFromShare(share) {
32
+ const spec = share?.spec;
33
+ return {
34
+ enabled: spec?.enabled ?? false,
35
+ audience: sharingAudienceFromProto(spec?.audience),
36
+ allowedOrigins: spec?.allowedOrigins ?? [],
37
+ messages: {
38
+ rateLimited: spec?.messages?.rateLimited ?? "",
39
+ unavailable: spec?.messages?.unavailable ?? "",
40
+ conversationEnded: spec?.messages?.conversationEnded ?? "",
41
+ },
42
+ environmentRefs: (spec?.environmentRefs ?? []).map((ref) => ({
43
+ org: ref.org,
44
+ slug: ref.slug,
45
+ })),
46
+ };
47
+ }
48
+ /**
49
+ * Behavior hook that persists an agent share's configuration.
21
50
  *
22
51
  * Commits via `stigmer.agentShare.apply()` — an idempotent upsert keyed
23
52
  * on the share's `(org, slug)` identity — so one code path serves both
24
- * the first enable (creates the share; the server authorizes on the
25
- * referenced agent's `can_edit`) and every later edit. There is
26
- * deliberately no create/update branching: the share slug is unique per
27
- * org and defaults to the agent's slug, which makes apply-by-identity
28
- * exact, and the generated `AgentShareInput` carries no `metadata.id`
29
- * for an update to route by anyway.
53
+ * creation (the server authorizes on the referenced agent's `can_edit`)
54
+ * and every later edit. There is deliberately no create/update
55
+ * branching: the share slug is unique per org, which makes
56
+ * apply-by-identity exact, and the generated `AgentShareInput` carries
57
+ * no `metadata.id` for an update to route by anyway. A create that
58
+ * collides on `(org, slug)` rejects with an already-exists error rather
59
+ * than touching the existing share.
30
60
  *
31
61
  * Disabling is a save with `enabled: false` — a config-preserving pause
32
62
  * (decision 011 D1). Deleting the share is a separate, destructive
33
- * operation this hook does not perform.
63
+ * operation ({@link useDeleteAgentShare}).
34
64
  *
35
- * `shareOrg` selects which org's channel a first save creates and
36
- * defaults to the agent's own org. Pass the viewer's org to create a
37
- * **cross-org share** (decision 013) — the server then authorizes on the
38
- * public agent's `can_execute` plus `can_create_agent_share` in the
39
- * sharing org, and bills that org.
65
+ * `shareOrg` selects which org owns a created share and defaults to the
66
+ * agent's own org. Pass the viewer's org to create a **cross-org
67
+ * share** (decision 013) — the server then authorizes on the public
68
+ * agent's `can_execute` plus `can_create_agent_share` in the sharing
69
+ * org, and bills that org.
40
70
  *
41
71
  * Pass `null` for `agent` to produce a stable no-op (useful while the
42
72
  * agent is still loading).
43
73
  *
44
74
  * @example
45
75
  * ```tsx
46
- * const { share } = useAgentShare(agent);
47
76
  * const { save, isPending } = useSaveAgentShare(agent);
48
77
  *
49
- * const persisted = await save({ ...draft, enabled: true }, share);
78
+ * const persisted = await save({ ...draftFromShare(share), enabled: true }, share);
50
79
  * // persisted.status.shareLinkToken is the live link token
51
80
  * ```
52
81
  */
@@ -58,7 +87,7 @@ export function useSaveAgentShare(agent, shareOrg) {
58
87
  const agentSlug = agent?.metadata?.slug ?? "";
59
88
  const agentName = agent?.metadata?.name ?? "";
60
89
  const resolvedShareOrg = shareOrg || agentOrg;
61
- const save = useCallback(async (draft, current) => {
90
+ const save = useCallback(async (draft, current, createIdentity) => {
62
91
  if (!agentOrg || !agentSlug)
63
92
  return undefined;
64
93
  setIsPending(true);
@@ -66,13 +95,17 @@ export function useSaveAgentShare(agent, shareOrg) {
66
95
  try {
67
96
  return await stigmer.agentShare.apply({
68
97
  // Identity: the existing share's org/slug when editing (a
69
- // manifest-created share may carry a non-default slug — apply
70
- // with the agent's slug would create a SECOND share), the
71
- // sharing org + the agent's slug when creating (the server's
72
- // own D2 default, made explicit).
98
+ // share may carry a non-default slug — apply with the agent's
99
+ // slug would create a SECOND share); when creating, the
100
+ // sharing org + the caller-chosen identity, falling back to
101
+ // the agent's own slug/name (the server's D2 default, made
102
+ // explicit).
73
103
  org: current?.metadata?.org || resolvedShareOrg,
74
- slug: current?.metadata?.slug || agentSlug,
75
- name: current?.metadata?.name || agentName || agentSlug,
104
+ slug: current?.metadata?.slug || createIdentity?.slug || agentSlug,
105
+ name: current?.metadata?.name ||
106
+ createIdentity?.name ||
107
+ agentName ||
108
+ agentSlug,
76
109
  agentRef: { org: agentOrg, slug: agentSlug },
77
110
  enabled: draft.enabled,
78
111
  // Written as the explicit enum value, never left unspecified —
@@ -1 +1 @@
1
- {"version":3,"file":"useSaveAgentShare.js","sourceRoot":"","sources":["../../src/sharing/useSaveAgentShare.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useSaveAgentShare.js","sourceRoot":"","sources":["../../src/sharing/useSaveAgentShare.ts"],"names":[],"mappings":"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"}
@@ -17,8 +17,7 @@ import { agentToInput } from "./internal/agentToInput.js";
17
17
  import { ErrorMessage } from "../error/ErrorMessage.js";
18
18
  import { VisibilityBadge } from "../library/VisibilitySelector.js";
19
19
  import { useManageAccess } from "../access/useManageAccess.js";
20
- import { useShareAgent } from "../sharing/useShareAgent.js";
21
- import { useCreateExternalShareLink } from "../sharing/useCreateExternalShareLink.js";
20
+ import { AgentShareList } from "../sharing/AgentShareList.js";
22
21
  import { ResourceDetailShell } from "../resource-detail/ResourceDetailShell.js";
23
22
  import { Section } from "../resource-detail/Section.js";
24
23
  import { useDetailTabs } from "../resource-detail/useDetailTabs.js";
@@ -38,6 +37,7 @@ const INSTRUCTIONS_COLLAPSED_HEIGHT = "12rem";
38
37
 
39
38
  const OVERVIEW_TAB: TabItem = { id: "overview", label: "Overview" };
40
39
  const INSTANCES_TAB: TabItem = { id: "instances", label: "Instances" };
40
+ const SHARES_TAB: TabItem = { id: "shares", label: "Shares" };
41
41
  const DEPENDENCIES_TAB: TabItem = { id: "dependencies", label: "Dependencies" };
42
42
 
43
43
  /** Props for {@link AgentDetailView}. */
@@ -130,21 +130,21 @@ export interface AgentDetailViewProps {
130
130
  */
131
131
  readonly onCreateInstanceClick?: () => void;
132
132
  /**
133
- * Builds the absolute public chat URL shown in the Share dialog.
133
+ * Builds the absolute public chat URL for shares in the Shares tab.
134
134
  * The host application owns URL construction — its configured public
135
135
  * origin may differ from the rendering origin (e.g. the desktop app).
136
- * When omitted, the dialog falls back to the relative
136
+ * When omitted, share links fall back to the relative
137
137
  * `/chat/<org>/<slug>` path.
138
138
  */
139
139
  readonly buildShareUrl?: (org: string, slug: string) => string;
140
140
  /**
141
- * The viewer's active organization slug. When it differs from the
142
- * agent's org and the agent is marketplace-public, the kebab gains a
143
- * "Create share link" action the viewer org's own share of this
144
- * agent (decision 013): its URL, billing, and credentials, against the
145
- * agent org's live blueprint. Omit to disable the cross-org entry
146
- * (the same-org Share action is unaffected it gates on agent
147
- * `can_edit`, not on this prop).
141
+ * The viewer's active organization slug, feeding the Shares and
142
+ * Instances tabs. It scopes both lists to this org's rows (a member
143
+ * of several orgs sees the current org context only), and a share
144
+ * created in the Shares tab lands in this org — its URL, billing,
145
+ * and credentials which for another org's marketplace-public agent
146
+ * is a **cross-org share** (decision 013). Omit to default to the
147
+ * agent's own org.
148
148
  */
149
149
  readonly viewerOrg?: string;
150
150
  /**
@@ -265,8 +265,8 @@ export function AgentDetailView({
265
265
  const builtInTabs = useMemo<readonly TabItem[]>(
266
266
  () =>
267
267
  noDeps
268
- ? [OVERVIEW_TAB, INSTANCES_TAB]
269
- : [OVERVIEW_TAB, INSTANCES_TAB, DEPENDENCIES_TAB],
268
+ ? [OVERVIEW_TAB, INSTANCES_TAB, SHARES_TAB]
269
+ : [OVERVIEW_TAB, INSTANCES_TAB, SHARES_TAB, DEPENDENCIES_TAB],
270
270
  [noDeps],
271
271
  );
272
272
 
@@ -328,27 +328,6 @@ export function AgentDetailView({
328
328
  : undefined,
329
329
  });
330
330
 
331
- // Share — the sibling consent to Manage access: visibility governs who
332
- // can read the blueprint; sharing governs who can chat with the running
333
- // agent (billed to the owning org). Same null-until-ready contract.
334
- const share = useShareAgent({
335
- agent,
336
- buildShareUrl,
337
- onSharingChanged: refetch,
338
- });
339
-
340
- // Cross-org "Create share link" (decision 013) — the marketplace entry
341
- // for another org's public agent. Mutually exclusive with the Share
342
- // action above by construction (same-org gates on can_edit; this one
343
- // requires viewerOrg != agent org), so both fold into the actions
344
- // array unconditionally.
345
- const externalShare = useCreateExternalShareLink({
346
- agent,
347
- viewerOrg: viewerOrg ?? "",
348
- buildShareUrl,
349
- onSharingChanged: refetch,
350
- });
351
-
352
331
  if (isLoading) return <LoadingSkeleton className={className} />;
353
332
  if (error)
354
333
  return <ErrorMessage error={error} retry={refetch} className={className} />;
@@ -387,15 +366,23 @@ export function AgentDetailView({
387
366
  <VisibilityBadge visibility={meta.visibility} />
388
367
  ) : undefined;
389
368
 
390
- // Share (or its cross-org sibling) precedes Manage access within the
391
- // "sharing" group.
392
- const injectedActions = [share.action, externalShare.action, access.action].filter(
369
+ // Share the sibling consent to Manage access: visibility governs who
370
+ // can read the blueprint; sharing governs who can chat with the running
371
+ // agent (billed to the org that owns each share). Pure navigation to
372
+ // the Shares tab, so it renders unconditionally (like the tab itself);
373
+ // all capability gating lives in the tab, next to its affordances.
374
+ const shareAction: DetailAction = {
375
+ id: "share",
376
+ label: "Share",
377
+ group: "sharing",
378
+ onAction: () => effectiveOnTabChange(SHARES_TAB.id),
379
+ };
380
+
381
+ // Share precedes Manage access within the "sharing" group.
382
+ const injectedActions = [shareAction, access.action].filter(
393
383
  (a): a is NonNullable<typeof a> => a != null,
394
384
  );
395
- const mergedActions =
396
- injectedActions.length > 0
397
- ? [...(actions ?? []), ...injectedActions]
398
- : actions;
385
+ const mergedActions = [...(actions ?? []), ...injectedActions];
399
386
 
400
387
  let tabContent: React.ReactNode;
401
388
  if (activeAdditionalTab) {
@@ -406,6 +393,7 @@ export function AgentDetailView({
406
393
  agentId={meta?.id ?? ""}
407
394
  defaultInstanceId={agent.status?.defaultInstanceId}
408
395
  org={org}
396
+ viewerOrg={viewerOrg}
409
397
  onCreateClick={onCreateInstanceClick}
410
398
  onInstanceClick={onInstanceClick}
411
399
  onStartSessionClick={onInstanceStartSessionClick}
@@ -413,6 +401,14 @@ export function AgentDetailView({
413
401
  refreshKey={instancesRefreshKey}
414
402
  />
415
403
  );
404
+ } else if (effectiveActiveTab === "shares") {
405
+ tabContent = (
406
+ <AgentShareList
407
+ agent={agent}
408
+ viewerOrg={viewerOrg}
409
+ buildShareUrl={buildShareUrl}
410
+ />
411
+ );
416
412
  } else if (effectiveActiveTab === "dependencies" && tree) {
417
413
  tabContent = (
418
414
  <DependencyGraph
@@ -451,8 +447,6 @@ export function AgentDetailView({
451
447
  {tabContent}
452
448
  </ResourceDetailShell>
453
449
  {access.dialog}
454
- {share.dialog}
455
- {externalShare.dialog}
456
450
  </>
457
451
  );
458
452
  }
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "@stigmer/theme";
3
+ import { EmptyState } from "../empty-state/EmptyState.js";
4
4
 
5
5
  /** Props for {@link AgentInstanceEmptyState}. */
6
6
  export interface AgentInstanceEmptyStateProps {
7
- /** Called when the user clicks "Create Instance". */
7
+ /** Called when the user clicks "Create instance". */
8
8
  readonly onCreateClick?: () => void;
9
9
  /** Additional CSS class names. */
10
10
  readonly className?: string;
@@ -17,62 +17,43 @@ export interface AgentInstanceEmptyStateProps {
17
17
  * the platform-managed default instance (an implementation detail). For
18
18
  * agents, an instance is a reusable, environment-bound deployment you can
19
19
  * start sessions against — and share with your team.
20
+ *
21
+ * A thin composition over the shared {@link EmptyState} primitive: only the
22
+ * icon and copy are specific to agent instances.
20
23
  */
21
24
  export function AgentInstanceEmptyState({
22
25
  onCreateClick,
23
26
  className,
24
27
  }: AgentInstanceEmptyStateProps) {
25
28
  return (
26
- <div
27
- className={cn(
28
- "flex flex-col items-center justify-center py-12 px-6 text-center",
29
- className,
30
- )}
31
- >
32
- <div className="mb-4 rounded-full bg-muted p-3">
33
- <LayersIcon />
34
- </div>
35
-
36
- <h3 className="text-sm font-semibold text-foreground mb-1">
37
- No instances yet
38
- </h3>
39
-
40
- <p className="text-sm text-muted-foreground max-w-sm mb-6">
41
- Create instances to start sessions with this agent using different
42
- credentials and settings. Each instance binds environments (secrets,
43
- configuration) and can be shared with your team independently.
44
- </p>
45
-
46
- {onCreateClick && (
47
- <button
48
- type="button"
49
- onClick={onCreateClick}
50
- className={cn(
51
- "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5",
52
- "text-sm font-medium",
53
- "bg-primary text-primary-foreground",
54
- "hover:bg-primary/90",
55
- "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
56
- )}
57
- >
58
- <PlusIcon />
59
- Create Instance
60
- </button>
61
- )}
62
- </div>
29
+ <EmptyState
30
+ variant="first-use"
31
+ icon={<LayersIcon />}
32
+ title="No instances yet"
33
+ description={
34
+ "Create instances to start sessions with this agent using different " +
35
+ "credentials and settings. Each instance binds environments (secrets, " +
36
+ "configuration) and can be shared with your team independently."
37
+ }
38
+ action={
39
+ onCreateClick
40
+ ? { label: "Create instance", onClick: onCreateClick, icon: <PlusIcon /> }
41
+ : undefined
42
+ }
43
+ className={className}
44
+ />
63
45
  );
64
46
  }
65
47
 
66
48
  function LayersIcon() {
67
49
  return (
68
- <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
50
+ <svg width="24" height="24" viewBox="0 0 20 20" fill="none" aria-hidden="true">
69
51
  <path
70
52
  d="M10 2L2 6l8 4 8-4-8-4zM2 10l8 4 8-4M2 14l8 4 8-4"
71
53
  stroke="currentColor"
72
54
  strokeWidth="1.5"
73
55
  strokeLinecap="round"
74
56
  strokeLinejoin="round"
75
- className="text-muted-foreground"
76
57
  />
77
58
  </svg>
78
59
  );
@@ -80,8 +61,8 @@ function LayersIcon() {
80
61
 
81
62
  function PlusIcon() {
82
63
  return (
83
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
84
- <path d="M7 2v10M2 7h10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
64
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
65
+ <path d="M6 2v8M2 6h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
85
66
  </svg>
86
67
  );
87
68
  }
@@ -5,6 +5,7 @@ import { cn } from "@stigmer/theme";
5
5
  import type { AgentInstance } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
6
6
  import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
7
7
  import { useAgentInstances } from "./useAgentInstances.js";
8
+ import { Button } from "../button/Button.js";
8
9
  import { useEnvironmentList } from "../environment/useEnvironmentList.js";
9
10
  import { ResourceVisibilityControl } from "../library/ResourceVisibilityControl.js";
10
11
  import { PermissionGate } from "../iam-policy/PermissionGate.js";
@@ -19,8 +20,19 @@ export interface AgentInstanceListProps {
19
20
  readonly agentId: string;
20
21
  /** The default instance ID (from agent.status.defaultInstanceId) — filtered out of the list. */
21
22
  readonly defaultInstanceId?: string;
22
- /** Organization slug (needed for environment resolution and create flow). */
23
+ /**
24
+ * The AGENT's organization slug (needed for environment resolution
25
+ * and the create flow). Not the list scope — that is `viewerOrg`,
26
+ * which differs from `org` when viewing another org's agent.
27
+ */
23
28
  readonly org: string;
29
+ /**
30
+ * The viewer's active organization slug. Scopes the list to this
31
+ * org's instances of the agent, so a member of several orgs sees
32
+ * exactly the current org context's instances. Omit to default to
33
+ * the agent's own org.
34
+ */
35
+ readonly viewerOrg?: string;
24
36
  /** Called when the user wants to create a new instance. */
25
37
  readonly onCreateClick?: () => void;
26
38
  /** Called when the user clicks an instance row (opens detail). */
@@ -53,6 +65,7 @@ export function AgentInstanceList({
53
65
  agentId,
54
66
  defaultInstanceId,
55
67
  org,
68
+ viewerOrg,
56
69
  onCreateClick,
57
70
  onInstanceClick,
58
71
  onStartSessionClick,
@@ -60,7 +73,13 @@ export function AgentInstanceList({
60
73
  refreshKey,
61
74
  className,
62
75
  }: AgentInstanceListProps) {
63
- const { instances, isLoading, error, refetch } = useAgentInstances(agentId);
76
+ // Scope the list to the org whose context the viewer is in; when the
77
+ // host passes no viewerOrg the scope falls back to the agent's own org
78
+ // (the same-org owner flow).
79
+ const { instances, isLoading, error, refetch } = useAgentInstances(
80
+ agentId,
81
+ viewerOrg || org,
82
+ );
64
83
  const { environments } = useEnvironmentList(org);
65
84
 
66
85
  // Refetch when refreshKey changes (signals external mutation like create/delete)
@@ -108,27 +127,21 @@ export function AgentInstanceList({
108
127
  {userInstances.length} {userInstances.length === 1 ? "instance" : "instances"}
109
128
  </h3>
110
129
  {onCreateClick && (
111
- <button
112
- type="button"
130
+ <Button
131
+ variant="outline"
132
+ size="xs"
133
+ icon={<PlusIcon />}
113
134
  onClick={onCreateClick}
114
- className={cn(
115
- "inline-flex items-center gap-1 rounded-md px-2.5 py-1",
116
- "text-xs font-medium",
117
- "border border-border text-foreground",
118
- "hover:bg-accent-hover",
119
- "focus:outline-none focus:ring-2 focus:ring-ring",
120
- )}
121
135
  >
122
- <PlusIcon />
123
- Create
124
- </button>
136
+ Create instance
137
+ </Button>
125
138
  )}
126
139
  </div>
127
140
 
128
141
  <div className="overflow-hidden rounded-lg border border-border">
129
142
  <table className="w-full text-sm">
130
143
  <thead>
131
- <tr className="border-b border-border bg-muted/50">
144
+ <tr className="border-b border-border bg-muted-subtle">
132
145
  <th className="px-4 py-2 text-left font-medium text-muted-foreground">Name</th>
133
146
  <th className="px-4 py-2 text-left font-medium text-muted-foreground">Environments</th>
134
147
  <th className="px-4 py-2 text-left font-medium text-muted-foreground">Visibility</th>
@@ -289,7 +302,7 @@ function LoadingSkeleton() {
289
302
  return (
290
303
  <div className="space-y-2 py-4">
291
304
  {[1, 2, 3].map((i) => (
292
- <div key={i} className="h-12 rounded-md bg-muted/50 animate-pulse" />
305
+ <div key={i} className="h-12 animate-pulse rounded-md bg-muted-faint" />
293
306
  ))}
294
307
  </div>
295
308
  );
@@ -200,6 +200,25 @@ describe("useAgentInstances", () => {
200
200
  expect(result.current.instances).toHaveLength(2);
201
201
  expect(result.current.instances[0]?.metadata?.id).toBe("ain-1");
202
202
  expect(mockGetByAgent).toHaveBeenCalledTimes(1);
203
+ expect(mockGetByAgent).toHaveBeenCalledWith(
204
+ expect.objectContaining({ agentId: "agent-001", org: "" }),
205
+ );
206
+ });
207
+
208
+ it("threads the org scope into the request", async () => {
209
+ // The org scope belongs in the RPC, never client-side filtering: the
210
+ // server narrows a multi-org member's view to one org's instances.
211
+ mockGetByAgent.mockResolvedValueOnce({ items: [], totalCount: 0 });
212
+
213
+ const { result } = renderHook(
214
+ () => useAgentInstances("agent-001", "acme"),
215
+ { wrapper: createWrapper(makeMockClient()) },
216
+ );
217
+
218
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
219
+ expect(mockGetByAgent).toHaveBeenCalledWith(
220
+ expect.objectContaining({ agentId: "agent-001", org: "acme" }),
221
+ );
203
222
  });
204
223
 
205
224
  it("does not fetch when agentId is null", async () => {
@@ -21,12 +21,19 @@ export interface UseAgentInstancesReturn {
21
21
  }
22
22
 
23
23
  /**
24
- * Data hook that fetches all instances for a specific agent.
24
+ * Data hook that fetches instances for a specific agent.
25
25
  *
26
26
  * Wraps `stigmer.agentInstance.getByAgent()` with loading and error
27
27
  * state management. Used on the Agent detail page "Instances" tab to
28
28
  * show environment-bound deployments of the agent blueprint.
29
29
  *
30
+ * Pass `org` to scope the list to one organization's instances — the
31
+ * org-context view a console tab needs. Permissions alone cannot provide
32
+ * this scope: a member of several orgs can view all of their orgs'
33
+ * instances of the agent, so the unscoped list merges them. The server
34
+ * applies the scope (it only ever narrows the permission-bounded view);
35
+ * omit `org` for the full permission-bounded list.
36
+ *
30
37
  * Pass `null` as `agentId` to skip fetching (stable no-op).
31
38
  *
32
39
  * Note: the `getByAgent` RPC returns an `AgentInstanceList` whose
@@ -35,11 +42,12 @@ export interface UseAgentInstancesReturn {
35
42
  *
36
43
  * @example
37
44
  * ```tsx
38
- * const { instances, isLoading } = useAgentInstances(agent.metadata?.id);
45
+ * const { instances, isLoading } = useAgentInstances(agent.metadata?.id, viewerOrg);
39
46
  * ```
40
47
  */
41
48
  export function useAgentInstances(
42
49
  agentId: string | null | undefined,
50
+ org?: string,
43
51
  ): UseAgentInstancesReturn {
44
52
  const stigmer = useStigmer();
45
53
 
@@ -48,6 +56,7 @@ export function useAgentInstances(
48
56
  const resp = await stigmer.agentInstance.getByAgent(
49
57
  create(GetAgentInstancesByAgentRequestSchema, {
50
58
  agentId,
59
+ org: org ?? "",
51
60
  }),
52
61
  );
53
62
  return resp.items ? [...resp.items] : [];
@@ -60,7 +69,7 @@ export function useAgentInstances(
60
69
  isRefetching,
61
70
  error,
62
71
  refetch,
63
- } = useFetch<readonly AgentInstance[]>(fetchFn, [agentId, stigmer], []);
72
+ } = useFetch<readonly AgentInstance[]>(fetchFn, [agentId, org, stigmer], []);
64
73
 
65
74
  return { instances, isLoading, isRefetching, error, refetch };
66
75
  }
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ import type { ButtonHTMLAttributes, ReactNode } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+
6
+ /**
7
+ * Visual weight of a {@link Button}, from most to least prominent.
8
+ *
9
+ * - `primary` — filled call to action (one per view: empty-state CTAs,
10
+ * dialog confirms).
11
+ * - `outline` — bordered pill for secondary actions that share a row
12
+ * with content (list-header actions like "Create share").
13
+ * - `ghost` — borderless, hover-surfaced; for repeated inline actions
14
+ * (table row actions) where borders would add noise.
15
+ * - `destructive` — filled danger action (dialog confirms for deletes).
16
+ * For inline destructive row actions prefer `ghost` with destructive
17
+ * text styling via `className`.
18
+ */
19
+ export type ButtonVariant = "primary" | "outline" | "ghost" | "destructive";
20
+
21
+ /**
22
+ * Size of a {@link Button}.
23
+ *
24
+ * - `xs` — compact pill (list headers, table rows).
25
+ * - `sm` — standard (empty-state CTAs, dialog actions).
26
+ */
27
+ export type ButtonSize = "xs" | "sm";
28
+
29
+ /** Props for {@link Button}. Extends the native button attributes. */
30
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
31
+ /** Visual weight. @default "primary" */
32
+ readonly variant?: ButtonVariant;
33
+ /** Size. @default "sm" */
34
+ readonly size?: ButtonSize;
35
+ /** Optional leading icon, rendered before the label. */
36
+ readonly icon?: ReactNode;
37
+ }
38
+
39
+ // One source of truth for each tier's classes: every mapping below is the
40
+ // established idiom already used across the SDK (list-header pills,
41
+ // EmptyState CTAs, ConfirmDialog actions) — collected here so surfaces
42
+ // can stop hand-rolling drifting copies of the same button.
43
+ const VARIANT_CLASSES: Record<ButtonVariant, string> = {
44
+ primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
45
+ outline: "border border-border text-foreground hover:bg-accent-hover",
46
+ ghost: "text-foreground hover:bg-accent-hover",
47
+ destructive:
48
+ "bg-destructive text-destructive-foreground hover:bg-destructive-hover",
49
+ };
50
+
51
+ const SIZE_CLASSES: Record<ButtonSize, string> = {
52
+ xs: "px-2.5 py-1 text-xs",
53
+ sm: "px-3 py-1.5 text-xs",
54
+ };
55
+
56
+ /**
57
+ * The SDK's shared button primitive.
58
+ *
59
+ * A thin, token-compliant wrapper over the native `<button>`: variants
60
+ * and sizes cover the console's established action tiers, everything
61
+ * else (handlers, ARIA, `disabled`, `form`) passes through natively.
62
+ * `type` defaults to `"button"` so a button inside a form never submits
63
+ * it accidentally.
64
+ *
65
+ * All visual properties flow through `--stgm-*` design tokens (DD-005);
66
+ * hover states use dedicated hover tokens, never opacity modifiers.
67
+ *
68
+ * This is an SDK component (DD-001) — embeddable by platform builders.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * <Button variant="outline" size="xs" icon={<PlusIcon />} onClick={onCreate}>
73
+ * Create share
74
+ * </Button>
75
+ * ```
76
+ */
77
+ export function Button({
78
+ variant = "primary",
79
+ size = "sm",
80
+ icon,
81
+ type = "button",
82
+ className,
83
+ children,
84
+ ...rest
85
+ }: ButtonProps) {
86
+ return (
87
+ <button
88
+ type={type}
89
+ className={cn(
90
+ "inline-flex items-center gap-1 rounded-md font-medium transition-colors",
91
+ "motion-reduce:transition-none",
92
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
93
+ "disabled:pointer-events-none disabled:opacity-50",
94
+ SIZE_CLASSES[size],
95
+ VARIANT_CLASSES[variant],
96
+ className,
97
+ )}
98
+ {...rest}
99
+ >
100
+ {icon}
101
+ {children}
102
+ </button>
103
+ );
104
+ }