@stigmer/react 3.4.1 → 3.5.0

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 (93) hide show
  1. package/agent/AgentDetailView.d.ts +14 -4
  2. package/agent/AgentDetailView.d.ts.map +1 -1
  3. package/agent/AgentDetailView.js +73 -11
  4. package/agent/AgentDetailView.js.map +1 -1
  5. package/agent/internal/agentToInput.d.ts +3 -0
  6. package/agent/internal/agentToInput.d.ts.map +1 -1
  7. package/agent/internal/agentToInput.js +21 -1
  8. package/agent/internal/agentToInput.js.map +1 -1
  9. package/api-key/ApiKeyListPanel.d.ts +8 -1
  10. package/api-key/ApiKeyListPanel.d.ts.map +1 -1
  11. package/api-key/ApiKeyListPanel.js +9 -7
  12. package/api-key/ApiKeyListPanel.js.map +1 -1
  13. package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
  14. package/dependency-graph/DependencyTreeNode.js +8 -0
  15. package/dependency-graph/DependencyTreeNode.js.map +1 -1
  16. package/dependency-graph/types.d.ts +20 -8
  17. package/dependency-graph/types.d.ts.map +1 -1
  18. package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
  19. package/dependency-graph/useDependencyGraph.js +21 -1
  20. package/dependency-graph/useDependencyGraph.js.map +1 -1
  21. package/index.d.ts +2 -0
  22. package/index.d.ts.map +1 -1
  23. package/index.js +4 -0
  24. package/index.js.map +1 -1
  25. package/internal/scroll-area.d.ts +4 -0
  26. package/internal/scroll-area.d.ts.map +1 -0
  27. package/internal/scroll-area.js +15 -0
  28. package/internal/scroll-area.js.map +1 -0
  29. package/internal/tooltip.d.ts +7 -0
  30. package/internal/tooltip.d.ts.map +1 -0
  31. package/internal/tooltip.js +31 -0
  32. package/internal/tooltip.js.map +1 -0
  33. package/mcp-server/StdioSandboxNotice.d.ts +12 -11
  34. package/mcp-server/StdioSandboxNotice.d.ts.map +1 -1
  35. package/mcp-server/StdioSandboxNotice.js +13 -12
  36. package/mcp-server/StdioSandboxNotice.js.map +1 -1
  37. package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
  38. package/mcp-server/steps/IdentityTransportStep.js +1 -1
  39. package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
  40. package/package.json +4 -4
  41. package/resource-creation/templates/mcp-server-templates.d.ts.map +1 -1
  42. package/resource-creation/templates/mcp-server-templates.js +17 -14
  43. package/resource-creation/templates/mcp-server-templates.js.map +1 -1
  44. package/sidebar/SettingsSidebar.d.ts +62 -0
  45. package/sidebar/SettingsSidebar.d.ts.map +1 -0
  46. package/sidebar/SettingsSidebar.js +60 -0
  47. package/sidebar/SettingsSidebar.js.map +1 -0
  48. package/sidebar/WorkspaceSidebar.d.ts +87 -0
  49. package/sidebar/WorkspaceSidebar.d.ts.map +1 -0
  50. package/sidebar/WorkspaceSidebar.js +95 -0
  51. package/sidebar/WorkspaceSidebar.js.map +1 -0
  52. package/sidebar/chrome.d.ts +48 -0
  53. package/sidebar/chrome.d.ts.map +1 -0
  54. package/sidebar/chrome.js +32 -0
  55. package/sidebar/chrome.js.map +1 -0
  56. package/sidebar/index.d.ts +6 -0
  57. package/sidebar/index.d.ts.map +1 -0
  58. package/sidebar/index.js +9 -0
  59. package/sidebar/index.js.map +1 -0
  60. package/sidebar/types.d.ts +54 -0
  61. package/sidebar/types.d.ts.map +1 -0
  62. package/sidebar/types.js +2 -0
  63. package/sidebar/types.js.map +1 -0
  64. package/src/agent/AgentDetailView.tsx +198 -11
  65. package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +166 -0
  66. package/src/agent/internal/__tests__/agentToInput.test.ts +154 -0
  67. package/src/agent/internal/agentToInput.ts +25 -7
  68. package/src/api-key/ApiKeyListPanel.tsx +17 -4
  69. package/src/dependency-graph/DependencyTreeNode.tsx +8 -0
  70. package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +108 -0
  71. package/src/dependency-graph/types.ts +20 -8
  72. package/src/dependency-graph/useDependencyGraph.ts +23 -1
  73. package/src/index.ts +13 -0
  74. package/src/internal/scroll-area.tsx +37 -0
  75. package/src/internal/tooltip.tsx +66 -0
  76. package/src/mcp-server/StdioSandboxNotice.tsx +16 -14
  77. package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +3 -2
  78. package/src/mcp-server/steps/IdentityTransportStep.tsx +7 -0
  79. package/src/resource-creation/templates/mcp-server-templates.ts +17 -14
  80. package/src/sidebar/SettingsSidebar.tsx +173 -0
  81. package/src/sidebar/WorkspaceSidebar.tsx +386 -0
  82. package/src/sidebar/__tests__/SettingsSidebar.test.tsx +107 -0
  83. package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +236 -0
  84. package/src/sidebar/chrome.tsx +129 -0
  85. package/src/sidebar/index.ts +16 -0
  86. package/src/sidebar/types.ts +55 -0
  87. package/src/test/__tests__/samples.test.ts +2 -0
  88. package/src/test/samples.ts +48 -0
  89. package/styles.css +1 -1
  90. package/test/samples.d.ts +21 -0
  91. package/test/samples.d.ts.map +1 -1
  92. package/test/samples.js +32 -0
  93. package/test/samples.js.map +1 -1
@@ -0,0 +1,48 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Organization } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/api_pb";
3
+ /** Props shared by both sidebar variants' outer chrome. */
4
+ export interface SidebarChromeProps {
5
+ /** Accessible name for the `<nav>` landmark. */
6
+ readonly ariaLabel: string;
7
+ /**
8
+ * Whether the sidebar is currently expanded, reflected as
9
+ * `aria-expanded` on the collapse toggle. @default true
10
+ */
11
+ readonly isOpen?: boolean;
12
+ /**
13
+ * Called when the collapse toggle is pressed. The sidebar renders the
14
+ * toggle either way (it is part of the depicted chrome); hosts that
15
+ * cannot collapse (fixed embeds) simply omit the handler.
16
+ */
17
+ readonly onCollapse?: () => void;
18
+ /** Passed through to {@link OrgSwitcher}. */
19
+ readonly onOrgChanged?: (org: Organization) => void;
20
+ /**
21
+ * Footer content — typically the host app's `UserMenu` wrapper, which
22
+ * bridges auth, theming, and routing. A slot rather than a built-in
23
+ * because every host wires those systems differently (DD-004).
24
+ */
25
+ readonly footer: ReactNode;
26
+ readonly children: ReactNode;
27
+ }
28
+ /**
29
+ * Outer sidebar frame: `<nav>` container, top row, content, footer band.
30
+ *
31
+ * The `id="sidebar"` / `aria-controls` pairing matches the console's DOM
32
+ * contract (the app shell's floating "Open sidebar" button targets it);
33
+ * only one sidebar renders at a time, so the id cannot collide.
34
+ */
35
+ export declare function SidebarChrome({ ariaLabel, isOpen, onCollapse, onOrgChanged, footer, children, }: SidebarChromeProps): import("react/jsx-runtime").JSX.Element;
36
+ /** Hairline separator between sidebar sections. */
37
+ export declare function SidebarSeparator(): import("react/jsx-runtime").JSX.Element;
38
+ /**
39
+ * The one nav-row class recipe both sidebars share: 14px medium labels,
40
+ * 16px icons (sized by callers), 8px/6px padding, rounded row highlight.
41
+ *
42
+ * `muted` renders the resting label in the muted sidebar tone — used by
43
+ * "Back to Sessions", which is an exit, not a destination.
44
+ */
45
+ export declare function navRowClassName(active: boolean, { muted }?: {
46
+ muted?: boolean;
47
+ }): string;
48
+ //# sourceMappingURL=chrome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../../src/sidebar/chrome.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2DAA2D,CAAC;AAc9F,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,MAAa,EACb,UAAU,EACV,YAAY,EACZ,MAAM,EACN,QAAQ,GACT,EAAE,kBAAkB,2CAsCpB;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,4CAM/B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,EACf,EAAE,KAAa,EAAE,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1C,MAAM,CAUR"}
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { PanelLeft } from "lucide-react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { OrgSwitcher } from "../organization/OrgSwitcher.js";
6
+ /**
7
+ * Outer sidebar frame: `<nav>` container, top row, content, footer band.
8
+ *
9
+ * The `id="sidebar"` / `aria-controls` pairing matches the console's DOM
10
+ * contract (the app shell's floating "Open sidebar" button targets it);
11
+ * only one sidebar renders at a time, so the id cannot collide.
12
+ */
13
+ export function SidebarChrome({ ariaLabel, isOpen = true, onCollapse, onOrgChanged, footer, children, }) {
14
+ return (_jsxs("nav", { id: "sidebar", "aria-label": ariaLabel, className: "bg-sidebar text-sidebar-foreground flex h-full flex-col", children: [_jsxs("div", { className: "flex flex-none items-center gap-1 px-2 py-2", children: [_jsx("button", { type: "button", onClick: onCollapse, "aria-expanded": isOpen, "aria-controls": "sidebar", "aria-label": "Collapse sidebar", className: cn("inline-flex size-7 shrink-0 items-center justify-center rounded-md", "text-sidebar-foreground transition-colors motion-reduce:transition-none", "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", "aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground", "focus-visible:ring-sidebar-ring focus-visible:ring-2 focus-visible:outline-none"), children: _jsx(PanelLeft, { className: "size-4" }) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(OrgSwitcher, { onOrgChanged: onOrgChanged }) })] }), children, _jsx("div", { className: "border-sidebar-border flex-none border-t px-3 py-2", children: footer })] }));
15
+ }
16
+ /** Hairline separator between sidebar sections. */
17
+ export function SidebarSeparator() {
18
+ return (_jsx("div", { className: "px-3 py-1", children: _jsx("div", { className: "bg-sidebar-border h-px" }) }));
19
+ }
20
+ /**
21
+ * The one nav-row class recipe both sidebars share: 14px medium labels,
22
+ * 16px icons (sized by callers), 8px/6px padding, rounded row highlight.
23
+ *
24
+ * `muted` renders the resting label in the muted sidebar tone — used by
25
+ * "Back to Sessions", which is an exit, not a destination.
26
+ */
27
+ export function navRowClassName(active, { muted = false } = {}) {
28
+ return cn("flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium transition-colors", active
29
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
30
+ : cn(muted ? "text-sidebar-muted-foreground" : "text-sidebar-foreground", "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"));
31
+ }
32
+ //# sourceMappingURL=chrome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome.js","sourceRoot":"","sources":["../../src/sidebar/chrome.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAEpC,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAuC7D;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,SAAS,EACT,MAAM,GAAG,IAAI,EACb,UAAU,EACV,YAAY,EACZ,MAAM,EACN,QAAQ,GACW;IACnB,OAAO,CACL,eACE,EAAE,EAAC,SAAS,gBACA,SAAS,EACrB,SAAS,EAAC,yDAAyD,aAGnE,eAAK,SAAS,EAAC,6CAA6C,aAC1D,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,UAAU,mBACJ,MAAM,mBACP,SAAS,gBACZ,kBAAkB,EAC7B,SAAS,EAAE,EAAE,CACX,oEAAoE,EACpE,yEAAyE,EACzE,8DAA8D,EAC9D,8EAA8E,EAC9E,iFAAiF,CAClF,YAED,KAAC,SAAS,IAAC,SAAS,EAAC,QAAQ,GAAG,GACzB,EACT,cAAK,SAAS,EAAC,gBAAgB,YAC7B,KAAC,WAAW,IAAC,YAAY,EAAE,YAAY,GAAI,GACvC,IACF,EAEL,QAAQ,EAGT,cAAK,SAAS,EAAC,oDAAoD,YAChE,MAAM,GACH,IACF,CACP,CAAC;AACJ,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CACL,cAAK,SAAS,EAAC,WAAW,YACxB,cAAK,SAAS,EAAC,wBAAwB,GAAG,GACtC,CACP,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAe,EACf,EAAE,KAAK,GAAG,KAAK,KAA0B,EAAE;IAE3C,OAAO,EAAE,CACP,sFAAsF,EACtF,MAAM;QACJ,CAAC,CAAC,kDAAkD;QACpD,CAAC,CAAC,EAAE,CACA,KAAK,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,yBAAyB,EACnE,8DAA8D,CAC/D,CACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { WorkspaceSidebar } from "./WorkspaceSidebar.js";
2
+ export type { WorkspaceSidebarProps, WorkspaceSidebarActivity, WorkspaceNavId, } from "./WorkspaceSidebar.js";
3
+ export { SettingsSidebar } from "./SettingsSidebar.js";
4
+ export type { SettingsSidebarProps } from "./SettingsSidebar.js";
5
+ export type { SidebarLinkRenderProps, RenderSidebarLink } from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sidebar/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EACV,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,YAAY,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ // Sidebar — the console's navigation chrome as shared SDK components.
2
+ //
3
+ // One source of truth for the sidebar the web console, desktop app, and
4
+ // documentation tours all render (stigmer/stigmer#317): hosts inject their
5
+ // router via `renderLink`, their data via props, and their user menu via
6
+ // the `footer` slot.
7
+ export { WorkspaceSidebar } from "./WorkspaceSidebar.js";
8
+ export { SettingsSidebar } from "./SettingsSidebar.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sidebar/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,yEAAyE;AACzE,qBAAqB;AAErB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAMzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { RecentActivityEntry } from "../activity/types.js";
3
+ /**
4
+ * Everything a sidebar hands to {@link RenderSidebarLink} to draw one
5
+ * interactive row.
6
+ *
7
+ * The sidebar owns the row's look (`className`, `children`) and state
8
+ * (`active`, `aria-current`); the consumer owns only the interactive
9
+ * element wrapping them. That split is what keeps one component serving
10
+ * three very different hosts: the web console returns `next/link`, the
11
+ * desktop app returns a `react-router` link, and documentation embeds
12
+ * return inert rows decorated with capture markers.
13
+ */
14
+ export interface SidebarLinkRenderProps {
15
+ /**
16
+ * Stable row identifier, for consumers that vary behavior per row:
17
+ * `"new-session"` / `"dashboard"` / `"library"` in the workspace
18
+ * sidebar, `"back-to-sessions"` or the settings route basename (e.g.
19
+ * `"api-keys"`) in the settings sidebar, and the entry id for recent
20
+ * activity rows.
21
+ */
22
+ readonly id: string;
23
+ /** Console route this row targets (e.g. `/library`, `/sessions/<id>`). */
24
+ readonly href: string;
25
+ /** Whether the row is the current location. */
26
+ readonly active: boolean;
27
+ /**
28
+ * Fully-styled row classes computed by the sidebar. Apply verbatim —
29
+ * the row's geometry and type scale are the product surface tours and
30
+ * embeds depict, so consumers must not restyle them.
31
+ */
32
+ readonly className: string;
33
+ /** `"page"` when active, for assistive tech. Spread onto the element. */
34
+ readonly "aria-current": "page" | undefined;
35
+ /** Row content (icon, label, timestamps). Render inside the element. */
36
+ readonly children: ReactNode;
37
+ /**
38
+ * The recent-activity entry behind this row. Present only for recents
39
+ * rows in the workspace sidebar — consumers use it to pick the right
40
+ * navigation flow (session viewer vs. execution viewer) without
41
+ * parsing `href`.
42
+ */
43
+ readonly entry?: RecentActivityEntry;
44
+ }
45
+ /**
46
+ * Renders one sidebar row as the consumer's interactive element.
47
+ *
48
+ * Must return a single element that spreads unknown props onto its
49
+ * underlying DOM node (all DOM elements, `next/link`, and `react-router`
50
+ * links qualify): the sidebar composes tooltip triggers around recents
51
+ * rows by cloning the returned element with merged event handlers.
52
+ */
53
+ export type RenderSidebarLink = (props: SidebarLinkRenderProps) => ReactElement;
54
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sidebar/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,sBAAsB,KAAK,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sidebar/types.ts"],"names":[],"mappings":""}
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
5
  import { timestampDate } from "@bufbuild/protobuf/wkt";
6
6
  import type {
7
+ DatastoreUsage,
7
8
  McpServerUsage,
8
9
  SubAgent,
9
10
  } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
@@ -14,7 +15,9 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
14
15
  import { useAgent } from "./useAgent.js";
15
16
  import { useUpdateAgent } from "./useUpdateAgent.js";
16
17
  import { agentToInput } from "./internal/agentToInput.js";
18
+ import { toError } from "../internal/toError.js";
17
19
  import { ErrorMessage } from "../error/ErrorMessage.js";
20
+ import { DatastoreIcon } from "../datastore/DatastoreDetailView.js";
18
21
  import { VisibilityBadge } from "../library/VisibilitySelector.js";
19
22
  import { useManageAccess } from "../access/useManageAccess.js";
20
23
  import { AgentShareList } from "../sharing/AgentShareList.js";
@@ -58,6 +61,13 @@ export interface AgentDetailViewProps {
58
61
  * Provides `org` and `slug` of the referenced skill.
59
62
  */
60
63
  readonly onSkillClick?: (ref: { org: string; slug: string }) => void;
64
+ /**
65
+ * Called when a datastore reference is clicked.
66
+ * Provides `org` and `slug` of the referenced datastore so the
67
+ * consumer can wire navigation. When the reference has no explicit
68
+ * org, the agent's own org is used as fallback.
69
+ */
70
+ readonly onDatastoreClick?: (ref: { org: string; slug: string }) => void;
61
71
  /**
62
72
  * Called once when the agent resource has been fetched successfully.
63
73
  * Provides the resource display name for use cases like breadcrumbs,
@@ -176,9 +186,9 @@ export interface AgentDetailViewProps {
176
186
  * Fetches the agent via {@link useAgent} internally and renders its
177
187
  * full configuration inside a {@link ResourceDetailShell}: a
178
188
  * standardized header with action bar, followed by structured content
179
- * sections (instructions, MCP server usages, skills, sub-agents, and
180
- * environment variables). Sections with no data are omitted entirely
181
- * — reducing visual noise per Nielsen heuristic #8.
189
+ * sections (instructions, MCP server usages, skills, datastores,
190
+ * sub-agents, and environment variables). Sections with no data are
191
+ * omitted entirely — reducing visual noise per Nielsen heuristic #8.
182
192
  *
183
193
  * The action bar transforms this from a read-only view into an
184
194
  * operational hub. Actions are provided by the consumer via
@@ -215,6 +225,7 @@ export function AgentDetailView({
215
225
  slug,
216
226
  onMcpServerClick,
217
227
  onSkillClick,
228
+ onDatastoreClick,
218
229
  onResourceLoad,
219
230
  primaryAction,
220
231
  actions,
@@ -236,12 +247,22 @@ export function AgentDetailView({
236
247
  const { agent, isLoading, error, refetch } = useAgent(org, slug);
237
248
  const { update, isUpdating } = useUpdateAgent();
238
249
 
250
+ // Last failed inline save, attributed to the field that was edited so
251
+ // only that section shows the message. The backend's message is the
252
+ // UX (DD-006): e.g. the datastore exposure guard refuses public +
253
+ // datastore_usages with an actionable FAILED_PRECONDITION message.
254
+ const [saveError, setSaveError] = useState<{
255
+ field: string;
256
+ message: string;
257
+ } | null>(null);
258
+
239
259
  const saveField = useCallback(
240
260
  async <K extends keyof import("@stigmer/sdk").AgentInput>(
241
261
  field: K,
242
262
  value: import("@stigmer/sdk").AgentInput[K],
243
263
  ): Promise<boolean> => {
244
264
  if (!agent) return false;
265
+ setSaveError(null);
245
266
  const input = agentToInput(agent);
246
267
  (input as unknown as Record<string, unknown>)[field] = value;
247
268
  try {
@@ -249,13 +270,16 @@ export function AgentDetailView({
249
270
  onResourceUpdated?.(updated);
250
271
  refetch();
251
272
  return true;
252
- } catch {
273
+ } catch (err) {
274
+ setSaveError({ field, message: toError(err).message });
253
275
  return false;
254
276
  }
255
277
  },
256
278
  [agent, update, onResourceUpdated, refetch],
257
279
  );
258
280
 
281
+ const clearSaveError = useCallback(() => setSaveError(null), []);
282
+
259
283
  const { tree, isEmpty: noDeps } = useDependencyGraph({
260
284
  agentName: agent?.metadata?.name || agent?.metadata?.slug || slug,
261
285
  agentOrg: agent?.metadata?.org || org,
@@ -290,9 +314,11 @@ export function AgentDetailView({
290
314
  onMcpServerClick?.(node.ref);
291
315
  } else if (node.kind === "skill") {
292
316
  onSkillClick?.(node.ref);
317
+ } else if (node.kind === "datastore") {
318
+ onDatastoreClick?.(node.ref);
293
319
  }
294
320
  },
295
- [onMcpServerClick, onSkillClick],
321
+ [onMcpServerClick, onSkillClick, onDatastoreClick],
296
322
  );
297
323
 
298
324
  const onResourceLoadRef = useRef(onResourceLoad);
@@ -430,9 +456,12 @@ export function AgentDetailView({
430
456
  description={spec?.description}
431
457
  onMcpServerClick={onMcpServerClick}
432
458
  onSkillClick={onSkillClick}
459
+ onDatastoreClick={onDatastoreClick}
433
460
  editable={editable}
434
461
  isSaving={isUpdating}
435
462
  saveField={saveField}
463
+ saveError={saveError}
464
+ clearSaveError={clearSaveError}
436
465
  />
437
466
  );
438
467
  }
@@ -467,22 +496,33 @@ function AgentOverview({
467
496
  description,
468
497
  onMcpServerClick,
469
498
  onSkillClick,
499
+ onDatastoreClick,
470
500
  editable,
471
501
  isSaving,
472
502
  saveField,
503
+ saveError,
504
+ clearSaveError,
473
505
  }: {
474
506
  readonly spec: NonNullable<ReturnType<typeof useAgent>["agent"]>["spec"];
475
507
  readonly agentOrg: string;
476
508
  readonly description?: string;
477
509
  readonly onMcpServerClick?: (ref: { org: string; slug: string }) => void;
478
510
  readonly onSkillClick?: (ref: { org: string; slug: string }) => void;
511
+ readonly onDatastoreClick?: (ref: { org: string; slug: string }) => void;
479
512
  readonly editable?: boolean;
480
513
  readonly isSaving?: boolean;
481
514
  readonly saveField?: <K extends keyof import("@stigmer/sdk").AgentInput>(
482
515
  field: K,
483
516
  value: import("@stigmer/sdk").AgentInput[K],
484
517
  ) => Promise<boolean>;
518
+ readonly saveError?: { field: string; message: string } | null;
519
+ readonly clearSaveError?: () => void;
485
520
  }) {
521
+ // The failed save's message, shown only under the section that owns
522
+ // the field — a rejection of a datastore edit must not appear under
523
+ // the skills editor.
524
+ const errorFor = (field: keyof import("@stigmer/sdk").AgentInput) =>
525
+ saveError?.field === field ? saveError.message : undefined;
486
526
  const handleInstructionsSave = useCallback(
487
527
  async (v: string) => saveField?.("instructions", v || undefined) ?? false,
488
528
  [saveField],
@@ -508,6 +548,15 @@ function AgentOverview({
508
548
  [saveField],
509
549
  );
510
550
 
551
+ const handleDatastoresSave = useCallback(
552
+ async (refs: ResourceRefRow[]) =>
553
+ saveField?.(
554
+ "datastoreUsages",
555
+ refs.map((r) => ({ datastoreRef: { org: r.org, slug: r.slug } })),
556
+ ) ?? false,
557
+ [saveField],
558
+ );
559
+
511
560
  const handleEnvSave = useCallback(
512
561
  async (rows: KeyValueRow[]) => {
513
562
  const env: Record<string, { isSecret?: boolean; description?: string; optional?: boolean }> = {};
@@ -551,6 +600,19 @@ function AgentOverview({
551
600
  [spec?.skillRefs, agentOrg],
552
601
  );
553
602
 
603
+ const datastoreRefRows: ResourceRefRow[] = useMemo(
604
+ () =>
605
+ (spec?.datastoreUsages ?? []).map((usage) => ({
606
+ org: usage.datastoreRef?.org || agentOrg,
607
+ slug: usage.datastoreRef?.slug ?? "",
608
+ label:
609
+ usage.datastoreRef?.org && usage.datastoreRef.org !== agentOrg
610
+ ? `${usage.datastoreRef.org}/${usage.datastoreRef.slug}`
611
+ : usage.datastoreRef?.slug ?? "",
612
+ })),
613
+ [spec?.datastoreUsages, agentOrg],
614
+ );
615
+
554
616
  const envRows: KeyValueRow[] = useMemo(
555
617
  () =>
556
618
  Object.entries(spec?.env ?? {})
@@ -569,13 +631,46 @@ function AgentOverview({
569
631
  const showInstructions = editable || !!spec?.instructions;
570
632
  const showMcpServers = editable || (spec && spec.mcpServerUsages.length > 0);
571
633
  const showSkills = editable || (spec && spec.skillRefs.length > 0);
634
+ const showDatastores = editable || (spec && spec.datastoreUsages.length > 0);
572
635
  const showSubAgents = editable || (spec && spec.subAgents.length > 0);
573
636
  const showEnv = editable || (spec?.env && Object.keys(spec.env).length > 0);
574
637
 
575
638
  const [mcpEditing, setMcpEditing] = useState(false);
576
639
  const [skillsEditing, setSkillsEditing] = useState(false);
640
+ const [datastoresEditing, setDatastoresEditing] = useState(false);
577
641
  const [envEditing, setEnvEditing] = useState(false);
578
642
 
643
+ // Entering edit mode discards the previous attempt's error — the
644
+ // message describes a stale draft, not the one being composed.
645
+ const handleMcpEditingChange = useCallback(
646
+ (editing: boolean) => {
647
+ if (editing) clearSaveError?.();
648
+ setMcpEditing(editing);
649
+ },
650
+ [clearSaveError],
651
+ );
652
+ const handleSkillsEditingChange = useCallback(
653
+ (editing: boolean) => {
654
+ if (editing) clearSaveError?.();
655
+ setSkillsEditing(editing);
656
+ },
657
+ [clearSaveError],
658
+ );
659
+ const handleDatastoresEditingChange = useCallback(
660
+ (editing: boolean) => {
661
+ if (editing) clearSaveError?.();
662
+ setDatastoresEditing(editing);
663
+ },
664
+ [clearSaveError],
665
+ );
666
+ const handleEnvEditingChange = useCallback(
667
+ (editing: boolean) => {
668
+ if (editing) clearSaveError?.();
669
+ setEnvEditing(editing);
670
+ },
671
+ [clearSaveError],
672
+ );
673
+
579
674
  return (
580
675
  <div className="flex flex-col gap-6">
581
676
  {showDescription && (
@@ -586,6 +681,7 @@ function AgentOverview({
586
681
  value={spec?.description || ""}
587
682
  onSave={(v) => saveField?.("description", v || undefined) ?? Promise.resolve(false)}
588
683
  isSaving={isSaving}
684
+ error={errorFor("description")}
589
685
  placeholder="Add a description"
590
686
  minRows={2}
591
687
  />
@@ -608,6 +704,7 @@ function AgentOverview({
608
704
  value={spec?.instructions ?? ""}
609
705
  onSave={handleInstructionsSave}
610
706
  isSaving={isSaving}
707
+ error={errorFor("instructions")}
611
708
  placeholder="Add instructions for the agent"
612
709
  minRows={4}
613
710
  />
@@ -619,14 +716,15 @@ function AgentOverview({
619
716
  )}
620
717
 
621
718
  {showMcpServers && (
622
- <Section title="MCP Servers" count={spec?.mcpServerUsages.length} onEdit={editable ? () => setMcpEditing((v) => !v) : undefined}>
719
+ <Section title="MCP Servers" count={spec?.mcpServerUsages.length} onEdit={editable ? () => handleMcpEditingChange(!mcpEditing) : undefined}>
623
720
  {editable ? (
624
721
  <InlineEditResourceList
625
722
  value={mcpRefRows}
626
723
  onSave={handleMcpServersSave}
627
724
  isSaving={isSaving}
725
+ error={errorFor("mcpServerUsages")}
628
726
  editing={mcpEditing}
629
- onEditingChange={setMcpEditing}
727
+ onEditingChange={handleMcpEditingChange}
630
728
  onItemClick={onMcpServerClick ? (ref) => onMcpServerClick({ org: ref.org, slug: ref.slug }) : undefined}
631
729
  itemIcon={<McpServerIcon className="size-4" />}
632
730
  resourceLabel="MCP server"
@@ -643,14 +741,15 @@ function AgentOverview({
643
741
  )}
644
742
 
645
743
  {showSkills && (
646
- <Section title="Skills" count={spec?.skillRefs.length} onEdit={editable ? () => setSkillsEditing((v) => !v) : undefined}>
744
+ <Section title="Skills" count={spec?.skillRefs.length} onEdit={editable ? () => handleSkillsEditingChange(!skillsEditing) : undefined}>
647
745
  {editable ? (
648
746
  <InlineEditResourceList
649
747
  value={skillRefRows}
650
748
  onSave={handleSkillsSave}
651
749
  isSaving={isSaving}
750
+ error={errorFor("skillRefs")}
652
751
  editing={skillsEditing}
653
- onEditingChange={setSkillsEditing}
752
+ onEditingChange={handleSkillsEditingChange}
654
753
  onItemClick={onSkillClick ? (ref) => onSkillClick({ org: ref.org, slug: ref.slug }) : undefined}
655
754
  itemIcon={<SkillIcon className="size-4" />}
656
755
  resourceLabel="skill"
@@ -666,24 +765,51 @@ function AgentOverview({
666
765
  </Section>
667
766
  )}
668
767
 
768
+ {showDatastores && (
769
+ <Section title="Datastores" count={spec?.datastoreUsages.length} onEdit={editable ? () => handleDatastoresEditingChange(!datastoresEditing) : undefined}>
770
+ {editable ? (
771
+ <InlineEditResourceList
772
+ value={datastoreRefRows}
773
+ onSave={handleDatastoresSave}
774
+ isSaving={isSaving}
775
+ error={errorFor("datastoreUsages")}
776
+ editing={datastoresEditing}
777
+ onEditingChange={handleDatastoresEditingChange}
778
+ onItemClick={onDatastoreClick ? (ref) => onDatastoreClick({ org: ref.org, slug: ref.slug }) : undefined}
779
+ itemIcon={<DatastoreIcon className="size-4" />}
780
+ resourceLabel="datastore"
781
+ defaultOrg={agentOrg}
782
+ />
783
+ ) : (
784
+ <DatastoresContent
785
+ usages={spec?.datastoreUsages ?? []}
786
+ defaultOrg={agentOrg}
787
+ onDatastoreClick={onDatastoreClick}
788
+ />
789
+ )}
790
+ </Section>
791
+ )}
792
+
669
793
  {showSubAgents && (
670
794
  <SubAgentsSection
671
795
  subAgents={spec?.subAgents ?? []}
672
796
  editable={editable}
673
797
  isSaving={isSaving}
798
+ error={errorFor("subAgents")}
674
799
  onSave={(subs) => saveField?.("subAgents", subs.length > 0 ? subs : undefined) ?? Promise.resolve(false)}
675
800
  />
676
801
  )}
677
802
 
678
803
  {showEnv && (
679
- <Section title="Environment Variables" count={Object.keys(spec?.env ?? {}).length} onEdit={editable ? () => setEnvEditing((v) => !v) : undefined}>
804
+ <Section title="Environment Variables" count={Object.keys(spec?.env ?? {}).length} onEdit={editable ? () => handleEnvEditingChange(!envEditing) : undefined}>
680
805
  {editable ? (
681
806
  <InlineEditKeyValue
682
807
  value={envRows}
683
808
  onSave={handleEnvSave}
684
809
  isSaving={isSaving}
810
+ error={errorFor("env")}
685
811
  editing={envEditing}
686
- onEditingChange={setEnvEditing}
812
+ onEditingChange={handleEnvEditingChange}
687
813
  showSecretToggle
688
814
  showOptionalToggle
689
815
  showDescription
@@ -860,6 +986,59 @@ function SkillsContent({
860
986
  );
861
987
  }
862
988
 
989
+ function DatastoresContent({
990
+ usages,
991
+ defaultOrg,
992
+ onDatastoreClick,
993
+ }: {
994
+ readonly usages: readonly DatastoreUsage[];
995
+ readonly defaultOrg: string;
996
+ readonly onDatastoreClick?: (ref: { org: string; slug: string }) => void;
997
+ }) {
998
+ return (
999
+ <div className="flex flex-col">
1000
+ {usages.map((usage, index) => {
1001
+ const ref = usage.datastoreRef;
1002
+ if (!ref) return null;
1003
+
1004
+ const refOrg = ref.org || defaultOrg;
1005
+ const label =
1006
+ ref.org && ref.org !== defaultOrg
1007
+ ? `${ref.org}/${ref.slug}`
1008
+ : ref.slug;
1009
+
1010
+ const row = (
1011
+ <div className="flex items-center gap-3">
1012
+ <DatastoreIcon className="size-4 shrink-0 text-muted-foreground" />
1013
+ <span className="text-sm font-medium text-foreground">
1014
+ {label}
1015
+ </span>
1016
+ </div>
1017
+ );
1018
+
1019
+ return onDatastoreClick ? (
1020
+ <button
1021
+ key={ref.slug || index}
1022
+ type="button"
1023
+ onClick={() => onDatastoreClick({ org: refOrg, slug: ref.slug })}
1024
+ className={cn(
1025
+ "w-full rounded-md px-3 py-2 text-left transition-colors",
1026
+ "hover:bg-accent-hover",
1027
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
1028
+ )}
1029
+ >
1030
+ {row}
1031
+ </button>
1032
+ ) : (
1033
+ <div key={ref.slug || index} className="px-3 py-2">
1034
+ {row}
1035
+ </div>
1036
+ );
1037
+ })}
1038
+ </div>
1039
+ );
1040
+ }
1041
+
863
1042
  interface SubAgentDraft {
864
1043
  name: string;
865
1044
  description: string;
@@ -870,11 +1049,13 @@ function SubAgentsSection({
870
1049
  subAgents,
871
1050
  editable,
872
1051
  isSaving,
1052
+ error,
873
1053
  onSave,
874
1054
  }: {
875
1055
  readonly subAgents: readonly SubAgent[];
876
1056
  readonly editable?: boolean;
877
1057
  readonly isSaving?: boolean;
1058
+ readonly error?: string | null;
878
1059
  readonly onSave?: (subs: import("@stigmer/sdk").SubAgentInput[]) => Promise<boolean>;
879
1060
  }) {
880
1061
  const [expanded, setExpanded] = useState<Set<number>>(new Set());
@@ -1025,6 +1206,12 @@ function SubAgentsSection({
1025
1206
  </div>
1026
1207
  )}
1027
1208
 
1209
+ {error && (
1210
+ <p className="border-t border-border px-3 py-2 text-xs text-destructive" role="alert">
1211
+ {error}
1212
+ </p>
1213
+ )}
1214
+
1028
1215
  {editable && showAddForm && (
1029
1216
  <div className="border-t border-border p-3 space-y-2">
1030
1217
  <input