@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.
- package/agent/AgentDetailView.d.ts +14 -4
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +73 -11
- package/agent/AgentDetailView.js.map +1 -1
- package/agent/internal/agentToInput.d.ts +3 -0
- package/agent/internal/agentToInput.d.ts.map +1 -1
- package/agent/internal/agentToInput.js +21 -1
- package/agent/internal/agentToInput.js.map +1 -1
- package/api-key/ApiKeyListPanel.d.ts +8 -1
- package/api-key/ApiKeyListPanel.d.ts.map +1 -1
- package/api-key/ApiKeyListPanel.js +9 -7
- package/api-key/ApiKeyListPanel.js.map +1 -1
- package/dependency-graph/DependencyTreeNode.d.ts.map +1 -1
- package/dependency-graph/DependencyTreeNode.js +8 -0
- package/dependency-graph/DependencyTreeNode.js.map +1 -1
- package/dependency-graph/types.d.ts +20 -8
- package/dependency-graph/types.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.d.ts.map +1 -1
- package/dependency-graph/useDependencyGraph.js +21 -1
- package/dependency-graph/useDependencyGraph.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +4 -0
- package/index.js.map +1 -1
- package/internal/scroll-area.d.ts +4 -0
- package/internal/scroll-area.d.ts.map +1 -0
- package/internal/scroll-area.js +15 -0
- package/internal/scroll-area.js.map +1 -0
- package/internal/tooltip.d.ts +7 -0
- package/internal/tooltip.d.ts.map +1 -0
- package/internal/tooltip.js +31 -0
- package/internal/tooltip.js.map +1 -0
- package/mcp-server/StdioSandboxNotice.d.ts +12 -11
- package/mcp-server/StdioSandboxNotice.d.ts.map +1 -1
- package/mcp-server/StdioSandboxNotice.js +13 -12
- package/mcp-server/StdioSandboxNotice.js.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
- package/mcp-server/steps/IdentityTransportStep.js +1 -1
- package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
- package/package.json +4 -4
- package/resource-creation/templates/mcp-server-templates.d.ts.map +1 -1
- package/resource-creation/templates/mcp-server-templates.js +17 -14
- package/resource-creation/templates/mcp-server-templates.js.map +1 -1
- package/sidebar/SettingsSidebar.d.ts +62 -0
- package/sidebar/SettingsSidebar.d.ts.map +1 -0
- package/sidebar/SettingsSidebar.js +60 -0
- package/sidebar/SettingsSidebar.js.map +1 -0
- package/sidebar/WorkspaceSidebar.d.ts +87 -0
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -0
- package/sidebar/WorkspaceSidebar.js +95 -0
- package/sidebar/WorkspaceSidebar.js.map +1 -0
- package/sidebar/chrome.d.ts +48 -0
- package/sidebar/chrome.d.ts.map +1 -0
- package/sidebar/chrome.js +32 -0
- package/sidebar/chrome.js.map +1 -0
- package/sidebar/index.d.ts +6 -0
- package/sidebar/index.d.ts.map +1 -0
- package/sidebar/index.js +9 -0
- package/sidebar/index.js.map +1 -0
- package/sidebar/types.d.ts +54 -0
- package/sidebar/types.d.ts.map +1 -0
- package/sidebar/types.js +2 -0
- package/sidebar/types.js.map +1 -0
- package/src/agent/AgentDetailView.tsx +198 -11
- package/src/agent/__tests__/AgentDetailView.datastores.test.tsx +166 -0
- package/src/agent/internal/__tests__/agentToInput.test.ts +154 -0
- package/src/agent/internal/agentToInput.ts +25 -7
- package/src/api-key/ApiKeyListPanel.tsx +17 -4
- package/src/dependency-graph/DependencyTreeNode.tsx +8 -0
- package/src/dependency-graph/__tests__/useDependencyGraph.test.ts +108 -0
- package/src/dependency-graph/types.ts +20 -8
- package/src/dependency-graph/useDependencyGraph.ts +23 -1
- package/src/index.ts +13 -0
- package/src/internal/scroll-area.tsx +37 -0
- package/src/internal/tooltip.tsx +66 -0
- package/src/mcp-server/StdioSandboxNotice.tsx +16 -14
- package/src/mcp-server/__tests__/StdioSandboxNotice.test.tsx +3 -2
- package/src/mcp-server/steps/IdentityTransportStep.tsx +7 -0
- package/src/resource-creation/templates/mcp-server-templates.ts +17 -14
- package/src/sidebar/SettingsSidebar.tsx +173 -0
- package/src/sidebar/WorkspaceSidebar.tsx +386 -0
- package/src/sidebar/__tests__/SettingsSidebar.test.tsx +107 -0
- package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +236 -0
- package/src/sidebar/chrome.tsx +129 -0
- package/src/sidebar/index.ts +16 -0
- package/src/sidebar/types.ts +55 -0
- package/src/test/__tests__/samples.test.ts +2 -0
- package/src/test/samples.ts +48 -0
- package/styles.css +1 -1
- package/test/samples.d.ts +21 -0
- package/test/samples.d.ts.map +1 -1
- package/test/samples.js +32 -0
- 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"}
|
package/sidebar/index.js
ADDED
|
@@ -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"}
|
package/sidebar/types.js
ADDED
|
@@ -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,
|
|
180
|
-
* environment variables). Sections with no data are
|
|
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 ? () =>
|
|
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={
|
|
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 ? () =>
|
|
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={
|
|
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 ? () =>
|
|
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={
|
|
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
|