diff --git a/cmd/oap/internal/installcmd/clean.go b/cmd/oap/internal/installcmd/clean.go index b8f704ac..805e2300 100644 --- a/cmd/oap/internal/installcmd/clean.go +++ b/cmd/oap/internal/installcmd/clean.go @@ -82,6 +82,12 @@ var crGroupVersionResources = []schema.GroupVersionResource{ // against Kubernetes' own owner-reference garbage collection. {Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "subagentrequests"}, {Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "relationshipsources"}, + // AccessToken carries FinalizerAccessToken, cleared only by the + // operator's accesstoken controller (pkg/controllers/accesstoken) + // removing the token's SpiceDB tuples. The PublicEndpoint concern above + // applies: omitting it here would wedge teardown on a finalizer nothing + // is left running to clear. + {Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "accesstokens"}, } // cleanDefaultTimeout is `oap clean`'s own budget for a full teardown. Named diff --git a/cmd/oap/internal/setupmcpcmd/emit.go b/cmd/oap/internal/setupmcpcmd/emit.go new file mode 100644 index 00000000..2e3a12f3 --- /dev/null +++ b/cmd/oap/internal/setupmcpcmd/emit.go @@ -0,0 +1,117 @@ +package setupmcpcmd + +import ( + "encoding/json" + "fmt" + "strings" +) + +// mcpURL appends "/mcp" to serverURL, trimming any trailing slash first so +// the result never contains a doubled slash. Every emitter routes the URL it +// prints through this function, so "the emitted URL always ends in /mcp" is +// enforced in one place rather than re-derived per tool. +func mcpURL(serverURL string) string { + return strings.TrimRight(serverURL, "/") + "/mcp" +} + +// authHeaders returns the single Authorization header map for bearer, or nil +// when bearer is empty. nil (not an empty map) so callers can tag the field +// `omitempty` and have it vanish entirely from unauthenticated output, rather +// than rendering as `"headers": {}`. +func authHeaders(bearer string) map[string]string { + if bearer == "" { + return nil + } + return map[string]string{"Authorization": "Bearer " + bearer} +} + +// mcpServerEntry is the common shape both Claude Code's and Cursor's +// mcpServers map use. +type mcpServerEntry struct { + Type string `json:"type,omitempty"` + URL string `json:"url"` + Headers map[string]string `json:"headers,omitempty"` +} + +// emitClaudeCode renders the `claude mcp add` command for this server plus +// the equivalent mcpServers JSON snippet, for a user who'd rather hand-edit +// their config. bearer, when non-empty, is embedded verbatim (never +// truncated) as an Authorization header in both the --header flag and the +// JSON snippet. +func emitClaudeCode(serverURL, bearer string) string { + url := mcpURL(serverURL) + + cmd := fmt.Sprintf("claude mcp add --transport http oap %s", url) + if bearer != "" { + cmd += fmt.Sprintf(" --header %s", quoteShellArg(fmt.Sprintf("Authorization: Bearer %s", bearer))) + } + + snippet := struct { + MCPServers map[string]mcpServerEntry `json:"mcpServers"` + }{ + MCPServers: map[string]mcpServerEntry{ + "oap": { + Type: "http", + URL: url, + Headers: authHeaders(bearer), + }, + }, + } + b, err := json.MarshalIndent(snippet, "", " ") + if err != nil { + // snippet is a fixed shape of strings and maps of strings; it cannot fail + // to marshal. Panic rather than silently drop the JSON half of the output. + panic(fmt.Sprintf("setupmcpcmd: marshal claude-code snippet: %v", err)) + } + + return cmd + "\n\n" + string(b) + "\n" +} + +// emitCursor renders the .cursor/mcp.json snippet for this server. The whole +// output is valid JSON. +func emitCursor(serverURL, bearer string) string { + url := mcpURL(serverURL) + + snippet := struct { + MCPServers map[string]mcpServerEntry `json:"mcpServers"` + }{ + MCPServers: map[string]mcpServerEntry{ + "oap": { + URL: url, + Headers: authHeaders(bearer), + }, + }, + } + b, err := json.MarshalIndent(snippet, "", " ") + if err != nil { + panic(fmt.Sprintf("setupmcpcmd: marshal cursor snippet: %v", err)) + } + return string(b) + "\n" +} + +// emitGeneric renders a minimal {"url": ..., "headers": {...}} snippet for any +// MCP client that doesn't have a dedicated emitter. The whole output is valid +// JSON. +func emitGeneric(serverURL, bearer string) string { + snippet := struct { + URL string `json:"url"` + Headers map[string]string `json:"headers,omitempty"` + }{ + URL: mcpURL(serverURL), + Headers: authHeaders(bearer), + } + b, err := json.MarshalIndent(snippet, "", " ") + if err != nil { + panic(fmt.Sprintf("setupmcpcmd: marshal generic snippet: %v", err)) + } + return string(b) + "\n" +} + +// quoteShellArg wraps s in double quotes for display in a copy-pasteable +// shell command line. s here is always "Authorization: Bearer ", +// which contains no double quotes or backslashes, so a full shell-quoting +// library is not needed — this only ever has to survive display, not +// execution. +func quoteShellArg(s string) string { + return `"` + s + `"` +} diff --git a/cmd/oap/internal/setupmcpcmd/emit_test.go b/cmd/oap/internal/setupmcpcmd/emit_test.go new file mode 100644 index 00000000..feb41b4a --- /dev/null +++ b/cmd/oap/internal/setupmcpcmd/emit_test.go @@ -0,0 +1,162 @@ +package setupmcpcmd + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// longBearer is deliberately long and distinctive so a test can catch any +// truncation/elision (e.g. a "..." middle-out summary) that a naive display +// helper might apply. +const longBearer = "oap_mcp_access_token_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.signature-part-that-is-also-quite-long-1234567890" + +func TestEmitClaudeCode(t *testing.T) { + cases := []struct { + name string + bearer string + }{ + {name: "with bearer", bearer: longBearer}, + {name: "without bearer", bearer: ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out := emitClaudeCode("https://cluster.example.com", tc.bearer) + + assert.Contains(t, out, "https://cluster.example.com/mcp", "emitted URL must end in /mcp") + assert.Contains(t, out, "claude mcp add --transport http oap https://cluster.example.com/mcp") + + if tc.bearer != "" { + assert.Contains(t, out, tc.bearer, "bearer must appear verbatim, never truncated") + assert.Contains(t, out, `--header "Authorization: Bearer `+tc.bearer+`"`) + } else { + assert.NotContains(t, out, "--header") + assert.NotContains(t, out, "Authorization") + } + + // The command line is followed by a blank line and then a JSON + // snippet; extract and validate that part unmarshals cleanly. + idx := strings.Index(out, "{") + require.GreaterOrEqual(t, idx, 0, "expected a JSON snippet in the output") + jsonPart := out[idx:] + + var decoded struct { + MCPServers map[string]struct { + Type string `json:"type"` + URL string `json:"url"` + Headers map[string]string `json:"headers"` + } `json:"mcpServers"` + } + require.NoError(t, json.Unmarshal([]byte(jsonPart), &decoded), "JSON snippet must unmarshal cleanly") + + entry, ok := decoded.MCPServers["oap"] + require.True(t, ok, "expected an \"oap\" entry in mcpServers") + assert.Equal(t, "https://cluster.example.com/mcp", entry.URL) + assert.Equal(t, "http", entry.Type) + + if tc.bearer != "" { + require.NotNil(t, entry.Headers) + assert.Equal(t, "Bearer "+tc.bearer, entry.Headers["Authorization"]) + } else { + assert.Empty(t, entry.Headers) + } + }) + } +} + +func TestEmitCursor(t *testing.T) { + cases := []struct { + name string + bearer string + }{ + {name: "with bearer", bearer: longBearer}, + {name: "without bearer", bearer: ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out := emitCursor("https://cluster.example.com/", tc.bearer) + + assert.Contains(t, out, "https://cluster.example.com/mcp", "emitted URL must end in /mcp, even when serverURL had a trailing slash") + assert.NotContains(t, out, "//mcp", "trailing slash on serverURL must not produce a doubled slash") + + var decoded struct { + MCPServers map[string]struct { + URL string `json:"url"` + Headers map[string]string `json:"headers"` + } `json:"mcpServers"` + } + require.NoError(t, json.Unmarshal([]byte(out), &decoded), "entire output must be valid JSON") + + entry, ok := decoded.MCPServers["oap"] + require.True(t, ok, "expected an \"oap\" entry in mcpServers") + assert.Equal(t, "https://cluster.example.com/mcp", entry.URL) + + if tc.bearer != "" { + assert.Contains(t, out, tc.bearer, "bearer must appear verbatim, never truncated") + require.NotNil(t, entry.Headers) + assert.Equal(t, "Bearer "+tc.bearer, entry.Headers["Authorization"]) + } else { + assert.NotContains(t, out, "Authorization") + assert.Empty(t, entry.Headers) + } + }) + } +} + +func TestEmitGeneric(t *testing.T) { + cases := []struct { + name string + bearer string + }{ + {name: "with bearer", bearer: longBearer}, + {name: "without bearer", bearer: ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out := emitGeneric("https://cluster.example.com", tc.bearer) + + assert.Contains(t, out, "https://cluster.example.com/mcp") + + var decoded struct { + URL string `json:"url"` + Headers map[string]string `json:"headers"` + } + require.NoError(t, json.Unmarshal([]byte(out), &decoded), "entire output must be valid JSON") + assert.Equal(t, "https://cluster.example.com/mcp", decoded.URL) + + if tc.bearer != "" { + assert.Contains(t, out, tc.bearer, "bearer must appear verbatim, never truncated") + require.NotNil(t, decoded.Headers) + assert.Equal(t, "Bearer "+tc.bearer, decoded.Headers["Authorization"]) + } else { + assert.NotContains(t, out, "Authorization") + assert.Empty(t, decoded.Headers) + } + }) + } +} + +// TestEmit_AllToolsAppendMCPSuffix is a compact cross-tool sanity pass: every +// emitter, regardless of whether serverURL already ends in a slash, must +// route the URL through the same "/mcp" suffix with no doubled slash. +func TestEmit_AllToolsAppendMCPSuffix(t *testing.T) { + emitters := map[string]func(serverURL, bearer string) string{ + "claude-code": emitClaudeCode, + "cursor": emitCursor, + "generic": emitGeneric, + } + + for name, emit := range emitters { + t.Run(name, func(t *testing.T) { + out := emit("https://cluster.example.com", "") + assert.True(t, strings.Contains(out, "https://cluster.example.com/mcp"), "expected /mcp suffix in output") + assert.False(t, strings.Contains(out, "mcp/mcp"), "must not double-append /mcp") + }) + } +} diff --git a/cmd/oap/internal/setupmcpcmd/root.go b/cmd/oap/internal/setupmcpcmd/root.go new file mode 100644 index 00000000..4cf79ec3 --- /dev/null +++ b/cmd/oap/internal/setupmcpcmd/root.go @@ -0,0 +1,125 @@ +// Package setupmcpcmd implements `oap setup-mcp`: emitting a config snippet +// that points a local MCP-capable tool (Claude Code, Cursor, or a generic +// client) at this cluster's MCP server. +package setupmcpcmd + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/authzed/openagentprimitives/cmd/oap/internal/apcmd" + "github.com/authzed/openagentprimitives/cmd/oap/internal/clilogin" + "github.com/authzed/openagentprimitives/pkg/tools/mcp/oauth" +) + +// toolTarget bundles one --tool value's pure emitter with the human-readable +// description of where the emitted bearer token (if any) ends up living -- +// used both to pick the emitter and to word the --mint warning. +type toolTarget struct { + emit func(serverURL, bearer string) string + credLocation string + nextStep string +} + +var toolTargets = map[string]toolTarget{ + "claude-code": { + emit: emitClaudeCode, + credLocation: "claude-code's own MCP config (~/.claude.json, under mcpServers.oap.headers.Authorization)", + nextStep: "run the `claude mcp add` command above, or merge the JSON snippet into Claude Code's MCP config, then restart Claude Code", + }, + "cursor": { + emit: emitCursor, + credLocation: ".cursor/mcp.json, under mcpServers.oap.headers.Authorization", + nextStep: "merge the JSON snippet above into .cursor/mcp.json, then reload Cursor's MCP servers", + }, + "generic": { + emit: emitGeneric, + credLocation: "whatever config file you save this JSON into, under headers.Authorization", + nextStep: "add the JSON snippet above to your MCP client's server configuration", + }, +} + +const defaultTool = "claude-code" + +// NewCmd is the `oap setup-mcp` command. +func NewCmd(g *apcmd.Globals) *cobra.Command { + var serverFlag string + var toolFlag string + var mint bool + + cmd := &cobra.Command{ + Use: "setup-mcp", + Short: "Emit MCP client config pointing a local tool at this cluster's MCP server", + Long: `Resolve this cluster's MCP server URL -- from --server, or by discovering the +webd external-URL ConfigMap via the current kube context -- and print a config +snippet for a local MCP-capable tool (claude-code, cursor, or generic). + +By default (no --mint), no credential is embedded: OAuth-native MCP clients +run their own discovery/consent flow against the server directly, and consent +there defaults to read-only. Pass --mint to run that flow here instead -- +dogfooding this cluster's own OAuth authorization server -- and embed the +resulting bearer token in the emitted config, for tools that do not speak MCP +OAuth themselves.`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + target, ok := toolTargets[toolFlag] + if !ok { + return fmt.Errorf("unknown --tool %q; valid values: claude-code, cursor, generic", toolFlag) + } + + serverURL, err := resolveServerURL(cmd.Context(), g, serverFlag) + if err != nil { + return err + } + + var bearer string + if mint { + tok, err := oauth.Login(cmd.Context(), mcpURL(serverURL), oauth.LoginOpts{ + Out: cmd.ErrOrStderr(), + }) + if err != nil { + return fmt.Errorf("mint a token via OAuth: %w", err) + } + bearer = tok.AccessToken + } + + fmt.Fprintln(cmd.OutOrStdout(), target.emit(serverURL, bearer)) + + if mint { + fmt.Fprintf(cmd.ErrOrStderr(), "warning: the bearer token above is embedded in %s -- oap does not store it anywhere; it lives only in that file from here on.\n", target.credLocation) + } + fmt.Fprintf(cmd.ErrOrStderr(), "next step: %s\n", target.nextStep) + + return nil + }, + } + + cmd.Flags().StringVar(&serverFlag, "server", "", "MCP server base URL (default: discover from the cluster's webd external-URL ConfigMap)") + cmd.Flags().StringVar(&toolFlag, "tool", defaultTool, "Target tool: claude-code, cursor, or generic") + cmd.Flags().BoolVar(&mint, "mint", false, "Run the OAuth flow now and embed the resulting bearer token in the emitted config") + + return cmd +} + +// resolveServerURL implements the task's URL-resolution contract: --server +// wins outright; otherwise fall back to kube discovery of the webd +// external-URL ConfigMap; if neither works, fail with a message that tells +// the user to pass --server rather than leaving them to guess. +func resolveServerURL(ctx context.Context, g *apcmd.Globals, flag string) (string, error) { + if flag != "" { + return strings.TrimRight(flag, "/"), nil + } + + b, err := g.Bundle() + if err != nil { + return "", fmt.Errorf("no --server given and could not reach the cluster to discover one (%w); pass --server explicitly", err) + } + url, err := clilogin.ResolveIdentitydBaseURL(ctx, b.Controller) + if err != nil { + return "", fmt.Errorf("no --server given and could not discover this cluster's MCP server URL (%w); pass --server explicitly", err) + } + return url, nil +} diff --git a/cmd/oap/root.go b/cmd/oap/root.go index 7c93da15..176d408c 100644 --- a/cmd/oap/root.go +++ b/cmd/oap/root.go @@ -22,6 +22,7 @@ import ( "github.com/authzed/openagentprimitives/cmd/oap/internal/sandboxcmd" "github.com/authzed/openagentprimitives/cmd/oap/internal/sessioncmd" "github.com/authzed/openagentprimitives/cmd/oap/internal/settingscmd" + "github.com/authzed/openagentprimitives/cmd/oap/internal/setupmcpcmd" "github.com/authzed/openagentprimitives/cmd/oap/internal/skillcmd" "github.com/authzed/openagentprimitives/cmd/oap/internal/toolscmd" ) @@ -84,5 +85,6 @@ func NewRootCmdWithGlobals(g *Globals) *cobra.Command { root.AddCommand(identitycmd.NewIdpCmd(g)) root.AddCommand(desktopcmd.NewCmd(g)) root.AddCommand(desktopcmd.NewWindowCmd(g)) + root.AddCommand(setupmcpcmd.NewCmd(g)) return root } diff --git a/config/crds/agentprimitives.authzed.com_accesstokens.yaml b/config/crds/agentprimitives.authzed.com_accesstokens.yaml new file mode 100644 index 00000000..13c224fa --- /dev/null +++ b/config/crds/agentprimitives.authzed.com_accesstokens.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: accesstokens.agentprimitives.authzed.com +spec: + group: agentprimitives.authzed.com + names: + categories: + - authzed + - spicebox + kind: AccessToken + listKind: AccessTokenList + plural: accesstokens + shortNames: + - atok + singular: accesstoken + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.owner + name: Owner + type: string + - jsonPath: .spec.clientName + name: Client + type: string + - jsonPath: .spec.expiresAt + name: Expires + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + AccessToken is the AUTHENTICATION record for one inbound delegated + credential minted at OAuth consent: hash → token identity, plus display + metadata and expiry. It deliberately carries NO role and NO scope — the + authorization truth lives entirely in SpiceDB on accesstoken: + (see pkg/authz/spicedb/schema/schema.zed). Deleting this object is + revocation: the finalizer removes the SpiceDB tuples. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + clientID: + description: ClientID is the OAuth client the token was issued to. + type: string + clientName: + description: ClientName is the DCR-supplied display name, sanitized + at mint. + type: string + expiresAt: + description: |- + ExpiresAt is when the token stops authenticating. The SpiceDB tuples + carry the same instant via the expiration trait; this copy gates the + authentication (hash-lookup) step without a SpiceDB round trip. + format: date-time + type: string + owner: + description: Owner is the canonical user id the token acts as. + minLength: 1 + type: string + tokenHash: + description: |- + TokenHash is hex(sha256(bearer value)). The value itself is never + stored server-side. + pattern: ^[0-9a-f]{64}$ + type: string + required: + - expiresAt + - owner + - tokenHash + type: object + x-kubernetes-validations: + - message: tokenHash is immutable + rule: self.tokenHash == oldSelf.tokenHash + - message: owner is immutable + rule: self.owner == oldSelf.owner + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastUsedAt: + description: |- + LastUsedAt is observation, controller-owned, updated on meaningful + change only (not every request). + format: date-time + type: string + observedGeneration: + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crds/kustomization.yaml b/config/crds/kustomization.yaml index efa3f1e4..f174aa21 100644 --- a/config/crds/kustomization.yaml +++ b/config/crds/kustomization.yaml @@ -1,6 +1,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: + - agentprimitives.authzed.com_accesstokens.yaml - agentprimitives.authzed.com_agentclasses.yaml - agentprimitives.authzed.com_agentidentities.yaml - agentprimitives.authzed.com_agentsessiongrants.yaml diff --git a/config/manager/role.yaml b/config/manager/role.yaml index 33f5773f..2203d7bb 100644 --- a/config/manager/role.yaml +++ b/config/manager/role.yaml @@ -85,32 +85,34 @@ rules: - apiGroups: - agentprimitives.authzed.com resources: - - agentclasses - - agentidentities - - agentsessions - - agentuis - - channels - - clusterskills - - mcpservers - - sidecartoolboxes - - skills - - spiceboxsessions - - spiceboxtoolkits - - spiceboxtoolspecs - - subagentrequests - - toolcalls - - workshops + - accesstokens verbs: - - create - delete - get - list - - patch - update - watch - apiGroups: - agentprimitives.authzed.com resources: + - accesstokens/finalizers + - agentsessions/finalizers + - artifactrenders/finalizers + - credentialupdaterequests/finalizers + - publicendpoints/finalizers + - sidecartoolboxes/finalizers + - spiceboxsessions/finalizers + - spicedbbootstraps/finalizers + - toolcalls/finalizers + - useridentities/finalizers + - workshops/finalizers + - workspacesources/finalizers + verbs: + - update +- apiGroups: + - agentprimitives.authzed.com + resources: + - accesstokens/status - agentclasses/status - agentidentities/status - agentsessiongrants/status @@ -151,29 +153,39 @@ rules: - apiGroups: - agentprimitives.authzed.com resources: - - agentsessiongrants + - agentclasses + - agentidentities + - agentsessions + - agentuis + - channels + - clusterskills + - mcpservers + - sidecartoolboxes + - skills + - spiceboxsessions + - spiceboxtoolkits + - spiceboxtoolspecs + - subagentrequests + - toolcalls + - workshops verbs: - create + - delete - get - list + - patch - update - watch - apiGroups: - agentprimitives.authzed.com resources: - - agentsessions/finalizers - - artifactrenders/finalizers - - credentialupdaterequests/finalizers - - publicendpoints/finalizers - - sidecartoolboxes/finalizers - - spiceboxsessions/finalizers - - spicedbbootstraps/finalizers - - toolcalls/finalizers - - useridentities/finalizers - - workshops/finalizers - - workspacesources/finalizers + - agentsessiongrants verbs: + - create + - get + - list - update + - watch - apiGroups: - agentprimitives.authzed.com resources: diff --git a/config/webd/clusterrole.yaml b/config/webd/clusterrole.yaml index ce76cd8f..af68a89e 100644 --- a/config/webd/clusterrole.yaml +++ b/config/webd/clusterrole.yaml @@ -147,3 +147,24 @@ rules: - apiGroups: ["agentprimitives.authzed.com"] resources: ["agentidentities"] verbs: ["get"] + # Task 8's /oauth/token mints an AccessToken CR as the authentication + # record for a newly-issued bearer token (pkg/web/mcpfront.Minter). create + # for the mint itself; list for the /mcp bearer middleware's hash-lookup + # relist (pkg/web/mcpfront/auth.go's tokenCache); get for touchLastUsed's + # read-before-status-patch. No watch: webd's client is direct (client.New, + # uncached — no informer ever lists/watches this kind), and the admin + # tokens page reads through admind, hosted in the OPERATOR binary, not + # here. No delete either: revocation is admind's authorized route plus the + # operator's finalizer — a browser-facing pod must not be able to destroy + # token records (the RBAC sufficiency harness pins that verb denied). + - apiGroups: ["agentprimitives.authzed.com"] + resources: ["accesstokens"] + verbs: ["create", "get", "list"] + # Task 9's /mcp bearer middleware records status.lastUsedAt as a + # best-effort OBSERVATION (see pkg/web/mcpfront/auth.go's touchLastUsed) via + # a MergeFrom status patch. The base object (spec+status together) is read + # through the main-resource "get" above — mirrors sessionuseridentities/status + # above, which likewise carries only the write verbs. + - apiGroups: ["agentprimitives.authzed.com"] + resources: ["accesstokens/status"] + verbs: ["update", "patch"] diff --git a/internal/cmd/operator/main.go b/internal/cmd/operator/main.go index c4d61da7..c8c36021 100644 --- a/internal/cmd/operator/main.go +++ b/internal/cmd/operator/main.go @@ -98,6 +98,7 @@ import ( _ "github.com/authzed/openagentprimitives/pkg/channels/channelkinds/onepassword" // register onepassword relsync.Kind for the relationshipsource controller (relsync.Get); also reachable transitively via the relsource/imports blank import above, but this is the explicit wiring site for THIS aspect, not an accident of that one _ "github.com/authzed/openagentprimitives/pkg/channels/channelkinds/slack" // register slack kind for channel-controller validation "github.com/authzed/openagentprimitives/pkg/cli/clikit" + accesstokenctrl "github.com/authzed/openagentprimitives/pkg/controllers/accesstoken" agentclassctrl "github.com/authzed/openagentprimitives/pkg/controllers/agentclass" "github.com/authzed/openagentprimitives/pkg/controllers/agentidentity" agentsessionctrl "github.com/authzed/openagentprimitives/pkg/controllers/agentsession" @@ -1642,6 +1643,21 @@ func run(cfg *config) { } log.Info("registered controller", "name", "UserIdentity") + // Registered here for the same reason the AgentIdentity and UserIdentity + // reconcilers above are: it needs the SpiceDB client to remove a revoked + // (or expired) token's tuples on finalization. spiceDBClient is a real, + // non-nil *spicedb.Client by this point (construction failure exits + // above), so assigning it into the TupleDeleter interface field cannot + // produce a typed-nil interface. + if err := (&accesstokenctrl.Reconciler{ + Client: mgr.GetClient(), + SpiceDB: spiceDBClient, + }).SetupWithManager(mgr); err != nil { + log.Error(err, "unable to create controller", "controller", "AccessToken") + os.Exit(1) + } + log.Info("registered controller", "name", "AccessToken") + // RelationshipSource — polls an upstream directory (Slack first) through // the registered relsync.Kind and syncs the membership relationships it // reports into SpiceDB. Registered here for the same reason AgentIdentity @@ -3030,6 +3046,18 @@ func run(cfg *config) { // *spicedb.Client here, so assigning it directly into the Scopes // interface field cannot produce a typed-nil interface either. Scopes: spiceDBClient, + // Same reasoning again: spiceDBClient is a real, non-nil + // *spicedb.Client here, so assigning it directly into the + // AccessTokenGrants interface field cannot produce a typed-nil + // interface either. + AccessTokenGrants: spiceDBClient, + // systemNS (computed above, operatorNamespace(cfg.podNamespace)) is + // the SAME namespace webd's own --accesstoken-namespace default + // ("agentprimitives-system") resolves to in a standard install — the + // AccessToken CRs the admin Tokens page lists are the ones webd's + // /mcp Minter creates there, not a second, independently-configured + // namespace that could silently drift from it. + AccessTokenNamespace: systemNS, }) if err != nil { log.Error(err, "admind: construction failed") diff --git a/internal/cmd/operator/main_controllers_test.go b/internal/cmd/operator/main_controllers_test.go index 88fb1b31..e6b341d2 100644 --- a/internal/cmd/operator/main_controllers_test.go +++ b/internal/cmd/operator/main_controllers_test.go @@ -48,6 +48,7 @@ import ( // (a SetupWithManager call removed, a controller added without a log). func TestOperatorRegistersExpectedControllers(t *testing.T) { expectedControllerNames := map[string]bool{ + "AccessToken": true, "SpiceboxClass": true, "SpiceboxSession": true, "SpiceboxToolchain": true, diff --git a/internal/cmd/webd/main.go b/internal/cmd/webd/main.go index c04674ce..b0ccf5fe 100644 --- a/internal/cmd/webd/main.go +++ b/internal/cmd/webd/main.go @@ -56,6 +56,17 @@ import ( "github.com/authzed/openagentprimitives/pkg/web/admind/agentcred" "github.com/authzed/openagentprimitives/pkg/web/adminui" "github.com/authzed/openagentprimitives/pkg/web/browsersession" + // mcpfront is imported BY NAME, not blank, for the same reason agentui and + // sessions are (see those imports' own comments below): main.go references + // mcpfront.Minter/mcpfront.MintParams/mcpfront.AccessTokenAuthz directly + // (buildArtifactViewDeps's Minter construction, AccessTokenAuthz's nil- + // guarded return, and the compile-time Deps guard below), so a future + // mcpfront.Deps signature drift is a `go build` failure, not a runtime + // deps.(mcpfront.Deps) cast that silently 404s. The import ALSO runs + // mcpfront's init() (webui plugin registration) — Task 9's controller + // ruling: a blank import would register the plugin but give main.go no way + // to reference mcpfront.Minter at all. + "github.com/authzed/openagentprimitives/pkg/web/mcpfront" "github.com/authzed/openagentprimitives/pkg/web/uibindings" "github.com/authzed/openagentprimitives/pkg/web/webui" "github.com/authzed/openagentprimitives/pkg/x/externalurl" @@ -242,6 +253,14 @@ type config struct { // one answer this binary reads. It is deliberately NOT a bool, so an // unset/unrecognized value cannot silently resolve to a permissive default. clusterKind string + // accessTokenNamespace is where the /mcp Minter creates AccessToken CRs + // and where the bearer middleware lists them back — the two MUST agree, or + // a freshly-minted token would never authenticate. Defaults to the + // standard install namespace. + accessTokenNamespace string + // accessTokenLifetime is how long a token minted by /oauth/token stays + // valid (webd's --accesstoken-lifetime). Default 2160h = 90 days. + accessTokenLifetime time.Duration } func main() { @@ -297,6 +316,8 @@ func newCommand() *cobra.Command { fs.StringVar(&cfg.admindURL, "admind-url", "", "admind API base URL; defaults to the operator URL when empty (e.g. http://spicebox-operator.agentprimitives-system.svc:8082)") fs.StringVar(&cfg.admindTokenPath, "admind-token-path", "/var/run/webd/admind-token/token", "path to the admind service token file") fs.StringVar(&cfg.clusterKind, "cluster-kind", "", fmt.Sprintf("REQUIRED cluster kind stamped by `oap install` (one of: %s); selects the install profile, whose AllowsSharedOrigin() decides whether the trusted and sandbox origins may share one host. Empty or unrecognized is a FATAL startup error — no silent default. A Deployment predating this flag must be re-applied via `oap install`.", strings.Join(cloud.RegisteredKeys(), ", "))) + fs.StringVar(&cfg.accessTokenNamespace, "accesstoken-namespace", "agentprimitives-system", "namespace the /mcp Minter creates AccessToken CRs in, and the bearer middleware lists them back from") + fs.DurationVar(&cfg.accessTokenLifetime, "accesstoken-lifetime", 2160*time.Hour, "how long a token minted by /oauth/token stays valid") cmd.PreRunE = clikit.EnvOverridePreRunE(map[string]string{ "trusted-base-url": "WEBD_TRUSTED_BASE_URL", "sandbox-base-url": "WEBD_SANDBOX_BASE_URL", @@ -316,6 +337,8 @@ func newCommand() *cobra.Command { "max-live-sessions-per-subject": "AP_WEBD_MAX_LIVE_SESSIONS_PER_SUBJECT", "max-live-sessions": "AP_WEBD_MAX_LIVE_SESSIONS", "session-start-namespaces": "AP_WEBD_SESSION_START_NAMESPACES", + "accesstoken-namespace": "WEBD_ACCESSTOKEN_NAMESPACE", + "accesstoken-lifetime": "WEBD_ACCESSTOKEN_LIFETIME", }) return cmd } @@ -512,7 +535,7 @@ func run(ctx context.Context, cfg *config) error { } var deps webui.Deps = base - if av := buildArtifactViewDeps(base, nc, cfg.operatorURL, cfg.webdTokenPath, cfg.spicedbEndpoint, cfg.spicedbInsecure, cfg.spicedbTokenPath, keyBytes, cookieSigner, trustedURL.Get, sandboxURL.Get, logger, admindURL, admindToken, cfg.startNamespaces); av != nil { + if av := buildArtifactViewDeps(base, nc, cfg.operatorURL, cfg.webdTokenPath, cfg.spicedbEndpoint, cfg.spicedbInsecure, cfg.spicedbTokenPath, keyBytes, cookieSigner, trustedURL.Get, sandboxURL.Get, logger, admindURL, admindToken, cfg.startNamespaces, cfg.accessTokenNamespace, cfg.accessTokenLifetime); av != nil { deps = av } // The transcript data plane needs NATS + SpiceDB + the operator URL (see @@ -747,6 +770,17 @@ type artifactViewDeps struct { // browser-facing start routes read it per request through // StartableNamespaces (see config.startNamespaces for what it means). startNamespaces []string + // accessTokenNamespace is --accesstoken-namespace: where the /mcp Minter + // creates AccessToken CRs and where the bearer middleware lists them back + // from (mcpfront.Deps.AccessTokenNamespace). + accessTokenNamespace string + // minter mints an AccessToken for an approved /oauth/token exchange. A + // concrete *mcpfront.Minter field (not an interface) so the nil check in + // MintAccessToken below is honest — see AGENTS.md's typed-nil rule: this + // is always either unset (nil) or assigned a real value in + // buildArtifactViewDeps, never a typed-nil promoted through an interface + // boundary. + minter *mcpfront.Minter } // var _ agentui.Deps = (*artifactViewDeps)(nil) is a COMPILE-TIME proof that @@ -779,6 +813,16 @@ var _ agentui.Deps = (*artifactViewDeps)(nil) // CheckInteract (identityd never needed one) and LookupInteractableSessions. var _ sessions.Deps = (*artifactViewDeps)(nil) +// var _ mcpfront.Deps = (*artifactViewDeps)(nil) is the same compile-time +// proof as the two guards above, for pkg/web/mcpfront's Deps interface +// (AccessTokenAuthz, AccessTokenNamespace, LookupReadableSessions, embedded +// K8s/OperatorURL/MemoryToken/Artifacts/ExternalBaseURL/Logger). *webdDeps — +// the degraded, identity-only umbrella — deliberately does NOT satisfy +// mcpfront.Deps either: it lacks AccessTokenAuthz and AccessTokenNamespace, +// so an unconfigured SpiceDB correctly yields NO /mcp surface rather than one +// whose every handler call would 500. +var _ mcpfront.Deps = (*artifactViewDeps)(nil) + // VerifyLink verifies a channelsd→webd deep-link and dispatches on Purpose. // PurposeArtifactView requires BOTH ArtifactID and SessionRef (unchanged from // before PurposeSessionView existed). PurposeSessionView requires ONLY @@ -848,6 +892,147 @@ func (d *artifactViewDeps) AgentCredentialWriter() identityd.AgentCredentialWrit return agentcred.New(d.admindURL, d.admindToken) } +// AccessTokenAuthz implements mcpfront.Deps: the SpiceDB half of an +// authorized /mcp tool call. Returned as the INTERFACE and nil-guarded for +// the same reason as AgentIdentityAuthz below — a typed-nil *spicedb.Client +// assigned into an interface field would yield a non-nil interface that +// panics on first call, and mcpfront's own fail-closed branch +// (ui.Routes: "AccessTokenAuthz() == nil → no routes") would never run. +func (d *artifactViewDeps) AccessTokenAuthz() mcpfront.AccessTokenAuthz { + if d.spdb == nil { + return nil + } + return d.spdb +} + +// AccessTokenNamespace implements mcpfront.Deps: the SAME namespace +// d.minter creates AccessToken CRs in (see buildArtifactViewDeps), so the +// bearer middleware's hash lookup finds every token this process's Minter +// could have produced. +func (d *artifactViewDeps) AccessTokenNamespace() string { return d.accessTokenNamespace } + +// MintAccessToken implements identityd.AccessTokenMinter: identityd cannot +// import pkg/web/mcpfront (the import direction in this repo is web -> +// platform, never the reverse — see handlers_oauthas_token.go's package +// doc), so AccessTokenMinter/MintParams/Minted there are a field-for-field +// MIRROR of mcpfront's real types, not an import of them. This method is the +// one place those two shapes are adapted into each other. +// +// Nil-guarded: d.minter is a concrete *mcpfront.Minter (never a typed-nil +// promoted through an interface — see artifactViewDeps.minter's own doc), set +// only in buildArtifactViewDeps once spdb/k8s are known real. identityd's own +// /oauth/token handler already treats a nil Minter as "unavailable" (503) at +// the webui.go wiring layer; this guard is belt to that braces, in case this +// method is ever reached some other way. +func (d *artifactViewDeps) MintAccessToken(ctx context.Context, p identityd.MintParams) (identityd.Minted, error) { + if d.minter == nil { + return identityd.Minted{}, fmt.Errorf("mcpfront: access-token minter not configured") + } + minted, err := d.minter.MintAccessToken(ctx, mcpfront.MintParams{ + Owner: p.Owner, + Role: p.Role, + ScopeClasses: p.ScopeClasses, + Unfiltered: p.Unfiltered, + ClientName: p.ClientName, + ClientID: p.ClientID, + }) + if err != nil { + return identityd.Minted{}, err + } + return identityd.Minted{Value: minted.Value, TokenID: minted.TokenID, ExpiresAt: minted.ExpiresAt}, nil +} + +// consentClassLookupLimit bounds ConsentClasses' two SpiceDB lookups — +// generous ceilings matching pkg/web/webui/sessions' own maxListedSessions / +// maxListedClasses constants (the consent screen asks the SAME two +// questions that dashboard does, for the same subject). A subject who can +// start or interact with more classes than this needs a paginated consent +// screen, a bigger design change than this constant. +const ( + consentClassSessionLookupLimit = 200 + consentClassLookupLimit = 500 +) + +// ConsentClasses implements identityd.ConsentDeps: the OAuth consent +// screen's "which agent classes may this subject grant a tool access to?" +// question. Answered as the union of LookupStartableClasses (classes the +// subject may start fresh) and the classes backing LookupInteractableSessions +// (classes behind sessions the subject may already interact with) — v1's +// answer to "what could this subject plausibly want to grant" rather than a +// dedicated SpiceDB relation of its own. +// +// Both lookups read a snapshot (fullyConsistent=false): this is a display +// list for a consent CHECKBOX, not an authorization decision — the actual +// mint narrows only what CheckAccessTokenOp's OwnerHas leg later re-confirms +// per call, so an over- or under-inclusive consent list cannot itself grant +// anything the owner doesn't independently hold. +func (d *artifactViewDeps) ConsentClasses(ctx context.Context, owner identity.CanonicalUserID) ([]identityd.ConsentClass, error) { + if d.spdb == nil { + return nil, fmt.Errorf("mcpfront: SpiceDB not configured") + } + + // classes maps a "ns/name" class id to its resolved AgentClass (nil until + // resolved) so the display-name pass below can reuse what AgentClassOf + // already fetched for an interactable session's class, rather than + // re-Getting it. + classes := map[string]*spiceboxv1alpha1.AgentClass{} + var order []string + note := func(id string) { + if _, ok := classes[id]; !ok { + classes[id] = nil + order = append(order, id) + } + } + + startable, err := d.LookupStartableClasses(ctx, owner, consentClassLookupLimit, false) + if err != nil { + return nil, fmt.Errorf("consent classes: lookup startable classes: %w", err) + } + for _, ref := range startable.Refs { + note(ref.Namespace + "/" + ref.Name) + } + + interactable, err := d.LookupInteractableSessions(ctx, owner, consentClassSessionLookupLimit, false) + if err != nil { + return nil, fmt.Errorf("consent classes: lookup interactable sessions: %w", err) + } + for _, ref := range interactable.Refs { + ac, acErr := d.AgentClassOf(ctx, ref.Namespace, ref.Name) + if acErr != nil { + d.logger.Info("consent classes: resolve session's agent class failed; skipping", + "session", ref.Namespace+"/"+ref.Name, "err", acErr.Error()) + continue + } + note(ac.Namespace + "/" + ac.Name) + classes[ac.Namespace+"/"+ac.Name] = ac + } + + sort.Strings(order) + out := make([]identityd.ConsentClass, 0, len(order)) + for _, id := range order { + ns, name, ok := strings.Cut(id, "/") + if !ok { + continue + } + ac := classes[id] + if ac == nil { + var fetched spiceboxv1alpha1.AgentClass + if getErr := d.K8s().Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, &fetched); getErr == nil { + ac = &fetched + } else { + d.logger.Info("consent classes: resolve display name failed; falling back to the class name", + "class", id, "err", getErr.Error()) + } + } + display := name + if ac != nil && ac.Spec.DisplayName != "" { + display = ac.Spec.DisplayName + } + out = append(out, identityd.ConsentClass{ID: id, DisplayName: display}) + } + return out, nil +} + func (d *artifactViewDeps) CheckView(ctx context.Context, artifactID, subject string) (bool, error) { // The cookie subject is the canonical form "user:"; SpiceDB's // CheckArtifactView builds the user object itself, so it wants the @@ -940,6 +1125,41 @@ func (d *artifactViewDeps) LookupInteractableSessions(ctx context.Context, canon return d.spdb.LookupInteractableSessions(ctx, canonicalID, limit, fullyConsistent) } +// mcpReadableSessionsLimit bounds LookupReadableSessions below, matching +// pkg/web/webui/sessions' own maxListedSessions — the /mcp tool surface +// enumerates the same interactable set the session dashboard does, for the +// same subject. +const mcpReadableSessionsLimit = 200 + +// LookupReadableSessions implements mcpfront.Deps: v1's answer to "which +// sessions may this token's owner READ" is the interactable set itself +// (interact implies read_transcript for a root session — see +// pkg/authz/spicedb's $sameperm mirror), not a dedicated read-only lookup. +// fullyConsistent=true: an /mcp tool caller enumerating sessions needs a +// just-granted (or just-revoked) interact relationship reflected immediately, +// the same justification interactCheckArgs gives CheckInteract above. +func (d *artifactViewDeps) LookupReadableSessions(ctx context.Context, owner identity.CanonicalUserID) (spicedb.InteractableSessions, error) { + if d.spdb == nil { + return spicedb.InteractableSessions{}, fmt.Errorf("mcpfront: SpiceDB not configured") + } + return d.spdb.LookupInteractableSessions(ctx, owner, mcpReadableSessionsLimit, true) +} + +// FetchArtifact implements mcpfront.Deps: the same two-step resolve-then-fetch +// ContentRender/FetchRender do for the browser live-view (ResolveToRender maps +// the artifactID handle to the ArtifactRender CR holding its current bytes, +// then FetchRender pulls those bytes through the operator), reused here rather +// than re-derived so the /mcp get_artifact tool can never read different bytes +// than the live-view frames for the same handle. +func (d *artifactViewDeps) FetchArtifact(ctx context.Context, ns, name, artifactID string) ([]byte, string, error) { + scope := memory.Scope{Kind: "session", ID: ns + "/" + name} + renderName, err := d.artSvc.ResolveToRender(ctx, scope, artifactID) + if err != nil { + return nil, "", err + } + return d.FetchRender(ctx, ns, name, renderName) +} + // LookupStartableClasses implements sessions.Deps: the bootstrap arm of the // start gate. Fails closed with an error rather than an empty set when SpiceDB // is absent — an empty answer here is indistinguishable from "you may start @@ -1843,7 +2063,7 @@ func (d *artifactViewDeps) CheckPlatformPermission(ctx context.Context, permissi // here. ctSigner reuses the same key bytes; webd both mints (trusted side, // after the SpiceDB view check) and verifies (cookieless sandbox side) the // content token, so a single shared key suffices. -func buildArtifactViewDeps(base *webdDeps, nc *nats.Conn, operatorURL, webdTokenPath, spicedbEndpoint string, spicedbInsecure bool, spicedbTokenPath string, keyBytes []byte, cookieSigner *passthroughlink.Signer, trustedURLGet, sandboxURLGet func() string, logger logr.Logger, admindURL, admindToken string, startNamespaces []string) *artifactViewDeps { +func buildArtifactViewDeps(base *webdDeps, nc *nats.Conn, operatorURL, webdTokenPath, spicedbEndpoint string, spicedbInsecure bool, spicedbTokenPath string, keyBytes []byte, cookieSigner *passthroughlink.Signer, trustedURLGet, sandboxURLGet func() string, logger logr.Logger, admindURL, admindToken string, startNamespaces []string, accessTokenNamespace string, accessTokenLifetime time.Duration) *artifactViewDeps { // SpiceDB connection params come from cfg (endpoint/insecure/token-path, // bound to the canonical SPICEDB_* env via clikit). The token VALUE is a // secret: read from the token-path file when set (taking precedence, as @@ -1889,6 +2109,12 @@ func buildArtifactViewDeps(base *webdDeps, nc *nats.Conn, operatorURL, webdToken mem := httpclient.New(operatorURL, token) artSvc := artifacts.NewService(mem, nil) ctSigner := contenttoken.New(keyBytes) + // The Minter is constructed here, not independently nil-guarded: spdb and + // base.k8s are both already confirmed real by the fail-closed checks + // above (this function has already returned nil on any missing + // prerequisite), so every *artifactViewDeps this function actually + // returns carries a real Minter. + minter := &mcpfront.Minter{SpiceDB: spdb, K8s: base.k8s, Namespace: accessTokenNamespace, Lifetime: accessTokenLifetime} fmt.Fprintln(os.Stderr, "webd: artifact viewer configured (SpiceDB + memory + content token)") return &artifactViewDeps{ @@ -1910,7 +2136,9 @@ func buildArtifactViewDeps(base *webdDeps, nc *nats.Conn, operatorURL, webdToken // Copied on the way IN as well as on the way out (see // StartableNamespaces): the caller keeps its own slice, and neither // side may reshape the other's. - startNamespaces: append([]string(nil), startNamespaces...), + startNamespaces: append([]string(nil), startNamespaces...), + accessTokenNamespace: accessTokenNamespace, + minter: minter, } } diff --git a/internal/cmd/webd/main_test.go b/internal/cmd/webd/main_test.go index 70ad73b5..ca9de971 100644 --- a/internal/cmd/webd/main_test.go +++ b/internal/cmd/webd/main_test.go @@ -576,7 +576,9 @@ func TestBuildArtifactViewDepsFailsClosed(t *testing.T) { func() string { return "https://sandbox.example.com" }, logr.Logger{}, // zero-value: nil-sink no-op; tests need not configure a real logger "", "", // admindURL, admindToken: unconfigured; adminui plugin fails closed - []string{"default"}, // startNamespaces: the flag's own default + []string{"default"}, // startNamespaces: the flag's own default + "agentprimitives-system", // accessTokenNamespace: unused on this fail-closed path + 2160*time.Hour, // accessTokenLifetime: unused on this fail-closed path ) assert.Nil(t, av, "viewer must fail closed when SpiceDB is unconfigured") } diff --git a/pkg/apis/v1alpha1/accesstoken_types.go b/pkg/apis/v1alpha1/accesstoken_types.go new file mode 100644 index 00000000..8d06463f --- /dev/null +++ b/pkg/apis/v1alpha1/accesstoken_types.go @@ -0,0 +1,84 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AccessToken is the AUTHENTICATION record for one inbound delegated +// credential minted at OAuth consent: hash → token identity, plus display +// metadata and expiry. It deliberately carries NO role and NO scope — the +// authorization truth lives entirely in SpiceDB on accesstoken: +// (see pkg/authz/spicedb/schema/schema.zed). Deleting this object is +// revocation: the finalizer removes the SpiceDB tuples. +// +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Namespaced,categories={authzed,spicebox},shortName=atok +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Owner",type="string",JSONPath=".spec.owner" +// +kubebuilder:printcolumn:name="Client",type="string",JSONPath=".spec.clientName" +// +kubebuilder:printcolumn:name="Expires",type="string",JSONPath=".spec.expiresAt" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +genclient +type AccessToken struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AccessTokenSpec `json:"spec,omitempty"` + Status AccessTokenStatus `json:"status,omitempty"` +} + +// +kubebuilder:validation:XValidation:rule="self.tokenHash == oldSelf.tokenHash",message="tokenHash is immutable" +// +kubebuilder:validation:XValidation:rule="self.owner == oldSelf.owner",message="owner is immutable" +type AccessTokenSpec struct { + // TokenHash is hex(sha256(bearer value)). The value itself is never + // stored server-side. + // +kubebuilder:validation:Pattern=`^[0-9a-f]{64}$` + TokenHash string `json:"tokenHash"` + + // Owner is the canonical user id the token acts as. + // +kubebuilder:validation:MinLength=1 + Owner string `json:"owner"` + + // ClientName is the DCR-supplied display name, sanitized at mint. + // +optional + ClientName string `json:"clientName,omitempty"` + + // ClientID is the OAuth client the token was issued to. + // +optional + ClientID string `json:"clientID,omitempty"` + + // ExpiresAt is when the token stops authenticating. The SpiceDB tuples + // carry the same instant via the expiration trait; this copy gates the + // authentication (hash-lookup) step without a SpiceDB round trip. + ExpiresAt metav1.Time `json:"expiresAt"` +} + +type AccessTokenStatus struct { + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // +optional + // +patchStrategy=merge + // +patchMergeKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + // LastUsedAt is observation, controller-owned, updated on meaningful + // change only (not every request). + // +optional + LastUsedAt *metav1.Time `json:"lastUsedAt,omitempty"` +} + +// +kubebuilder:object:root=true +type AccessTokenList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AccessToken `json:"items"` +} + +const ( + // AccessTokenConditionReady is True once the reconciler has observed the + // object and its finalizer is in place. + AccessTokenConditionReady = "Ready" +) + +func init() { + SchemeBuilder.Register(&AccessToken{}, &AccessTokenList{}) +} diff --git a/pkg/apis/v1alpha1/conditions.go b/pkg/apis/v1alpha1/conditions.go index 8f91ee88..7138391a 100644 --- a/pkg/apis/v1alpha1/conditions.go +++ b/pkg/apis/v1alpha1/conditions.go @@ -236,6 +236,7 @@ const ( FinalizerPublicEndpoint = "publicendpoint.agentprimitives.authzed.com/finalizer" FinalizerUserIdentity = "useridentity.agentprimitives.authzed.com/finalizer" FinalizerWorkshop = "workshop.agentprimitives.authzed.com/finalizer" + FinalizerAccessToken = "accesstoken.agentprimitives.authzed.com/finalizer" // FinalizerSubagentRequest is added ONLY to an `attended`-mode // SubagentRequest, giving the controller a chance to notify a live // attended child's watching parent (a fixed inbound line + a forced wake) diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go index dcb66ad0..ce833798 100644 --- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -13,6 +13,107 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessToken) DeepCopyInto(out *AccessToken) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessToken. +func (in *AccessToken) DeepCopy() *AccessToken { + if in == nil { + return nil + } + out := new(AccessToken) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AccessToken) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessTokenList) DeepCopyInto(out *AccessTokenList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AccessToken, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessTokenList. +func (in *AccessTokenList) DeepCopy() *AccessTokenList { + if in == nil { + return nil + } + out := new(AccessTokenList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AccessTokenList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessTokenSpec) DeepCopyInto(out *AccessTokenSpec) { + *out = *in + in.ExpiresAt.DeepCopyInto(&out.ExpiresAt) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessTokenSpec. +func (in *AccessTokenSpec) DeepCopy() *AccessTokenSpec { + if in == nil { + return nil + } + out := new(AccessTokenSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessTokenStatus) DeepCopyInto(out *AccessTokenStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LastUsedAt != nil { + in, out := &in.LastUsedAt, &out.LastUsedAt + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessTokenStatus. +func (in *AccessTokenStatus) DeepCopy() *AccessTokenStatus { + if in == nil { + return nil + } + out := new(AccessTokenStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AgentClass) DeepCopyInto(out *AgentClass) { *out = *in diff --git a/pkg/authz/guardian/schema/validate_fragment_test.go b/pkg/authz/guardian/schema/validate_fragment_test.go index 6e8880ba..003a3d40 100644 --- a/pkg/authz/guardian/schema/validate_fragment_test.go +++ b/pkg/authz/guardian/schema/validate_fragment_test.go @@ -48,7 +48,14 @@ func TestReservedDefinitionNames_MatchesScaffold(t *testing.T) { "platform": {}, "cluster": {}, "externaltoken": {}, - "agentidentity": {}, + // accesstoken is the OAuth-minted inbound credential (contrast + // externaltoken, the outbound credential a session uses). Reservation + // matters: a fragment that redeclared it could widen scope_class past the + // agents the token is authorized for, or narrow the permission ladder's + // role-relation matching, silently changing token authorization for every + // session downstream. + "accesstoken": {}, + "agentidentity": {}, // agentclass joined the scaffold with the browser start gate. Being // reserved is the point: an MCPServer fragment that redeclared it would // silently replace the definition the start gate resolves through, and diff --git a/pkg/authz/spicedb/client_accesstoken.go b/pkg/authz/spicedb/client_accesstoken.go new file mode 100644 index 00000000..ba11d429 --- /dev/null +++ b/pkg/authz/spicedb/client_accesstoken.go @@ -0,0 +1,351 @@ +package spicedb + +import ( + "context" + "errors" + "fmt" + "io" + "time" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "google.golang.org/protobuf/types/known/timestamppb" + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +// wildcardSubjectID is the SpiceDB object id for a wildcard subject +// (`agentclass:*`), matching the ObjectId: "*" shape SyncArtifactOrgViewer +// already uses for `user:*` — a plain wildcard object id, not a typed +// relation-set wildcard. +const wildcardSubjectID = "*" + +// AccessTokenGrant is the complete authorization shape of one access token: +// who it acts as, what role ladder it climbed, and which agentclasses it may +// touch (or Unfiltered for every class). WriteAccessTokenGrant and +// ReadAccessTokenGrant are the write/read halves of this same shape. +type AccessTokenGrant struct { + TokenID string + Owner identity.CanonicalUserID + Role string // accesstoken.Role* + ScopeClasses []string // agentclass object ids "ns/name"; ignored when Unfiltered + Unfiltered bool + ExpiresAt time.Time +} + +// WriteAccessTokenGrant writes the token's complete tuple set in ONE +// WriteRelationships call: the role tuple (subject = owner) plus either the +// wildcard scope tuple or one scope tuple per class — all carrying the +// expiration trait, so an expired token fails every check with no cleanup +// job required. TOUCH keeps re-mints idempotent. +// +// TOUCH cuts the other way for a ROLE CHANGE: re-minting the SAME TokenID +// with a DIFFERENT role TOUCHes a second role relation alongside the first, +// and the ladder then grants the UNION of both roles. Callers must always +// use a fresh TokenID (the mint path does) or call DeleteAccessTokenTuples +// first — never re-grant an existing id with a new role. +func (c *Client) WriteAccessTokenGrant(ctx context.Context, g AccessTokenGrant) error { + if g.TokenID == "" || g.Owner.IsZero() { + return fmt.Errorf("access token grant for %q: requires token id and owner", g.TokenID) + } + if g.ExpiresAt.IsZero() { + return fmt.Errorf("access token grant for %q: requires an expiry", g.TokenID) + } + roleRel, err := accesstoken.RoleRelation(g.Role) + if err != nil { + return fmt.Errorf("access token grant for %q: %w", g.TokenID, err) + } + if !g.Unfiltered && len(g.ScopeClasses) == 0 { + return fmt.Errorf("access token grant for %q: requires scope classes or Unfiltered", g.TokenID) + } + + expires := timestamppb.New(g.ExpiresAt) + tokenRes := &v1.ObjectReference{ObjectType: accesstoken.ObjectType, ObjectId: g.TokenID} + + updates := []*v1.RelationshipUpdate{{ + Operation: v1.RelationshipUpdate_OPERATION_TOUCH, + Relationship: &v1.Relationship{ + Resource: tokenRes, + Relation: roleRel, + Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "user", ObjectId: g.Owner.String()}}, + OptionalExpiresAt: expires, + }, + }} + + scopeSubjectIDs := g.ScopeClasses + if g.Unfiltered { + scopeSubjectIDs = []string{wildcardSubjectID} + } + for _, classID := range scopeSubjectIDs { + updates = append(updates, &v1.RelationshipUpdate{ + Operation: v1.RelationshipUpdate_OPERATION_TOUCH, + Relationship: &v1.Relationship{ + Resource: tokenRes, + Relation: accesstoken.RelationScopeClass, + Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "agentclass", ObjectId: classID}}, + OptionalExpiresAt: expires, + }, + }) + } + + if _, err := c.cl.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{Updates: updates}); err != nil { + return fmt.Errorf("write access token grant for %s: %w", g.TokenID, err) + } + return nil +} + +// DeleteAccessTokenTuples removes every relationship on the token resource — +// revocation, with the same "revoked on the next fully-consistent check" +// semantics externaltoken uses elsewhere in this package. Idempotent: +// deleting an absent token's tuples is a no-op. +func (c *Client) DeleteAccessTokenTuples(ctx context.Context, tokenID string) error { + if tokenID == "" { + return fmt.Errorf("delete access token tuples: empty token id") + } + if _, err := c.cl.DeleteRelationships(ctx, &v1.DeleteRelationshipsRequest{ + RelationshipFilter: &v1.RelationshipFilter{ + ResourceType: accesstoken.ObjectType, + OptionalResourceId: tokenID, + }, + }); err != nil { + return fmt.Errorf("delete access token tuples for %s: %w", tokenID, err) + } + return nil +} + +// AccessTokenCheck is one authorized operation attempted through an access +// token: the token being presented, who it claims to act as, the permission +// the caller wants, and the resource (plus its owning agentclass) the +// permission is being checked against. +type AccessTokenCheck struct { + TokenID string + Owner identity.CanonicalUserID + Permission string // the $sameperm name, e.g. "read_transcript" — leg 1 (token mirror) + ResourceType string // "agentsession" | "artifact" | "agentclass" | "memory_entry" + ResourceID string + ClassID string // agentclass object id "ns/name" for leg 2 + // ResourcePermission, when set, is the permission leg 3 checks on the + // RESOURCE, decoupled from the mirror name leg 1 checks on the token. + // Empty means "same as Permission" — the common case, where the $sameperm + // mirror name and the resource's own permission are literally the same + // string. It exists for the mirrors whose resource-side permission lives + // under a different name than the token-side one (e.g. a "view" or "read" + // mirror whose agentsession-level truth is read_transcript — see + // mcpfront's authorizeSessionOp for the mapping and its rationale). + ResourcePermission string +} + +// effectiveResourcePermission is the permission leg 3 actually checks: +// ResourcePermission when set, else Permission. +func (chk AccessTokenCheck) effectiveResourcePermission() string { + if chk.ResourcePermission != "" { + return chk.ResourcePermission + } + return chk.Permission +} + +// AccessTokenDecision is the three-legged intersection CheckAccessTokenOp +// answers: does the token itself grant this permission (its role mirror), +// does its scope cover the resource's class, and does the owner it acts as +// actually hold the permission on the resource. All three must hold — a +// token narrows its owner's standing, it never widens it. +type AccessTokenDecision struct{ TokenGrants, ScopeCovers, OwnerHas bool } + +// Allowed reports whether all three legs held. +func (d AccessTokenDecision) Allowed() bool { return d.TokenGrants && d.ScopeCovers && d.OwnerHas } + +// CheckAccessTokenOp runs the three-legged intersection as one +// CheckBulkPermissions round trip. Responses are matched by echoed request +// identity (resource type/id + permission), never by index; anything +// unmatched or errored stays false — deny-on-mangle, the spicedbauthorizer +// discipline (pkg/memory/spicedbauthorizer/authorizer.go's filterAllowed). +func (c *Client) CheckAccessTokenOp(ctx context.Context, chk AccessTokenCheck, fullyConsistent bool) (AccessTokenDecision, error) { + var dec AccessTokenDecision + if chk.TokenID == "" || chk.Owner.IsZero() || chk.Permission == "" || + chk.ResourceType == "" || chk.ResourceID == "" || chk.ClassID == "" { + return dec, fmt.Errorf("access token check for %q: requires token, owner, permission, resource, and class", chk.TokenID) + } + // Leg 1 and leg 2 are told apart below by permission name alone (both + // address the token resource), so a check FOR "covers" itself would be + // attributed to the wrong leg. "owner" is likewise an internal permission, + // never a $sameperm mirror. Refuse both by intent rather than letting the + // switch mis-attribute them by coincidence — and guard BOTH fields: a + // ResourcePermission of "covers"/"owner" would aim leg 3 at an internal + // permission just as wrongly as the mirror name would. + if chk.Permission == accesstoken.PermissionCovers || chk.Permission == accesstoken.PermissionOwner { + return dec, fmt.Errorf("access token check for %q: permission %q is internal to the token definition, not a checkable mirror", chk.TokenID, chk.Permission) + } + if chk.ResourcePermission == accesstoken.PermissionCovers || chk.ResourcePermission == accesstoken.PermissionOwner { + return dec, fmt.Errorf("access token check for %q: resource permission %q is internal to the token definition, not a checkable permission", chk.TokenID, chk.ResourcePermission) + } + + ownerSubj := &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "user", ObjectId: chk.Owner.String()}} + items := []*v1.CheckBulkPermissionsRequestItem{ + { + Resource: &v1.ObjectReference{ObjectType: accesstoken.ObjectType, ObjectId: chk.TokenID}, + Permission: chk.Permission, + Subject: ownerSubj, + }, + { + Resource: &v1.ObjectReference{ObjectType: accesstoken.ObjectType, ObjectId: chk.TokenID}, + Permission: accesstoken.PermissionCovers, + Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "agentclass", ObjectId: chk.ClassID}}, + }, + { + Resource: &v1.ObjectReference{ObjectType: chk.ResourceType, ObjectId: chk.ResourceID}, + Permission: chk.effectiveResourcePermission(), + Subject: ownerSubj, + }, + } + + resp, err := c.cl.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ + Consistency: consistencyFor(fullyConsistent), + Items: items, + }) + if err != nil { + return dec, fmt.Errorf("access token bulk check for %s: %w", chk.TokenID, err) + } + + for _, pair := range resp.GetPairs() { + req := pair.GetRequest() + if perr := pair.GetError(); perr != nil { + log.FromContext(ctx).Info("access token bulk check pair errored; leg denied", + "tokenID", chk.TokenID, "resourceType", req.GetResource().GetObjectType(), + "permission", req.GetPermission(), "code", perr.GetCode(), "err", perr.GetMessage()) + continue + } + item := pair.GetItem() + if item == nil || item.GetPermissionship() != v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { + continue + } + switch { + case req.GetResource().GetObjectType() == accesstoken.ObjectType && + req.GetResource().GetObjectId() == chk.TokenID && + req.GetPermission() == accesstoken.PermissionCovers: + dec.ScopeCovers = true + case req.GetResource().GetObjectType() == accesstoken.ObjectType && + req.GetResource().GetObjectId() == chk.TokenID: + dec.TokenGrants = true + case req.GetResource().GetObjectType() == chk.ResourceType && + req.GetResource().GetObjectId() == chk.ResourceID: + dec.OwnerHas = true + } + } + return dec, nil +} + +// CheckAccessTokenMirror answers leg 1 alone (token#@user:) +// — used by enumeration ops (list_sessions) that filter legs 2/3 per result +// rather than per-call via CheckAccessTokenOp. +func (c *Client) CheckAccessTokenMirror(ctx context.Context, tokenID string, owner identity.CanonicalUserID, permission string, fullyConsistent bool) (bool, error) { + return c.CheckOnResource(ctx, accesstoken.ObjectType, tokenID, permission, owner, fullyConsistent) +} + +// FilterAccessTokenCoveredClasses bulk-answers leg 2 (token#covers@agentclass) +// for a class set in one round trip. Results are matched by the subject +// object id echoed on each pair's request, never by index. +func (c *Client) FilterAccessTokenCoveredClasses(ctx context.Context, tokenID string, classIDs []string, fullyConsistent bool) (map[string]bool, error) { + out := make(map[string]bool, len(classIDs)) + if len(classIDs) == 0 { + return out, nil + } + + items := make([]*v1.CheckBulkPermissionsRequestItem, 0, len(classIDs)) + for _, classID := range classIDs { + items = append(items, &v1.CheckBulkPermissionsRequestItem{ + Resource: &v1.ObjectReference{ObjectType: accesstoken.ObjectType, ObjectId: tokenID}, + Permission: accesstoken.PermissionCovers, + Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "agentclass", ObjectId: classID}}, + }) + } + + resp, err := c.cl.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ + Consistency: consistencyFor(fullyConsistent), + Items: items, + }) + if err != nil { + return nil, fmt.Errorf("filter covered classes for %s: %w", tokenID, err) + } + + for _, pair := range resp.GetPairs() { + if perr := pair.GetError(); perr != nil { + log.FromContext(ctx).Info("filter covered classes bulk check pair errored; class denied", + "tokenID", tokenID, "classID", pair.GetRequest().GetSubject().GetObject().GetObjectId(), + "code", perr.GetCode(), "err", perr.GetMessage()) + continue + } + item := pair.GetItem() + if item == nil || item.GetPermissionship() != v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION { + continue + } + classID := pair.GetRequest().GetSubject().GetObject().GetObjectId() + if classID == "" { + continue + } + out[classID] = true + } + return out, nil +} + +// ReadAccessTokenGrant reads the token's tuples back for display (the admin +// tokens page). found=false when no tuples exist (revoked or expired-out — +// SpiceDB drops expired relationships from reads once their expiration has +// elapsed). +func (c *Client) ReadAccessTokenGrant(ctx context.Context, tokenID string) (AccessTokenGrant, bool, error) { + g := AccessTokenGrant{TokenID: tokenID} + stream, err := c.cl.ReadRelationships(ctx, &v1.ReadRelationshipsRequest{ + Consistency: consistencyFor(true), + RelationshipFilter: &v1.RelationshipFilter{ + ResourceType: accesstoken.ObjectType, + OptionalResourceId: tokenID, + }, + }) + if err != nil { + return g, false, fmt.Errorf("read access token grant for %s: %w", tokenID, err) + } + + found := false + for { + resp, rerr := stream.Recv() + if rerr != nil { + if errors.Is(rerr, io.EOF) { + break + } + return g, false, fmt.Errorf("read access token grant stream for %s: %w", tokenID, rerr) + } + rel := resp.GetRelationship() + found = true + switch rel.GetRelation() { + case accesstoken.RelationRoleRead: + g.Role = accesstoken.RoleRead + g.Owner = identity.CanonicalFromTrusted(rel.GetSubject().GetObject().GetObjectId(), "read back from the accesstoken role tuple SpiceDB stores") + case accesstoken.RelationRoleInteract: + g.Role = accesstoken.RoleInteract + g.Owner = identity.CanonicalFromTrusted(rel.GetSubject().GetObject().GetObjectId(), "read back from the accesstoken role tuple SpiceDB stores") + case accesstoken.RelationRoleFull: + g.Role = accesstoken.RoleFull + g.Owner = identity.CanonicalFromTrusted(rel.GetSubject().GetObject().GetObjectId(), "read back from the accesstoken role tuple SpiceDB stores") + case accesstoken.RelationScopeClass: + id := rel.GetSubject().GetObject().GetObjectId() + if id == wildcardSubjectID { + g.Unfiltered = true + } else { + g.ScopeClasses = append(g.ScopeClasses, id) + } + } + if exp := rel.GetOptionalExpiresAt(); exp != nil { + g.ExpiresAt = exp.AsTime() + } + } + return g, found, nil +} + +// TouchSessionOwner writes agentsession:/#owner@user: — +// the typed convenience wrapper over TouchOwner for the common case of a +// bare canonical user owner, needed by this package's own integration tests +// and by Task 13's fixture (test/e2e/steelthread and friends) to stand up an +// owner tuple without constructing a "user:" subjectRef string by hand. +func (c *Client) TouchSessionOwner(ctx context.Context, ns, name string, canonicalID identity.CanonicalUserID) error { + return c.TouchOwner(ctx, ns, name, "user:"+canonicalID.String()) +} diff --git a/pkg/authz/spicedb/client_accesstoken_integration_test.go b/pkg/authz/spicedb/client_accesstoken_integration_test.go new file mode 100644 index 00000000..6b7fb322 --- /dev/null +++ b/pkg/authz/spicedb/client_accesstoken_integration_test.go @@ -0,0 +1,123 @@ +//go:build integration + +package spicedb + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +func TestAccessTokenGrantRoundTrip(t *testing.T) { + c := newIntegrationClient(t) + ctx := context.Background() + owner := uniqCanon(t, "alice") + tokenID := uniq(t, "tok") + classID := uniq(t, "default") + "/demo-agent" + + g := AccessTokenGrant{ + TokenID: tokenID, Owner: owner, Role: accesstoken.RoleRead, + ScopeClasses: []string{classID}, ExpiresAt: time.Now().Add(time.Hour), + } + require.NoError(t, c.WriteAccessTokenGrant(ctx, g)) + + // Read-role mirror grants read_transcript, refuses interact. + ok, err := c.CheckAccessTokenMirror(ctx, tokenID, owner, "read_transcript", true) + require.NoError(t, err) + assert.True(t, ok, "read role must grant read_transcript mirror") + ok, err = c.CheckAccessTokenMirror(ctx, tokenID, owner, "interact", true) + require.NoError(t, err) + assert.False(t, ok, "read role must not grant interact mirror") + + // Scope covers the named class only. + covered, err := c.FilterAccessTokenCoveredClasses(ctx, tokenID, []string{classID, "default/other-agent"}, true) + require.NoError(t, err) + assert.True(t, covered[classID]) + assert.False(t, covered["default/other-agent"]) + + // Read-back for display. + got, found, err := c.ReadAccessTokenGrant(ctx, tokenID) + require.NoError(t, err) + require.True(t, found) + assert.Equal(t, accesstoken.RoleRead, got.Role) + assert.Equal(t, []string{classID}, got.ScopeClasses) + assert.False(t, got.Unfiltered) + + // Delete revokes everything; read-back finds nothing. + require.NoError(t, c.DeleteAccessTokenTuples(ctx, tokenID)) + ok, err = c.CheckAccessTokenMirror(ctx, tokenID, owner, "read_transcript", true) + require.NoError(t, err) + assert.False(t, ok, "deleted token must fail the mirror check") + _, found, err = c.ReadAccessTokenGrant(ctx, tokenID) + require.NoError(t, err) + assert.False(t, found) +} + +func TestAccessTokenWildcardAndFullRole(t *testing.T) { + c := newIntegrationClient(t) + ctx := context.Background() + owner := uniqCanon(t, "bob") + tokenID := uniq(t, "tok") + + g := AccessTokenGrant{ + TokenID: tokenID, Owner: owner, Role: accesstoken.RoleFull, + Unfiltered: true, ExpiresAt: time.Now().Add(time.Hour), + } + require.NoError(t, c.WriteAccessTokenGrant(ctx, g)) + + // Full role satisfies the whole ladder. + for _, perm := range accesstoken.MirrorPermissions() { + ok, err := c.CheckAccessTokenMirror(ctx, tokenID, owner, perm, true) + require.NoError(t, err) + assert.True(t, ok, "full role must grant %s", perm) + } + // Wildcard covers any class. + covered, err := c.FilterAccessTokenCoveredClasses(ctx, tokenID, []string{"ns-a/x", "ns-b/y"}, true) + require.NoError(t, err) + assert.True(t, covered["ns-a/x"]) + assert.True(t, covered["ns-b/y"]) + // A different user is not the owner. + ok, err := c.CheckAccessTokenMirror(ctx, tokenID, uniqCanon(t, "mallory"), "read_transcript", true) + require.NoError(t, err) + assert.False(t, ok, "mirror check binds to the owner subject") +} + +func TestCheckAccessTokenOpThreeLegs(t *testing.T) { + c := newIntegrationClient(t) + ctx := context.Background() + owner := uniqCanon(t, "carol") + tokenID := uniq(t, "tok") + ns, sess := uniq(t, "ns"), "demo-session" + classID := uniq(t, "ns") + "/demo-agent" + + require.NoError(t, c.WriteAccessTokenGrant(ctx, AccessTokenGrant{ + TokenID: tokenID, Owner: owner, Role: accesstoken.RoleRead, + ScopeClasses: []string{classID}, ExpiresAt: time.Now().Add(time.Hour), + })) + // Owner can interact (owner tuple on the session) — reuse an existing write helper. + require.NoError(t, c.TouchSessionOwner(ctx, ns, sess, owner)) + + dec, err := c.CheckAccessTokenOp(ctx, AccessTokenCheck{ + TokenID: tokenID, Owner: owner, Permission: "read_transcript", + ResourceType: "agentsession", ResourceID: ns + "/" + sess, ClassID: classID, + }, true) + require.NoError(t, err) + assert.True(t, dec.TokenGrants) + assert.True(t, dec.ScopeCovers) + assert.True(t, dec.OwnerHas) + assert.True(t, dec.Allowed()) + + // Same op against an out-of-scope class: only leg 2 flips. + dec, err = c.CheckAccessTokenOp(ctx, AccessTokenCheck{ + TokenID: tokenID, Owner: owner, Permission: "read_transcript", + ResourceType: "agentsession", ResourceID: ns + "/" + sess, ClassID: "elsewhere/other", + }, true) + require.NoError(t, err) + assert.False(t, dec.ScopeCovers) + assert.False(t, dec.Allowed()) +} diff --git a/pkg/authz/spicedb/client_accesstoken_test.go b/pkg/authz/spicedb/client_accesstoken_test.go new file mode 100644 index 00000000..8be28228 --- /dev/null +++ b/pkg/authz/spicedb/client_accesstoken_test.go @@ -0,0 +1,496 @@ +// Pure-Go unit tests (no build tag, no real SpiceDB) for the access-token +// client helpers. The integration file proves the SpiceDB semantics end to +// end; these pin what a fake can pin without Docker: +// +// - WriteAccessTokenGrant's validation guards and tuple SHAPE — one +// WriteRelationships call, every update a TOUCH, every relationship +// carrying OptionalExpiresAt, the role relation matching the role, and +// the wildcard scope tuple when Unfiltered. +// - CheckAccessTokenOp's response matching by echoed request identity — +// the deny-on-mangle discipline: an omitted pair, an errored pair, or a +// mangled echo leaves its leg false instead of panicking or leaking. +// - FilterAccessTokenCoveredClasses' covered-subset mapping, with +// unmatched classes absent/false. +// +// Follows the fakePermClient idiom from client_artifact_write_test.go: embed +// the interface nil and override only the methods under test, so any other +// call panics rather than silently succeeding. +package spicedb + +import ( + "context" + "testing" + "time" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + authzed "github.com/authzed/authzed-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + rpcstatus "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" + + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +// fakeAccessTokenPermClient captures WriteRelationships requests and answers +// CheckBulkPermissions from a canned respond func. Only those two methods are +// overridden — anything else panics on the nil embed, keeping each test +// honest about which RPCs its code path issues. +type fakeAccessTokenPermClient struct { + v1.PermissionsServiceClient + capturedWrites []*v1.WriteRelationshipsRequest + respond func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse +} + +func (f *fakeAccessTokenPermClient) WriteRelationships(_ context.Context, in *v1.WriteRelationshipsRequest, _ ...grpc.CallOption) (*v1.WriteRelationshipsResponse, error) { + f.capturedWrites = append(f.capturedWrites, in) + return &v1.WriteRelationshipsResponse{}, nil +} + +func (f *fakeAccessTokenPermClient) CheckBulkPermissions(_ context.Context, req *v1.CheckBulkPermissionsRequest, _ ...grpc.CallOption) (*v1.CheckBulkPermissionsResponse, error) { + return f.respond(req), nil +} + +func newAccessTokenFakeClient(t *testing.T) (*Client, *fakeAccessTokenPermClient) { + t.Helper() + fake := &fakeAccessTokenPermClient{} + return &Client{cl: &authzed.Client{PermissionsServiceClient: fake}}, fake +} + +// grantedPair echoes item back as a HAS_PERMISSION pair — what a well-behaved +// SpiceDB returns for an allowed check. +func grantedPair(item *v1.CheckBulkPermissionsRequestItem) *v1.CheckBulkPermissionsPair { + return &v1.CheckBulkPermissionsPair{ + Request: item, + Response: &v1.CheckBulkPermissionsPair_Item{Item: &v1.CheckBulkPermissionsResponseItem{ + Permissionship: v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, + }}, + } +} + +func deniedPair(item *v1.CheckBulkPermissionsRequestItem) *v1.CheckBulkPermissionsPair { + return &v1.CheckBulkPermissionsPair{ + Request: item, + Response: &v1.CheckBulkPermissionsPair_Item{Item: &v1.CheckBulkPermissionsResponseItem{ + Permissionship: v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION, + }}, + } +} + +func erroredPair(item *v1.CheckBulkPermissionsRequestItem) *v1.CheckBulkPermissionsPair { + return &v1.CheckBulkPermissionsPair{ + Request: item, + Response: &v1.CheckBulkPermissionsPair_Error{Error: &rpcstatus.Status{Code: 13, Message: "backend mangled this item"}}, + } +} + +func TestAccessTokenWriteGrant_ValidationGuards(t *testing.T) { + owner := identity.CanonicalFromTrusted("alice", "test fixture") + valid := AccessTokenGrant{ + TokenID: "at-1", Owner: owner, Role: accesstoken.RoleRead, + ScopeClasses: []string{"default/demo-agent"}, ExpiresAt: time.Now().Add(time.Hour), + } + + cases := []struct { + name string + mutate func(g *AccessTokenGrant) + wantErr string + }{ + { + name: "missing token id: refused before any write", + mutate: func(g *AccessTokenGrant) { g.TokenID = "" }, + wantErr: "requires token id and owner", + }, + { + name: "missing owner: refused before any write", + mutate: func(g *AccessTokenGrant) { g.Owner = identity.CanonicalUserID{} }, + wantErr: "requires token id and owner", + }, + { + name: "missing expiry: refused before any write", + mutate: func(g *AccessTokenGrant) { g.ExpiresAt = time.Time{} }, + wantErr: "requires an expiry", + }, + { + name: "unknown role: refused before any write", + mutate: func(g *AccessTokenGrant) { g.Role = "superadmin" }, + wantErr: "unknown access-token role", + }, + { + name: "no scope classes and not Unfiltered: refused before any write", + mutate: func(g *AccessTokenGrant) { g.ScopeClasses = nil }, + wantErr: "requires scope classes or Unfiltered", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + g := valid + tc.mutate(&g) + err := c.WriteAccessTokenGrant(context.Background(), g) + require.Error(t, err) + assert.ErrorContains(t, err, tc.wantErr) + assert.Empty(t, fake.capturedWrites, "a refused grant must issue no WriteRelationships call") + }) + } +} + +func TestAccessTokenWriteGrant_ScopedTupleShape(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + owner := identity.CanonicalFromTrusted("alice", "test fixture") + expiry := time.Now().Add(time.Hour).Truncate(time.Second) + + require.NoError(t, c.WriteAccessTokenGrant(context.Background(), AccessTokenGrant{ + TokenID: "at-1", Owner: owner, Role: accesstoken.RoleInteract, + ScopeClasses: []string{"default/demo-agent", "other-ns/second-agent"}, + ExpiresAt: expiry, + })) + + require.Len(t, fake.capturedWrites, 1, "the whole grant must be ONE WriteRelationships call") + updates := fake.capturedWrites[0].GetUpdates() + require.Len(t, updates, 3, "one role tuple plus one scope tuple per class") + + // Index by (relation, subject) so update order is not pinned. + type tuple struct{ rel, sub string } + got := map[tuple]*v1.Relationship{} + for _, u := range updates { + assert.Equal(t, v1.RelationshipUpdate_OPERATION_TOUCH, u.GetOperation(), "every update must be a TOUCH") + rel := u.GetRelationship() + assert.Equal(t, accesstoken.ObjectType, rel.GetResource().GetObjectType()) + assert.Equal(t, "at-1", rel.GetResource().GetObjectId()) + require.NotNil(t, rel.GetOptionalExpiresAt(), "every relationship must carry OptionalExpiresAt") + assert.True(t, rel.GetOptionalExpiresAt().AsTime().Equal(expiry), "the expiry must be the grant's ExpiresAt") + sub := rel.GetSubject().GetObject() + got[tuple{rel.GetRelation(), sub.GetObjectType() + ":" + sub.GetObjectId()}] = rel + } + + assert.Contains(t, got, tuple{accesstoken.RelationRoleInteract, "user:alice"}, + "the role relation must match the role (interact -> role_interact)") + assert.Contains(t, got, tuple{accesstoken.RelationScopeClass, "agentclass:default/demo-agent"}) + assert.Contains(t, got, tuple{accesstoken.RelationScopeClass, "agentclass:other-ns/second-agent"}) +} + +func TestAccessTokenWriteGrant_UnfilteredWritesTheWildcardScopeTuple(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + owner := identity.CanonicalFromTrusted("bob", "test fixture") + + require.NoError(t, c.WriteAccessTokenGrant(context.Background(), AccessTokenGrant{ + TokenID: "at-2", Owner: owner, Role: accesstoken.RoleFull, + // ScopeClasses deliberately set alongside Unfiltered: the wildcard + // must REPLACE them, not join them, or a revoked class filter would + // linger as extra tuples. + ScopeClasses: []string{"default/demo-agent"}, + Unfiltered: true, + ExpiresAt: time.Now().Add(time.Hour), + })) + + require.Len(t, fake.capturedWrites, 1, "the whole grant must be ONE WriteRelationships call") + updates := fake.capturedWrites[0].GetUpdates() + require.Len(t, updates, 2, "one role tuple plus exactly one wildcard scope tuple") + + var scope *v1.Relationship + for _, u := range updates { + if u.GetRelationship().GetRelation() == accesstoken.RelationScopeClass { + scope = u.GetRelationship() + } + } + require.NotNil(t, scope, "the wildcard scope tuple must be written") + assert.Equal(t, "agentclass", scope.GetSubject().GetObject().GetObjectType()) + assert.Equal(t, "*", scope.GetSubject().GetObject().GetObjectId(), "Unfiltered means the agentclass:* wildcard subject") + assert.NotNil(t, scope.GetOptionalExpiresAt(), "the wildcard tuple expires with the grant too") +} + +// checkFixture is the valid three-leg check the CheckAccessTokenOp tests +// perturb per case. +func checkFixture() AccessTokenCheck { + return AccessTokenCheck{ + TokenID: "at-1", + Owner: identity.CanonicalFromTrusted("carol", "test fixture"), + Permission: "read_transcript", + ResourceType: "agentsession", + ResourceID: "default/demo-session", + ClassID: "default/demo-agent", + } +} + +func TestAccessTokenCheckOp_ResponseMatching(t *testing.T) { + cases := []struct { + name string + respond func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse + want AccessTokenDecision + }{ + { + name: "all three legs HAS_PERMISSION: Allowed, each leg attributed to its own field", + respond: func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + resp.Pairs = append(resp.Pairs, grantedPair(item)) + } + return resp + }, + want: AccessTokenDecision{TokenGrants: true, ScopeCovers: true, OwnerHas: true}, + }, + { + name: "covers pair omitted from the response: ScopeCovers stays false (deny-on-mangle)", + respond: func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + if item.GetPermission() == accesstoken.PermissionCovers { + continue + } + resp.Pairs = append(resp.Pairs, grantedPair(item)) + } + return resp + }, + want: AccessTokenDecision{TokenGrants: true, ScopeCovers: false, OwnerHas: true}, + }, + { + name: "mirror pair carries a pair-level error: TokenGrants stays false, other legs unaffected", + respond: func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + if item.GetResource().GetObjectType() == accesstoken.ObjectType && + item.GetPermission() != accesstoken.PermissionCovers { + resp.Pairs = append(resp.Pairs, erroredPair(item)) + continue + } + resp.Pairs = append(resp.Pairs, grantedPair(item)) + } + return resp + }, + want: AccessTokenDecision{TokenGrants: false, ScopeCovers: true, OwnerHas: true}, + }, + { + name: "resource leg NO_PERMISSION: only OwnerHas false — a token cannot outrank its owner", + respond: func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + if item.GetResource().GetObjectType() == "agentsession" { + resp.Pairs = append(resp.Pairs, deniedPair(item)) + continue + } + resp.Pairs = append(resp.Pairs, grantedPair(item)) + } + return resp + }, + want: AccessTokenDecision{TokenGrants: true, ScopeCovers: true, OwnerHas: false}, + }, + { + name: "a granted pair echoing an unrecognized resource: no leg flips (deny-on-mangle)", + respond: func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + return &v1.CheckBulkPermissionsResponse{Pairs: []*v1.CheckBulkPermissionsPair{ + grantedPair(&v1.CheckBulkPermissionsRequestItem{ + Resource: &v1.ObjectReference{ObjectType: "agentsession", ObjectId: "elsewhere/other"}, + Permission: "read_transcript", + }), + }} + }, + want: AccessTokenDecision{}, + }, + { + name: "empty response: nothing granted", + respond: func(_ *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + return &v1.CheckBulkPermissionsResponse{} + }, + want: AccessTokenDecision{}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + fake.respond = tc.respond + dec, err := c.CheckAccessTokenOp(context.Background(), checkFixture(), true) + require.NoError(t, err) + assert.Equal(t, tc.want, dec) + assert.Equal(t, tc.want.TokenGrants && tc.want.ScopeCovers && tc.want.OwnerHas, dec.Allowed()) + }) + } +} + +// TestAccessTokenCheckOp_ResourcePermissionDecouplesLeg3 pins the +// ResourcePermission contract: leg 1 (the token mirror) keeps chk.Permission, +// leg 3 (the resource check) uses ResourcePermission when set, and each +// granted pair is still attributed to its own leg — the "view" mirror whose +// agentsession-level truth is read_transcript (mcpfront's authorizeSessionOp +// mapping) is the motivating caller. +func TestAccessTokenCheckOp_ResourcePermissionDecouplesLeg3(t *testing.T) { + legPermissions := func(req *v1.CheckBulkPermissionsRequest) (mirror, resource string) { + for _, item := range req.GetItems() { + switch { + case item.GetResource().GetObjectType() == accesstoken.ObjectType && + item.GetPermission() != accesstoken.PermissionCovers: + mirror = item.GetPermission() + case item.GetResource().GetObjectType() != accesstoken.ObjectType: + resource = item.GetPermission() + } + } + return mirror, resource + } + + t.Run("view mirror + read_transcript resource perm: legs carry different names, each attributed correctly", func(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + var captured *v1.CheckBulkPermissionsRequest + fake.respond = func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + captured = req + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + resp.Pairs = append(resp.Pairs, grantedPair(item)) + } + return resp + } + + chk := checkFixture() + chk.Permission = "view" + chk.ResourcePermission = "read_transcript" + dec, err := c.CheckAccessTokenOp(context.Background(), chk, true) + require.NoError(t, err) + assert.Equal(t, AccessTokenDecision{TokenGrants: true, ScopeCovers: true, OwnerHas: true}, dec) + + require.NotNil(t, captured) + require.Len(t, captured.GetItems(), 3) + mirror, resource := legPermissions(captured) + assert.Equal(t, "view", mirror, "leg 1 must keep the token mirror name") + assert.Equal(t, "read_transcript", resource, "leg 3 must check the resource's own permission") + }) + + t.Run("only the resource leg granted: OwnerHas alone flips, no cross-leg bleed", func(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + fake.respond = func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + if item.GetResource().GetObjectType() == "agentsession" { + resp.Pairs = append(resp.Pairs, grantedPair(item)) + continue + } + resp.Pairs = append(resp.Pairs, deniedPair(item)) + } + return resp + } + + chk := checkFixture() + chk.Permission = "view" + chk.ResourcePermission = "read_transcript" + dec, err := c.CheckAccessTokenOp(context.Background(), chk, true) + require.NoError(t, err) + assert.Equal(t, AccessTokenDecision{OwnerHas: true}, dec) + assert.False(t, dec.Allowed()) + }) + + t.Run("empty ResourcePermission: leg 3 defaults to Permission, exactly the pre-field behavior", func(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + var captured *v1.CheckBulkPermissionsRequest + fake.respond = func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + captured = req + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + resp.Pairs = append(resp.Pairs, grantedPair(item)) + } + return resp + } + + dec, err := c.CheckAccessTokenOp(context.Background(), checkFixture(), true) + require.NoError(t, err) + assert.True(t, dec.Allowed()) + + require.NotNil(t, captured) + mirror, resource := legPermissions(captured) + assert.Equal(t, "read_transcript", mirror) + assert.Equal(t, "read_transcript", resource, "an unset ResourcePermission must leave leg 3 on Permission") + }) +} + +func TestAccessTokenCheckOp_RefusesIncompleteAndInternalPermissions(t *testing.T) { + cases := []struct { + name string + mutate func(chk *AccessTokenCheck) + wantErr string + }{ + { + name: "missing class id: refused", + mutate: func(chk *AccessTokenCheck) { chk.ClassID = "" }, + wantErr: "requires token, owner, permission, resource, and class", + }, + { + name: "missing owner: refused", + mutate: func(chk *AccessTokenCheck) { chk.Owner = identity.CanonicalUserID{} }, + wantErr: "requires token, owner, permission, resource, and class", + }, + { + name: "permission \"covers\" is internal: refused, never mis-attributed to leg 2", + mutate: func(chk *AccessTokenCheck) { chk.Permission = accesstoken.PermissionCovers }, + wantErr: "internal to the token definition", + }, + { + name: "permission \"owner\" is internal: refused", + mutate: func(chk *AccessTokenCheck) { chk.Permission = accesstoken.PermissionOwner }, + wantErr: "internal to the token definition", + }, + { + name: "resource permission \"covers\" is internal: refused, the guard covers both fields", + mutate: func(chk *AccessTokenCheck) { chk.ResourcePermission = accesstoken.PermissionCovers }, + wantErr: "internal to the token definition", + }, + { + name: "resource permission \"owner\" is internal: refused", + mutate: func(chk *AccessTokenCheck) { chk.ResourcePermission = accesstoken.PermissionOwner }, + wantErr: "internal to the token definition", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + fake.respond = func(_ *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + t.Fatal("a refused check must never reach SpiceDB") + return nil + } + chk := checkFixture() + tc.mutate(&chk) + dec, err := c.CheckAccessTokenOp(context.Background(), chk, true) + require.Error(t, err) + assert.ErrorContains(t, err, tc.wantErr) + assert.False(t, dec.Allowed()) + }) + } +} + +func TestAccessTokenFilterCoveredClasses_CoveredSubsetMapping(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + fake.respond = func(req *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + resp := &v1.CheckBulkPermissionsResponse{} + for _, item := range req.GetItems() { + switch item.GetSubject().GetObject().GetObjectId() { + case "default/covered-agent": + resp.Pairs = append(resp.Pairs, grantedPair(item)) + case "default/denied-agent": + resp.Pairs = append(resp.Pairs, deniedPair(item)) + case "default/errored-agent": + resp.Pairs = append(resp.Pairs, erroredPair(item)) + default: + // default/omitted-agent: no pair at all — a mangled response. + } + } + return resp + } + + covered, err := c.FilterAccessTokenCoveredClasses(context.Background(), "at-1", + []string{"default/covered-agent", "default/denied-agent", "default/errored-agent", "default/omitted-agent"}, true) + require.NoError(t, err) + + assert.True(t, covered["default/covered-agent"]) + assert.False(t, covered["default/denied-agent"], "NO_PERMISSION stays false") + assert.False(t, covered["default/errored-agent"], "a pair-level error stays false (deny-on-mangle)") + assert.False(t, covered["default/omitted-agent"], "an omitted pair stays false (deny-on-mangle)") + assert.Len(t, covered, 1, "only covered classes land in the map") +} + +func TestAccessTokenFilterCoveredClasses_EmptyInputSkipsTheRoundTrip(t *testing.T) { + c, fake := newAccessTokenFakeClient(t) + fake.respond = func(_ *v1.CheckBulkPermissionsRequest) *v1.CheckBulkPermissionsResponse { + t.Fatal("an empty class set must never reach SpiceDB") + return nil + } + covered, err := c.FilterAccessTokenCoveredClasses(context.Background(), "at-1", nil, true) + require.NoError(t, err) + assert.Empty(t, covered) +} diff --git a/pkg/authz/spicedb/schema/schema.zed b/pkg/authz/spicedb/schema/schema.zed index d783b773..4939db43 100644 --- a/pkg/authz/spicedb/schema/schema.zed +++ b/pkg/authz/spicedb/schema/schema.zed @@ -185,6 +185,51 @@ definition github_user { // token on the next fully-consistent use_token check. definition externaltoken {} +// accesstoken is an INBOUND delegated credential minted by identityd at OAuth +// consent. It acts AS its owner, narrowed to a role and an optional resource +// filter. Contrast externaltoken above: an OUTBOUND credential a session is +// authorized to use. Authorization truth for a token lives ENTIRELY here — +// the AccessToken CRD is only the authentication record (hash → token id). +// +// Exactly one role relation is written at mint; its subject is the owner, so +// a single tuple binds owner + role + (via the expiration trait) expiry. +// scope_class holds the agentclasses the token may touch — one wildcard +// tuple for an unfiltered token. +// +// The ladder permissions (can_*) are internal and never checked directly — +// the platform#can_admin pattern. Consumers check the $sameperm mirrors +// below, each named EXACTLY like the resource permission it delegates, so +// the checker fans "need P on R" into token#P + token#covers + R#P with no +// role→permission table anywhere in Go. Each mirror is spelled from the +// ladder rather than aliasing a sibling (the hold-vs-approve discipline) so +// any one can diverge without touching call sites. +definition accesstoken { + // Every relation here carries `with expiration`: ExpiresAt on the CRD is + // meaningless unless the tuple itself expires, and without the trait + // SpiceDB refuses a write that sets OptionalExpiresAt outright (it does + // not silently drop it) — caught by TestAccessTokenGrantRoundTrip + // ("subjects of type `user with expiration` are not allowed") the first + // time the client package tried to write a real grant. + relation role_read: user with expiration + relation role_interact: user with expiration + relation role_full: user with expiration + relation scope_class: agentclass with expiration | agentclass:* with expiration + + permission owner = role_read + role_interact + role_full + permission covers = scope_class + + permission can_full = role_full + permission can_interact = role_interact + can_full + permission can_read = role_read + can_interact + + permission read_transcript = can_read + permission read = can_read + permission view = can_read + permission interact = can_interact + permission approve = can_full + permission start_session = can_full +} + definition agentsession { // started_by records who initiated the session. It confers interact and // NOTHING else — see the interact/approve split below. @@ -509,6 +554,11 @@ definition platform { permission view_config = can_admin permission kill_session = can_admin permission install_agent = can_admin + // view_tokens / revoke_token gate the admin console's access-token page. + // Per-area permissions (not can_admin at the consumer) per this + // definition's contract above. + permission view_tokens = can_admin + permission revoke_token = can_admin // start_session is the platform-wide arm of the session-start gate: "may // start ANY agent", as opposed to agentclass#starter's "may start THIS // agent". diff --git a/pkg/authz/spicedb/schema/schema_test.go b/pkg/authz/spicedb/schema/schema_test.go index 05a62423..0b5db6cb 100644 --- a/pkg/authz/spicedb/schema/schema_test.go +++ b/pkg/authz/spicedb/schema/schema_test.go @@ -146,6 +146,43 @@ func TestAgentClassInteractorRelationAndPersonalizePermission(t *testing.T) { assert.Contains(t, authzschema.Schema, "permission can_personalize = interactor") } +// TestAccessTokenDefinition pins the accesstoken definition and platform +// token permissions required by the OAuth access-token flow. The accesstoken +// definition mints delegated credentials narrowed to a role and optional +// resource filter; the platform permissions gate the admin console's +// access-token management page. +// +// The four relation lines are pinned with requireExactRelationLine, not +// Contains, for the same subject-closure reason as +// TestAgentSessionInteractSubjectsAreClosed: widening a type union by +// appending (e.g. "user" → "user | service") leaves the original text as an +// unbroken prefix, so a substring check would keep passing while the token's +// role relations silently admitted new subject kinds. +func TestAccessTokenDefinition(t *testing.T) { + requireExactRelationLine(t, authzschema.Schema, "relation role_read: user with expiration") + requireExactRelationLine(t, authzschema.Schema, "relation role_interact: user with expiration") + requireExactRelationLine(t, authzschema.Schema, "relation role_full: user with expiration") + requireExactRelationLine(t, authzschema.Schema, "relation scope_class: agentclass with expiration | agentclass:* with expiration") + for _, want := range []string{ + "definition accesstoken {", + "permission covers = scope_class", + "permission owner = role_read + role_interact + role_full", + "permission can_full = role_full", + "permission can_interact = role_interact + can_full", + "permission can_read = role_read + can_interact", + "permission read_transcript = can_read", + "permission read = can_read", + "permission view = can_read", + "permission interact = can_interact", + "permission approve = can_full", + "permission start_session = can_full", + "permission view_tokens = can_admin", + "permission revoke_token = can_admin", + } { + assert.Contains(t, authzschema.Schema, want) + } +} + // requireExactRelationLine requires that schema contains a line — after // trimming leading/trailing whitespace — that equals want exactly. Unlike // require.Contains, this fails on an append (e.g. "user" widened to diff --git a/pkg/controllers/accesstoken/controller.go b/pkg/controllers/accesstoken/controller.go new file mode 100644 index 00000000..57a942e4 --- /dev/null +++ b/pkg/controllers/accesstoken/controller.go @@ -0,0 +1,121 @@ +// Package accesstoken reconciles AccessToken objects: it guarantees that a +// deleted (or expired) token's SpiceDB tuples are removed, and nothing else. +// Minting — tuple write + CR create — happens in webd at the OAuth token +// endpoint (pkg/web/mcpfront), tuples FIRST, so a freshly minted token is +// checkable before the CR exists. This controller is the cleanup half. +package accesstoken + +// Main-resource verbs are minimized to actual client calls: get (LoadInto), +// list;watch (the For informer — and admind's tokens list, hosted in this same +// operator binary), update (EnsureFinalizer/RemoveFinalizer's plain Updates), +// delete (the expiry self-delete, and admind's revoke). No create — minting is +// webd's (pkg/web/mcpfront.Minter) — and no main-resource patch: the only +// patch this reconciler issues targets the status subresource below. +// +kubebuilder:rbac:groups=agentprimitives.authzed.com,resources=accesstokens,verbs=get;list;watch;update;delete +// +kubebuilder:rbac:groups=agentprimitives.authzed.com,resources=accesstokens/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=agentprimitives.authzed.com,resources=accesstokens/finalizers,verbs=update + +import ( + "context" + "fmt" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/controllers/conditions" + apreconcile "github.com/authzed/openagentprimitives/pkg/controllers/internal/reconcile" +) + +// TupleDeleter is the one SpiceDB capability this reconciler needs. Declared +// as an interface so an unwired SpiceDB stays a genuine nil interface +// (AGENTS.md typed-nil rule). +type TupleDeleter interface { + DeleteAccessTokenTuples(ctx context.Context, tokenID string) error +} + +type Reconciler struct { + Client client.Client + SpiceDB TupleDeleter + Clock func() time.Time +} + +func (r *Reconciler) now() time.Time { + if r.Clock != nil { + return r.Clock() + } + return time.Now() +} + +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + var tok spiceboxv1alpha1.AccessToken + if cont, err := apreconcile.LoadInto(ctx, r.Client, req.NamespacedName, &tok); !cont || err != nil { + return ctrl.Result{}, err + } + + if tok.DeletionTimestamp != nil { + return ctrl.Result{}, r.finalize(ctx, &tok) + } + + // One clock read for the whole pass: the expiry gate and the RequeueAfter + // below must agree on "now". With two reads straddling the API writes, a + // clock crossing spec.expiresAt in between yields RequeueAfter <= 0 — + // which controller-runtime never schedules — so the token would sit + // unreconciled until the cache resync. (Same idiom as workshop's + // testwatch: capture once, reuse.) + now := r.now() + + // Expired: delete the CR; the finalizer path removes the tuples. The + // tuples self-expire via the expiration trait anyway — this is hygiene, + // not the enforcement. + if exp := tok.Spec.ExpiresAt.Time; !exp.IsZero() && now.After(exp) { + log.Info("deleting expired access token", "token", tok.Name) + if err := r.Client.Delete(ctx, &tok); client.IgnoreNotFound(err) != nil { + return ctrl.Result{}, fmt.Errorf("delete expired AccessToken %s: %w", tok.Name, err) + } + return ctrl.Result{}, nil + } + + // Finalizer only when SpiceDB is wired: a finalizer that can never have + // work to do is just a way to wedge a deletion (useridentity precedent). + if r.SpiceDB != nil { + if _, err := apreconcile.EnsureFinalizer(ctx, r.Client, &tok, spiceboxv1alpha1.FinalizerAccessToken); err != nil { + return ctrl.Result{}, fmt.Errorf("ensure AccessToken finalizer: %w", err) + } + } + + prior := tok.DeepCopy() + conditions.SetTrue(&tok, &tok.Status.Conditions, spiceboxv1alpha1.AccessTokenConditionReady, "Observed") + tok.Status.ObservedGeneration = tok.Generation + if err := r.Client.Status().Patch(ctx, &tok, client.MergeFrom(prior)); err != nil { + return ctrl.Result{}, fmt.Errorf("patch AccessToken status: %w", err) + } + + if exp := tok.Spec.ExpiresAt.Time; !exp.IsZero() { + return ctrl.Result{RequeueAfter: exp.Sub(now)}, nil + } + return ctrl.Result{}, nil +} + +func (r *Reconciler) finalize(ctx context.Context, tok *spiceboxv1alpha1.AccessToken) error { + if !controllerutil.ContainsFinalizer(tok, spiceboxv1alpha1.FinalizerAccessToken) { + return nil + } + if r.SpiceDB == nil { + ctrllog.FromContext(ctx).Info("AccessToken deleted with no SpiceDB wiring; tuples (if any) are left to their expiration trait", "token", tok.Name) + } else if err := r.SpiceDB.DeleteAccessTokenTuples(ctx, tok.Name); err != nil { + return fmt.Errorf("delete SpiceDB tuples for AccessToken %s: %w", tok.Name, err) + } + controllerutil.RemoveFinalizer(tok, spiceboxv1alpha1.FinalizerAccessToken) + return r.Client.Update(ctx, tok) +} + +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&spiceboxv1alpha1.AccessToken{}). + Complete(r) +} diff --git a/pkg/controllers/accesstoken/controller_test.go b/pkg/controllers/accesstoken/controller_test.go new file mode 100644 index 00000000..d12c09b7 --- /dev/null +++ b/pkg/controllers/accesstoken/controller_test.go @@ -0,0 +1,155 @@ +package accesstoken + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/controllers/conditions" +) + +type fakeDeleter struct { + deleted []string + err error +} + +func (f *fakeDeleter) DeleteAccessTokenTuples(_ context.Context, tokenID string) error { + if f.err != nil { + return f.err + } + f.deleted = append(f.deleted, tokenID) + return nil +} + +func newScheme(t *testing.T) *runtime.Scheme { + t.Helper() + s := runtime.NewScheme() + require.NoError(t, spiceboxv1alpha1.AddToScheme(s)) + return s +} + +func newToken(expiry time.Time) *spiceboxv1alpha1.AccessToken { + return &spiceboxv1alpha1.AccessToken{ + ObjectMeta: metav1.ObjectMeta{Name: "at-0123456789ab", Namespace: "agentprimitives-system"}, + Spec: spiceboxv1alpha1.AccessTokenSpec{ + TokenHash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + Owner: "dXNlckBleGFtcGxlLmNvbQ", + ExpiresAt: metav1.NewTime(expiry), + }, + } +} + +func reconcileOnce(t *testing.T, r *Reconciler, name string) ctrl.Result { + t.Helper() + res, err := r.Reconcile(context.Background(), + ctrl.Request{NamespacedName: client.ObjectKeyFromObject(&spiceboxv1alpha1.AccessToken{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "agentprimitives-system"}, + })}) + require.NoError(t, err, "Reconcile") + return res +} + +func TestLiveTokenGetsFinalizerReadyAndExpiryRequeue(t *testing.T) { + now := time.Now() + tok := newToken(now.Add(time.Hour)) + c := fake.NewClientBuilder().WithScheme(newScheme(t)).WithObjects(tok).WithStatusSubresource(tok).Build() + r := &Reconciler{Client: c, SpiceDB: &fakeDeleter{}, Clock: func() time.Time { return now }} + + res := reconcileOnce(t, r, tok.Name) + + var got spiceboxv1alpha1.AccessToken + require.NoError(t, c.Get(context.Background(), client.ObjectKeyFromObject(tok), &got)) + assert.True(t, controllerutil.ContainsFinalizer(&got, spiceboxv1alpha1.FinalizerAccessToken)) + assert.True(t, conditions.IsTrue(got.Status.Conditions, spiceboxv1alpha1.AccessTokenConditionReady)) + assert.InDelta(t, time.Hour, res.RequeueAfter, float64(time.Minute), "requeue lands at expiry") +} + +func TestClockCrossesExpiryMidReconcile_RequeueAfterStaysPositive(t *testing.T) { + // Reconcile must read the clock ONCE and reuse it for both the expiry + // gate and the RequeueAfter computation. With a second read after the + // API writes, a clock that crosses spec.expiresAt in between yields + // RequeueAfter <= 0 — and controller-runtime only schedules when + // RequeueAfter > 0, so the token would sit unreconciled until the ~10h + // cache resync. The first clock read says "1s before expiry"; every + // later read is past it. + expiry := time.Now().Add(time.Hour) + tok := newToken(expiry) + c := fake.NewClientBuilder().WithScheme(newScheme(t)).WithObjects(tok).WithStatusSubresource(tok).Build() + calls := 0 + r := &Reconciler{Client: c, SpiceDB: &fakeDeleter{}, Clock: func() time.Time { + calls++ + if calls == 1 { + return expiry.Add(-time.Second) // not yet expired at the gate... + } + return expiry.Add(time.Minute) // ...but past expiry on any later read + }} + + res := reconcileOnce(t, r, tok.Name) + + assert.Greater(t, res.RequeueAfter, time.Duration(0), + "clock crossed expiry between reads: the single captured now must still yield a positive requeue") +} + +func TestExpiredTokenIsDeleted(t *testing.T) { + now := time.Now() + tok := newToken(now.Add(-time.Minute)) + controllerutil.AddFinalizer(tok, spiceboxv1alpha1.FinalizerAccessToken) + c := fake.NewClientBuilder().WithScheme(newScheme(t)).WithObjects(tok).WithStatusSubresource(tok).Build() + del := &fakeDeleter{} + r := &Reconciler{Client: c, SpiceDB: del, Clock: func() time.Time { return now }} + + reconcileOnce(t, r, tok.Name) // issues the Delete + reconcileOnce(t, r, tok.Name) // finalizes + + var got spiceboxv1alpha1.AccessToken + err := c.Get(context.Background(), client.ObjectKeyFromObject(tok), &got) + assert.True(t, apierrors.IsNotFound(err), "expired token CR is gone") + assert.Equal(t, []string{tok.Name}, del.deleted, "tuples removed during finalization") +} + +func TestDeletionRemovesTuplesThenFinalizer(t *testing.T) { + now := time.Now() + tok := newToken(now.Add(time.Hour)) + tok.Finalizers = []string{spiceboxv1alpha1.FinalizerAccessToken} + tok.DeletionTimestamp = &metav1.Time{Time: now} + c := fake.NewClientBuilder().WithScheme(newScheme(t)).WithObjects(tok).WithStatusSubresource(tok).Build() + del := &fakeDeleter{} + r := &Reconciler{Client: c, SpiceDB: del, Clock: func() time.Time { return now }} + + reconcileOnce(t, r, tok.Name) + + assert.Equal(t, []string{tok.Name}, del.deleted) + var got spiceboxv1alpha1.AccessToken + err := c.Get(context.Background(), client.ObjectKeyFromObject(tok), &got) + assert.True(t, apierrors.IsNotFound(err), "finalizer released, object gone") +} + +func TestTupleDeleteFailureKeepsFinalizer(t *testing.T) { + now := time.Now() + tok := newToken(now.Add(time.Hour)) + tok.Finalizers = []string{spiceboxv1alpha1.FinalizerAccessToken} + tok.DeletionTimestamp = &metav1.Time{Time: now} + c := fake.NewClientBuilder().WithScheme(newScheme(t)).WithObjects(tok).WithStatusSubresource(tok).Build() + r := &Reconciler{Client: c, SpiceDB: &fakeDeleter{err: errors.New("spicedb down")}, Clock: func() time.Time { return now }} + + _, err := r.Reconcile(context.Background(), + ctrl.Request{NamespacedName: client.ObjectKeyFromObject(tok)}) + require.Error(t, err, "failed tuple delete must surface and retry") + + var got spiceboxv1alpha1.AccessToken + require.NoError(t, c.Get(context.Background(), client.ObjectKeyFromObject(tok), &got)) + assert.True(t, controllerutil.ContainsFinalizer(&got, spiceboxv1alpha1.FinalizerAccessToken), + "finalizer stays until tuples are actually gone — fail closed") +} diff --git a/pkg/platform/identity/accesstoken/accesstoken.go b/pkg/platform/identity/accesstoken/accesstoken.go new file mode 100644 index 00000000..7cdb0514 --- /dev/null +++ b/pkg/platform/identity/accesstoken/accesstoken.go @@ -0,0 +1,66 @@ +// Package accesstoken centralizes every name and derivation shared between +// the schema, the SpiceDB client, the minter, the /mcp middleware, and the +// CLI — the externaltoken precedent: one package so derivations stay +// byte-identical across writer and checker. +package accesstoken + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "fmt" +) + +const ( + ObjectType = "accesstoken" + RelationRoleRead = "role_read" + RelationRoleInteract = "role_interact" + RelationRoleFull = "role_full" + RelationScopeClass = "scope_class" + PermissionCovers = "covers" + PermissionOwner = "owner" + + RoleRead = "read" + RoleInteract = "interact" + RoleFull = "full" + + TokenPrefix = "oap_at_" +) + +func RoleRelation(role string) (string, error) { + switch role { + case RoleRead: + return RelationRoleRead, nil + case RoleInteract: + return RelationRoleInteract, nil + case RoleFull: + return RelationRoleFull, nil + } + return "", fmt.Errorf("unknown access-token role %q", role) +} + +func NewTokenValue() (string, error) { + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + return "", fmt.Errorf("generate token value: %w", err) + } + return TokenPrefix + base64.RawURLEncoding.EncodeToString(b[:]), nil +} + +func HashTokenValue(v string) string { + sum := sha256.Sum256([]byte(v)) + return hex.EncodeToString(sum[:]) +} + +func NewTokenID() (string, error) { + var b [6]byte + if _, err := rand.Read(b[:]); err != nil { + return "", fmt.Errorf("generate token id: %w", err) + } + return "at-" + hex.EncodeToString(b[:]), nil +} + +func MirrorPermissions() []string { + return []string{"read_transcript", "read", "view", "interact", "approve", "start_session"} +} diff --git a/pkg/platform/identity/accesstoken/accesstoken_test.go b/pkg/platform/identity/accesstoken/accesstoken_test.go new file mode 100644 index 00000000..31361e08 --- /dev/null +++ b/pkg/platform/identity/accesstoken/accesstoken_test.go @@ -0,0 +1,56 @@ +package accesstoken + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewTokenValueShapeAndUniqueness(t *testing.T) { + a, err := NewTokenValue() + require.NoError(t, err) + b, err := NewTokenValue() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(a, TokenPrefix)) + assert.Len(t, a, len(TokenPrefix)+43) // 32 bytes base64url, unpadded + assert.NotEqual(t, a, b) +} + +func TestHashTokenValueIsStableHexSHA256(t *testing.T) { + h := HashTokenValue("oap_at_fixed") + assert.Len(t, h, 64) + assert.Equal(t, h, HashTokenValue("oap_at_fixed")) + assert.NotEqual(t, h, HashTokenValue("oap_at_other")) +} + +func TestRoleRelation(t *testing.T) { + cases := []struct { + role, want string + wantErr bool + }{ + {role: RoleRead, want: RelationRoleRead}, + {role: RoleInteract, want: RelationRoleInteract}, + {role: RoleFull, want: RelationRoleFull}, + {role: "admin", wantErr: true}, + {role: "", wantErr: true}, + } + for _, tc := range cases { + t.Run(tc.role+": maps or errors", func(t *testing.T) { + got, err := RoleRelation(tc.role) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestNewTokenIDIsDNSSafe(t *testing.T) { + id, err := NewTokenID() + require.NoError(t, err) + assert.Regexp(t, `^at-[0-9a-f]{12}$`, id) +} diff --git a/pkg/platform/identity/passthroughlink/link.go b/pkg/platform/identity/passthroughlink/link.go index 5dfd86dd..50a6a6ae 100644 --- a/pkg/platform/identity/passthroughlink/link.go +++ b/pkg/platform/identity/passthroughlink/link.go @@ -250,6 +250,16 @@ const ( // can tell "first-time connect" from "replacing a credential a human was // asked to fix" without inspecting RequiredCredentials. PurposeCredentialUpdate = "credential_update" + // PurposeOAuthClient marks a STATELESS OAuth 2.0 client_id: identityd's + // POST /oauth/register (RFC 7591 Dynamic Client Registration) mints one of + // these instead of writing to a store, so the registration itself — + // {client_name, redirect_uris}, JSON-encoded into BackLink — survives an + // identityd restart or a different replica verifying it, with no shared + // state at all. 2-year expiry (see oauthClientIDTTL in + // pkg/platform/identityd/handlers_oauthas.go): long enough that a + // registered local tool keeps working across routine signer-key + // rotations' overlap window, but not eternal. + PurposeOAuthClient = "oauth_client" // PurposeWorkshopCredential marks a deep-link that connects a bot // credential for an AgentIdentity living in a workshop namespace W. It // reuses SessionRef for the builder session ("/") that requested diff --git a/pkg/platform/identityd/handlers_oauthas.go b/pkg/platform/identityd/handlers_oauthas.go new file mode 100644 index 00000000..f1158303 --- /dev/null +++ b/pkg/platform/identityd/handlers_oauthas.go @@ -0,0 +1,212 @@ +// pkg/platform/identityd/handlers_oauthas.go — identityd acting as an OAuth +// 2.0 AUTHORIZATION SERVER for its own /oauth/authorize + /oauth/token (Tasks +// 7-8), advertised via RFC 8414 metadata and RFC 7591 Dynamic Client +// Registration here. +// +// This is the opposite role from handlers_oauth.go, which makes identityd an +// OAuth CLIENT of upstream MCP servers. The two never share state: a client_id +// minted here is for a tool authenticating TO identityd, not a credential +// identityd holds for some other service. +// +// client_id is STATELESS: it is itself a passthroughlink-signed blob carrying +// the registration ({name, redirect_uris}), so DCR needs no storage and a +// client_id verifies the same on any replica, before or after a restart. +package identityd + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + "unicode" + + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/authzed/openagentprimitives/pkg/platform/identity/passthroughlink" +) + +const ( + // oauthClientIDTTL is how long a DCR'd client_id remains valid. Long + // enough that a registered local tool keeps working across routine + // operation; see PurposeOAuthClient's doc for the rotation-overlap + // rationale. + oauthClientIDTTL = 2 * 365 * 24 * time.Hour + // maxClientNameLen caps the sanitized client_name stored in the + // client_id blob — it rides in a cookie-sized token and is rendered back + // to the user later, so it is capped rather than left open-ended. + maxClientNameLen = 64 + // maxRegisterBodyLen caps the POST /oauth/register request body, mirroring + // handleCLIExchange's 4KB cap on the CLI-exchange JSON endpoint; 8KB gives + // a few redirect_uris plenty of room without accepting an unbounded body. + maxRegisterBodyLen = 8 << 10 +) + +// clientReg is the registration a client_id encodes. Exported-shaped (but +// package-private) so Tasks 7-8's verifyClientID callers can read it. +type clientReg struct { + Name string `json:"name"` + RedirectURIs []string `json:"redirect_uris"` +} + +// handleOAuthASMetadata handles GET /.well-known/oauth-authorization-server +// (RFC 8414). AuthNone: this document is, by definition, what an unauthenticated +// client fetches first. +func (s *Server) handleOAuthASMetadata(w http.ResponseWriter, _ *http.Request) { + base := strings.TrimSuffix(s.deps.ExternalBaseURL(), "/") + writeJSONBody(w, http.StatusOK, map[string]any{ + "issuer": base, + "authorization_endpoint": base + "/oauth/authorize", + "token_endpoint": base + "/oauth/token", + "registration_endpoint": base + "/oauth/register", + "response_types_supported": []string{"code"}, + "grant_types_supported": []string{"authorization_code"}, + "code_challenge_methods_supported": []string{"S256"}, + "token_endpoint_auth_methods_supported": []string{"none"}, + }) +} + +// handleOAuthRegister handles POST /oauth/register (RFC 7591 Dynamic Client +// Registration). AuthHandlerManaged: a not-yet-registered client has no +// credential to authenticate with, so this is necessarily an open endpoint; +// what it hands back is a signed, expiring, otherwise-inert blob, not access +// to anything. +func (s *Server) handleOAuthRegister(w http.ResponseWriter, r *http.Request) { + logger := log.FromContext(r.Context()) + + var req struct { + ClientName string `json:"client_name"` + RedirectURIs []string `json:"redirect_uris"` + } + // Deliberately NOT DisallowUnknownFields (contrast handleCLIExchange): + // RFC 7591 §2 says a registration server SHOULD ignore unrecognized + // client metadata, and real MCP clients send fields we don't model + // (token_endpoint_auth_method, grant_types, ...). The fields we DO + // read are each validated below. + dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxRegisterBodyLen)) + if err := dec.Decode(&req); err != nil { + oauthError(w, http.StatusBadRequest, "invalid_client_metadata", "malformed registration body") + return + } + if len(req.RedirectURIs) == 0 { + oauthError(w, http.StatusBadRequest, "invalid_redirect_uri", "redirect_uris is required") + return + } + for _, raw := range req.RedirectURIs { + if err := validateRedirectURI(raw); err != nil { + oauthError(w, http.StatusBadRequest, "invalid_redirect_uri", err.Error()) + return + } + } + name := sanitizeClientName(req.ClientName) + clientID, err := s.mintClientID(name, req.RedirectURIs) + if err != nil { + logger.Info("oauth register: mint client_id failed", "err", err.Error()) + oauthError(w, http.StatusInternalServerError, "server_error", "could not register client") + return + } + writeJSONBody(w, http.StatusCreated, map[string]any{ + "client_id": clientID, + "client_name": name, + "redirect_uris": req.RedirectURIs, + "token_endpoint_auth_method": "none", + "grant_types": []string{"authorization_code"}, + "response_types": []string{"code"}, + }) +} + +// validateRedirectURI allows loopback http (127.0.0.1/::1/localhost, any +// port) and https anywhere — OAuth 2.1's public-client rule. +func validateRedirectURI(raw string) error { + u, err := url.Parse(raw) + if err != nil { + return fmt.Errorf("unparseable redirect_uri") + } + switch u.Scheme { + case "https": + return nil + case "http": + host := u.Hostname() + if host == "127.0.0.1" || host == "::1" || host == "localhost" { + return nil + } + return fmt.Errorf("http redirect_uri must be loopback") + } + return fmt.Errorf("redirect_uri scheme must be https or loopback http") +} + +// sanitizeClientName strips non-printable runes and '<'/'>' (the client_name +// is later rendered back to a human at the authorize step; this is a +// display-safety net, not a substitute for the renderer's own escaping), +// trims whitespace, defaults an empty result to "unnamed client", and caps +// length at maxClientNameLen. +func sanitizeClientName(name string) string { + cleaned := strings.Map(func(r rune) rune { + if unicode.IsPrint(r) && r != '<' && r != '>' { + return r + } + return -1 + }, name) + cleaned = strings.TrimSpace(cleaned) + if cleaned == "" { + cleaned = "unnamed client" + } + if len(cleaned) > maxClientNameLen { + cleaned = cleaned[:maxClientNameLen] + } + return cleaned +} + +// mintClientID encodes the registration as a signed, expiring blob — the +// client_id IS the registration, so DCR needs no storage and survives +// restarts and replicas. +func (s *Server) mintClientID(name string, redirectURIs []string) (string, error) { + blob, err := json.Marshal(clientReg{Name: name, RedirectURIs: redirectURIs}) + if err != nil { + return "", err + } + return s.deps.LinkSigner.Mint(passthroughlink.Payload{ + Purpose: passthroughlink.PurposeOAuthClient, + BackLink: string(blob), + ExpiresAt: time.Now().Add(oauthClientIDTTL).Unix(), + }) +} + +// verifyClientID decodes + verifies a client_id minted by mintClientID. +// Tasks 7-8 (/oauth/authorize, /oauth/token) call this to resolve the +// client making the request — a stateless lookup, not a store Get. +func (s *Server) verifyClientID(clientID string) (clientReg, error) { + var reg clientReg + p, err := s.deps.LinkSigner.Verify(clientID) + if err != nil { + return reg, fmt.Errorf("invalid client_id: %w", err) + } + if p.Purpose != passthroughlink.PurposeOAuthClient { + return reg, fmt.Errorf("client_id has wrong purpose") + } + if err := json.Unmarshal([]byte(p.BackLink), ®); err != nil { + return reg, fmt.Errorf("client_id payload undecodable: %w", err) + } + return reg, nil +} + +// oauthError writes an RFC 7591 / RFC 6749 §5.2-shaped error envelope +// ({"error","error_description"}) — distinct from handlers_cli.go's +// jsonError (a bare {"error":""} for the CLI-exchange endpoint, which +// predates and isn't an OAuth wire format): pkg/tools/mcp/oauth.Register, +// our own client-side DCR caller, parses exactly this two-field shape out of +// a non-2xx response, so our own server's errors must speak it too. +func oauthError(w http.ResponseWriter, status int, code, desc string) { + writeJSONBody(w, status, map[string]string{"error": code, "error_description": desc}) +} + +// writeJSONBody writes body as JSON with the given status. Shared by every +// handler in this file (metadata, registration success, oauthError) since +// none of the package's other JSON helpers (jsonError's single-field shape) +// fit a body with more than one key. +func writeJSONBody(w http.ResponseWriter, status int, body any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(body) +} diff --git a/pkg/platform/identityd/handlers_oauthas_authorize.go b/pkg/platform/identityd/handlers_oauthas_authorize.go new file mode 100644 index 00000000..8f89e667 --- /dev/null +++ b/pkg/platform/identityd/handlers_oauthas_authorize.go @@ -0,0 +1,380 @@ +// pkg/platform/identityd/handlers_oauthas_authorize.go — GET /oauth/authorize +// and POST /oauth/consent, the browser-facing half of identityd's OWN OAuth +// AUTHORIZATION-SERVER role (see handlers_oauthas.go's package doc). Task 8 +// adds the /oauth/token exchange that redeems the code these two mint. +// +// The security-load-bearing property both handlers share: redirect_uri is +// validated ONCE, against the client's DCR-registered set, before anything is +// ever redirected to it. Every validation failure after that point renders an +// HTML error page in place — never a redirect — because an unvalidated +// redirect_uri is attacker-controlled (the query string of a GET a user was +// sent to click). Once validated, redirect_uri is carried only through the +// pendingAuthEntry the server itself minted, never re-read from client input. +package identityd + +import ( + "context" + "fmt" + "html" + "net/http" + "net/url" + "slices" + "strings" + + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" + "github.com/authzed/openagentprimitives/pkg/web/webui" +) + +// ConsentDeps is the OPTIONAL half of identityd's deps answering the one +// question the authorization-server's consent screen needs: which agent +// classes may this subject grant a tool access to? Separate from WebDeps +// because webd derives it from SpiceDB-backed lookups (LookupStartableClasses +// + the classes behind LookupInteractableSessions, unioned and deduped) that +// are unavailable without SpiceDB wired. A deps value not implementing this +// leaves Deps.Consent nil, and server.go's routes() does not register ANY of +// the authorization-server surface (metadata, register, authorize, consent) — +// the feature is OFF without SpiceDB, not degraded, since none of it can +// complete without a consent-class lookup. +type ConsentDeps interface { + ConsentClasses(ctx context.Context, owner identity.CanonicalUserID) ([]ConsentClass, error) +} + +// ConsentClass is one agent class the subject may grant an OAuth client +// access to, shown as a scope checkbox on the consent screen. +type ConsentClass struct { + // ID is the "ns/name" AgentClass reference — what actually lands in an + // authCodeEntry.ScopeClasses value, never a free-text label. + ID string + // DisplayName is the human-readable label shown next to the checkbox. + DisplayName string +} + +// handleOAuthAuthorize handles GET /oauth/authorize: validates the request +// per RFC 6749 §4.1.1 + PKCE (RFC 7636, S256 only), then renders the consent +// screen. See the package doc above for the redirect_uri ordering invariant. +func (s *Server) handleOAuthAuthorize(w http.ResponseWriter, r *http.Request) { + logger := log.FromContext(r.Context()) + q := r.URL.Query() + + if q.Get("response_type") != "code" { + s.writeError(w, r, http.StatusBadRequest, "Unsupported response_type", + "Only response_type=code is supported.") + return + } + + clientID := q.Get("client_id") + reg, err := s.verifyClientID(clientID) + if err != nil { + logger.Info("oauth authorize: invalid client_id", "err", err.Error()) + s.writeError(w, r, http.StatusBadRequest, "Unknown client", + "This tool is not registered with this server.") + return + } + + redirectURI := q.Get("redirect_uri") + if !slices.Contains(reg.RedirectURIs, redirectURI) { + logger.Info("oauth authorize: redirect_uri not registered", "client", reg.Name) + s.writeError(w, r, http.StatusBadRequest, "Invalid redirect_uri", + "This redirect address was not registered for this tool.") + return + } + + // From here on redirectURI is a value the client registered via DCR, not + // unvalidated client input — but every failure below STILL renders an + // error page rather than redirecting, because none of these remaining + // checks is specified to carry an error back through redirect_uri before + // PKCE itself is confirmed sound. + challenge := q.Get("code_challenge") + if challenge == "" { + s.writeError(w, r, http.StatusBadRequest, "Missing code_challenge", + "This tool did not send a PKCE code_challenge.") + return + } + if q.Get("code_challenge_method") != "S256" { + s.writeError(w, r, http.StatusBadRequest, "Unsupported code_challenge_method", + "Only the S256 PKCE method is supported.") + return + } + + if s.deps.Consent == nil { + // routes() only mounts this handler when Consent is wired; reachable + // only if something calls it directly outside that gate. + logger.Info("oauth authorize: no consent deps wired") + s.writeError(w, r, http.StatusInternalServerError, "Not available", + "This server is not configured to grant tool access.") + return + } + + subj := webui.SubjectFromContext(r.Context()) + canonical, err := identity.Subject(subj).CanonicalUserID() + if err != nil { + logger.Info("oauth authorize: non-user subject", "err", err.Error()) + s.writeError(w, r, http.StatusForbidden, "Not supported", + "Only a signed-in person may authorize a tool.") + return + } + + classes, err := s.deps.Consent.ConsentClasses(r.Context(), canonical) + if err != nil { + logger.Info("oauth authorize: ConsentClasses failed", "err", err.Error()) + s.writeError(w, r, http.StatusInternalServerError, "Could not load your agents", + "Try again shortly.") + return + } + + pendingID, err := s.pendingAuth.NewPending(pendingAuthEntry{ + ClientID: clientID, + ClientName: reg.Name, + RedirectURI: redirectURI, + Challenge: challenge, + State: q.Get("state"), + Subject: canonical, + }) + if err != nil { + logger.Info("oauth authorize: mint pending failed", "err", err.Error()) + s.writeError(w, r, http.StatusInternalServerError, "Could not start authorization", + "Try again.") + return + } + + writeConsentForm(w, reg.Name, identity.DecodeForDisplay(canonical.String()), pendingID, classes) +} + +// handleOAuthConsent handles POST /oauth/consent — the consent screen's +// Approve/Deny submit. redirect_uri, client_id, and the PKCE challenge all +// came from the pendingAuthEntry minted at /oauth/authorize, never from this +// POST body: the form carries only `pending`, `role`, `scope`/`scope_all`, +// and the `deny` marker, so there is nothing here for a forged submission to +// override except the grant's role and scope. +func (s *Server) handleOAuthConsent(w http.ResponseWriter, r *http.Request) { + logger := log.FromContext(r.Context()) + + // 1. CSRF origin pin — this POST creates a durable grant, so it must come + // from the consent page itself, not a cross-site form. + if !webui.TrustedOriginMatch(r, s.deps.ExternalBaseURL()) { + logger.Info("oauth consent: Origin mismatch", "origin", r.Header.Get("Origin")) + s.writeError(w, r, http.StatusForbidden, "Request blocked", + "This request did not come from a trusted page.") + return + } + + if err := r.ParseForm(); err != nil { + logger.Info("oauth consent: ParseForm failed", "err", err.Error()) + s.writeError(w, r, http.StatusBadRequest, "Invalid form", "Could not read the form submission.") + return + } + + // 2. Consume the pending entry — single-use, so a replayed submit (or a + // second tab) cannot mint a second code off the same authorize visit. + pending, ok := s.pendingAuth.Consume(r.FormValue("pending")) + if !ok { + s.writeError(w, r, http.StatusBadRequest, "Authorization expired", + "Start over from the tool you were connecting.") + return + } + + // 3. The posting cookie subject MUST equal the pending entry's subject — + // never trust the form alone for who is granting this. + subj := webui.SubjectFromContext(r.Context()) + canonical, err := identity.Subject(subj).CanonicalUserID() + if err != nil || canonical != pending.Subject { + logger.Info("oauth consent: subject mismatch") + s.writeError(w, r, http.StatusForbidden, "Not allowed", + "This authorization does not belong to your sign-in session.") + return + } + + redirectURL, err := url.Parse(pending.RedirectURI) + if err != nil { + // pending.RedirectURI was exact-matched against the client's registered + // set at /oauth/authorize; this should be unreachable. + logger.Info("oauth consent: pending redirect_uri unparseable", "err", err.Error()) + s.writeError(w, r, http.StatusInternalServerError, "Something went wrong", "Try again.") + return + } + + // Deny never mints a code — it is handled before any role/scope + // validation, and returns straight to the client with access_denied. + if r.FormValue("deny") != "" { + redirectWithParams(w, r, redirectURL, map[string]string{ + "error": "access_denied", + "state": pending.State, + }) + return + } + + role := r.FormValue("role") + switch role { + case accesstoken.RoleRead, accesstoken.RoleInteract, accesstoken.RoleFull: + default: + s.writeError(w, r, http.StatusBadRequest, "Invalid access level", "Choose a valid access level.") + return + } + + if s.deps.Consent == nil { + logger.Info("oauth consent: no consent deps wired") + s.writeError(w, r, http.StatusInternalServerError, "Not available", + "This server is not configured to grant tool access.") + return + } + + // 4. Scope is validated against a RE-FETCHED ConsentClasses list, not the + // set rendered on the form minutes earlier and not any free-text value a + // forged submission could supply — what the subject can grant may have + // changed since the GET, and the class IDs themselves must be real. + classes, err := s.deps.Consent.ConsentClasses(r.Context(), canonical) + if err != nil { + logger.Info("oauth consent: ConsentClasses failed", "err", err.Error()) + s.writeError(w, r, http.StatusInternalServerError, "Could not verify access", "Try again shortly.") + return + } + allowed := make(map[string]bool, len(classes)) + for _, c := range classes { + allowed[c.ID] = true + } + + unfiltered := r.FormValue("scope_all") == "1" + var scopeClasses []string + if !unfiltered { + for _, v := range r.Form["scope"] { + if !allowed[v] { + logger.Info("oauth consent: scope not in re-fetched ConsentClasses", "scope", v) + s.writeError(w, r, http.StatusBadRequest, "Invalid scope", + "One of the selected items is not available to you.") + return + } + scopeClasses = append(scopeClasses, v) + } + } + + code, err := s.authCodes.NewCode(authCodeEntry{ + ClientID: pending.ClientID, + RedirectURI: pending.RedirectURI, + Challenge: pending.Challenge, + Subject: canonical, + Role: role, + ScopeClasses: scopeClasses, + Unfiltered: unfiltered, + }) + if err != nil { + logger.Info("oauth consent: mint code failed", "err", err.Error()) + s.writeError(w, r, http.StatusInternalServerError, "Could not finish authorization", "Try again.") + return + } + + redirectWithParams(w, r, redirectURL, map[string]string{ + "code": code, + "state": pending.State, + }) +} + +// redirectWithParams 302s to base with params merged into its query string, +// skipping any empty value (state is often absent) — shared by the +// deny/approve tails of handleOAuthConsent so neither path hand-rolls query +// construction differently from the other. +func redirectWithParams(w http.ResponseWriter, r *http.Request, base *url.URL, params map[string]string) { + q := base.Query() + for k, v := range params { + if v != "" { + q.Set(k, v) + } + } + base.RawQuery = q.Encode() + http.Redirect(w, r, base.String(), http.StatusFound) +} + +// writeConsentForm renders the consent screen: a self-contained HTML page +// (no third-party assets, no JS) with html.EscapeString on every +// interpolation — clientName and ownerDisplay are both user-influenced +// (DCR client_name, and whatever string decoded the subject's canonical), +// and classes come from SpiceDB-backed lookups this handler does not control. +func writeConsentForm(w http.ResponseWriter, clientName, ownerDisplay, pendingID string, classes []ConsentClass) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + // Never cached: a per-visit page carrying a single-use pending id. + w.Header().Set("Cache-Control", "no-store") + // Anti-clickjacking: this page approves a delegated grant with one click + // (the approve button), so it must never be framable — an attacker page + // could otherwise overlay invisible UI and trick the signed-in subject + // into approving a grant they never saw. Both headers say the same thing + // to old and new clients respectively; belt-and-suspenders, not redundant. + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Content-Security-Policy", "frame-ancestors 'none'") + w.WriteHeader(http.StatusOK) + + var rows strings.Builder + for _, c := range classes { + fmt.Fprintf(&rows, consentScopeRowHTML, html.EscapeString(c.ID), html.EscapeString(c.DisplayName)) + } + + fmt.Fprintf(w, consentFormHTML, + html.EscapeString(webui.FaviconHref), + html.EscapeString(clientName), + html.EscapeString(ownerDisplay), + html.EscapeString(pendingID), + rows.String(), + ) +} + +const consentScopeRowHTML = ` +` + +const consentFormHTML = ` + + + + +Authorize access + + + + +
+

%s wants to access your account

+

Signed in as %s

+
+ +
+ Access level + + + +
+
+ Which agents? +%s +
+
+ + +
+
+
+ + +` diff --git a/pkg/platform/identityd/handlers_oauthas_authorize_test.go b/pkg/platform/identityd/handlers_oauthas_authorize_test.go new file mode 100644 index 00000000..e5f3f914 --- /dev/null +++ b/pkg/platform/identityd/handlers_oauthas_authorize_test.go @@ -0,0 +1,267 @@ +package identityd + +import ( + "context" + "encoding/base64" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authzed/openagentprimitives/pkg/web/webui" +) + +// testAuthorizeSubject is the canonical subject the test helpers inject into +// the request context, standing in for the cookie subject the webui +// framework would normally set via its auth middleware (see +// webui.WithSubjectForTest's doc comment). +var testAuthorizeSubject = "user:" + base64.RawURLEncoding.EncodeToString([]byte("alice@example.org")) + +// mintTestClientID registers a DCR client directly through the server's own +// minter (bypassing the HTTP /oauth/register round trip, which Task 6's own +// tests already cover) and returns the resulting client_id. +func mintTestClientID(t *testing.T, s *Server, name string, redirectURIs []string) string { + t.Helper() + id, err := s.mintClientID(name, redirectURIs) + require.NoError(t, err) + return id +} + +// doAuthorizeGet sends GET /oauth/authorize with an authenticated +// subject injected into the request context, the way sibling webui packages +// test AuthLoginIfNecessary/AuthAuthenticated handlers outside the framework +// (webui.WithSubjectForTest). +func doAuthorizeGet(t *testing.T, s *Server, query string) *httptest.ResponseRecorder { + t.Helper() + ctx := webui.WithSubjectForTest(context.Background(), testAuthorizeSubject) + req := httptest.NewRequest(http.MethodGet, "/oauth/authorize"+query, nil).WithContext(ctx) + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + return rec +} + +// doConsentPost sends POST /oauth/consent with form, the same injected +// subject as doAuthorizeGet, and an Origin header matching the test server's +// ExternalBaseURL (the CSRF pin doAuthorizeGet's counterpart must satisfy). +func doConsentPost(t *testing.T, s *Server, form url.Values) *httptest.ResponseRecorder { + t.Helper() + ctx := webui.WithSubjectForTest(context.Background(), testAuthorizeSubject) + req := httptest.NewRequest(http.MethodPost, "/oauth/consent", strings.NewReader(form.Encode())).WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Origin", "https://example.org") + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + return rec +} + +// extractHiddenValue pulls `value="..."` out of `name="" value="..."` +// in a rendered form body — just enough parsing to recover the pending id +// the consent form carries, without a full HTML parser. +func extractHiddenValue(t *testing.T, body, name string) string { + t.Helper() + marker := `name="` + name + `" value="` + idx := strings.Index(body, marker) + require.NotEqual(t, -1, idx, "hidden field %q not found in form body", name) + rest := body[idx+len(marker):] + end := strings.Index(rest, `"`) + require.NotEqual(t, -1, end, "unterminated value for hidden field %q", name) + return rest[:end] +} + +// primePendingAuthorize performs the authorize GET for validClient against a +// fixed redirect_uri/state and returns the pending id the consent form +// embeds, for a subsequent POST /oauth/consent. +func primePendingAuthorize(t *testing.T, s *Server, clientID string) string { + t.Helper() + query := "?response_type=code&client_id=" + clientID + + "&redirect_uri=http://127.0.0.1:7777/cb&code_challenge=x&code_challenge_method=S256&state=st" + rec := doAuthorizeGet(t, s, query) + require.Equal(t, http.StatusOK, rec.Code, "priming GET must render the consent form") + return extractHiddenValue(t, rec.Body.String(), "pending") +} + +func TestAuthorizeValidation(t *testing.T) { + s := newTestServerWithConsent(t) // fixture: stub ConsentClasses returns [{ID:"default/demo-agent",DisplayName:"Demo"}] + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + cases := []struct { + name string + query string + wantCode int + }{ + {name: "missing client_id: 400", query: "?response_type=code", wantCode: 400}, + {name: "bogus client_id: 400", query: "?response_type=code&client_id=garbage&redirect_uri=http://127.0.0.1:7777/cb&code_challenge=x&code_challenge_method=S256", wantCode: 400}, + {name: "redirect_uri not registered: 400", query: "?response_type=code&client_id=" + validClient + "&redirect_uri=http://127.0.0.1:9999/other&code_challenge=x&code_challenge_method=S256", wantCode: 400}, + {name: "plain challenge method: 400", query: "?response_type=code&client_id=" + validClient + "&redirect_uri=http://127.0.0.1:7777/cb&code_challenge=x&code_challenge_method=plain", wantCode: 400}, + {name: "happy path renders consent form: 200", query: "?response_type=code&client_id=" + validClient + "&redirect_uri=http://127.0.0.1:7777/cb&code_challenge=x&code_challenge_method=S256&state=st", wantCode: 200}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rec := doAuthorizeGet(t, s, tc.query) // helper: injects an authenticated subject into ctx the way sibling tests do + assert.Equal(t, tc.wantCode, rec.Code) + if tc.wantCode == 200 { + body := rec.Body.String() + assert.Contains(t, body, "demo tool") + assert.Contains(t, body, `value="read" checked`) // read-only preselected + assert.Contains(t, body, "default/demo-agent") + assert.Equal(t, "DENY", rec.Header().Get("X-Frame-Options"), "consent page must refuse framing") + assert.Equal(t, "frame-ancestors 'none'", rec.Header().Get("Content-Security-Policy"), "consent page must refuse framing") + } + }) + } +} + +func TestConsentPostMintsCodeAndRedirects(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) // helper: performs the GET, extracts the hidden pending id from the form + + rec := doConsentPost(t, s, url.Values{ + "pending": {pendingID}, + "role": {"read"}, + "scope": {"default/demo-agent"}, + }) + require.Equal(t, http.StatusFound, rec.Code) + loc, err := url.Parse(rec.Header().Get("Location")) + require.NoError(t, err) + assert.Equal(t, "127.0.0.1:7777", loc.Host) + assert.Equal(t, "st", loc.Query().Get("state")) + code := loc.Query().Get("code") + require.NotEmpty(t, code) + + entry, ok := s.authCodes.Consume(code) + require.True(t, ok) + assert.Equal(t, "read", entry.Role) + assert.Equal(t, []string{"default/demo-agent"}, entry.ScopeClasses) + assert.False(t, entry.Unfiltered) +} + +func TestConsentPostDenyNeverMintsCode(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) + + rec := doConsentPost(t, s, url.Values{ + "pending": {pendingID}, + "deny": {"1"}, + }) + require.Equal(t, http.StatusFound, rec.Code) + loc, err := url.Parse(rec.Header().Get("Location")) + require.NoError(t, err) + assert.Equal(t, "127.0.0.1:7777", loc.Host) + assert.Equal(t, "access_denied", loc.Query().Get("error")) + assert.Equal(t, "st", loc.Query().Get("state")) + assert.Empty(t, loc.Query().Get("code"), "deny must never mint a code") +} + +func TestConsentPostPendingIsSingleUse(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) + + form := url.Values{"pending": {pendingID}, "role": {"read"}, "scope": {"default/demo-agent"}} + first := doConsentPost(t, s, form) + require.Equal(t, http.StatusFound, first.Code) + + second := doConsentPost(t, s, form) + assert.Equal(t, http.StatusBadRequest, second.Code, "a consumed pending id must be refused on replay") +} + +func TestConsentPostRejectsSubjectMismatch(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) + + otherSubject := "user:" + base64.RawURLEncoding.EncodeToString([]byte("mallory@example.org")) + ctx := webui.WithSubjectForTest(context.Background(), otherSubject) + form := url.Values{"pending": {pendingID}, "role": {"read"}, "scope": {"default/demo-agent"}} + req := httptest.NewRequest(http.MethodPost, "/oauth/consent", strings.NewReader(form.Encode())).WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Origin", "https://example.org") + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + + assert.Equal(t, http.StatusForbidden, rec.Code, "a different signed-in subject must not redeem another's pending consent") +} + +func TestConsentPostRejectsUntrustedOrigin(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) + + ctx := webui.WithSubjectForTest(context.Background(), testAuthorizeSubject) + form := url.Values{"pending": {pendingID}, "role": {"read"}, "scope": {"default/demo-agent"}} + req := httptest.NewRequest(http.MethodPost, "/oauth/consent", strings.NewReader(form.Encode())).WithContext(ctx) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Origin", "https://evil.example") + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + + assert.Equal(t, http.StatusForbidden, rec.Code, "a mismatched Origin must be refused") +} + +func TestConsentPostRejectsUnlistedScope(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) + + rec := doConsentPost(t, s, url.Values{ + "pending": {pendingID}, + "role": {"read"}, + "scope": {"default/not-a-real-class"}, + }) + assert.Equal(t, http.StatusBadRequest, rec.Code, "a scope not in the re-fetched ConsentClasses must be refused") +} + +func TestConsentPostEverythingScope(t *testing.T) { + s := newTestServerWithConsent(t) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + pendingID := primePendingAuthorize(t, s, validClient) + + rec := doConsentPost(t, s, url.Values{ + "pending": {pendingID}, + "role": {"full"}, + "scope_all": {"1"}, + }) + require.Equal(t, http.StatusFound, rec.Code) + loc, err := url.Parse(rec.Header().Get("Location")) + require.NoError(t, err) + code := loc.Query().Get("code") + require.NotEmpty(t, code) + + entry, ok := s.authCodes.Consume(code) + require.True(t, ok) + assert.True(t, entry.Unfiltered) + assert.Empty(t, entry.ScopeClasses) +} + +// TestOAuthAuthorizationServerRoutesGatedOnConsent is the route-registration +// half of "feature off without SpiceDB": a server built WITHOUT ConsentDeps +// (newTestServer, not newTestServerWithConsent) must 404 the entire +// authorization-server surface, Task 6's metadata/register pair included — +// none of it can complete without a consent-class lookup. +func TestOAuthAuthorizationServerRoutesGatedOnConsent(t *testing.T) { + s := newTestServer(t) + require.Nil(t, s.deps.Consent, "fixture must not wire Consent") + + paths := []struct { + method string + path string + }{ + {http.MethodGet, "/.well-known/oauth-authorization-server"}, + {http.MethodPost, "/oauth/register"}, + {http.MethodGet, "/oauth/authorize"}, + {http.MethodPost, "/oauth/consent"}, + } + for _, p := range paths { + t.Run(p.method+" "+p.path, func(t *testing.T) { + req := httptest.NewRequest(p.method, p.path, nil) + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + assert.Equal(t, http.StatusNotFound, rec.Code) + }) + } +} diff --git a/pkg/platform/identityd/handlers_oauthas_test.go b/pkg/platform/identityd/handlers_oauthas_test.go new file mode 100644 index 00000000..a1d1bfc7 --- /dev/null +++ b/pkg/platform/identityd/handlers_oauthas_test.go @@ -0,0 +1,82 @@ +package identityd + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOAuthASMetadata(t *testing.T) { + s := newTestServerWithConsent(t) // metadata/register are gated on Consent != nil (Task 7) + req := httptest.NewRequest(http.MethodGet, "/.well-known/oauth-authorization-server", nil) + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + var md struct { + Issuer string `json:"issuer"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + RegistrationEndpoint string `json:"registration_endpoint"` + ResponseTypes []string `json:"response_types_supported"` + GrantTypes []string `json:"grant_types_supported"` + CodeChallengeMethods []string `json:"code_challenge_methods_supported"` + TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported"` + } + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &md)) + base := strings.TrimSuffix(md.Issuer, "/") + assert.Equal(t, base+"/oauth/authorize", md.AuthorizationEndpoint) + assert.Equal(t, base+"/oauth/token", md.TokenEndpoint) + assert.Equal(t, base+"/oauth/register", md.RegistrationEndpoint) + assert.Equal(t, []string{"code"}, md.ResponseTypes) + assert.Equal(t, []string{"authorization_code"}, md.GrantTypes) + assert.Equal(t, []string{"S256"}, md.CodeChallengeMethods) + assert.Equal(t, []string{"none"}, md.TokenEndpointAuthMethods) +} + +func TestDynamicClientRegistration(t *testing.T) { + s := newTestServerWithConsent(t) + body := `{"client_name":"demo local tool","redirect_uris":["http://127.0.0.1:7777/callback"],"token_endpoint_auth_method":"none"}` + req := httptest.NewRequest(http.MethodPost, "/oauth/register", strings.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + + require.Equal(t, http.StatusCreated, rec.Code) + var resp struct { + ClientID string `json:"client_id"` + RedirectURIs []string `json:"redirect_uris"` + } + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NotEmpty(t, resp.ClientID) + + reg, err := s.verifyClientID(resp.ClientID) + require.NoError(t, err) + assert.Equal(t, "demo local tool", reg.Name) + assert.Equal(t, []string{"http://127.0.0.1:7777/callback"}, reg.RedirectURIs) +} + +func TestDynamicClientRegistrationRejections(t *testing.T) { + cases := []struct { + name, body string + }{ + {name: "no redirect_uris: 400", body: `{"client_name":"x"}`}, + {name: "non-loopback http redirect: 400", body: `{"client_name":"x","redirect_uris":["http://evil.test/cb"]}`}, + {name: "malformed JSON: 400", body: `{"client_name":`}, + } + s := newTestServerWithConsent(t) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/oauth/register", strings.NewReader(tc.body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + assert.Equal(t, http.StatusBadRequest, rec.Code) + }) + } +} diff --git a/pkg/platform/identityd/handlers_oauthas_token.go b/pkg/platform/identityd/handlers_oauthas_token.go new file mode 100644 index 00000000..a8c5c551 --- /dev/null +++ b/pkg/platform/identityd/handlers_oauthas_token.go @@ -0,0 +1,146 @@ +// pkg/platform/identityd/handlers_oauthas_token.go — POST /oauth/token, the +// final leg of identityd's OWN OAuth AUTHORIZATION-SERVER role (see +// handlers_oauthas.go's package doc). Redeems the single-use authorization +// code handlers_oauthas_authorize.go minted at consent for a bearer access +// token, via pkg/web/mcpfront's Minter. +// +// identityd cannot import pkg/web/mcpfront directly: mcpfront is under +// pkg/web, and the import direction in this repo is web -> platform, never +// the reverse (see AGENTS.md). AccessTokenMinter, MintParams and Minted below +// are therefore a MIRROR of mcpfront.Minter.MintAccessToken's signature, not +// an import of it — webd (which can see both packages) adapts its concrete +// *mcpfront.Minter to satisfy AccessTokenMinter structurally, with no shared +// type between the two packages. Deps.Minter is nil until that adapter is +// wired (see webui.go); a nil Minter 503s rather than panicking. +package identityd + +import ( + "context" + "net/http" + "strings" + "time" + + "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/tools/mcp/oauth" +) + +// AccessTokenMinter mints an access token for an approved, validated +// authorization-code exchange. Mirrors +// pkg/web/mcpfront.Minter.MintAccessToken's signature — see the package doc +// above for why this is a mirror rather than an import. +type AccessTokenMinter interface { + MintAccessToken(ctx context.Context, p MintParams) (Minted, error) +} + +// MintParams mirrors pkg/web/mcpfront.MintParams field-for-field. +type MintParams struct { + Owner identity.CanonicalUserID + Role string + ScopeClasses []string + Unfiltered bool + ClientName string + ClientID string +} + +// Minted mirrors pkg/web/mcpfront.Minted field-for-field. +type Minted struct { + Value string + TokenID string + ExpiresAt time.Time +} + +// handleOAuthToken handles POST /oauth/token (RFC 6749 §4.1.3): redeems a +// single-use authorization code for a bearer access token. Every validation +// failure returns the SAME 400 invalid_grant (grant_type itself is the one +// exception, at unsupported_grant_type) — there is no oracle telling a caller +// WHICH check failed beyond that first one, matching the no-foothold posture +// the rest of this authorization-server surface already takes. +// +// The code is consumed (single-use, burned either way) BEFORE the +// client_id/redirect_uri/PKCE binding checks run — a mismatched exchange +// still spends the code, per RFC 6749 §4.1.2's "the authorization code MUST +// NOT be used more than once". A replayed or mismatched exchange can never +// succeed on a second attempt with the same code. +func (s *Server) handleOAuthToken(w http.ResponseWriter, r *http.Request) { + logger := log.FromContext(r.Context()) + + if err := r.ParseForm(); err != nil { + logger.Info("oauth token: ParseForm failed", "err", err.Error()) + oauthError(w, http.StatusBadRequest, "invalid_grant", "could not read the form submission") + return + } + + if r.FormValue("grant_type") != "authorization_code" { + oauthError(w, http.StatusBadRequest, "unsupported_grant_type", "only authorization_code is supported") + return + } + + clientID := r.FormValue("client_id") + reg, err := s.verifyClientID(clientID) + if err != nil { + logger.Info("oauth token: invalid client_id", "err", err.Error()) + oauthError(w, http.StatusBadRequest, "invalid_grant", "invalid authorization grant") + return + } + + entry, ok := s.authCodes.Consume(r.FormValue("code")) + if !ok { + oauthError(w, http.StatusBadRequest, "invalid_grant", "invalid authorization grant") + return + } + + if entry.ClientID != clientID { + logger.Info("oauth token: client_id mismatch") + oauthError(w, http.StatusBadRequest, "invalid_grant", "invalid authorization grant") + return + } + if entry.RedirectURI != r.FormValue("redirect_uri") { + logger.Info("oauth token: redirect_uri mismatch") + oauthError(w, http.StatusBadRequest, "invalid_grant", "invalid authorization grant") + return + } + if !oauth.VerifyS256(r.FormValue("code_verifier"), entry.Challenge) { + logger.Info("oauth token: PKCE verification failed") + oauthError(w, http.StatusBadRequest, "invalid_grant", "invalid authorization grant") + return + } + + if s.deps.Minter == nil { + logger.Info("oauth token: no access-token minter wired") + oauthError(w, http.StatusServiceUnavailable, "server_error", "token minting unavailable") + return + } + + minted, err := s.deps.Minter.MintAccessToken(r.Context(), MintParams{ + Owner: entry.Subject, + Role: entry.Role, + ScopeClasses: entry.ScopeClasses, + Unfiltered: entry.Unfiltered, + ClientName: reg.Name, + ClientID: clientID, + }) + if err != nil { + logger.Info("oauth token: mint failed", "err", err.Error()) + oauthError(w, http.StatusInternalServerError, "server_error", "could not mint access token") + return + } + + writeJSONBody(w, http.StatusOK, map[string]any{ + "access_token": minted.Value, + "token_type": "Bearer", + "expires_in": int(time.Until(minted.ExpiresAt).Seconds()), + "scope": scopeString(entry), + }) +} + +// scopeString renders the RFC 6749 §5.1 `scope` response field for a minted +// token: "role: classes:", or "role: classes:*" +// when the grant is unfiltered. +func scopeString(e authCodeEntry) string { + if e.Unfiltered { + return "role:" + e.Role + " classes:*" + } + return "role:" + e.Role + " classes:" + strings.Join(e.ScopeClasses, ",") +} diff --git a/pkg/platform/identityd/handlers_oauthas_token_test.go b/pkg/platform/identityd/handlers_oauthas_token_test.go new file mode 100644 index 00000000..9dbf7779 --- /dev/null +++ b/pkg/platform/identityd/handlers_oauthas_token_test.go @@ -0,0 +1,272 @@ +package identityd + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" + "github.com/authzed/openagentprimitives/pkg/platform/identity/passthroughlink" + "github.com/authzed/openagentprimitives/pkg/tools/mcp/oauth" +) + +// fakeMinter is the AccessTokenMinter test double: a canned Minted/error, +// recording every MintParams it receives in call order. +type fakeMinter struct { + calls []MintParams + minted Minted + err error +} + +func (f *fakeMinter) MintAccessToken(_ context.Context, p MintParams) (Minted, error) { + f.calls = append(f.calls, p) + if f.err != nil { + return Minted{}, f.err + } + return f.minted, nil +} + +// newTestServerWithMinter is newTestServerWithConsent plus a stubbed +// AccessTokenMinter, so /oauth/token (gated on Consent, same as +// authorize/consent) can be exercised without a real SpiceDB/K8s round trip. +func newTestServerWithMinter(t *testing.T, m AccessTokenMinter) *Server { + t.Helper() + c := fake.NewClientBuilder().Build() + return NewServer(Deps{ + K8s: c, + LinkSigner: passthroughlink.New([]byte("test-key")), + ExternalBaseURL: func() string { return "https://example.org" }, + Consent: stubConsentDeps{classes: []ConsentClass{ + {ID: "default/demo-agent", DisplayName: "Demo"}, + }}, + Minter: m, + }) +} + +func doTokenPost(t *testing.T, s *Server, form url.Values) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(http.MethodPost, "/oauth/token", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + s.Handler().ServeHTTP(rec, req) + return rec +} + +func TestTokenEndpoint(t *testing.T) { + mintedValue := accesstoken.TokenPrefix + "test-minted-value" + minter := &fakeMinter{minted: Minted{ + Value: mintedValue, + TokenID: "at-test", + ExpiresAt: time.Now().Add(90 * 24 * time.Hour), + }} + s := newTestServerWithMinter(t, minter) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + otherClient := mintTestClientID(t, s, "other tool", []string{"http://127.0.0.1:8888/cb"}) + subject := identity.CanonicalFromTrusted("alice", "test fixture") + + // newCode mints a fresh single-use authorization code bound to a fresh + // PKCE pair, and returns the matching baseline form for /oauth/token. + newCode := func(t *testing.T) url.Values { + t.Helper() + p, err := oauth.NewPKCE() + require.NoError(t, err) + code, err := s.authCodes.NewCode(authCodeEntry{ + ClientID: validClient, + RedirectURI: "http://127.0.0.1:7777/cb", + Challenge: p.Challenge, + Subject: subject, + Role: accesstoken.RoleRead, + ScopeClasses: []string{"default/demo-agent"}, + }) + require.NoError(t, err) + return url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "redirect_uri": {"http://127.0.0.1:7777/cb"}, + "client_id": {validClient}, + "code_verifier": {p.Verifier}, + } + } + + cases := []struct { + name string + mutate func(f url.Values) url.Values + exchangeTwice bool + wantCode int + wantErr string + }{ + { + name: "happy exchange: 200 + bearer", + mutate: func(f url.Values) url.Values { return f }, + wantCode: http.StatusOK, + }, + { + name: "wrong grant_type: 400 unsupported_grant_type", + mutate: func(f url.Values) url.Values { + f.Set("grant_type", "client_credentials") + return f + }, + wantCode: http.StatusBadRequest, + wantErr: "unsupported_grant_type", + }, + { + name: "unknown code: 400 invalid_grant", + mutate: func(f url.Values) url.Values { + f.Set("code", "does-not-exist") + return f + }, + wantCode: http.StatusBadRequest, + wantErr: "invalid_grant", + }, + { + name: "reused code: 400 invalid_grant", + mutate: func(f url.Values) url.Values { return f }, + exchangeTwice: true, + wantCode: http.StatusBadRequest, + wantErr: "invalid_grant", + }, + { + name: "client_id mismatch: 400 invalid_grant", + mutate: func(f url.Values) url.Values { + f.Set("client_id", otherClient) + return f + }, + wantCode: http.StatusBadRequest, + wantErr: "invalid_grant", + }, + { + name: "redirect_uri mismatch: 400 invalid_grant", + mutate: func(f url.Values) url.Values { + f.Set("redirect_uri", "http://127.0.0.1:9999/other") + return f + }, + wantCode: http.StatusBadRequest, + wantErr: "invalid_grant", + }, + { + name: "bad verifier: 400 invalid_grant", + mutate: func(f url.Values) url.Values { + f.Set("code_verifier", "this-is-not-the-right-verifier-at-all") + return f + }, + wantCode: http.StatusBadRequest, + wantErr: "invalid_grant", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + form := tc.mutate(newCode(t)) + + if tc.exchangeTwice { + first := doTokenPost(t, s, form) + require.Equal(t, http.StatusOK, first.Code, "first exchange of a fresh code must succeed before replay is attempted") + } + + rec := doTokenPost(t, s, form) + assert.Equal(t, tc.wantCode, rec.Code) + + var body map[string]any + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + + if tc.wantErr != "" { + assert.Equal(t, tc.wantErr, body["error"]) + return + } + + require.Equal(t, http.StatusOK, tc.wantCode) + assert.True(t, strings.HasPrefix(body["access_token"].(string), accesstoken.TokenPrefix)) + assert.Equal(t, "Bearer", body["token_type"]) + expiresIn, ok := body["expires_in"].(float64) + require.True(t, ok, "expires_in must be numeric") + assert.Greater(t, expiresIn, float64(0)) + assert.Equal(t, "role:read classes:default/demo-agent", body["scope"]) + + require.NotEmpty(t, minter.calls) + last := minter.calls[len(minter.calls)-1] + assert.Equal(t, accesstoken.RoleRead, last.Role) + assert.Equal(t, []string{"default/demo-agent"}, last.ScopeClasses) + assert.False(t, last.Unfiltered) + assert.Equal(t, subject, last.Owner) + assert.Equal(t, "demo tool", last.ClientName) + assert.Equal(t, validClient, last.ClientID) + }) + } +} + +// TestTokenEndpointMinterNilIsUnavailable asserts the Minter==nil fail-closed +// path: the route still mounts (gated on Consent, not Minter), but every +// exchange 503s rather than panicking. +func TestTokenEndpointMinterNilIsUnavailable(t *testing.T) { + s := newTestServerWithMinter(t, nil) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + p, err := oauth.NewPKCE() + require.NoError(t, err) + code, err := s.authCodes.NewCode(authCodeEntry{ + ClientID: validClient, + RedirectURI: "http://127.0.0.1:7777/cb", + Challenge: p.Challenge, + Subject: identity.CanonicalFromTrusted("alice", "test fixture"), + Role: accesstoken.RoleRead, + Unfiltered: true, + }) + require.NoError(t, err) + + rec := doTokenPost(t, s, url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "redirect_uri": {"http://127.0.0.1:7777/cb"}, + "client_id": {validClient}, + "code_verifier": {p.Verifier}, + }) + assert.Equal(t, http.StatusServiceUnavailable, rec.Code) + var body map[string]any + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + assert.Equal(t, "server_error", body["error"]) +} + +// TestTokenEndpointMintFailureIs500 asserts a Minter error surfaces as 500 +// server_error, logged, never a 2xx with a partial body. +func TestTokenEndpointMintFailureIs500(t *testing.T) { + minter := &fakeMinter{err: assertAnError{}} + s := newTestServerWithMinter(t, minter) + validClient := mintTestClientID(t, s, "demo tool", []string{"http://127.0.0.1:7777/cb"}) + p, err := oauth.NewPKCE() + require.NoError(t, err) + code, err := s.authCodes.NewCode(authCodeEntry{ + ClientID: validClient, + RedirectURI: "http://127.0.0.1:7777/cb", + Challenge: p.Challenge, + Subject: identity.CanonicalFromTrusted("alice", "test fixture"), + Role: accesstoken.RoleRead, + Unfiltered: true, + }) + require.NoError(t, err) + + rec := doTokenPost(t, s, url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "redirect_uri": {"http://127.0.0.1:7777/cb"}, + "client_id": {validClient}, + "code_verifier": {p.Verifier}, + }) + assert.Equal(t, http.StatusInternalServerError, rec.Code) + var body map[string]any + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + assert.Equal(t, "server_error", body["error"]) +} + +// assertAnError is a trivial non-nil error for TestTokenEndpointMintFailureIs500. +type assertAnError struct{} + +func (assertAnError) Error() string { return "mint boom" } diff --git a/pkg/platform/identityd/oauthas_code_store.go b/pkg/platform/identityd/oauthas_code_store.go new file mode 100644 index 00000000..6f8e114a --- /dev/null +++ b/pkg/platform/identityd/oauthas_code_store.go @@ -0,0 +1,183 @@ +// pkg/platform/identityd/oauthas_code_store.go — TTL'd, single-use stores for +// identityd's OWN OAuth AUTHORIZATION-SERVER role (Tasks 7-8). Same shape as +// oauth_state.go's oauthStateStore, which plays the equivalent role for +// identityd's OPPOSITE role — an OAuth CLIENT of an upstream MCP server. The +// two never share state: a code minted here authenticates a TOOL to +// identityd, not identityd to some other service. +// +// Two stores live here because the authorize/consent split needs two +// different TTL'd handoffs: pendingAuthStore carries the validated +// /oauth/authorize GET params to the /oauth/consent POST (minutes, one +// browser round trip); authCodeStore carries the approved grant to the +// /oauth/token exchange (Task 8), per RFC 6749 §4.1.2's short-lived code. +// +// Both are process-local: a restart mid-flow invalidates every outstanding +// pending-authorize or code, which fails the flow closed and costs only a +// re-attempt. +// +// The same process-locality means pendingAuthStore and authCodeStore ALSO +// cannot be read across replicas: the authorize -> consent -> token sequence +// for one flow must land on the SAME webd replica that minted the pending id +// (and later the code), or the later step's lookup simply misses. Current +// install manifests pin webd to replicas:1, so this is latent today. Running +// webd at >1 replica would require either session affinity on the OAuth AS +// paths (/oauth/authorize, /oauth/consent, /oauth/token) so one flow always +// hits the replica that holds its state, or a future signed-code design that +// makes the pending id / authorization code self-describing and verifiable +// by any replica — mirroring how the OAuth client_id itself is already +// stateless. This does NOT affect /mcp's bearer auth: that check is a +// stateless hash lookup against the AccessToken CR (via the K8s API / its +// informer cache), not against either store here, so /mcp itself is +// replica-safe regardless of webd's replica count. +package identityd + +import ( + "crypto/rand" + "encoding/hex" + "sync" + "time" + + "github.com/authzed/openagentprimitives/pkg/platform/identity" +) + +// authCodeEntry is everything /oauth/token (Task 8) needs to mint an access +// token, bound to one authorization code. +type authCodeEntry struct { + ClientID string + RedirectURI string + // Challenge is the PKCE S256 code_challenge from /oauth/authorize; the + // token exchange recomputes SHA256(code_verifier) and compares against it. + Challenge string + // Subject is the human who approved the grant at /oauth/consent — proven + // there, never trusted from the token request. + Subject identity.CanonicalUserID + // Role is one of accesstoken.Role{Read,Interact,Full}. + Role string + // ScopeClasses is the "ns/name" agent classes the subject scoped the grant + // to. Empty + Unfiltered=true means "everything this subject can access" — + // NOT "nothing": the two must never be conflated. + ScopeClasses []string + Unfiltered bool + // Expires is set by NewCode from the store's TTL at mint time. + Expires time.Time +} + +type authCodeStore struct { + mu sync.Mutex + entries map[string]authCodeEntry + ttl time.Duration +} + +func newAuthCodeStore(ttl time.Duration) *authCodeStore { + return &authCodeStore{entries: map[string]authCodeEntry{}, ttl: ttl} +} + +// NewCode mints a fresh random authorization code bound to e. +func (s *authCodeStore) NewCode(e authCodeEntry) (string, error) { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return "", err + } + code := hex.EncodeToString(b[:]) + s.mu.Lock() + defer s.mu.Unlock() + s.gcLocked() + e.Expires = time.Now().Add(s.ttl) + s.entries[code] = e + return code, nil +} + +// Consume removes the entry and returns it. Single-use: a missing, +// already-used, or expired code yields (zero, false), and the code is gone +// either way. +func (s *authCodeStore) Consume(code string) (authCodeEntry, bool) { + s.mu.Lock() + defer s.mu.Unlock() + s.gcLocked() + e, ok := s.entries[code] + if !ok { + return authCodeEntry{}, false + } + delete(s.entries, code) + if time.Now().After(e.Expires) { + return authCodeEntry{}, false + } + return e, true +} + +func (s *authCodeStore) gcLocked() { + now := time.Now() + for k, e := range s.entries { + if now.After(e.Expires) { + delete(s.entries, k) + } + } +} + +// pendingAuthEntry is the validated /oauth/authorize GET state, carried to +// /oauth/consent via the hidden `pending` form field. +type pendingAuthEntry struct { + ClientID string + ClientName string + RedirectURI string + Challenge string + State string + // Subject is the cookie subject AT AUTHORIZE TIME; the consent POST + // requires the posting cookie to still prove this same subject — a + // pending entry minted for one subject must never be redeemable by + // another, even if the pending id leaks. + Subject identity.CanonicalUserID + // Expires is set by NewPending from the store's TTL at mint time. + Expires time.Time +} + +type pendingAuthStore struct { + mu sync.Mutex + entries map[string]pendingAuthEntry + ttl time.Duration +} + +func newPendingAuthStore(ttl time.Duration) *pendingAuthStore { + return &pendingAuthStore{entries: map[string]pendingAuthEntry{}, ttl: ttl} +} + +// NewPending mints a fresh random pending id bound to e. +func (s *pendingAuthStore) NewPending(e pendingAuthEntry) (string, error) { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return "", err + } + id := hex.EncodeToString(b[:]) + s.mu.Lock() + defer s.mu.Unlock() + s.gcLocked() + e.Expires = time.Now().Add(s.ttl) + s.entries[id] = e + return id, nil +} + +// Consume removes the entry and returns it. Single-use, same contract as +// authCodeStore.Consume. +func (s *pendingAuthStore) Consume(id string) (pendingAuthEntry, bool) { + s.mu.Lock() + defer s.mu.Unlock() + s.gcLocked() + e, ok := s.entries[id] + if !ok { + return pendingAuthEntry{}, false + } + delete(s.entries, id) + if time.Now().After(e.Expires) { + return pendingAuthEntry{}, false + } + return e, true +} + +func (s *pendingAuthStore) gcLocked() { + now := time.Now() + for k, e := range s.entries { + if now.After(e.Expires) { + delete(s.entries, k) + } + } +} diff --git a/pkg/platform/identityd/oauthas_code_store_test.go b/pkg/platform/identityd/oauthas_code_store_test.go new file mode 100644 index 00000000..c79791a2 --- /dev/null +++ b/pkg/platform/identityd/oauthas_code_store_test.go @@ -0,0 +1,47 @@ +package identityd + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAuthCodeStoreSingleUseAndTTL(t *testing.T) { + st := newAuthCodeStore(time.Minute) + code, err := st.NewCode(authCodeEntry{ClientID: "c", Challenge: "ch"}) + require.NoError(t, err) + + got, ok := st.Consume(code) + require.True(t, ok) + assert.Equal(t, "ch", got.Challenge) + + _, ok = st.Consume(code) + assert.False(t, ok, "codes are single-use") + + st2 := newAuthCodeStore(-time.Second) // already expired + code2, err := st2.NewCode(authCodeEntry{ClientID: "c"}) + require.NoError(t, err) + _, ok = st2.Consume(code2) + assert.False(t, ok, "expired codes are refused") +} + +func TestPendingAuthStoreSingleUseAndTTL(t *testing.T) { + st := newPendingAuthStore(time.Minute) + id, err := st.NewPending(pendingAuthEntry{ClientID: "c", ClientName: "demo tool"}) + require.NoError(t, err) + + got, ok := st.Consume(id) + require.True(t, ok) + assert.Equal(t, "demo tool", got.ClientName) + + _, ok = st.Consume(id) + assert.False(t, ok, "pending entries are single-use") + + st2 := newPendingAuthStore(-time.Second) // already expired + id2, err := st2.NewPending(pendingAuthEntry{ClientID: "c"}) + require.NoError(t, err) + _, ok = st2.Consume(id2) + assert.False(t, ok, "expired pending entries are refused") +} diff --git a/pkg/platform/identityd/server.go b/pkg/platform/identityd/server.go index ac4521bf..9eef3ff5 100644 --- a/pkg/platform/identityd/server.go +++ b/pkg/platform/identityd/server.go @@ -75,6 +75,28 @@ type Deps struct { // message rather than anything reading as a permission problem, because the // fix is wiring, not a grant. AgentCredentialWriter AgentCredentialWriter + + // Consent answers the OAuth authorization-server consent screen's one + // question: which agent classes may a given subject grant a tool access + // to? See ConsentDeps's doc (handlers_oauthas_authorize.go) for why it is + // optional and what registering nothing means. + // + // Nil means the ENTIRE authorization-server surface — metadata, DCR + // register, authorize, consent, and Task 8's token endpoint — is not + // registered in routes(): a cluster without SpiceDB has no consent-class + // lookup and therefore no token feature to offer. + Consent ConsentDeps + + // Minter mints the access token /oauth/token exchanges an approved + // authorization code for. See handlers_oauthas_token.go's package doc for + // why this is a mirror interface rather than an import of + // pkg/web/mcpfront.Minter. + // + // Nil is FAIL-CLOSED: the route still mounts (it is gated on Consent, not + // on Minter), but every exchange 503s "token minting unavailable" rather + // than panicking. Task 9 wires the concrete adapter in webd; until then + // this stays nil in production. + Minter AccessTokenMinter } // Server is the identityd HTTP server. One per process. @@ -87,6 +109,12 @@ type Server struct { oauthState *oauthStateStore idp *idpLoader cliCodes *cliCodeStore + // authCodes and pendingAuth back identityd's OWN authorization-server role + // (Tasks 7-8) — see oauthas_code_store.go's package doc. Always + // constructed (unlike the Consent dep they depend on): the routes using + // them are what's conditionally registered, not the stores themselves. + authCodes *authCodeStore + pendingAuth *pendingAuthStore } // NewServer builds a Server with the given deps + registers routes. @@ -118,6 +146,11 @@ func NewServer(d Deps) *Server { now: time.Now, }, cliCodes: newCLICodeStore(), + // 5-min TTL on the code, matching RFC 6749 §4.1.2's "short lived" + // recommendation; 10-min on pending, generous enough for a human to + // read the consent screen and decide without feeling rushed. + authCodes: newAuthCodeStore(5 * time.Minute), + pendingAuth: newPendingAuthStore(10 * time.Minute), } s.registerRoutes() return s @@ -137,6 +170,14 @@ func (s *Server) Handler() http.Handler { return s.mux } // enforces only the method set, because every handler self-verifies the // idd_session cookie via checkOIDCCookie. /healthz is deliberately absent — // webd's health WebUI owns it, and registerRoutes adds it standalone-only. +// +// The OAuth authorize/consent pair is the one exception to "self-verifies via +// checkOIDCCookie": they read the subject through webui.SubjectFromContext +// instead, which only the webui framework's auth middleware populates. On the +// standalone mux that context value is never set by anything in this package, +// so those two routes have no real auth there — acceptable because the +// surface they belong to (gated on Consent below) exists for webd-mounted +// deployments with SpiceDB, not the standalone mux's test/e2e callers. func (s *Server) routes() []webui.Route { r := func(pattern string, methods []string, auth webui.AuthLevel, h http.HandlerFunc) webui.Route { return webui.Route{Origin: webui.OriginTrusted, Pattern: pattern, Methods: methods, Auth: auth, Handler: h} @@ -181,6 +222,31 @@ func (s *Server) routes() []webui.Route { r("/heartbeat", post, webui.AuthAuthenticated, s.handleHeartbeat), r("/link/oauth/", get, webui.AuthAuthenticated, s.handleLinkOAuthGet), } + // identityd's OWN OAuth AUTHORIZATION-SERVER surface — RFC 8414 metadata, + // RFC 7591 DCR, and the authorize/consent pair (Task 8 adds /oauth/token + // to this same block) — the opposite role from /link/oauth/ and + // /oauth/callback/ above, where identityd is the OAuth CLIENT of an + // upstream MCP server. Registered ONLY when Consent is wired: a cluster + // without SpiceDB has no consent-class lookup and therefore no token + // feature to offer, so these paths must 404 rather than serve a flow + // nothing can complete. Metadata/register are AuthHandlerManaged for the + // same reason /cli/exchange is: a not-yet-registered client has no + // credential to present, so the framework has nothing to authenticate — + // see handlers_oauthas.go. + if s.deps.Consent != nil { + routes = append(routes, + r("/.well-known/oauth-authorization-server", get, webui.AuthNone, s.handleOAuthASMetadata), + r("/oauth/register", post, webui.AuthHandlerManaged, s.handleOAuthRegister), + r("/oauth/authorize", get, webui.AuthLoginIfNecessary, s.handleOAuthAuthorize), + r("/oauth/consent", post, webui.AuthAuthenticated, s.handleOAuthConsent), + // /oauth/token is AuthHandlerManaged for the same reason + // metadata/register are: the credential being authenticated (the + // authorization code + PKCE verifier) is carried in the POST body + // itself, so the framework has nothing to check ahead of the + // handler — see handlers_oauthas_token.go. + r("/oauth/token", post, webui.AuthHandlerManaged, s.handleOAuthToken), + ) + } // /icon/ — public favicons; only when an IconHandler is wired. if s.deps.IconHandler != nil { routes = append(routes, webui.Route{ diff --git a/pkg/platform/identityd/server_test.go b/pkg/platform/identityd/server_test.go index 337a1ffd..d2ed79ad 100644 --- a/pkg/platform/identityd/server_test.go +++ b/pkg/platform/identityd/server_test.go @@ -1,6 +1,7 @@ package identityd import ( + "context" "net/http" "net/http/httptest" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/authzed/openagentprimitives/pkg/platform/identity" "github.com/authzed/openagentprimitives/pkg/platform/identity/passthroughlink" ) @@ -22,6 +24,34 @@ func newTestServer(t *testing.T) *Server { }) } +// stubConsentDeps is the test double for ConsentDeps: fixed classes/err per +// test, so the OAuth authorization-server's authorize/consent handlers (Task +// 7) can be exercised without SpiceDB. +type stubConsentDeps struct { + classes []ConsentClass + err error +} + +func (d stubConsentDeps) ConsentClasses(_ context.Context, _ identity.CanonicalUserID) ([]ConsentClass, error) { + return d.classes, d.err +} + +// newTestServerWithConsent is newTestServer plus a stub ConsentDeps, so the +// AS routes gated on Deps.Consent != nil (metadata, register, authorize, +// consent) are mounted. +func newTestServerWithConsent(t *testing.T) *Server { + t.Helper() + c := fake.NewClientBuilder().Build() + return NewServer(Deps{ + K8s: c, + LinkSigner: passthroughlink.New([]byte("test-key")), + ExternalBaseURL: func() string { return "https://example.org" }, + Consent: stubConsentDeps{classes: []ConsentClass{ + {ID: "default/demo-agent", DisplayName: "Demo"}, + }}, + }) +} + func TestHealthz(t *testing.T) { srv := newTestServer(t) req := httptest.NewRequest(http.MethodGet, "/healthz", nil) diff --git a/pkg/platform/identityd/webui.go b/pkg/platform/identityd/webui.go index a6ee4091..b1300d3e 100644 --- a/pkg/platform/identityd/webui.go +++ b/pkg/platform/identityd/webui.go @@ -84,6 +84,31 @@ func (ui) Routes(deps webui.Deps) []webui.Route { "(authorization client wired=%t, operator client wired=%t)\n", sd.AgentIdentityAuthz != nil, sd.AgentCredentialWriter != nil) } + // Optional, same shape as WebAuthzDeps above: only a SpiceDB-configured + // webd implements ConsentDeps directly (its ConsentClasses method, built + // over LookupStartableClasses + LookupInteractableSessions). Absent, the + // field stays a genuine nil interface and server.go's routes() does not + // register ANY of the OAuth authorization-server surface. + if cd, ok := deps.(ConsentDeps); ok { + sd.Consent = cd + } + if sd.Consent == nil { + fmt.Fprintf(os.Stderr, "identityd: OAuth authorization-server routes "+ + "(metadata/register/authorize/consent) are NOT mounted (no consent-class deps wired)\n") + } + // Optional, same shape as ConsentDeps above: Task 9 wires webd's concrete + // *mcpfront.Minter to satisfy AccessTokenMinter structurally (identityd + // cannot import pkg/web/mcpfront — see handlers_oauthas_token.go's package + // doc). No deps value implements this yet, so sd.Minter stays a genuine + // nil interface in production: /oauth/token still mounts (it is gated on + // Consent, not on Minter) but 503s "token minting unavailable" until Task + // 9 lands. + if m, ok := deps.(AccessTokenMinter); ok { + sd.Minter = m + } + if sd.Minter == nil { + fmt.Fprintf(os.Stderr, "identityd: /oauth/token will respond 503 (no access-token minter wired)\n") + } return NewServer(sd).routes() } diff --git a/pkg/platform/identityd/webui_test.go b/pkg/platform/identityd/webui_test.go index 5c028088..58258598 100644 --- a/pkg/platform/identityd/webui_test.go +++ b/pkg/platform/identityd/webui_test.go @@ -1,6 +1,7 @@ package identityd import ( + "context" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/authzed/openagentprimitives/pkg/channels/channelkinds" + "github.com/authzed/openagentprimitives/pkg/platform/identity" "github.com/authzed/openagentprimitives/pkg/platform/identity/passthroughlink" "github.com/authzed/openagentprimitives/pkg/web/webui" webuiregistry "github.com/authzed/openagentprimitives/pkg/web/webui/registry" @@ -41,6 +43,14 @@ func (d fakeWebDeps) Authenticators() map[string]channelkinds.WebAuthenticator { } func (d fakeWebDeps) InsecureTrustLinks() bool { return false } +// ConsentClasses makes fakeWebDeps satisfy identityd.ConsentDeps (the Task 7 +// optional interface), so the OAuth authorization-server routes (metadata, +// register, authorize, consent) are part of the base route set these tests +// assert against. +func (d fakeWebDeps) ConsentClasses(_ context.Context, _ identity.CanonicalUserID) ([]ConsentClass, error) { + return []ConsentClass{{ID: "default/demo-agent", DisplayName: "Demo"}}, nil +} + // routeKey is patterns paired with the auth level we expect. func routeIndex(routes []webui.Route) map[string]webui.Route { out := make(map[string]webui.Route, len(routes)) @@ -76,21 +86,26 @@ func TestIdentityUI_Routes_PatternsAndAuthLevels(t *testing.T) { methods []string } expected := map[string]want{ - "/link": {webui.AuthNone, []string{http.MethodGet}}, - "/link/agent-oauth/": {webui.AuthNone, []string{http.MethodGet}}, - "/oidc/login": {webui.AuthNone, []string{http.MethodGet}}, - "/oidc/callback/": {webui.AuthNone, []string{http.MethodGet}}, - "/oauth/callback/": {webui.AuthNone, []string{http.MethodGet}}, - "/oauth/agent-callback/": {webui.AuthNone, []string{http.MethodGet}}, - "/my/accounts": {webui.AuthNone, []string{http.MethodGet}}, - "/cli/login": {webui.AuthNone, []string{http.MethodGet}}, - "/cli/exchange": {webui.AuthHandlerManaged, []string{http.MethodPost}}, - "/password/login": {webui.AuthNone, []string{http.MethodGet}}, - "/password/verify": {webui.AuthHandlerManaged, []string{http.MethodPost}}, - "/link/submit": {webui.AuthAuthenticated, []string{http.MethodPost}}, - "/my/accounts/": {webui.AuthAuthenticated, []string{http.MethodGet, http.MethodPost}}, - "/heartbeat": {webui.AuthAuthenticated, []string{http.MethodPost}}, - "/link/oauth/": {webui.AuthAuthenticated, []string{http.MethodGet}}, + "/.well-known/oauth-authorization-server": {webui.AuthNone, []string{http.MethodGet}}, + "/oauth/register": {webui.AuthHandlerManaged, []string{http.MethodPost}}, + "/oauth/authorize": {webui.AuthLoginIfNecessary, []string{http.MethodGet}}, + "/oauth/consent": {webui.AuthAuthenticated, []string{http.MethodPost}}, + "/oauth/token": {webui.AuthHandlerManaged, []string{http.MethodPost}}, + "/link": {webui.AuthNone, []string{http.MethodGet}}, + "/link/agent-oauth/": {webui.AuthNone, []string{http.MethodGet}}, + "/oidc/login": {webui.AuthNone, []string{http.MethodGet}}, + "/oidc/callback/": {webui.AuthNone, []string{http.MethodGet}}, + "/oauth/callback/": {webui.AuthNone, []string{http.MethodGet}}, + "/oauth/agent-callback/": {webui.AuthNone, []string{http.MethodGet}}, + "/my/accounts": {webui.AuthNone, []string{http.MethodGet}}, + "/cli/login": {webui.AuthNone, []string{http.MethodGet}}, + "/cli/exchange": {webui.AuthHandlerManaged, []string{http.MethodPost}}, + "/password/login": {webui.AuthNone, []string{http.MethodGet}}, + "/password/verify": {webui.AuthHandlerManaged, []string{http.MethodPost}}, + "/link/submit": {webui.AuthAuthenticated, []string{http.MethodPost}}, + "/my/accounts/": {webui.AuthAuthenticated, []string{http.MethodGet, http.MethodPost}}, + "/heartbeat": {webui.AuthAuthenticated, []string{http.MethodPost}}, + "/link/oauth/": {webui.AuthAuthenticated, []string{http.MethodGet}}, } assert.Len(t, routes, len(expected), "no-icon deps yields exactly the base route set") for pattern, w := range expected { @@ -107,21 +122,26 @@ func TestIdentityUI_Routes_IconConditional(t *testing.T) { // expected mirrors the base route set from TestIdentityUI_Routes_PatternsAndAuthLevels // so that "icon adds exactly one route" is expressed as len(expected)+1. expected := map[string]struct{}{ - "/link": {}, - "/link/agent-oauth/": {}, - "/oidc/login": {}, - "/oidc/callback/": {}, - "/oauth/callback/": {}, - "/oauth/agent-callback/": {}, - "/my/accounts": {}, - "/cli/login": {}, - "/cli/exchange": {}, - "/password/login": {}, - "/password/verify": {}, - "/link/submit": {}, - "/my/accounts/": {}, - "/heartbeat": {}, - "/link/oauth/": {}, + "/.well-known/oauth-authorization-server": {}, + "/oauth/register": {}, + "/oauth/authorize": {}, + "/oauth/consent": {}, + "/oauth/token": {}, + "/link": {}, + "/link/agent-oauth/": {}, + "/oidc/login": {}, + "/oidc/callback/": {}, + "/oauth/callback/": {}, + "/oauth/agent-callback/": {}, + "/my/accounts": {}, + "/cli/login": {}, + "/cli/exchange": {}, + "/password/login": {}, + "/password/verify": {}, + "/link/submit": {}, + "/my/accounts/": {}, + "/heartbeat": {}, + "/link/oauth/": {}, } iconH := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {}) routes := ui{}.Routes(newFakeWebDeps(t, iconH)) diff --git a/pkg/platform/manifests/install.yaml b/pkg/platform/manifests/install.yaml index c0e362f8..1c30593c 100644 --- a/pkg/platform/manifests/install.yaml +++ b/pkg/platform/manifests/install.yaml @@ -39,6 +39,177 @@ volumeBindingMode: WaitForFirstConsumer --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: accesstokens.agentprimitives.authzed.com +spec: + group: agentprimitives.authzed.com + names: + categories: + - authzed + - spicebox + kind: AccessToken + listKind: AccessTokenList + plural: accesstokens + shortNames: + - atok + singular: accesstoken + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.owner + name: Owner + type: string + - jsonPath: .spec.clientName + name: Client + type: string + - jsonPath: .spec.expiresAt + name: Expires + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + AccessToken is the AUTHENTICATION record for one inbound delegated + credential minted at OAuth consent: hash → token identity, plus display + metadata and expiry. It deliberately carries NO role and NO scope — the + authorization truth lives entirely in SpiceDB on accesstoken: + (see pkg/authz/spicedb/schema/schema.zed). Deleting this object is + revocation: the finalizer removes the SpiceDB tuples. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + clientID: + description: ClientID is the OAuth client the token was issued to. + type: string + clientName: + description: ClientName is the DCR-supplied display name, sanitized + at mint. + type: string + expiresAt: + description: |- + ExpiresAt is when the token stops authenticating. The SpiceDB tuples + carry the same instant via the expiration trait; this copy gates the + authentication (hash-lookup) step without a SpiceDB round trip. + format: date-time + type: string + owner: + description: Owner is the canonical user id the token acts as. + minLength: 1 + type: string + tokenHash: + description: |- + TokenHash is hex(sha256(bearer value)). The value itself is never + stored server-side. + pattern: ^[0-9a-f]{64}$ + type: string + required: + - expiresAt + - owner + - tokenHash + type: object + x-kubernetes-validations: + - message: tokenHash is immutable + rule: self.tokenHash == oldSelf.tokenHash + - message: owner is immutable + rule: self.owner == oldSelf.owner + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastUsedAt: + description: |- + LastUsedAt is observation, controller-owned, updated on meaningful + change only (not every request). + format: date-time + type: string + observedGeneration: + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.20.1 @@ -25485,32 +25656,34 @@ rules: - apiGroups: - agentprimitives.authzed.com resources: - - agentclasses - - agentidentities - - agentsessions - - agentuis - - channels - - clusterskills - - mcpservers - - sidecartoolboxes - - skills - - spiceboxsessions - - spiceboxtoolkits - - spiceboxtoolspecs - - subagentrequests - - toolcalls - - workshops + - accesstokens verbs: - - create - delete - get - list - - patch - update - watch - apiGroups: - agentprimitives.authzed.com resources: + - accesstokens/finalizers + - agentsessions/finalizers + - artifactrenders/finalizers + - credentialupdaterequests/finalizers + - publicendpoints/finalizers + - sidecartoolboxes/finalizers + - spiceboxsessions/finalizers + - spicedbbootstraps/finalizers + - toolcalls/finalizers + - useridentities/finalizers + - workshops/finalizers + - workspacesources/finalizers + verbs: + - update +- apiGroups: + - agentprimitives.authzed.com + resources: + - accesstokens/status - agentclasses/status - agentidentities/status - agentsessiongrants/status @@ -25551,29 +25724,39 @@ rules: - apiGroups: - agentprimitives.authzed.com resources: - - agentsessiongrants + - agentclasses + - agentidentities + - agentsessions + - agentuis + - channels + - clusterskills + - mcpservers + - sidecartoolboxes + - skills + - spiceboxsessions + - spiceboxtoolkits + - spiceboxtoolspecs + - subagentrequests + - toolcalls + - workshops verbs: - create + - delete - get - list + - patch - update - watch - apiGroups: - agentprimitives.authzed.com resources: - - agentsessions/finalizers - - artifactrenders/finalizers - - credentialupdaterequests/finalizers - - publicendpoints/finalizers - - sidecartoolboxes/finalizers - - spiceboxsessions/finalizers - - spicedbbootstraps/finalizers - - toolcalls/finalizers - - useridentities/finalizers - - workshops/finalizers - - workspacesources/finalizers + - agentsessiongrants verbs: + - create + - get + - list - update + - watch - apiGroups: - agentprimitives.authzed.com resources: @@ -25880,6 +26063,21 @@ rules: - agentidentities verbs: - get +- apiGroups: + - agentprimitives.authzed.com + resources: + - accesstokens + verbs: + - create + - get + - list +- apiGroups: + - agentprimitives.authzed.com + resources: + - accesstokens/status + verbs: + - update + - patch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/pkg/platform/manifests/rbac_sufficiency_test.go b/pkg/platform/manifests/rbac_sufficiency_test.go index a3f11938..f270c3af 100644 --- a/pkg/platform/manifests/rbac_sufficiency_test.go +++ b/pkg/platform/manifests/rbac_sufficiency_test.go @@ -350,6 +350,16 @@ func operatorComponent() component { required := reqs(apGroup, map[string][]string{ "agentsessions": {"get", "list", "watch", "create", "update", "patch", "delete"}, + // AccessToken reconciler (pkg/controllers/accesstoken): get is + // LoadInto's per-reconcile read, list;watch back the For informer — + // and admind's tokens list (pkg/web/admind/tokens.go), hosted in this + // same operator binary — update is EnsureFinalizer/RemoveFinalizer's + // plain metadata writes, delete is the expiry self-delete plus + // admind's authorized revoke. No create (minting is webd's + // pkg/web/mcpfront.Minter) and no main-resource patch (the only patch + // targets accesstokens/status; see the sufficiency row below) — + // minimized out of the marker, so minimality guards both trims. + "accesstokens": {"get", "list", "watch", "update", "delete"}, // agentclasses/agentidentities/channels/mcpservers/sidecartoolboxes/ // spicebox{classes,toolkits,toolspecs} carry create+patch beyond the // reconcilers' own get;list;watch(;update;patch) needs — admind's @@ -493,6 +503,11 @@ func operatorComponent() component { // subagentrequests above: "update", not "patch". required = append(required, req{ group: apGroup, resource: "relationshipsources", subresource: "status", verb: "update"}) + // AccessToken reconciler writes Ready/observedGeneration via + // Status().Patch (MergeFrom) — sufficiency-only, like every other status + // row here (grantedKeys skips */status for minimality). + required = append(required, req{ + group: apGroup, resource: "accesstokens", subresource: "status", verb: "patch"}) required = append(required, reqs("", map[string][]string{ "secrets": {"get", "list", "watch", "create", "update", "patch"}, // patch: admind's oap-install endpoint SSA-applies a bundle-created @@ -711,6 +726,11 @@ func operatorComponent() component { {group: rbacv1.GroupName, resource: "clusterrolebindings", verb: "get"}, g("spiceboxclasses", "update"), g("agentsessiongrants", "delete"), + // AccessToken minting is webd's alone (pkg/web/mcpfront.Minter); + // the operator only reconciles/expires/revokes. create re-appearing + // on the operator role would let the cleanup half mint + // authentication records, inverting the mint-vs-finalize split. + g("accesstokens", "create"), core("configmaps", "deletecollection"), // WorkspaceSource least-privilege: the reconciler never deletes a // WorkspaceSource (author-owned). Guard the trim so a re-broadened @@ -824,6 +844,20 @@ func webdComponent() component { {resource: "secrets", verb: "delete", namespace: "agentprimitives-system"}, g("sessionuseridentities", "get"), {group: apGroup, resource: "sessionuseridentities", subresource: "status", verb: "update"}, + // The OAuth-minted /mcp surface (pkg/web/mcpfront): create is + // /oauth/token's mint (Minter.MintAccessToken creates the + // AccessToken CR), list is the bearer middleware's hash-lookup + // relist (tokenCache.relist), get is touchLastUsed's + // read-before-status-patch. No watch — webd's client is direct + // (client.New, uncached; no informer) — and no delete: revocation + // is admind's authorized route in the OPERATOR binary plus the + // AccessToken finalizer, never the browser-facing pod (pinned + // denied in forbidden below). + g("accesstokens", "create"), g("accesstokens", "get"), g("accesstokens", "list"), + // touchLastUsed records status.lastUsedAt via a MergeFrom status + // patch — sufficiency-only, like every status row (grantedKeys + // skips */status for minimality). + {group: apGroup, resource: "accesstokens", subresource: "status", verb: "patch"}, g("mcpservers", "get"), g("mcpservers", "list"), g("clusteridentityproviders", "get"), // list: webd reads AgentClasses cluster-wide (agent picker / icon @@ -911,6 +945,12 @@ func webdComponent() component { // A tenant namespace no webd Role covers is the discriminator. {resource: "secrets", verb: "get", namespace: "some-tenant-namespace"}, {resource: "secrets", verb: "update", namespace: "some-tenant-namespace"}, + // A browser-facing pod must never destroy token records: + // revocation flows through admind (operator binary, authorized + // per request) and the AccessToken finalizer. delete appearing on + // webd's role would let a webd compromise erase the audit-visible + // authentication record for a still-granted SpiceDB tuple set. + g("accesstokens", "delete"), }, } } diff --git a/pkg/tools/mcp/oauth/verify.go b/pkg/tools/mcp/oauth/verify.go new file mode 100644 index 00000000..a17d4e55 --- /dev/null +++ b/pkg/tools/mcp/oauth/verify.go @@ -0,0 +1,19 @@ +package oauth + +import ( + "crypto/sha256" + "crypto/subtle" + "encoding/base64" +) + +// VerifyS256 reports whether verifier hashes to challenge under the PKCE +// S256 method — the server-side half of NewPKCE, kept in this package so +// client and server share one spelling. +func VerifyS256(verifier, challenge string) bool { + if verifier == "" || challenge == "" { + return false + } + sum := sha256.Sum256([]byte(verifier)) + want := base64.RawURLEncoding.EncodeToString(sum[:]) + return subtle.ConstantTimeCompare([]byte(want), []byte(challenge)) == 1 +} diff --git a/pkg/tools/mcp/oauth/verify_test.go b/pkg/tools/mcp/oauth/verify_test.go new file mode 100644 index 00000000..18f158ec --- /dev/null +++ b/pkg/tools/mcp/oauth/verify_test.go @@ -0,0 +1,17 @@ +package oauth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVerifyS256MatchesNewPKCE(t *testing.T) { + p, err := NewPKCE() + require.NoError(t, err) + assert.True(t, VerifyS256(p.Verifier, p.Challenge)) + assert.False(t, VerifyS256(p.Verifier+"x", p.Challenge)) + assert.False(t, VerifyS256("", p.Challenge)) + assert.False(t, VerifyS256(p.Verifier, "")) +} diff --git a/pkg/web/admind/admind.go b/pkg/web/admind/admind.go index f4e3aaab..be6763d1 100644 --- a/pkg/web/admind/admind.go +++ b/pkg/web/admind/admind.go @@ -158,6 +158,19 @@ type Config struct { // treatment Identities and KG get, rather than failing admind construction // over an additive read. Scopes SourceScopeReader + // AccessTokenGrants reads an AccessToken's SpiceDB authorization grant + // (role, scope classes, unfiltered) for the admin Tokens page's role + // column — *spicedb.Client.ReadAccessTokenGrant (Task 3) satisfies this. + // OPTIONAL and NOT in the required-deps check below — nil degrades every + // row's role to "unknown" rather than failing admind construction, same + // treatment as Identities/Scopes/KG (see tokens.go). + AccessTokenGrants AccessTokenGrantReader + // AccessTokenNamespace is where AccessToken CRs live — the SAME namespace + // webd's --accesstoken-namespace mints into (default + // "agentprimitives-system"), so the Tokens page lists exactly what the + // /mcp bearer middleware checks against. Empty fails the tokens routes + // closed (see tokens.go) rather than listing cluster-wide. + AccessTokenNamespace string } // Admind is the admin API: mount Handler() under /admin/ on the @@ -521,6 +534,14 @@ var routeTable = []Route{ plain: func(a *Admind) http.HandlerFunc { return a.handleCluster }}, {Method: http.MethodGet, Pattern: "/admin/v1/access", Permission: "view_config", plain: func(a *Admind) http.HandlerFunc { return a.handleAccess }}, + // The access-token page: list is view_tokens, revoke is revoke_token — both + // alias can_admin today (see schema.zed), but are their OWN permissions so + // a future narrower grant (view without revoke) is a schema change, not a + // handler change. + {Method: http.MethodGet, Pattern: "/admin/v1/tokens", Permission: "view_tokens", + plain: func(a *Admind) http.HandlerFunc { return a.handleTokensList }}, + {Method: http.MethodPost, Pattern: "/admin/v1/tokens/revoke", Permission: "revoke_token", + plain: func(a *Admind) http.HandlerFunc { return a.handleTokensRevoke }}, {Method: http.MethodPost, Pattern: "/admin/v1/agents/oap-install", Permission: "install_agent", plain: func(a *Admind) http.HandlerFunc { return a.handleOapInstall }}, // The three channel routes sit behind the SAME permission as the install diff --git a/pkg/web/admind/tokens.go b/pkg/web/admind/tokens.go new file mode 100644 index 00000000..02ac3dd1 --- /dev/null +++ b/pkg/web/admind/tokens.go @@ -0,0 +1,190 @@ +package admind + +import ( + "context" + "encoding/json" + "net/http" + "sort" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" +) + +// maxTokenRevokeBody caps the revoke POST body — it carries nothing but a +// CR name, so this is generous rather than load-bearing. +const maxTokenRevokeBody = 4096 + +// roleUnknown is the role shown for a row admind could not resolve a SpiceDB +// grant for (a read error, or no AccessTokenGrants reader configured) — NOT +// the same as revoked: an unknown role means "we couldn't tell", a revoked +// row means ReadAccessTokenGrant positively found no tuples. +const roleUnknown = "unknown" + +// AccessTokenGrantReader reads an AccessToken's SpiceDB authorization grant +// (role, scope classes, unfiltered) for display on the admin tokens page — +// *spicedb.Client.ReadAccessTokenGrant (Task 3) satisfies this. +// +// OPTIONAL and NOT in admind.New's required-deps check: nil degrades every +// row's role to "unknown" rather than failing admind construction — the same +// degrade-not-fail treatment SubjectIdentityReader/SourceScopeReader get. The +// CR list itself (name/owner/client/expiry/last-used) renders either way, +// because those fields come from the CR, never from SpiceDB. +type AccessTokenGrantReader interface { + ReadAccessTokenGrant(ctx context.Context, tokenID string) (spicedb.AccessTokenGrant, bool, error) +} + +// accessTokenRow is one AccessToken projected for the admin Tokens table. +// Deliberately omits spec.TokenHash: the hash is a credential-adjacent value +// that must never reach a response, a log line, or this struct. +type accessTokenRow struct { + Name string `json:"name"` + Owner string `json:"owner"` + ClientName string `json:"clientName,omitempty"` + // Role is the SpiceDB role ladder this token climbed ("read"/"interact"/ + // "full"), or roleUnknown when the grant could not be read. + Role string `json:"role"` + ScopeClasses []string `json:"scopeClasses,omitempty"` + Unfiltered bool `json:"unfiltered"` + CreatedAt string `json:"createdAt"` + ExpiresAt string `json:"expiresAt"` + LastUsedAt string `json:"lastUsedAt,omitempty"` + // Revoked is true when the CR still exists but ReadAccessTokenGrant found + // no SpiceDB tuples for it — a token whose authorization was pulled out + // from under an otherwise-present CR. + Revoked bool `json:"revoked"` +} + +// tokensResponse is the GET /admin/v1/tokens payload. +type tokensResponse struct { + Tokens []accessTokenRow `json:"tokens"` +} + +// handleTokensList lists every AccessToken in cfg.AccessTokenNamespace, +// joining each one's SpiceDB grant for its role/scope. A per-item grant-read +// error (or no AccessTokenGrants configured at all) degrades that ROW to +// role "unknown" — logged — rather than 500ing the whole page: one bad token +// must not blank the console for every admin. +func (a *Admind) handleTokensList(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if a.cfg.AccessTokenNamespace == "" { + // Fail closed: client.InNamespace("") lists CLUSTER-WIDE, which is never + // the intended behavior for an unconfigured namespace. + a.cfg.Logger.Info("admind: tokens list refused; AccessTokenNamespace not configured") + writeJSONError(w, http.StatusInternalServerError, "access token namespace not configured") + return + } + + var list spiceboxv1alpha1.AccessTokenList + if err := a.cfg.K8s.List(ctx, &list, client.InNamespace(a.cfg.AccessTokenNamespace)); err != nil { + a.cfg.Logger.Info("admind: tokens list failed", "namespace", a.cfg.AccessTokenNamespace, "err", err.Error()) + writeJSONError(w, http.StatusInternalServerError, "list access tokens failed: "+err.Error()) + return + } + + if a.cfg.AccessTokenGrants == nil { + a.cfg.Logger.Info("admind: tokens list: no AccessTokenGrants reader configured; every row's role is unavailable") + } + + items := list.Items + sort.Slice(items, func(i, j int) bool { + ti, tj := items[i].CreationTimestamp.Time, items[j].CreationTimestamp.Time + if !ti.Equal(tj) { + return ti.After(tj) + } + return items[i].Name < items[j].Name + }) + + rows := make([]accessTokenRow, 0, len(items)) + for i := range items { + rows = append(rows, a.projectAccessToken(ctx, &items[i])) + } + writeJSON(w, http.StatusOK, tokensResponse{Tokens: rows}) +} + +// projectAccessToken builds one table row from an AccessToken CR plus (when +// available) its SpiceDB grant. Everything that can come from the CR alone — +// name, owner, client, created/expires, last-used — is filled in first and +// unconditionally, so a grant-read failure never blanks those fields, only +// Role/ScopeClasses/Unfiltered/Revoked. +func (a *Admind) projectAccessToken(ctx context.Context, at *spiceboxv1alpha1.AccessToken) accessTokenRow { + row := accessTokenRow{ + Name: at.Name, + Owner: at.Spec.Owner, + ClientName: at.Spec.ClientName, + Role: roleUnknown, + CreatedAt: at.CreationTimestamp.UTC().Format(time.RFC3339), + ExpiresAt: at.Spec.ExpiresAt.UTC().Format(time.RFC3339), + } + if at.Status.LastUsedAt != nil { + row.LastUsedAt = at.Status.LastUsedAt.Time.UTC().Format(time.RFC3339) + } + + if a.cfg.AccessTokenGrants == nil { + return row + } + + grant, found, err := a.cfg.AccessTokenGrants.ReadAccessTokenGrant(ctx, at.Name) + if err != nil { + a.cfg.Logger.Info("admind: read access token grant failed", "token", at.Name, "err", err.Error()) + return row + } + if !found { + row.Revoked = true + return row + } + row.Role = grant.Role + row.ScopeClasses = grant.ScopeClasses + row.Unfiltered = grant.Unfiltered + return row +} + +// accessTokenRevokeRequest is the POST /admin/v1/tokens/revoke body. +type accessTokenRevokeRequest struct { + Name string `json:"name"` +} + +// accessTokenRevokeResponse is the 200 body. +type accessTokenRevokeResponse struct { + OK bool `json:"ok"` +} + +// handleTokensRevoke deletes the named AccessToken CR in +// cfg.AccessTokenNamespace — the CR's finalizer removes its SpiceDB tuples, +// so admind itself never touches SpiceDB for a revoke. 404 when the CR is +// already absent. +func (a *Admind) handleTokensRevoke(w http.ResponseWriter, r *http.Request) { + if a.cfg.AccessTokenNamespace == "" { + a.cfg.Logger.Info("admind: token revoke refused; AccessTokenNamespace not configured") + writeJSONError(w, http.StatusInternalServerError, "access token namespace not configured") + return + } + + var body accessTokenRevokeRequest + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxTokenRevokeBody)).Decode(&body); err != nil { + writeJSONError(w, http.StatusBadRequest, "parse JSON body: "+err.Error()) + return + } + if body.Name == "" { + writeJSONError(w, http.StatusBadRequest, "name is required") + return + } + + obj := &spiceboxv1alpha1.AccessToken{} + obj.Namespace, obj.Name = a.cfg.AccessTokenNamespace, body.Name + subject := r.Header.Get("X-Admin-Subject") + if err := a.cfg.K8s.Delete(r.Context(), obj); err != nil { + if apierrors.IsNotFound(err) { + writeJSONError(w, http.StatusNotFound, "access token not found") + return + } + a.cfg.Logger.Info("admind: token revoke failed", "token", body.Name, "subject", subject, "err", err.Error()) + writeJSONError(w, http.StatusInternalServerError, "revoke failed: "+err.Error()) + return + } + a.cfg.Logger.Info("admind: access token revoked by admin", "token", body.Name, "subject", subject) + writeJSON(w, http.StatusOK, accessTokenRevokeResponse{OK: true}) +} diff --git a/pkg/web/admind/tokens_test.go b/pkg/web/admind/tokens_test.go new file mode 100644 index 00000000..0fce9e83 --- /dev/null +++ b/pkg/web/admind/tokens_test.go @@ -0,0 +1,274 @@ +package admind_test + +import ( + "context" + "encoding/json" + "net/http" + "testing" + "time" + + "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/memory" + "github.com/authzed/openagentprimitives/pkg/memory/inmem" + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" + "github.com/authzed/openagentprimitives/pkg/web/admind" +) + +// --- fixtures --------------------------------------------------------------- + +const ( + tkToken = "test-admind-token" + tkAdmin = "user:demo-admin" // holds view_tokens / revoke_token + tkNonAdmin = "user:nobody" // holds nothing + tkAdminID = "demo-admin" + tkNS = "agentprimitives-system" + + tkFullName = "at-full0000001" + tkFullOwner = "alice" + tkFullHash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" // 64 hex chars + tkRevokedName = "at-revoked0001" + tkRevokedHash = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +) + +// stubGrantReader scripts ReadAccessTokenGrant per token id: a grant in +// `grants` returns found=true with that value; a tokenID in `errs` returns an +// error; anything else returns found=false (the "revoked" shape) with no +// error — mirrors *spicedb.Client.ReadAccessTokenGrant's own found=false +// contract. +type stubGrantReader struct { + grants map[string]spicedb.AccessTokenGrant + errs map[string]error +} + +func (s stubGrantReader) ReadAccessTokenGrant(_ context.Context, tokenID string) (spicedb.AccessTokenGrant, bool, error) { + if err, ok := s.errs[tokenID]; ok { + return spicedb.AccessTokenGrant{}, false, err + } + if g, ok := s.grants[tokenID]; ok { + return g, true, nil + } + return spicedb.AccessTokenGrant{}, false, nil +} + +type grantReadErr struct{} + +func (grantReadErr) Error() string { return "spicedb read boom" } + +// tkChecker grants every platform permission to tkAdminID and nothing to +// anyone else — the same binary allow/deny shape handler_test.go's +// stubChecker uses, reused directly from that file (same package). +func tkChecker() stubChecker { + return stubChecker{allow: map[string]bool{tkAdminID: true}} +} + +func tokenCR(name, owner, hash string, expires time.Time, opts ...func(*spiceboxv1alpha1.AccessToken)) *spiceboxv1alpha1.AccessToken { + at := &spiceboxv1alpha1.AccessToken{ + ObjectMeta: metav1.ObjectMeta{Namespace: tkNS, Name: name}, + Spec: spiceboxv1alpha1.AccessTokenSpec{ + TokenHash: hash, + Owner: owner, + ClientName: "demo-client", + ClientID: "demo-client-id", + ExpiresAt: metav1.NewTime(expires), + }, + } + for _, o := range opts { + o(at) + } + return at +} + +func newTokensAdmind(t *testing.T, checker admind.PlatformChecker, grants admind.AccessTokenGrantReader, objs ...client.Object) (http.Handler, client.Client) { + t.Helper() + c := fake.NewClientBuilder().WithScheme(newScheme(t)).WithObjects(objs...).Build() + a, err := admind.New(admind.Config{ + Mem: memory.NewLocal(inmem.NewBackend()), + K8s: c, + Checker: checker, + Token: tkToken, + Logger: testr.New(t), + AccessTokenGrants: grants, + AccessTokenNamespace: tkNS, + }) + require.NoError(t, err) + return a.Handler(), c +} + +type tokenRowWire struct { + Name string `json:"name"` + Owner string `json:"owner"` + ClientName string `json:"clientName"` + Role string `json:"role"` + ScopeClasses []string `json:"scopeClasses"` + Unfiltered bool `json:"unfiltered"` + CreatedAt string `json:"createdAt"` + ExpiresAt string `json:"expiresAt"` + LastUsedAt string `json:"lastUsedAt"` + Revoked bool `json:"revoked"` +} + +type tokensListWire struct { + Tokens []tokenRowWire `json:"tokens"` +} + +// --- (a) list: joins the grant, classifies revoked, never leaks the hash --- + +func TestTokensList_JoinsGrantAndNeverLeaksHash(t *testing.T) { + future := time.Now().Add(24 * time.Hour) + full := tokenCR(tkFullName, tkFullOwner, tkFullHash, future, func(at *spiceboxv1alpha1.AccessToken) { + at.CreationTimestamp = metav1.NewTime(time.Now().Add(-time.Hour)) + used := metav1.Now() + at.Status.LastUsedAt = &used + }) + revoked := tokenCR(tkRevokedName, "bob", tkRevokedHash, future, func(at *spiceboxv1alpha1.AccessToken) { + at.CreationTimestamp = metav1.NewTime(time.Now()) + }) + + grants := stubGrantReader{grants: map[string]spicedb.AccessTokenGrant{ + tkFullName: { + TokenID: tkFullName, + Owner: identity.CanonicalFromTrusted(tkFullOwner, "test fixture"), + Role: accesstoken.RoleFull, + Unfiltered: true, + ExpiresAt: future, + }, + }} + h, _ := newTokensAdmind(t, tkChecker(), grants, full, revoked) + + w := do(t, h, http.MethodGet, "/admin/v1/tokens", tkToken, tkAdmin, "") + require.Equal(t, http.StatusOK, w.Code, "body=%s", w.Body.String()) + + var resp tokensListWire + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + require.Len(t, resp.Tokens, 2) + + byName := map[string]tokenRowWire{} + for _, r := range resp.Tokens { + byName[r.Name] = r + } + + fullRow := byName[tkFullName] + assert.Equal(t, tkFullOwner, fullRow.Owner) + assert.Equal(t, "demo-client", fullRow.ClientName) + assert.Equal(t, accesstoken.RoleFull, fullRow.Role) + assert.True(t, fullRow.Unfiltered) + assert.False(t, fullRow.Revoked) + assert.NotEmpty(t, fullRow.LastUsedAt) + + revokedRow := byName[tkRevokedName] + assert.True(t, revokedRow.Revoked, "a CR with no SpiceDB tuples must show revoked") + assert.Equal(t, "unknown", revokedRow.Role) + + // Newest-first: the revoked CR (created last) sorts before the full one. + require.Len(t, resp.Tokens, 2) + assert.Equal(t, tkRevokedName, resp.Tokens[0].Name, "newest-created token sorts first") + + // Invariant: the raw body never contains either fixture's tokenHash value. + assert.NotContains(t, w.Body.String(), tkFullHash) + assert.NotContains(t, w.Body.String(), tkRevokedHash) +} + +// --- (b) a per-item grant-read error degrades that row, not the page ------- + +func TestTokensList_GrantReadErrorDegradesRowNotPage(t *testing.T) { + future := time.Now().Add(24 * time.Hour) + ok := tokenCR(tkFullName, tkFullOwner, tkFullHash, future) + broken := tokenCR(tkRevokedName, "bob", tkRevokedHash, future) + + grants := stubGrantReader{ + grants: map[string]spicedb.AccessTokenGrant{ + tkFullName: {TokenID: tkFullName, Owner: identity.CanonicalFromTrusted(tkFullOwner, "test"), Role: accesstoken.RoleRead}, + }, + errs: map[string]error{tkRevokedName: grantReadErr{}}, + } + h, _ := newTokensAdmind(t, tkChecker(), grants, ok, broken) + + w := do(t, h, http.MethodGet, "/admin/v1/tokens", tkToken, tkAdmin, "") + require.Equal(t, http.StatusOK, w.Code, "one bad token must not 500 the page; body=%s", w.Body.String()) + + var resp tokensListWire + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + require.Len(t, resp.Tokens, 2) + + byName := map[string]tokenRowWire{} + for _, r := range resp.Tokens { + byName[r.Name] = r + } + assert.Equal(t, accesstoken.RoleRead, byName[tkFullName].Role) + assert.Equal(t, "unknown", byName[tkRevokedName].Role, "a grant READ ERROR degrades to unknown, not revoked") + assert.False(t, byName[tkRevokedName].Revoked, "a read error is not the same claim as 'no tuples found'") +} + +func TestTokensList_NonAdminForbidden(t *testing.T) { + h, _ := newTokensAdmind(t, tkChecker(), stubGrantReader{}) + w := do(t, h, http.MethodGet, "/admin/v1/tokens", tkToken, tkNonAdmin, "") + assert.Equal(t, http.StatusForbidden, w.Code, "tokens list needs view_tokens") +} + +func TestTokensList_MissingServiceTokenUnauthorized(t *testing.T) { + h, _ := newTokensAdmind(t, tkChecker(), stubGrantReader{}) + w := do(t, h, http.MethodGet, "/admin/v1/tokens", "", tkAdmin, "") + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +// --- (c) revoke -------------------------------------------------------- + +func TestTokensRevoke_DeletesCR(t *testing.T) { + future := time.Now().Add(24 * time.Hour) + full := tokenCR(tkFullName, tkFullOwner, tkFullHash, future) + h, c := newTokensAdmind(t, tkChecker(), stubGrantReader{}, full) + + body, err := json.Marshal(map[string]string{"name": tkFullName}) + require.NoError(t, err) + w := do(t, h, http.MethodPost, "/admin/v1/tokens/revoke", tkToken, tkAdmin, string(body)) + require.Equal(t, http.StatusOK, w.Code, "body=%s", w.Body.String()) + + var resp struct { + OK bool `json:"ok"` + } + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.True(t, resp.OK) + + err = c.Get(context.Background(), client.ObjectKey{Namespace: tkNS, Name: tkFullName}, &spiceboxv1alpha1.AccessToken{}) + assert.True(t, apierrors.IsNotFound(err), "the AccessToken CR must be deleted; err=%v", err) +} + +func TestTokensRevoke_MissingReturns404(t *testing.T) { + h, _ := newTokensAdmind(t, tkChecker(), stubGrantReader{}) + body, err := json.Marshal(map[string]string{"name": "at-does-not-exist"}) + require.NoError(t, err) + w := do(t, h, http.MethodPost, "/admin/v1/tokens/revoke", tkToken, tkAdmin, string(body)) + assert.Equal(t, http.StatusNotFound, w.Code) +} + +func TestTokensRevoke_NonAdminForbiddenNoDelete(t *testing.T) { + future := time.Now().Add(24 * time.Hour) + full := tokenCR(tkFullName, tkFullOwner, tkFullHash, future) + h, c := newTokensAdmind(t, tkChecker(), stubGrantReader{}, full) + + body, err := json.Marshal(map[string]string{"name": tkFullName}) + require.NoError(t, err) + w := do(t, h, http.MethodPost, "/admin/v1/tokens/revoke", tkToken, tkNonAdmin, string(body)) + require.Equal(t, http.StatusForbidden, w.Code) + + getErr := c.Get(context.Background(), client.ObjectKey{Namespace: tkNS, Name: tkFullName}, &spiceboxv1alpha1.AccessToken{}) + assert.NoError(t, getErr, "a denied revoke must not delete the CR") +} + +func TestTokensRevoke_MissingServiceTokenUnauthorized(t *testing.T) { + h, _ := newTokensAdmind(t, tkChecker(), stubGrantReader{}) + body, err := json.Marshal(map[string]string{"name": tkFullName}) + require.NoError(t, err) + w := do(t, h, http.MethodPost, "/admin/v1/tokens/revoke", "", tkAdmin, string(body)) + assert.Equal(t, http.StatusUnauthorized, w.Code) +} diff --git a/pkg/web/adminui/adminui.go b/pkg/web/adminui/adminui.go index bee89ae7..66b760fb 100644 --- a/pkg/web/adminui/adminui.go +++ b/pkg/web/adminui/adminui.go @@ -156,6 +156,13 @@ func (ui) Routes(deps webui.Deps) []webui.Route { Auth: webui.AuthAuthorized, Authorize: authorize("view_audit"), Handler: px}, {Origin: webui.OriginTrusted, Pattern: "/admin/api/access", Methods: []string{http.MethodGet}, Auth: webui.AuthAuthorized, Authorize: authorize("view_config"), Handler: px}, + // The access-token page: list (view_tokens) and revoke (revoke_token) — + // both EXACT patterns, so TestAProxiedRouteAsksForTheSamePermissionAdmindDoes + // checks this pairing directly against admind.Routes(). + {Origin: webui.OriginTrusted, Pattern: "/admin/api/tokens", Methods: []string{http.MethodGet}, + Auth: webui.AuthAuthorized, Authorize: authorize("view_tokens"), Handler: px}, + {Origin: webui.OriginTrusted, Pattern: "/admin/api/tokens/revoke", Methods: []string{http.MethodPost}, + Auth: webui.AuthAuthorized, Authorize: authorize("revoke_token"), Handler: px}, // The workshops page: GET lists (view_sessions); the subtree fronts // POST .../install + .../decline (install_agent) and DELETE .../{ns}/{name} // (kill_session), resolved by workshopsAuthorize above. Both are derived diff --git a/pkg/web/adminui/ui/AdminApp.tsx b/pkg/web/adminui/ui/AdminApp.tsx index fe7bc518..ca543ea1 100644 --- a/pkg/web/adminui/ui/AdminApp.tsx +++ b/pkg/web/adminui/ui/AdminApp.tsx @@ -25,6 +25,7 @@ import { ChannelsView } from "./config/ChannelsView"; import { IdentityView } from "./config/IdentityView"; import { UsersView } from "./config/UsersView"; import { AccessView } from "./config/AccessView"; +import { TokensPanel } from "./tokens/TokensPanel"; import { SettingsView } from "./config/SettingsView"; import { AgentPage } from "./config/detail/AgentPage"; import { ToolPage } from "./config/detail/ToolPage"; @@ -117,6 +118,8 @@ export function AdminApp({ apiBase, currentUser }: AdminAppProps) { return ; case "access": return ; + case "tokens": + return ; case "settings": return ; default: diff --git a/pkg/web/adminui/ui/lib/api.ts b/pkg/web/adminui/ui/lib/api.ts index c48300e9..0fc09670 100644 --- a/pkg/web/adminui/ui/lib/api.ts +++ b/pkg/web/adminui/ui/lib/api.ts @@ -280,6 +280,37 @@ export interface WorkshopDecisionResponse { approvedBy: string; } +// AccessTokenRow mirrors pkg/web/admind.accessTokenRow — one AccessToken +// projected for the admin Tokens table. owner is the BARE canonical user id +// (spec.owner), not a "user:"-prefixed subject — decode it by prefixing +// "user:" before calling decodeSubject, the same way EntityRollups/AccessView +// decode a users-axis key. role is "unknown" when the SpiceDB grant could not +// be read (never the same claim as revoked — see tokens.go). Nothing here +// ever carries spec.tokenHash. +export interface AccessTokenRow { + name: string; + owner: string; + clientName?: string; + role: string; + scopeClasses?: string[]; + unfiltered: boolean; + createdAt: string; + expiresAt: string; + lastUsedAt?: string; + revoked: boolean; +} + +// AccessTokensData mirrors pkg/web/admind.tokensResponse (GET /tokens). +export interface AccessTokensData { + tokens: AccessTokenRow[]; +} + +// AccessTokenRevokeResponse mirrors pkg/web/admind.accessTokenRevokeResponse +// (the 200 body for POST /tokens/revoke). +export interface AccessTokenRevokeResponse { + ok: boolean; +} + // MemoryKindRollup mirrors pkg/web/admind.memoryKindRollup — one per-Kind rollup. export interface MemoryKindRollup { kind: string; @@ -1029,6 +1060,16 @@ export const getArtifactDetail = (apiBase: string, ns: string, name: string): Pr export const getWorkshops = (apiBase: string): Promise => getJSON(`${apiBase}/workshops`); +// getTokens fetches every AccessToken in the configured namespace for the +// admin Tokens page. +export const getTokens = (apiBase: string): Promise => + getJSON(`${apiBase}/tokens`).then((d) => d.tokens ?? []); + +// revokeToken deletes the named AccessToken CR — POST /admin/api/tokens/revoke +// (handleTokensRevoke). The CR's finalizer removes its SpiceDB tuples. +export const revokeToken = (apiBase: string, name: string): Promise => + postJSON(`${apiBase}/tokens/revoke`, { name }); + // getMemory fetches the Memory browser payload: per-Kind rollups, plus an entry // list when `kind` (recent entries) or `q` (ranked search) is given. export const getMemory = ( diff --git a/pkg/web/adminui/ui/shell/nav.ts b/pkg/web/adminui/ui/shell/nav.ts index a6380036..f4ffae36 100644 --- a/pkg/web/adminui/ui/shell/nav.ts +++ b/pkg/web/adminui/ui/shell/nav.ts @@ -1,14 +1,14 @@ import type { LucideIcon } from "lucide-react"; import { LayoutDashboard, Activity, Terminal, Shield, - Bot, Wrench, Sparkles, GitBranch, FolderSync, Hash, KeyRound, Users, Lock, SlidersHorizontal, + Bot, Wrench, Sparkles, GitBranch, FolderSync, Hash, KeyRound, Users, Lock, SlidersHorizontal, KeySquare, ScrollText, History, File, Database, Network, Wallet, Hammer, } from "lucide-react"; export type ViewId = | "overview" | "sessions" | "toolcalls" | "approvals" | "workshops" - | "agents" | "tools" | "skills" | "sources" | "directory" | "channels" | "identity" | "users" | "access" | "settings" + | "agents" | "tools" | "skills" | "sources" | "directory" | "channels" | "identity" | "users" | "access" | "tokens" | "settings" | "logs" | "sessionsAudit" | "budget" | "artifacts" | "memory" | "knowledge"; export interface NavItem { @@ -42,6 +42,7 @@ export const NAV: NavGroup[] = [ { id: "identity", label: "Identity", icon: KeyRound }, { id: "users", label: "Users", icon: Users }, { id: "access", label: "Access", icon: Lock }, + { id: "tokens", label: "Tokens", icon: KeySquare }, { id: "settings", label: "Settings", icon: SlidersHorizontal}, ]}, { group: "Audit", items: [ @@ -69,6 +70,7 @@ export const VIEW_META: Record = { identity: { title: "Identity", sub: "Agent identities, credentials, and login providers · oap identity / oap idp" }, users: { title: "Users", sub: "People linked to the platform · oap user-identity" }, access: { title: "Access", sub: "Platform admins & the SpiceDB authorization schema · oap platform / oap spicedb" }, + tokens: { title: "Tokens", sub: "Delegated OAuth access tokens minted via MCP consent — view and revoke" }, settings: { title: "Settings", sub: "Cluster configuration — defaults, ceilings, security · oap settings" }, logs: { title: "Logs", sub: "Cross-session audit — approvals, authz, tool calls, scope changes · oap audit" }, sessionsAudit:{ title: "Sessions", sub: "Every agent session, current and historical" }, diff --git a/pkg/web/adminui/ui/tokens/TokensPanel.test.tsx b/pkg/web/adminui/ui/tokens/TokensPanel.test.tsx new file mode 100644 index 00000000..60bd6ce3 --- /dev/null +++ b/pkg/web/adminui/ui/tokens/TokensPanel.test.tsx @@ -0,0 +1,115 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { TokensPanel } from "./TokensPanel"; +import type { AccessTokenRow } from "../lib/api"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +// jsonResponse builds a fetch Response the way admind's handlers do (mirrors +// WorkshopsView.test.tsx's own helper). +function jsonResponse(body: unknown, status: number) { + return new Response(JSON.stringify(body), { status }); +} + +// row builds one AccessTokenRow fixture, overridable per test. +function row(overrides: Partial = {}): AccessTokenRow { + return { + // user:alice@example.com, base64url — decodeSubject round-trips this back + // to the email once TokensPanel prefixes it with "user:". + name: "at-full0000001", + owner: "YWxpY2VAZXhhbXBsZS5jb20", + clientName: "demo-client", + role: "full", + scopeClasses: [], + unfiltered: true, + createdAt: "2026-06-30T12:00:00Z", + expiresAt: "2026-07-30T12:00:00Z", + lastUsedAt: "2026-06-30T13:00:00Z", + revoked: false, + ...overrides, + }; +} + +describe("TokensPanel", () => { + it("lists tokens with the decoded owner, role, and scope — a revoked row shows a badge instead of Revoke", async () => { + const rows: AccessTokenRow[] = [ + row(), + row({ + name: "at-revoked0001", + owner: "Ym9iQGV4YW1wbGUuY29t", + role: "unknown", + unfiltered: false, + scopeClasses: undefined, + revoked: true, + lastUsedAt: undefined, + }), + ]; + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ tokens: rows }, 200))); + + render(); + await waitFor(() => expect(screen.getByText("alice@example.com")).toBeTruthy()); + + expect(screen.getByText("bob@example.com")).toBeTruthy(); + expect(screen.getByText("full")).toBeTruthy(); + expect(screen.getByText("all classes")).toBeTruthy(); + expect(screen.getByText("never")).toBeTruthy(); // the revoked row's absent lastUsedAt + + // The healthy row gets a Revoke button; the revoked row gets a badge, not a button. + expect(screen.getByRole("button", { name: "Revoke" })).toBeTruthy(); + expect(screen.getByText("Revoked")).toBeTruthy(); + }); + + it("asks for confirmation before revoking, then POSTs and refetches", async () => { + const initial = [row()]; + const afterRevoke = [row({ revoked: true, role: "unknown" })]; + const fetchMock = vi + .fn() + .mockResolvedValueOnce(jsonResponse({ tokens: initial }, 200)) + .mockResolvedValueOnce(jsonResponse({ ok: true }, 200)) + .mockResolvedValueOnce(jsonResponse({ tokens: afterRevoke }, 200)); + vi.stubGlobal("fetch", fetchMock); + vi.spyOn(window, "confirm").mockReturnValue(true); + + render(); + await waitFor(() => expect(screen.getByRole("button", { name: "Revoke" })).toBeTruthy()); + + fireEvent.click(screen.getByRole("button", { name: "Revoke" })); + + await waitFor(() => expect(screen.getByText("Revoked")).toBeTruthy()); + + expect(window.confirm).toHaveBeenCalled(); + const revokeCall = fetchMock.mock.calls.find(([url]) => String(url).endsWith("/tokens/revoke")); + expect(revokeCall).toBeTruthy(); + expect(revokeCall![1]).toMatchObject({ method: "POST" }); + expect(JSON.parse((revokeCall![1] as RequestInit).body as string)).toEqual({ name: row().name }); + }); + + it("does nothing when the confirmation is declined", async () => { + const fetchMock = vi.fn().mockResolvedValueOnce(jsonResponse({ tokens: [row()] }, 200)); + vi.stubGlobal("fetch", fetchMock); + vi.spyOn(window, "confirm").mockReturnValue(false); + + render(); + await waitFor(() => expect(screen.getByRole("button", { name: "Revoke" })).toBeTruthy()); + + fireEvent.click(screen.getByRole("button", { name: "Revoke" })); + + await waitFor(() => expect(window.confirm).toHaveBeenCalled()); + // Only the initial list load — no revoke POST was ever issued. + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + it("shows an empty state with no tokens, and a load error as an alert", async () => { + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ tokens: [] }, 200))); + render(); + await waitFor(() => expect(screen.getByText("No access tokens yet.")).toBeTruthy()); + + cleanup(); + vi.stubGlobal("fetch", vi.fn(async () => jsonResponse({ error: "boom" }, 500))); + render(); + await waitFor(() => expect(screen.getByText(/Could not load access tokens: boom/)).toBeTruthy()); + }); +}); diff --git a/pkg/web/adminui/ui/tokens/TokensPanel.tsx b/pkg/web/adminui/ui/tokens/TokensPanel.tsx new file mode 100644 index 00000000..90e36c12 --- /dev/null +++ b/pkg/web/adminui/ui/tokens/TokensPanel.tsx @@ -0,0 +1,144 @@ +import * as React from "react"; +import { + Alert, AlertDescription, Badge, Button, + Table, TableBody, TableCell, TableHead, TableHeader, TableRow, +} from "@ap/design"; +import { getTokens, revokeToken, type AccessTokenRow } from "../lib/api"; +import { decodeSubject } from "../lib/decodeSubject"; +import { fmtRelative } from "../lib/fmt"; + +// roleBadgeVariant picks the Badge treatment for a row's role: "unknown" +// (the SpiceDB grant could not be read — see pkg/web/admind/tokens.go) is +// visually distinct from a resolved role, which is distinct again from a +// revoked row (handled separately below, not through this). +function roleBadgeVariant(role: string): "secondary" | "outline" { + return role === "unknown" ? "outline" : "secondary"; +} + +// RevokeButton is the confirm-then-POST affordance for one row. A revoked row +// never reaches this — TokensPanel renders a "Revoked" badge instead — so +// there is no double-revoke path to guard against here. +function RevokeButton({ + apiBase, row, onDone, +}: { + apiBase: string; + row: AccessTokenRow; + onDone: () => void; +}) { + const [revoking, setRevoking] = React.useState(false); + const [error, setError] = React.useState(null); + + const revoke = async () => { + const label = row.clientName || row.name; + if (!window.confirm(`Revoke access token "${label}"? This cannot be undone.`)) return; + setError(null); + setRevoking(true); + try { + await revokeToken(apiBase, row.name); + onDone(); + } catch (e) { + setError((e as Error).message); + } finally { + setRevoking(false); + } + }; + + return ( + + + {error && {error}} + + ); +} + +// TokensPanel lists every delegated OAuth access token (AccessToken CRs in +// the operator's configured namespace), joined with each one's SpiceDB +// grant for its role and scope. Revoke deletes the CR — its finalizer tears +// down the SpiceDB tuples — and the list refetches so the row's revoked +// state reflects the outcome immediately. +export function TokensPanel({ apiBase }: { apiBase: string }) { + const [rows, setRows] = React.useState([]); + const [error, setError] = React.useState(null); + const [loaded, setLoaded] = React.useState(false); + + const load = React.useCallback(() => { + getTokens(apiBase) + .then((r) => { + setRows(r); + setError(null); + setLoaded(true); + }) + .catch((e: Error) => { + setError(e.message); + setLoaded(true); + }); + }, [apiBase]); + + React.useEffect(() => { + load(); + }, [load]); + + if (error) { + return ( + + Could not load access tokens: {error} + + ); + } + + return ( +
+ {loaded && rows.length === 0 &&

No access tokens yet.

} + {rows.length > 0 && ( + + + + Owner + Client + Role + Scope + Created + Expires + Last used + Revoke + + + + {rows.map((row) => ( + + + {decodeSubject(`user:${row.owner}`)} + + {row.clientName || "—"} + + {row.role} + + + {row.unfiltered ? "all classes" : row.scopeClasses?.length ? row.scopeClasses.join(", ") : "—"} + + {fmtRelative(row.createdAt)} + {fmtRelative(row.expiresAt)} + + {row.lastUsedAt ? fmtRelative(row.lastUsedAt) : "never"} + + + {row.revoked ? ( + Revoked + ) : ( + + )} + + + ))} + +
+ )} +

+ Delegated OAuth access tokens minted via MCP consent · revoking deletes the AccessToken CR — its finalizer + removes the underlying SpiceDB grant. +

+
+ ); +} diff --git a/pkg/web/mcpfront/auth.go b/pkg/web/mcpfront/auth.go new file mode 100644 index 00000000..aca1ca16 --- /dev/null +++ b/pkg/web/mcpfront/auth.go @@ -0,0 +1,374 @@ +package mcpfront + +import ( + "context" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "golang.org/x/sync/singleflight" + "golang.org/x/time/rate" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +const ( + // tokenCacheTTL bounds how long the bearer middleware trusts its + // in-process hash->identity map before re-listing AccessTokens. A hash + // MISS triggers a re-list sooner than this — as soon as the cache is + // older than missRelistFloor (see tokenCache.resolve) — so this TTL only + // bounds how stale a HIT may be, i.e. how long a REVOKED token (its CR + // deleted) keeps authenticating purely from cache. Revocation itself is + // still enforced immediately by the SpiceDB leg checks a tool call makes + // afterward (the finalizer deletes the token's tuples on CR delete) — + // this cache only gates AUTHENTICATION (does a hash resolve to a live, + // unexpired token at all), never authorization. + tokenCacheTTL = 30 * time.Second + + // missRelistFloor bounds how often an UNKNOWN hash may trigger a re-list: + // a miss re-lists only when the cache is older than this floor; a miss + // against a younger cache trusts the recent authoritative set and 401s + // with NO K8s round trip. This is the amplification bound for + // unauthenticated garbage bearers — without it, every junk request is a + // namespace-wide List an attacker controls, entirely upstream of any rate + // limit (the limiter is per TOKEN, and a garbage bearer never resolves to + // one). A single rule, no per-hash bookkeeping: distinct garbage hashes + // within one floor window share the same "the list is seconds old; you + // are not in it" answer. + // + // The accepted cost: a token minted mid-window authenticates up to this + // long after mint, not instantly. In practice the mint flow's first /mcp + // call (the OAuth client finishing its token exchange, then connecting) + // lands later than this anyway. + missRelistFloor = 2 * time.Second + + // lastUsedResolution is the minimum gap between two status.lastUsedAt + // patches for the same token — an observation, not an enforcement signal, + // so it is written on a coarse cadence rather than on every request (see + // AGENTS.md's server-side-apply idempotency note: a write per request is + // exactly the pattern to avoid). + lastUsedResolution = time.Hour + + // tokenRateLimitRPS / tokenRateLimitBurst bound each individual token's + // request rate, independent of every other token. + tokenRateLimitRPS = 10 + tokenRateLimitBurst = 20 + + // bearerPrefix is the only Authorization scheme this middleware accepts. + bearerPrefix = "Bearer " + + // logHashPrefixLen is how many hex characters of a token's hash are safe + // to log — enough to correlate repeated failures without ever writing + // the full hash (AGENTS.md: "never the hash") to a log an operator might + // grep and paste elsewhere. + logHashPrefixLen = 8 +) + +// acting is who a /mcp request is authenticated as: the presented +// AccessToken's id and the owner it acts on behalf of. Carried on the +// request context by the bearer middleware; read back via actingFrom by +// every tool handler (Task 10) that needs to run a SpiceDB check. +type acting struct { + TokenID string + Owner identity.CanonicalUserID +} + +type actingKeyT struct{} + +var actingKey actingKeyT + +func withActing(ctx context.Context, a acting) context.Context { + return context.WithValue(ctx, actingKey, a) +} + +// actingFrom returns the acting identity the bearer middleware attached to +// ctx. ok is false for any context that didn't pass through the middleware +// (e.g. a test calling a tool handler directly without it). +func actingFrom(ctx context.Context) (acting, bool) { + a, ok := ctx.Value(actingKey).(acting) + return a, ok +} + +// cachedToken is one AccessToken's authentication-relevant state, keyed by +// its hash in tokenCache.byHash. +type cachedToken struct { + TokenID string + Owner identity.CanonicalUserID + ExpiresAt time.Time + LastUsedAt time.Time // zero means "never patched" (or not yet observed in this cache generation) +} + +// tokenCache is the /mcp bearer middleware's in-process view of every live +// AccessToken in d.AccessTokenNamespace(), plus the per-token rate limiters. +// The two share one mutex but deliberately NOT one lifecycle — see +// rebuildLocked for why the limiters are pruned rather than wiped. See +// tokenCacheTTL's doc for exactly what staleness here does and does not mean +// for revocation. +type tokenCache struct { + mu sync.Mutex + byHash map[string]cachedToken + // listedAt is when byHash was last rebuilt from a List. Both freshness + // rules derive from it: a HIT is served until listedAt+tokenCacheTTL, and + // a MISS re-lists only once the cache is older than missRelistFloor. + listedAt time.Time + limiters map[string]*rate.Limiter + // relistGroup collapses concurrent re-lists into ONE List call: N + // concurrent requests bearing unknown tokens (or arriving just as the TTL + // lapses) share a single K8s round trip rather than issuing N. + relistGroup singleflight.Group +} + +func newTokenCache() *tokenCache { + return &tokenCache{byHash: map[string]cachedToken{}, limiters: map[string]*rate.Limiter{}} +} + +// resolve answers whether hash names a live, unexpired AccessToken. A cache +// HIT inside the TTL is served with no K8s round trip — this runs on every +// request. A stale cache (TTL lapsed) re-lists before answering either way. +// A MISS against a cache younger than missRelistFloor is answered 401 from +// the cache alone — the authoritative set is seconds old, and a hash absent +// from it is a miss WITHOUT another List (the unauthenticated-amplification +// bound; see missRelistFloor's doc). A miss against an older cache re-lists +// (through singleflight, so concurrent misses share one List) and retries +// once: a token minted after the floor elapsed (by identityd's /oauth/token, +// running in a different request) is visible on its very first use, not only +// after the TTL next lapses. +func (c *tokenCache) resolve(ctx context.Context, d Deps, hash string, now time.Time) (cachedToken, bool, error) { + c.mu.Lock() + tok, hit := c.byHash[hash] + age := now.Sub(c.listedAt) + c.mu.Unlock() + + if hit && age < tokenCacheTTL { + return checkExpiry(tok, now) + } + if !hit && age < missRelistFloor { + return cachedToken{}, false, nil + } + + if err := c.relist(ctx, d, now); err != nil { + return cachedToken{}, false, err + } + + c.mu.Lock() + tok, hit = c.byHash[hash] + c.mu.Unlock() + if !hit { + return cachedToken{}, false, nil + } + return checkExpiry(tok, now) +} + +// relist re-lists every AccessToken in d.AccessTokenNamespace() and rebuilds +// the cache, through singleflight so concurrent callers share one List. The +// double-check inside the flight handles the caller that lost the race: a +// flight that completed between its outer age check and its Do call has +// already rebuilt, so re-listing again would defeat the floor — it skips +// instead and lets the caller re-read the fresh cache. A canceled or failed +// leader fails every sharer closed (their requests 401, logged); the next +// request simply starts a new flight. +func (c *tokenCache) relist(ctx context.Context, d Deps, now time.Time) error { + _, err, _ := c.relistGroup.Do("relist", func() (any, error) { + c.mu.Lock() + rebuiltMoments := now.Sub(c.listedAt) < missRelistFloor + c.mu.Unlock() + if rebuiltMoments { + return nil, nil + } + var list spiceboxv1alpha1.AccessTokenList + if err := d.K8s().List(ctx, &list, client.InNamespace(d.AccessTokenNamespace())); err != nil { + return nil, fmt.Errorf("mcpfront: list access tokens: %w", err) + } + c.mu.Lock() + c.rebuildLocked(list, now) + c.mu.Unlock() + return nil, nil + }) + return err +} + +// checkExpiry denies a cache hit whose AccessToken has passed its own +// spec.expiresAt — the "expired CR: 401 even though hash matches" case: the +// CR can still exist (GC hasn't swept it yet) while the token itself must no +// longer authenticate. +func checkExpiry(tok cachedToken, now time.Time) (cachedToken, bool, error) { + if !tok.ExpiresAt.IsZero() && !now.Before(tok.ExpiresAt) { + return cachedToken{}, false, nil + } + return tok, true, nil +} + +// rebuildLocked replaces the cache's hash map wholesale from a freshly-listed +// set and resets listedAt. The limiter map is PRUNED, never wiped: only +// entries whose token id is absent from the fresh list are dropped — a +// revoked or rotated token's rate-limit state must not linger once it's no +// longer live, but a LIVE token's spent budget must survive every rebuild. +// Wiping the whole map here was a real bug: a rebuild is triggerable by any +// stale-cache miss, so an attacker who exhausted their token's budget could +// send one garbage bearer and collect a fresh burst — the rate limit reset on +// demand, by the very party it limits. Caller holds c.mu. +func (c *tokenCache) rebuildLocked(list spiceboxv1alpha1.AccessTokenList, now time.Time) { + fresh := make(map[string]cachedToken, len(list.Items)) + live := make(map[string]bool, len(list.Items)) + for i := range list.Items { + item := &list.Items[i] + var lastUsed time.Time + if item.Status.LastUsedAt != nil { + lastUsed = item.Status.LastUsedAt.Time + } + fresh[item.Spec.TokenHash] = cachedToken{ + TokenID: item.Name, + Owner: identity.CanonicalFromTrusted(item.Spec.Owner, + "read back from the AccessToken CR's spec.owner, written only by the mint path"), + ExpiresAt: item.Spec.ExpiresAt.Time, + LastUsedAt: lastUsed, + } + live[item.Name] = true + } + c.byHash = fresh + c.listedAt = now + for id := range c.limiters { + if !live[id] { + delete(c.limiters, id) + } + } +} + +// limiterFor returns tokenID's rate limiter, creating a fresh one (full +// burst) on the token's first use. The limiter then lives for as long as the +// token stays in the listed set — rebuilds prune dead entries but never +// reset a live token's budget (see rebuildLocked). +func (c *tokenCache) limiterFor(tokenID string) *rate.Limiter { + c.mu.Lock() + defer c.mu.Unlock() + l, ok := c.limiters[tokenID] + if !ok { + l = rate.NewLimiter(rate.Limit(tokenRateLimitRPS), tokenRateLimitBurst) + c.limiters[tokenID] = l + } + return l +} + +// touchLastUsed patches status.lastUsedAt when the cached value is zero or at +// least lastUsedResolution old, then remembers the new value in the cache ON +// SUCCESS ONLY — a transient apiserver failure is retried on the very next +// request rather than silently debounced for an hour. This is OBSERVATION, +// not enforcement: a patch failure is logged and never fails the request +// (AGENTS.md's no-silent-errors rule — logging is the allowed second option). +// +// The Get-then-MergeFrom-Patch shape (rather than constructing a bare patch +// object by hand) mirrors pkg/web/webui/agentui's wake.go: diffing against a +// real, just-fetched copy means the computed patch touches only +// status.lastUsedAt, never clobbering Conditions or observedGeneration this +// process doesn't know about. +func (c *tokenCache) touchLastUsed(ctx context.Context, d Deps, hash string, tok cachedToken, now time.Time) { + if !tok.LastUsedAt.IsZero() && now.Sub(tok.LastUsedAt) < lastUsedResolution { + return + } + var cr spiceboxv1alpha1.AccessToken + if err := d.K8s().Get(ctx, client.ObjectKey{Namespace: d.AccessTokenNamespace(), Name: tok.TokenID}, &cr); err != nil { + d.Logger().Info("mcpfront: lastUsed Get failed; status not patched", "tokenID", tok.TokenID, "err", err.Error()) + return + } + original := cr.DeepCopy() + patchedAt := metav1.NewTime(now) + cr.Status.LastUsedAt = &patchedAt + if err := d.K8s().Status().Patch(ctx, &cr, client.MergeFrom(original)); err != nil { + d.Logger().Info("mcpfront: lastUsed patch failed", "tokenID", tok.TokenID, "err", err.Error()) + return + } + c.mu.Lock() + if entry, ok := c.byHash[hash]; ok { + entry.LastUsedAt = now + c.byHash[hash] = entry + } + c.mu.Unlock() +} + +// parseBearer extracts the token value from an Authorization header, +// rejecting anything not of the exact form "Bearer ". +func parseBearer(header string) (string, bool) { + v, ok := strings.CutPrefix(header, bearerPrefix) + if !ok || v == "" { + return "", false + } + return v, true +} + +// hashPrefix returns a short, log-safe prefix of a token hash — enough to +// correlate repeated failures across log lines without ever writing the full +// hash, which (per AGENTS.md) must never appear in a log. +func hashPrefix(hash string) string { + if len(hash) <= logHashPrefixLen { + return hash + } + return hash[:logHashPrefixLen] +} + +// writeUnauthorized is the ONE place every bearer-auth failure writes its +// response: status 401, a WWW-Authenticate challenge pointing at the +// protected-resource metadata, and an empty body. Every caller (missing +// header, malformed header, unknown token, expired token, lookup error, a +// revoked/deleted CR) routes through this single function, which is what +// makes the 401 byte-identical across every failure mode — there is no +// oracle here telling a caller WHY authentication failed. +func writeUnauthorized(w http.ResponseWriter, d Deps) { + base := strings.TrimSuffix(d.ExternalBaseURL(), "/") + w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer resource_metadata="%s/.well-known/oauth-protected-resource"`, base)) + w.WriteHeader(http.StatusUnauthorized) +} + +// newBearerMiddleware builds the /mcp authentication middleware: hash the +// presented bearer, resolve it against d's AccessTokens (cached — see +// tokenCache), rate-limit per token, best-effort record lastUsedAt, and +// attach the resolved acting identity to the request context. clock is +// time.Now in production and an injectable fake in tests, so the rate +// limiter's refill and the lastUsed resolution window are both deterministic +// under test. +func newBearerMiddleware(d Deps, clock func() time.Time) func(http.Handler) http.Handler { + cache := newTokenCache() + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + now := clock() + + value, ok := parseBearer(r.Header.Get("Authorization")) + if !ok { + d.Logger().Info("mcpfront: bearer rejected", "reason", "missing or malformed Authorization header") + writeUnauthorized(w, d) + return + } + + hash := accesstoken.HashTokenValue(value) + tok, found, err := cache.resolve(r.Context(), d, hash, now) + if err != nil { + d.Logger().Info("mcpfront: bearer rejected", "reason", "token lookup errored", "hashPrefix", hashPrefix(hash), "err", err.Error()) + writeUnauthorized(w, d) + return + } + if !found { + d.Logger().Info("mcpfront: bearer rejected", "reason", "unknown, expired, or revoked token", "hashPrefix", hashPrefix(hash)) + writeUnauthorized(w, d) + return + } + + limiter := cache.limiterFor(tok.TokenID) + if !limiter.AllowN(now, 1) { + d.Logger().Info("mcpfront: rate limit exceeded", "tokenID", tok.TokenID) + w.Header().Set("Retry-After", "1") + w.WriteHeader(http.StatusTooManyRequests) + return + } + + cache.touchLastUsed(r.Context(), d, hash, tok, now) + + ctx := withActing(r.Context(), acting{TokenID: tok.TokenID, Owner: tok.Owner}) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} diff --git a/pkg/web/mcpfront/auth_test.go b/pkg/web/mcpfront/auth_test.go new file mode 100644 index 00000000..63c9cba5 --- /dev/null +++ b/pkg/web/mcpfront/auth_test.go @@ -0,0 +1,503 @@ +package mcpfront + +import ( + "context" + "net/http" + "net/http/httptest" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/platform/artifacts" + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +const testAccessTokenNamespace = "agentprimitives-system" + +// fakeDeps is the auth.go test double for Deps: a real controller-runtime +// fake client plus a fixed namespace/base URL. AccessTokenAuthz/OperatorURL/ +// MemoryToken/Artifacts/LookupReadableSessions are never exercised by the +// bearer middleware itself (they exist for registerTools, Task 10) and are +// stubbed to their zero values. +type fakeDeps struct { + k8s client.Client + ns string + externalBaseURL string +} + +func (f *fakeDeps) K8s() client.Client { return f.k8s } +func (f *fakeDeps) AccessTokenAuthz() AccessTokenAuthz { return nil } +func (f *fakeDeps) AccessTokenNamespace() string { return f.ns } +func (f *fakeDeps) OperatorURL() string { return "" } +func (f *fakeDeps) MemoryToken() string { return "" } +func (f *fakeDeps) Artifacts() *artifacts.Service { return nil } +func (f *fakeDeps) LookupReadableSessions(_ context.Context, _ identity.CanonicalUserID) (spicedb.InteractableSessions, error) { + return spicedb.InteractableSessions{}, nil +} +func (f *fakeDeps) FetchArtifact(_ context.Context, _, _, _ string) ([]byte, string, error) { + return nil, "", nil +} +func (f *fakeDeps) ExternalBaseURL() string { return f.externalBaseURL } +func (f *fakeDeps) Logger() logr.Logger { return logr.Discard() } + +var _ Deps = (*fakeDeps)(nil) + +func newFakeDeps(c client.Client) *fakeDeps { + return &fakeDeps{k8s: c, ns: testAccessTokenNamespace, externalBaseURL: "https://example.test"} +} + +func mustNewTokenValue(t *testing.T) string { + t.Helper() + v, err := accesstoken.NewTokenValue() + require.NoError(t, err) + return v +} + +// accessTokenCR builds a test AccessToken CR: name, the bearer value it +// authenticates (stored only as its hash — the plaintext is never +// persisted, matching the real mint path), the owner, and its expiry. +func accessTokenCR(name, value, owner string, expiresAt time.Time) *spiceboxv1alpha1.AccessToken { + return &spiceboxv1alpha1.AccessToken{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: testAccessTokenNamespace}, + Spec: spiceboxv1alpha1.AccessTokenSpec{ + TokenHash: accesstoken.HashTokenValue(value), + Owner: owner, + ExpiresAt: metav1.NewTime(expiresAt), + }, + } +} + +func newAuthTestClient(t *testing.T, objects ...client.Object) client.Client { + t.Helper() + return fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithStatusSubresource(&spiceboxv1alpha1.AccessToken{}). + WithObjects(objects...). + Build() +} + +// recordingHandler is the inner http.Handler every middleware test wraps: it +// records whether it ran at all and what actingFrom returned, so a test can +// assert both "did the request reach the application" and "with what +// identity" in one place. +type recordingHandler struct { + ran bool + acting acting + ok bool +} + +func (h *recordingHandler) handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.ran = true + h.acting, h.ok = actingFrom(r.Context()) + w.WriteHeader(http.StatusOK) + }) +} + +func doBearerRequest(mw func(http.Handler) http.Handler, inner http.Handler, header string) *httptest.ResponseRecorder { + h := mw(inner) + req := httptest.NewRequest(http.MethodGet, "/mcp", nil) + if header != "" { + req.Header.Set("Authorization", header) + } + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + return rec +} + +func TestBearerMiddleware(t *testing.T) { + now := time.Now() + good := mustNewTokenValue(t) + goodCR := accessTokenCR("at-aaaaaaaaaaaa", good, "owner-canonical", now.Add(time.Hour)) + expiredVal := mustNewTokenValue(t) + expiredCR := accessTokenCR("at-bbbbbbbbbbbb", expiredVal, "owner-canonical", now.Add(-time.Minute)) + + cases := []struct { + name string + header string + objects []client.Object + wantStatus int + wantActing string // expected TokenID when 200 + }{ + {name: "no header: 401 + challenge", header: "", objects: []client.Object{goodCR}, wantStatus: http.StatusUnauthorized}, + {name: "malformed header: 401", header: "Token abc", objects: []client.Object{goodCR}, wantStatus: http.StatusUnauthorized}, + {name: "unknown token: 401", header: "Bearer " + mustNewTokenValue(t), objects: []client.Object{goodCR}, wantStatus: http.StatusUnauthorized}, + {name: "expired CR: 401 even though hash matches", header: "Bearer " + expiredVal, objects: []client.Object{expiredCR}, wantStatus: http.StatusUnauthorized}, + {name: "deleted CR (revocation race): 401", header: "Bearer " + good, objects: nil, wantStatus: http.StatusUnauthorized}, + {name: "valid: 200, acting carries token id and owner", header: "Bearer " + good, objects: []client.Object{goodCR}, wantStatus: http.StatusOK, wantActing: "at-aaaaaaaaaaaa"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + c := newAuthTestClient(t, tc.objects...) + d := newFakeDeps(c) + mw := newBearerMiddleware(d, func() time.Time { return now }) + + rh := &recordingHandler{} + rec := doBearerRequest(mw, rh.handler(), tc.header) + + assert.Equal(t, tc.wantStatus, rec.Code) + if tc.wantStatus == http.StatusOK { + assert.True(t, rh.ran, "inner handler must run on success") + require.True(t, rh.ok, "acting must be present in context") + assert.Equal(t, tc.wantActing, rh.acting.TokenID) + assert.Equal(t, "owner-canonical", rh.acting.Owner.String()) + } else { + assert.False(t, rh.ran, "inner handler must NOT run on a rejected request") + } + }) + } +} + +// TestBearerMiddleware401IsUniform asserts the 401 body and WWW-Authenticate +// header are byte-identical for missing, unknown, and expired tokens — no +// revocation oracle telling a caller which of the three actually happened. +func TestBearerMiddleware401IsUniform(t *testing.T) { + now := time.Now() + goodCR := accessTokenCR("at-aaaaaaaaaaaa", mustNewTokenValue(t), "owner-canonical", now.Add(time.Hour)) + expiredVal := mustNewTokenValue(t) + expiredCR := accessTokenCR("at-cccccccccccc", expiredVal, "owner-canonical", now.Add(-time.Minute)) + + headers := []string{ + "", // missing + "Bearer " + mustNewTokenValue(t), // unknown + "Bearer " + expiredVal, // expired + } + + type captured struct { + body []byte + wwwAuth string + } + var results []captured + for _, h := range headers { + c := newAuthTestClient(t, goodCR, expiredCR) + d := newFakeDeps(c) + mw := newBearerMiddleware(d, func() time.Time { return now }) + + rh := &recordingHandler{} + rec := doBearerRequest(mw, rh.handler(), h) + require.Equal(t, http.StatusUnauthorized, rec.Code) + require.False(t, rh.ran) + results = append(results, captured{body: rec.Body.Bytes(), wwwAuth: rec.Header().Get("WWW-Authenticate")}) + } + + for i := 1; i < len(results); i++ { + assert.Equal(t, results[0].body, results[i].body, "401 body must be byte-identical across every failure mode") + assert.Equal(t, results[0].wwwAuth, results[i].wwwAuth, "WWW-Authenticate must be byte-identical across every failure mode") + } + assert.Empty(t, results[0].body, "401 body must be empty") + assert.Equal(t, `Bearer resource_metadata="https://example.test/.well-known/oauth-protected-resource"`, results[0].wwwAuth) +} + +// TestTokenCacheRefreshesOnMiss: a request with token B misses the cache +// warmed with only A and, once the cache is older than missRelistFloor, +// triggers a re-list and succeeds — a token minted after warm-up (by +// identityd's /oauth/token, running in a different request against the same +// K8s namespace) works on its first use after the floor, long before the +// 30s TTL would next lapse. The pre-floor request pins the documented +// trade-off: a miss against a cache younger than the floor is answered 401 +// from the cache alone (the unauthenticated-List amplification bound), so a +// just-minted token has a ≤missRelistFloor first-use delay. +func TestTokenCacheRefreshesOnMiss(t *testing.T) { + cur := time.Now() + clock := func() time.Time { return cur } + valA := mustNewTokenValue(t) + crA := accessTokenCR("at-aaaaaaaaaaaa", valA, "owner-a", cur.Add(time.Hour)) + c := newAuthTestClient(t, crA) + d := newFakeDeps(c) + mw := newBearerMiddleware(d, clock) + + // Warm the cache with A. + rhA := &recordingHandler{} + recA := doBearerRequest(mw, rhA.handler(), "Bearer "+valA) + require.Equal(t, http.StatusOK, recA.Code) + + // Mint B directly against the SAME fake client — simulating a token + // minted elsewhere after the cache warmed, never going through the + // middleware itself. + valB := mustNewTokenValue(t) + crB := accessTokenCR("at-bbbbbbbbbbbb", valB, "owner-b", cur.Add(time.Hour)) + require.NoError(t, c.Create(context.Background(), crB)) + + // Within the floor: the cache is seconds old, so the miss is answered + // from it without a re-list — B is not yet usable (the documented, + // accepted first-use delay). + recEarly := doBearerRequest(mw, (&recordingHandler{}).handler(), "Bearer "+valB) + assert.Equal(t, http.StatusUnauthorized, recEarly.Code, + "a miss against a fresher-than-floor cache must be refused without a re-list") + + // Past the floor: the miss re-lists and B authenticates. + cur = cur.Add(missRelistFloor + time.Millisecond) + rhB := &recordingHandler{} + recB := doBearerRequest(mw, rhB.handler(), "Bearer "+valB) + assert.Equal(t, http.StatusOK, recB.Code, "a token minted after cache warm-up must authenticate via the on-miss re-list once the floor has elapsed") + assert.True(t, rhB.ran) +} + +// TestRateLimiterSurvivesCacheRebuild is the regression test for the +// limiter-reset-on-demand bug: rebuildLocked used to WIPE the limiter map, +// and any stale-cache miss triggers a rebuild — so an attacker who exhausted +// their token's budget could send one garbage bearer and collect a fresh +// burst of 20. The limiter map must survive a rebuild for every token still +// in the listed set. +func TestRateLimiterSurvivesCacheRebuild(t *testing.T) { + cur := time.Now() + clock := func() time.Time { return cur } + + valA := mustNewTokenValue(t) + crA := accessTokenCR("at-aaaaaaaaaaaa", valA, "owner-a", cur.Add(time.Hour)) + + var listCount atomic.Int32 + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithStatusSubresource(&spiceboxv1alpha1.AccessToken{}). + WithObjects(crA). + WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, cl client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + listCount.Add(1) + return cl.List(ctx, list, opts...) + }, + }). + Build() + d := newFakeDeps(c) + mw := newBearerMiddleware(d, clock) + + doReq := func(value string) *httptest.ResponseRecorder { + return doBearerRequest(mw, (&recordingHandler{}).handler(), "Bearer "+value) + } + + // Warm the cache, then advance PAST the floor before exhausting, so the + // garbage bearer below is able to trigger a rebuild with no further + // clock movement (no movement ⇒ no limiter refill between exhaustion and + // the post-rebuild probe — the refill and the reset must not be + // conflated). + require.Equal(t, http.StatusOK, doReq(valA).Code) + cur = cur.Add(missRelistFloor + time.Second) + + for i := 0; i < tokenRateLimitBurst; i++ { + require.Equal(t, http.StatusOK, doReq(valA).Code, "request %d within burst should succeed", i) + } + require.Equal(t, http.StatusTooManyRequests, doReq(valA).Code, "the budget must be spent before the rebuild is forced") + + // Force a rebuild: an unknown bearer against a stale-enough cache + // re-lists. Prove the rebuild actually happened via the List count. + before := listCount.Load() + require.Equal(t, http.StatusUnauthorized, doReq(mustNewTokenValue(t)).Code) + require.Equal(t, before+1, listCount.Load(), "the garbage bearer must have triggered a re-list for this test to prove anything") + + // The hot token's spent budget must have survived the rebuild. + assert.Equal(t, http.StatusTooManyRequests, doReq(valA).Code, + "an exhausted token must STILL be refused after a rebuild — a wiped limiter map hands the attacker a fresh burst on demand") +} + +// TestMissRelistFloorBoundsUnauthenticatedLists pins both halves of the +// List-amplification bound: a miss against a fresher-than-floor cache +// answers 401 with NO List at all (so repeated garbage bearers — same or +// distinct — within one window cost zero K8s round trips), and a miss +// against an older cache re-lists exactly once. +func TestMissRelistFloorBoundsUnauthenticatedLists(t *testing.T) { + cur := time.Now() + clock := func() time.Time { return cur } + + valA := mustNewTokenValue(t) + crA := accessTokenCR("at-aaaaaaaaaaaa", valA, "owner-a", cur.Add(time.Hour)) + + var listCount atomic.Int32 + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithStatusSubresource(&spiceboxv1alpha1.AccessToken{}). + WithObjects(crA). + WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, cl client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + listCount.Add(1) + return cl.List(ctx, list, opts...) + }, + }). + Build() + d := newFakeDeps(c) + mw := newBearerMiddleware(d, clock) + + // Warm-up costs the one bootstrap List. + require.Equal(t, http.StatusOK, doBearerRequest(mw, (&recordingHandler{}).handler(), "Bearer "+valA).Code) + require.Equal(t, int32(1), listCount.Load()) + + // A burst of DISTINCT garbage bearers within the floor window: every one + // is refused from the cache alone — zero further Lists. + for i := 0; i < 5; i++ { + rec := doBearerRequest(mw, (&recordingHandler{}).handler(), "Bearer "+mustNewTokenValue(t)) + assert.Equal(t, http.StatusUnauthorized, rec.Code) + } + assert.Equal(t, int32(1), listCount.Load(), "misses within the floor window must not List at all") + + // Past the floor, one more garbage bearer re-lists — exactly once. + cur = cur.Add(missRelistFloor + time.Millisecond) + require.Equal(t, http.StatusUnauthorized, doBearerRequest(mw, (&recordingHandler{}).handler(), "Bearer "+mustNewTokenValue(t)).Code) + assert.Equal(t, int32(2), listCount.Load(), "a stale-cache miss must re-list") + + // And the window resets: further garbage at the same instant is again + // answered without a List. + require.Equal(t, http.StatusUnauthorized, doBearerRequest(mw, (&recordingHandler{}).handler(), "Bearer "+mustNewTokenValue(t)).Code) + assert.Equal(t, int32(2), listCount.Load(), "the re-list must start a fresh floor window") +} + +// TestConcurrentUnknownBearersShareOneList: N concurrent requests bearing the +// same unknown token against a never-listed cache produce exactly ONE List — +// singleflight collapses the in-flight callers, and the double-check inside +// the flight stops a caller that lost the race from re-listing a cache +// another flight just rebuilt. +func TestConcurrentUnknownBearersShareOneList(t *testing.T) { + now := time.Now() + + var listCount atomic.Int32 + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithStatusSubresource(&spiceboxv1alpha1.AccessToken{}). + WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, cl client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + listCount.Add(1) + return cl.List(ctx, list, opts...) + }, + }). + Build() + d := newFakeDeps(c) + mw := newBearerMiddleware(d, func() time.Time { return now }) + + unknown := "Bearer " + mustNewTokenValue(t) + const n = 8 + var wg sync.WaitGroup + codes := make([]int, n) + for i := 0; i < n; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + codes[i] = doBearerRequest(mw, (&recordingHandler{}).handler(), unknown).Code + }(i) + } + wg.Wait() + + for i, code := range codes { + assert.Equal(t, http.StatusUnauthorized, code, "request %d", i) + } + assert.Equal(t, int32(1), listCount.Load(), "concurrent unknown bearers must share exactly one List") +} + +// TestRebuildPrunesOnlyDeadLimiters pins rebuildLocked's limiter contract +// directly: a limiter whose token survives the rebuild is the SAME limiter +// (its spent budget intact), and one whose token is gone from the listed set +// is dropped. +func TestRebuildPrunesOnlyDeadLimiters(t *testing.T) { + now := time.Now() + cache := newTokenCache() + + liveLimiter := cache.limiterFor("at-live") + _ = cache.limiterFor("at-dead") + + list := spiceboxv1alpha1.AccessTokenList{Items: []spiceboxv1alpha1.AccessToken{ + *accessTokenCR("at-live", mustNewTokenValue(t), "owner-a", now.Add(time.Hour)), + }} + cache.mu.Lock() + cache.rebuildLocked(list, now) + cache.mu.Unlock() + + cache.mu.Lock() + defer cache.mu.Unlock() + assert.Same(t, liveLimiter, cache.limiters["at-live"], "a live token's limiter must survive the rebuild untouched") + _, deadKept := cache.limiters["at-dead"] + assert.False(t, deadKept, "a token absent from the listed set must have its limiter pruned") +} + +// TestPerTokenRateLimit: hammering one token past its burst (20) within one +// window yields 429 + Retry-After, a DIFFERENT token is unaffected (the +// limiter is per token id, not global), and after the fake clock advances +// the first token is admitted again. +func TestPerTokenRateLimit(t *testing.T) { + cur := time.Now() + clock := func() time.Time { return cur } + + valA := mustNewTokenValue(t) + crA := accessTokenCR("at-aaaaaaaaaaaa", valA, "owner-a", cur.Add(time.Hour)) + valB := mustNewTokenValue(t) + crB := accessTokenCR("at-bbbbbbbbbbbb", valB, "owner-b", cur.Add(time.Hour)) + c := newAuthTestClient(t, crA, crB) + d := newFakeDeps(c) + mw := newBearerMiddleware(d, clock) + + doReq := func(value string) *httptest.ResponseRecorder { + rh := &recordingHandler{} + return doBearerRequest(mw, rh.handler(), "Bearer "+value) + } + + for i := 0; i < tokenRateLimitBurst; i++ { + rec := doReq(valA) + require.Equal(t, http.StatusOK, rec.Code, "request %d within burst should succeed", i) + } + + overBudget := doReq(valA) + assert.Equal(t, http.StatusTooManyRequests, overBudget.Code) + assert.Equal(t, "1", overBudget.Header().Get("Retry-After")) + + // A different token's budget is independent. + assert.Equal(t, http.StatusOK, doReq(valB).Code, "a different token's budget must be independent") + + // Advance the clock enough to refill at least one token (10 rps == + // 1 token per 100ms) and the first token is admitted again. + cur = cur.Add(200 * time.Millisecond) + assert.Equal(t, http.StatusOK, doReq(valA).Code, "after the clock advances, the token is admitted again") +} + +// TestLastUsedPatchedOnMeaningfulChangeOnly: two requests in quick succession +// patch status.lastUsedAt exactly once; a request after the fake clock +// advances past lastUsedResolution patches a second time. The SSA/ +// idempotency rule under test: never a write per request. +func TestLastUsedPatchedOnMeaningfulChangeOnly(t *testing.T) { + cur := time.Now() + clock := func() time.Time { return cur } + + val := mustNewTokenValue(t) + // Expiry must outlive the clock advance below (2h) or the third request + // would correctly 401 on token expiry rather than exercising the lastUsed + // patch cadence this test is actually about. + cr := accessTokenCR("at-aaaaaaaaaaaa", val, "owner-a", cur.Add(24*time.Hour)) + + patchCount := 0 + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithStatusSubresource(&spiceboxv1alpha1.AccessToken{}). + WithObjects(cr). + WithInterceptorFuncs(interceptor.Funcs{ + SubResourcePatch: func(ctx context.Context, cl client.Client, subResourceName string, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + if subResourceName == "status" { + patchCount++ + } + return cl.SubResource(subResourceName).Patch(ctx, obj, patch, opts...) + }, + }). + Build() + d := newFakeDeps(c) + mw := newBearerMiddleware(d, clock) + + doReq := func() { + rh := &recordingHandler{} + rec := doBearerRequest(mw, rh.handler(), "Bearer "+val) + require.Equal(t, http.StatusOK, rec.Code) + } + + doReq() + doReq() + assert.Equal(t, 1, patchCount, "two requests in quick succession must patch status.lastUsedAt exactly once") + + cur = cur.Add(2 * time.Hour) + doReq() + assert.Equal(t, 2, patchCount, "a request after lastUsedResolution has elapsed must patch again") +} diff --git a/pkg/web/mcpfront/deps.go b/pkg/web/mcpfront/deps.go new file mode 100644 index 00000000..733c830c --- /dev/null +++ b/pkg/web/mcpfront/deps.go @@ -0,0 +1,65 @@ +package mcpfront + +import ( + "context" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/platform/artifacts" + "github.com/authzed/openagentprimitives/pkg/platform/identity" +) + +// Deps is what internal/cmd/webd's umbrella must implement for the /mcp +// plugin to mount. ui.Routes casts the generic webui.Deps to this interface; +// a failed cast, or a nil AccessTokenAuthz(), means NO routes are returned at +// all — an unconfigured SpiceDB yields no MCP surface, fail closed by +// absence rather than by every handler call refusing one request at a time. +type Deps interface { + K8s() client.Client + // AccessTokenAuthz is the SpiceDB half of an authorized tool call (the + // three-legged intersection check, the role mirror, and the scope + // filter). nil means SpiceDB is not configured. + AccessTokenAuthz() AccessTokenAuthz + // AccessTokenNamespace is where AccessToken CRs live — the SAME + // namespace pkg/web/mcpfront's Minter creates them in (webd's + // --accesstoken-namespace), so the bearer middleware's hash lookup finds + // every token a mint on this process could have produced. + AccessTokenNamespace() string + OperatorURL() string + MemoryToken() string + Artifacts() *artifacts.Service + // LookupReadableSessions answers which sessions owner may read. v1 + // enumerates the interactable set — interact implies read_transcript for + // a root session — rather than a dedicated read-only lookup; see the + // webd implementation's own doc comment. + LookupReadableSessions(ctx context.Context, owner identity.CanonicalUserID) (spicedb.InteractableSessions, error) + // FetchArtifact fetches an artifact's rendered content bytes: resolve + // artifactID to its head revision's render, then fetch that render's + // bytes, the same two-step webd's own ContentRender/FetchRender pair does + // for the browser live-view. Returns the bytes and the render's MIME type. + FetchArtifact(ctx context.Context, ns, name, artifactID string) ([]byte, string, error) + // ExternalBaseURL is the trusted-origin base URL this process is reached + // at. Used both to build the protected-resource metadata's "resource"/ + // "authorization_servers" fields and the WWW-Authenticate challenge's + // resource_metadata URL every 401 carries. + ExternalBaseURL() string + Logger() logr.Logger +} + +// AccessTokenAuthz is the SpiceDB half of an authorized /mcp tool call. +// Declared here (not imported as spicedb.Client directly) so a fake is +// trivial to construct in tests — the same shape as mint.go's GrantWriter. +// Satisfied by *spicedb.Client; see the compile-time guard below. +type AccessTokenAuthz interface { + CheckAccessTokenOp(ctx context.Context, chk spicedb.AccessTokenCheck, fullyConsistent bool) (spicedb.AccessTokenDecision, error) + CheckAccessTokenMirror(ctx context.Context, tokenID string, owner identity.CanonicalUserID, permission string, fullyConsistent bool) (bool, error) + FilterAccessTokenCoveredClasses(ctx context.Context, tokenID string, classIDs []string, fullyConsistent bool) (map[string]bool, error) +} + +// var _ AccessTokenAuthz = (*spicedb.Client)(nil) is a compile-time proof +// that *spicedb.Client still satisfies this interface — a signature drift on +// either side fails `go build`, not a runtime deps.(AccessTokenAuthz) +// assertion deep inside webd's artifactViewDeps.AccessTokenAuthz nil-guard. +var _ AccessTokenAuthz = (*spicedb.Client)(nil) diff --git a/pkg/web/mcpfront/integration_test.go b/pkg/web/mcpfront/integration_test.go new file mode 100644 index 00000000..3ff04c2b --- /dev/null +++ b/pkg/web/mcpfront/integration_test.go @@ -0,0 +1,595 @@ +//go:build integration + +// End-to-end integration coverage for the OAuth-minted /mcp surface: a real +// SpiceDB (test/testspicedb), a real memory backend served over HTTP +// (pkg/memory/httpsrv), a real identityd authorization-server (DCR, PKCE +// authorize/consent/token), a real mcpfront bearer middleware + tool set, and +// a real MCP client (pkg/tools/mcp/probe) — wired together exactly as webd +// wires its own production umbrella (internal/cmd/webd's artifactViewDeps), +// minus the things an integration test has no business standing up (K8s +// apiserver, NATS, the browser). +// +// Only TestFullOAuthAndMCPFlow drives the full browser-grade OAuth dance +// (DCR -> authorize -> consent -> token). The other three mint directly +// through the same production Minter the token endpoint calls — the claim +// under test in each of them is about SpiceDB-backed authorization at tool-call +// time, not the OAuth wire protocol, which TestFullOAuthAndMCPFlow already +// covers once. +package mcpfront + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "regexp" + "strings" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/channels/channelkinds" + "github.com/authzed/openagentprimitives/pkg/memory" + "github.com/authzed/openagentprimitives/pkg/memory/httpsrv" + "github.com/authzed/openagentprimitives/pkg/memory/inmem" + "github.com/authzed/openagentprimitives/pkg/memory/kinds/turn" + "github.com/authzed/openagentprimitives/pkg/memory/tokens" + "github.com/authzed/openagentprimitives/pkg/platform/artifacts" + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" + "github.com/authzed/openagentprimitives/pkg/platform/identity/passthroughlink" + "github.com/authzed/openagentprimitives/pkg/platform/identityd" + "github.com/authzed/openagentprimitives/pkg/tools/mcp/oauth" + "github.com/authzed/openagentprimitives/pkg/tools/mcp/probe" + "github.com/authzed/openagentprimitives/pkg/web/webui" + "github.com/authzed/openagentprimitives/test/testspicedb" +) + +const ( + fixtureAccessTokenNamespace = "agentprimitives-system" + fixtureExternalBaseURL = "https://mcp.example.test" + fixtureMemoryToken = "integration-webd-token" + fixtureSessionNamespace = "demo-ns" + fixtureSessionName = "demo-session" + fixtureClassName = "demo-agent" + fixtureOtherClassName = "other-agent" + fixtureTranscriptText = "hello from the integration fixture" +) + +// pendingFieldRe pulls the single-use pending id out of the consent form's +// hidden input (see handlers_oauthas_authorize.go's consentFormHTML). +var pendingFieldRe = regexp.MustCompile(`name="pending" value="([^"]*)"`) + +// mcpFixture is the shared scaffolding every flow test below drives: a real +// SpiceDB client + schema, a real memory backend served over HTTP, a fake K8s +// client holding the AgentSession fixture(s), a production Minter, and a +// webui.Server (identityd + mcpfront) served over httptest. +type mcpFixture struct { + owner identity.CanonicalUserID + spdb *spicedb.Client + k8s client.Client + mem *memory.Local + minter *Minter + baseURL string +} + +// newMCPFixture stands up everything TestFullOAuthAndMCPFlow and its three +// siblings need: testspicedb + spicedb.NewClient -> schema, inmem memory + +// httpsrv on httptest (webd read-only token registered), a fake K8s client +// seeded with AgentSession demo-session (class demo-agent) in ns demo-ns plus +// a turn fixture written to memory, and a webui.Server (identityd + mcpfront) +// over httptest with an authenticate stub that always returns the fixture +// owner — the brief's "authenticate stub returns a fixed subject for +// cookie-path requests" verbatim, since none of these tests exercise a real +// browser session cookie. +func newMCPFixture(t *testing.T) *mcpFixture { + t.Helper() + ctx := context.Background() + + endpoint := testspicedb.Endpoint(t) + token := testspicedb.UniqueToken(t) + testspicedb.WriteSchema(t, endpoint, token) + spdb, err := spicedb.NewClient(endpoint, token, true) + require.NoError(t, err, "spicedb.NewClient") + t.Cleanup(func() { _ = spdb.Close() }) + + mem := memory.NewLocal(inmem.NewBackend()) + reg := tokens.NewRegistry() + reg.SetWebdToken(fixtureMemoryToken) + memSrv := httptest.NewServer(httpsrv.NewHandler(mem, reg)) + t.Cleanup(memSrv.Close) + + k8s := fake.NewClientBuilder().WithScheme(newMintScheme(t)).Build() + + owner := identity.CanonicalFromTrusted("mcp-fixture-owner", "integration test fixture") + + sess := sessionFixture(fixtureSessionNamespace, fixtureSessionName, fixtureClassName) + require.NoError(t, k8s.Create(ctx, sess), "create demo-session") + require.NoError(t, spdb.TouchSessionOwner(ctx, fixtureSessionNamespace, fixtureSessionName, owner), "touch demo-session owner") + + turnCtx := memory.WithSystemApproval(ctx, "integration test fixture") + appender := turn.NewAppender(mem, memory.Scope{Kind: "session", ID: fixtureSessionNamespace + "/" + fixtureSessionName}) + require.NoError(t, appender.Append(turnCtx, memory.Turn{ + Index: 0, + Role: "user", + Content: []memory.ContentBlock{{Type: "text", Text: fixtureTranscriptText}}, + CreatedAt: time.Now().UTC(), + }), "append transcript turn fixture") + + minter := &Minter{ + SpiceDB: spdb, + K8s: k8s, + Namespace: fixtureAccessTokenNamespace, + Lifetime: time.Hour, + } + + deps := &integrationDeps{ + owner: owner, + spdb: spdb, + k8s: k8s, + mem: mem, + memURL: memSrv.URL, + minter: minter, + linkSigner: passthroughlink.New([]byte("integration-test-signing-key-0123456789")), + } + + authFn := func(*http.Request) (string, bool) { return owner.Subject().String(), true } + trustedHost := "127.0.0.1" // httptest.NewServer listens on 127.0.0.1; hostOnly strips the port on both sides. + srv, err := webui.NewServer( + authFn, + nil, // beginLogin: authFn never reports "unauthenticated", so this is never consulted. + func() string { return trustedHost }, + func() string { return "sandbox.invalid" }, + nil, // sharedOriginOK: distinct origins, never shared. + deps, + []webui.WebUI{identityd.New(), ui{}}, + ) + require.NoError(t, err, "webui.NewServer") + + httpSrv := httptest.NewServer(srv) + t.Cleanup(httpSrv.Close) + + return &mcpFixture{ + owner: owner, + spdb: spdb, + k8s: k8s, + mem: mem, + minter: minter, + baseURL: httpSrv.URL, + } +} + +// mint mints an access token directly through the fixture's production +// Minter — the same component /oauth/token calls — for the three tests that +// aren't exercising the OAuth wire protocol itself. +func (f *mcpFixture) mint(t *testing.T, role string, scopeClasses []string, unfiltered bool) Minted { + t.Helper() + minted, err := f.minter.MintAccessToken(context.Background(), MintParams{ + Owner: f.owner, + Role: role, + ScopeClasses: scopeClasses, + Unfiltered: unfiltered, + ClientName: "integration test", + ClientID: "integration-test-client", + }) + require.NoError(t, err, "MintAccessToken") + return minted +} + +// callTool calls name on the /mcp endpoint with bearer and requires the +// round trip itself to succeed (a non-2xx HTTP response is a test failure +// here; IsError tool results are a normal, assertable outcome handled by the +// caller). +func (f *mcpFixture) callTool(t *testing.T, bearer, name string, args any) *probe.CallOutcome { + t.Helper() + out, err := f.callToolRaw(context.Background(), bearer, name, args) + require.NoError(t, err, "CallTool %s", name) + return out +} + +// callToolRaw is callTool without the NoError requirement, for the one case +// (TestRevocationIsImmediateViaTupleDelete's final call) that expects the +// round trip itself to fail with an HTTP-level error. +func (f *mcpFixture) callToolRaw(ctx context.Context, bearer, name string, args any) (*probe.CallOutcome, error) { + return probe.CallTool(ctx, f.baseURL+"/mcp", &http.Client{}, name, args, "Authorization", "Bearer "+bearer, nil) +} + +// listSessionsUntil polls list_sessions until pred holds of the decoded +// result or a short deadline passes, returning the last decoded result +// either way. coveredSessions' leg-1 (CheckAccessTokenMirror) and leg-2 +// (FilterAccessTokenCoveredClasses) checks both run at SpiceDB's +// MinimizeLatency consistency — ops.go's deliberate latency/freshness +// tradeoff for cheap per-row enumeration checks (contrast +// authorizeSessionOp's single-resource checks, which are always fully +// consistent) — so a token minted microseconds earlier can briefly list as +// covering nothing. This rides out that same window the way a real polling +// client would, rather than asserting on an instant SpiceDB's own consistency +// model does not guarantee. +func listSessionsUntil(t *testing.T, f *mcpFixture, bearer string, pred func(ListSessionsOut) bool) ListSessionsOut { + t.Helper() + deadline := time.Now().Add(5 * time.Second) + var last ListSessionsOut + for { + out := f.callTool(t, bearer, "list_sessions", nil) + require.False(t, out.IsError, "list_sessions must succeed: %+v", out) + require.NoError(t, json.Unmarshal([]byte(out.Content[0].Text), &last)) + if pred(last) || time.Now().After(deadline) { + return last + } + time.Sleep(50 * time.Millisecond) + } +} + +// assertDenialText decodes out's single text block as the {"error": "..."} +// envelope toolErr produces and asserts it equals want exactly — the uniform +// no-existence-oracle text every denial in this package returns verbatim. +func assertDenialText(t *testing.T, out *probe.CallOutcome, want string) { + t.Helper() + require.True(t, out.IsError, "expected a tool-level denial") + require.NotEmpty(t, out.Content, "denial result must carry a text block") + var body struct { + Error string `json:"error"` + } + require.NoError(t, json.Unmarshal([]byte(out.Content[0].Text), &body)) + assert.Equal(t, want, body.Error) +} + +// integrationDeps is this file's single concrete deps value: it satisfies +// identityd.WebDeps + identityd.ConsentDeps + identityd.AccessTokenMinter +// (so identityd's full OAuth authorization-server surface mounts) AND +// mcpfront.Deps (so the /mcp surface mounts) simultaneously — the same shape +// internal/cmd/webd's artifactViewDeps uses in production, over real +// collaborators (a live SpiceDB client, a fake K8s client, a real memory +// backend) rather than webd's NATS/admind/browser-session extras this test +// has no use for. +type integrationDeps struct { + owner identity.CanonicalUserID + spdb *spicedb.Client + k8s client.Client + mem memory.Memory + memURL string + minter *Minter + linkSigner *passthroughlink.Signer +} + +// --- identityd.WebDeps ------------------------------------------------- + +func (d *integrationDeps) K8s() client.Client { return d.k8s } +func (d *integrationDeps) LinkSigner() *passthroughlink.Signer { return d.linkSigner } +func (d *integrationDeps) ExternalBaseURL() string { return fixtureExternalBaseURL } +func (d *integrationDeps) Authenticators() map[string]channelkinds.WebAuthenticator { + return map[string]channelkinds.WebAuthenticator{} +} +func (d *integrationDeps) IconHandler() http.Handler { return nil } +func (d *integrationDeps) InsecureTrustLinks() bool { return false } + +// --- identityd.ConsentDeps ----------------------------------------------- + +// ConsentClasses is a fixed list rather than a SpiceDB-derived one (contrast +// webd's production LookupStartableClasses/LookupInteractableSessions union): +// this test's authorization boundary is the three-legged CheckAccessTokenOp +// every tool call drives for real against the live SpiceDB client, not the +// checkbox list the consent screen renders. Both fixture classes are listed +// so TestScopeFilterExcludesOtherClasses' second session's class is a valid +// consent option too, even though no test currently grants it. +func (d *integrationDeps) ConsentClasses(context.Context, identity.CanonicalUserID) ([]identityd.ConsentClass, error) { + return []identityd.ConsentClass{ + {ID: fixtureSessionNamespace + "/" + fixtureClassName, DisplayName: "Demo Agent"}, + {ID: fixtureSessionNamespace + "/" + fixtureOtherClassName, DisplayName: "Other Agent"}, + }, nil +} + +// --- identityd.AccessTokenMinter ------------------------------------------ + +// MintAccessToken adapts identityd's mirror MintParams/Minted shapes onto the +// real mcpfront.MintParams/Minted the fixture's Minter speaks — the same +// adaptation internal/cmd/webd's artifactViewDeps.MintAccessToken performs in +// production, over the SAME *Minter the direct-mint helper (mcpFixture.mint) +// uses, so a token minted via /oauth/token and one minted directly are +// byte-for-byte the same shape. +func (d *integrationDeps) MintAccessToken(ctx context.Context, p identityd.MintParams) (identityd.Minted, error) { + minted, err := d.minter.MintAccessToken(ctx, MintParams{ + Owner: p.Owner, + Role: p.Role, + ScopeClasses: p.ScopeClasses, + Unfiltered: p.Unfiltered, + ClientName: p.ClientName, + ClientID: p.ClientID, + }) + if err != nil { + return identityd.Minted{}, err + } + return identityd.Minted{Value: minted.Value, TokenID: minted.TokenID, ExpiresAt: minted.ExpiresAt}, nil +} + +// --- mcpfront.Deps --------------------------------------------------------- + +func (d *integrationDeps) AccessTokenAuthz() AccessTokenAuthz { return d.spdb } +func (d *integrationDeps) AccessTokenNamespace() string { return fixtureAccessTokenNamespace } +func (d *integrationDeps) OperatorURL() string { return d.memURL } +func (d *integrationDeps) MemoryToken() string { return fixtureMemoryToken } +func (d *integrationDeps) Artifacts() *artifacts.Service { return artifacts.NewService(d.mem, nil) } + +func (d *integrationDeps) LookupReadableSessions(ctx context.Context, owner identity.CanonicalUserID) (spicedb.InteractableSessions, error) { + return d.spdb.LookupInteractableSessions(ctx, owner, 200, true) +} + +func (d *integrationDeps) FetchArtifact(context.Context, string, string, string) ([]byte, string, error) { + return nil, "", fmt.Errorf("integration fixture: FetchArtifact not implemented (unused by these flows)") +} + +func (d *integrationDeps) Logger() logr.Logger { return logr.Discard() } + +// Compile-time proofs that integrationDeps satisfies every interface the +// fixture relies on — a signature drift on any side fails `go build`, not a +// runtime cast deep inside webui.NewServer's plugin mounting. +var ( + _ Deps = (*integrationDeps)(nil) + _ identityd.WebDeps = (*integrationDeps)(nil) + _ identityd.ConsentDeps = (*integrationDeps)(nil) + _ identityd.AccessTokenMinter = (*integrationDeps)(nil) +) + +// TestFullOAuthAndMCPFlow drives the complete browser-grade OAuth dance — +// protected-resource metadata, DCR, authorize, consent, token exchange — and +// then proves the minted bearer actually works against the real /mcp surface: +// exactly the six read tools are advertised, list_sessions sees demo-session, +// and get_transcript returns the fixture's own turn text (asserted on the +// TOOL RESULT, never on a reply). +func TestFullOAuthAndMCPFlow(t *testing.T) { + f := newMCPFixture(t) + ctx := context.Background() + hc := &http.Client{} + + // 1. GET /.well-known/oauth-protected-resource -> AS URL. + presp, err := hc.Get(f.baseURL + "/.well-known/oauth-protected-resource") + require.NoError(t, err, "GET protected-resource metadata") + defer presp.Body.Close() + require.Equal(t, http.StatusOK, presp.StatusCode) + var prm struct { + Resource string `json:"resource"` + AuthorizationServers []string `json:"authorization_servers"` + } + require.NoError(t, json.NewDecoder(presp.Body).Decode(&prm)) + require.NotEmpty(t, prm.AuthorizationServers, "protected-resource metadata must name an authorization server") + assert.Equal(t, fixtureExternalBaseURL, prm.AuthorizationServers[0]) + assert.Equal(t, fixtureExternalBaseURL+"/mcp", prm.Resource) + + // 2. DCR -> client_id. + const redirectURI = "http://127.0.0.1:7777/cb" + clientID, _, err := oauth.Register(ctx, hc, f.baseURL+"/oauth/register", []string{redirectURI}) + require.NoError(t, err, "DCR register") + require.NotEmpty(t, clientID) + + // 3. GET /oauth/authorize (authenticated via the fixture's stub) -> parse + // the pending id from the consent form HTML. + pkce, err := oauth.NewPKCE() + require.NoError(t, err) + + authorizeURL := f.baseURL + "/oauth/authorize?" + url.Values{ + "response_type": {"code"}, + "client_id": {clientID}, + "redirect_uri": {redirectURI}, + "code_challenge": {pkce.Challenge}, + "code_challenge_method": {"S256"}, + "state": {pkce.State}, + }.Encode() + aresp, err := hc.Get(authorizeURL) + require.NoError(t, err, "GET /oauth/authorize") + defer aresp.Body.Close() + require.Equal(t, http.StatusOK, aresp.StatusCode, "authorize must render the consent form") + abody, err := io.ReadAll(aresp.Body) + require.NoError(t, err) + m := pendingFieldRe.FindSubmatch(abody) + require.Len(t, m, 2, "consent form must carry a hidden pending field") + pendingID := string(m[1]) + + // 4. POST /oauth/consent role=read scope=demo-ns/demo-agent -> code from + // the redirect Location. CheckRedirect intercepts the 302 rather than + // following it into an unbound loopback port. + noRedirect := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }} + consentForm := url.Values{ + "pending": {pendingID}, + "role": {accesstoken.RoleRead}, + "scope": {fixtureSessionNamespace + "/" + fixtureClassName}, + "approve": {"1"}, + } + creq, err := http.NewRequest(http.MethodPost, f.baseURL+"/oauth/consent", strings.NewReader(consentForm.Encode())) + require.NoError(t, err) + creq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + creq.Header.Set("Origin", fixtureExternalBaseURL) // CSRF origin pin: must equal ExternalBaseURL() exactly. + cresp, err := noRedirect.Do(creq) + require.NoError(t, err, "POST /oauth/consent") + defer cresp.Body.Close() + require.Equal(t, http.StatusFound, cresp.StatusCode, "approved consent must redirect with a code") + loc, err := url.Parse(cresp.Header.Get("Location")) + require.NoError(t, err) + code := loc.Query().Get("code") + require.NotEmpty(t, code, "redirect must carry an authorization code") + assert.Equal(t, pkce.State, loc.Query().Get("state")) + + // 5. POST /oauth/token with the PKCE verifier -> bearer. + tokenForm := url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "redirect_uri": {redirectURI}, + "client_id": {clientID}, + "code_verifier": {pkce.Verifier}, + } + tresp, err := hc.Post(f.baseURL+"/oauth/token", "application/x-www-form-urlencoded", strings.NewReader(tokenForm.Encode())) + require.NoError(t, err, "POST /oauth/token") + defer tresp.Body.Close() + require.Equal(t, http.StatusOK, tresp.StatusCode, "token exchange must succeed") + var tok struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` + } + require.NoError(t, json.NewDecoder(tresp.Body).Decode(&tok)) + assert.True(t, strings.HasPrefix(tok.AccessToken, accesstoken.TokenPrefix)) + assert.Equal(t, "Bearer", tok.TokenType) + assert.Equal(t, "role:read classes:"+fixtureSessionNamespace+"/"+fixtureClassName, tok.Scope) + require.NotEmpty(t, tok.AccessToken) + + // 6/8. tools/list has exactly the six read tools (no write/send tool + // exists in Phase 1). + probeClient := &probe.Client{HTTP: &http.Client{}, URL: f.baseURL + "/mcp"} + toolList, err := probeClient.ListTools(ctx, "Authorization", "Bearer "+tok.AccessToken) + require.NoError(t, err, "tools/list") + names := make([]string, 0, len(toolList)) + for _, tl := range toolList { + names = append(names, tl.Name) + } + assert.ElementsMatch(t, + []string{"list_sessions", "get_session", "get_transcript", "search_memory", "list_artifacts", "get_artifact"}, + names) + + // 6. list_sessions contains demo-session. + lso := listSessionsUntil(t, f, tok.AccessToken, func(o ListSessionsOut) bool { + for _, s := range o.Sessions { + if s.Namespace == fixtureSessionNamespace && s.Name == fixtureSessionName { + return true + } + } + return false + }) + var sawDemoSession bool + for _, s := range lso.Sessions { + if s.Namespace == fixtureSessionNamespace && s.Name == fixtureSessionName { + sawDemoSession = true + } + } + assert.True(t, sawDemoSession, "list_sessions must contain demo-session") + + // 7. get_transcript returns the fixture text — asserted on the TOOL + // RESULT. + transcriptOut := f.callTool(t, tok.AccessToken, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: fixtureSessionName}) + require.False(t, transcriptOut.IsError, "get_transcript must succeed: %+v", transcriptOut) + var gto GetTranscriptOut + require.NoError(t, json.Unmarshal([]byte(transcriptOut.Content[0].Text), >o)) + var sawFixtureText bool + for _, e := range gto.Entries { + if e.Text == fixtureTranscriptText { + sawFixtureText = true + } + } + assert.True(t, sawFixtureText, "get_transcript must return the fixture turn text") +} + +// TestRevocationIsImmediateViaTupleDelete pins the revocation race from the +// plan's Review Focus #1: deleting the SpiceDB tuples (what the AccessToken +// finalizer does) denies the NEXT call immediately at the tool layer, and +// deleting the CR too (once the bearer cache notices, at tokenCacheTTL) denies +// at the HTTP layer. +func TestRevocationIsImmediateViaTupleDelete(t *testing.T) { + f := newMCPFixture(t) + ctx := context.Background() + + minted := f.mint(t, accesstoken.RoleRead, []string{fixtureSessionNamespace + "/" + fixtureClassName}, false) + + out := f.callTool(t, minted.Value, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: fixtureSessionName}) + require.False(t, out.IsError, "freshly minted token must work: %+v", out) + + // Simulate the finalizer: tuples gone, CR still present. + require.NoError(t, f.spdb.DeleteAccessTokenTuples(ctx, minted.TokenID)) + + out2 := f.callTool(t, minted.Value, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: fixtureSessionName}) + assertDenialText(t, out2, "get_transcript: "+errSessionNotAccessible.Error()) + + // Now delete the CR itself. The bearer middleware's tokenCache still + // authenticates from its in-process snapshot until that snapshot goes + // stale (tokenCacheTTL) and a relist notices the CR is gone — Routes() + // wires the middleware to the real clock (time.Now), so this is a real + // wait, not a simulated one. + require.NoError(t, f.k8s.Delete(ctx, &spiceboxv1alpha1.AccessToken{ + ObjectMeta: metav1.ObjectMeta{Name: minted.TokenID, Namespace: fixtureAccessTokenNamespace}, + })) + time.Sleep(tokenCacheTTL + 2*time.Second) + + _, err := f.callToolRaw(ctx, minted.Value, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: fixtureSessionName}) + require.Error(t, err, "a token whose CR is gone must fail at the HTTP layer, not the tool layer") + var herr *probe.HTTPError + require.ErrorAs(t, err, &herr, "must be an HTTP-level failure") + assert.Equal(t, http.StatusUnauthorized, herr.StatusCode) +} + +// TestOwnerDeniedKillsToken pins the plan's Review Focus #4: a valid, +// full-role, unfiltered token whose OWNER has since been denied on the +// session must be refused — leg 3 (OwnerHas) of the three-legged check, not +// leg 1 or leg 2. Exercised TWICE to pin durability (the bronzethread rule): +// a missing decision-writer could pass the first call and leak state the +// second. +func TestOwnerDeniedKillsToken(t *testing.T) { + f := newMCPFixture(t) + ctx := context.Background() + + require.NoError(t, f.spdb.TouchDeniedUser(ctx, fixtureSessionNamespace, fixtureSessionName, f.owner)) + + minted := f.mint(t, accesstoken.RoleFull, nil, true) + + for i := 1; i <= 2; i++ { + out := f.callTool(t, minted.Value, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: fixtureSessionName}) + assertDenialText(t, out, "get_transcript: "+errSessionNotAccessible.Error()) + t.Logf("call %d: denied as expected", i) + } +} + +// TestScopeFilterExcludesOtherClasses pins the plan's Review Focus #3: a +// token scoped to one agentclass must not see — via list_sessions — or reach +// — via get_transcript — a session of a different class, even though the same +// owner may read it directly. The denial for the excluded session must read +// identically to the denial for a session that doesn't exist at all: no +// existence oracle. +func TestScopeFilterExcludesOtherClasses(t *testing.T) { + f := newMCPFixture(t) + ctx := context.Background() + + const otherSessionName = "other-session" + other := sessionFixture(fixtureSessionNamespace, otherSessionName, fixtureOtherClassName) + require.NoError(t, f.k8s.Create(ctx, other), "create other-session") + require.NoError(t, f.spdb.TouchSessionOwner(ctx, fixtureSessionNamespace, otherSessionName, f.owner)) + + minted := f.mint(t, accesstoken.RoleRead, []string{fixtureSessionNamespace + "/" + fixtureClassName}, false) + + lso := listSessionsUntil(t, f, minted.Value, func(o ListSessionsOut) bool { + return len(o.Sessions) == 1 && o.Sessions[0].Name == fixtureSessionName + }) + names := make([]string, 0, len(lso.Sessions)) + for _, s := range lso.Sessions { + names = append(names, s.Name) + } + assert.Equal(t, []string{fixtureSessionName}, names, + "list_sessions must return only the session whose class the token's scope covers") + + otherOut := f.callTool(t, minted.Value, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: otherSessionName}) + require.True(t, otherOut.IsError, "out-of-scope session must be denied: %+v", otherOut) + + missingOut := f.callTool(t, minted.Value, "get_transcript", + GetTranscriptIn{Namespace: fixtureSessionNamespace, Name: "does-not-exist"}) + require.True(t, missingOut.IsError, "nonexistent session must be denied: %+v", missingOut) + + var otherBody, missingBody struct { + Error string `json:"error"` + } + require.NoError(t, json.Unmarshal([]byte(otherOut.Content[0].Text), &otherBody)) + require.NoError(t, json.Unmarshal([]byte(missingOut.Content[0].Text), &missingBody)) + assert.Equal(t, missingBody.Error, otherBody.Error, + "a scope-excluded session must read identically to a nonexistent one — no existence oracle") +} diff --git a/pkg/web/mcpfront/mint.go b/pkg/web/mcpfront/mint.go new file mode 100644 index 00000000..ffc680aa --- /dev/null +++ b/pkg/web/mcpfront/mint.go @@ -0,0 +1,156 @@ +// Package mcpfront is the externally-reachable MCP surface: the OAuth +// minter, the /mcp bearer middleware, and the typed ops layer. +package mcpfront + +import ( + "context" + "errors" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +// GrantWriter is the SpiceDB half of minting: write the token's complete +// tuple set, or tear it back down on a compensating failure. Satisfied by +// *spicedb.Client; declared here (not imported from spicedb) so Minter's +// dependency is exactly the two calls it makes, and a fake is trivial to +// write in tests. +type GrantWriter interface { + WriteAccessTokenGrant(ctx context.Context, g spicedb.AccessTokenGrant) error + DeleteAccessTokenTuples(ctx context.Context, tokenID string) error +} + +// Minter mints an AccessToken: a SpiceDB grant (the authorization truth) plus +// an AccessToken CR (the authentication record — hash, owner, display +// metadata, expiry). See MintAccessToken's doc for why the two are written in +// that order and what happens when the second half fails. +type Minter struct { + SpiceDB GrantWriter + K8s client.Client + // Namespace is where the AccessToken CR is created (webd flag + // --accesstoken-namespace, default "agentprimitives-system"). + Namespace string + // Lifetime is how long a minted token lives (webd flag + // --accesstoken-lifetime, default 90*24h). + Lifetime time.Duration + // Clock stands in for time.Now in tests; nil uses the real clock (see now()). + Clock func() time.Time +} + +// MintParams is what the OAuth token endpoint has decided to grant: the +// subject who approved it, the role/scope ladder they chose, and the client +// display fields the AccessToken CR surfaces back to an admin. +type MintParams struct { + Owner identity.CanonicalUserID + Role string + ScopeClasses []string + Unfiltered bool + ClientName string + ClientID string +} + +// Minted is the result of a successful mint. Value is the ONE AND ONLY time +// the plaintext bearer value is ever surfaced — it is never stored (the CR +// carries only its hash) and never retrievable again. +type Minted struct { + Value string + TokenID string + ExpiresAt time.Time +} + +// now returns the current time, substituting Clock in tests. +func (m *Minter) now() time.Time { + if m.Clock != nil { + return m.Clock() + } + return time.Now() +} + +// MintAccessToken mints a fresh token for p: a random value + id, a SpiceDB +// grant carrying the role/scope, and an AccessToken CR carrying the value's +// hash plus display metadata. +// +// Tuples are written FIRST, then the CR is created. This ordering matters: +// the /mcp bearer middleware authenticates a request by looking up the +// AccessToken CR's hash and then checking SpiceDB (fully consistent) for the +// matching grant. Writing SpiceDB first means the token is fully usable the +// instant this call returns — there is no reconciler race where a freshly +// minted token's first use could find tuples not yet written. Writing the CR +// first would invert that: a client could present a token that authenticates +// (the CR exists) before it is actually authorized, or worse, a crash between +// the two writes would leave a CR with no backing grant that could later be +// mistaken for a revoked-but-still-recognized token. +// +// If the CR create fails after the grant is written, this compensates by +// deleting the SpiceDB tuples it just wrote (best effort) and returns both +// errors joined — a grant with no corresponding CR is unreachable (nothing +// can ever present the matching bearer value) but would otherwise clutter +// SpiceDB forever. +func (m *Minter) MintAccessToken(ctx context.Context, p MintParams) (Minted, error) { + var out Minted + if p.Owner.IsZero() { + return out, fmt.Errorf("mint access token: empty owner") + } + if _, err := accesstoken.RoleRelation(p.Role); err != nil { + return out, fmt.Errorf("mint access token: %w", err) + } + if !p.Unfiltered && len(p.ScopeClasses) == 0 { + return out, fmt.Errorf("mint access token: scope classes or unfiltered required") + } + + value, err := accesstoken.NewTokenValue() + if err != nil { + return out, fmt.Errorf("mint access token: %w", err) + } + tokenID, err := accesstoken.NewTokenID() + if err != nil { + return out, fmt.Errorf("mint access token: %w", err) + } + + now := m.now() + expires := now.Add(m.Lifetime) + + // Tuples FIRST: the first /mcp check is fully consistent, so a token is + // usable the instant the endpoint responds — no reconciler race. + if err := m.SpiceDB.WriteAccessTokenGrant(ctx, spicedb.AccessTokenGrant{ + TokenID: tokenID, + Owner: p.Owner, + Role: p.Role, + ScopeClasses: p.ScopeClasses, + Unfiltered: p.Unfiltered, + ExpiresAt: expires, + }); err != nil { + return out, fmt.Errorf("mint access token %s: write grant: %w", tokenID, err) + } + + cr := &spiceboxv1alpha1.AccessToken{ + ObjectMeta: metav1.ObjectMeta{Name: tokenID, Namespace: m.Namespace}, + Spec: spiceboxv1alpha1.AccessTokenSpec{ + TokenHash: accesstoken.HashTokenValue(value), + Owner: p.Owner.String(), + ClientName: p.ClientName, + ClientID: p.ClientID, + ExpiresAt: metav1.NewTime(expires), + }, + } + if err := m.K8s.Create(ctx, cr); err != nil { + // Compensate: a grant without its authentication record is + // unreachable but would clutter SpiceDB; best-effort removal, and + // the failure of THAT is surfaced by joining it into the returned + // error rather than being swallowed. + delErr := m.SpiceDB.DeleteAccessTokenTuples(ctx, tokenID) + return out, errors.Join( + fmt.Errorf("mint access token %s: create AccessToken CR: %w", tokenID, err), + delErr, + ) + } + + return Minted{Value: value, TokenID: tokenID, ExpiresAt: expires}, nil +} diff --git a/pkg/web/mcpfront/mint_test.go b/pkg/web/mcpfront/mint_test.go new file mode 100644 index 00000000..d241abd5 --- /dev/null +++ b/pkg/web/mcpfront/mint_test.go @@ -0,0 +1,200 @@ +package mcpfront + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/platform/identity" + "github.com/authzed/openagentprimitives/pkg/platform/identity/accesstoken" +) + +// recordedCall is one call recorded by fakeGrantWriter, in the order it +// happened — the Minter test's only window onto "tuples before CR". +type recordedCall struct { + method string // "write" | "delete" + tokenID string + grant spicedb.AccessTokenGrant +} + +// fakeGrantWriter is the GrantWriter test double: it records every call it +// receives (and, via sharedOrder, interleaves with the fake K8s client's +// Create calls below) so ordering can be asserted directly. +type fakeGrantWriter struct { + order *[]string + calls []recordedCall + writeErr error + deleteErr error +} + +func (f *fakeGrantWriter) WriteAccessTokenGrant(_ context.Context, g spicedb.AccessTokenGrant) error { + f.calls = append(f.calls, recordedCall{method: "write", tokenID: g.TokenID, grant: g}) + if f.order != nil { + *f.order = append(*f.order, "write-grant") + } + return f.writeErr +} + +func (f *fakeGrantWriter) DeleteAccessTokenTuples(_ context.Context, tokenID string) error { + f.calls = append(f.calls, recordedCall{method: "delete", tokenID: tokenID}) + if f.order != nil { + *f.order = append(*f.order, "delete-tuples") + } + return f.deleteErr +} + +func newMintScheme(t *testing.T) *runtime.Scheme { + t.Helper() + s := runtime.NewScheme() + require.NoError(t, spiceboxv1alpha1.AddToScheme(s)) + return s +} + +func testOwner() identity.CanonicalUserID { + return identity.CanonicalFromTrusted("alice", "test fixture") +} + +func TestMintWritesTuplesThenCR(t *testing.T) { + var order []string + writer := &fakeGrantWriter{order: &order} + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithInterceptorFuncs(interceptor.Funcs{ + Create: func(ctx context.Context, cl client.WithWatch, obj client.Object, opts ...client.CreateOption) error { + order = append(order, "create-cr") + return cl.Create(ctx, obj, opts...) + }, + }).Build() + + fixedNow := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + m := &Minter{ + SpiceDB: writer, + K8s: c, + Namespace: "agentprimitives-system", + Lifetime: 90 * 24 * time.Hour, + Clock: func() time.Time { return fixedNow }, + } + + minted, err := m.MintAccessToken(context.Background(), MintParams{ + Owner: testOwner(), + Role: accesstoken.RoleRead, + ScopeClasses: []string{"default/demo-agent"}, + ClientName: "demo tool", + ClientID: "client-abc", + }) + require.NoError(t, err) + + assert.True(t, strings.HasPrefix(minted.Value, accesstoken.TokenPrefix)) + assert.NotEmpty(t, minted.TokenID) + assert.Equal(t, fixedNow.Add(90*24*time.Hour), minted.ExpiresAt) + + require.Equal(t, []string{"write-grant", "create-cr"}, order, "tuples must be written before the CR is created") + + require.Len(t, writer.calls, 1) + grant := writer.calls[0].grant + assert.Equal(t, minted.TokenID, grant.TokenID) + assert.Equal(t, testOwner(), grant.Owner) + assert.Equal(t, accesstoken.RoleRead, grant.Role) + assert.Equal(t, []string{"default/demo-agent"}, grant.ScopeClasses) + assert.Equal(t, minted.ExpiresAt, grant.ExpiresAt, "the grant's expiry must be the SAME instant as the CR's") + + var tok spiceboxv1alpha1.AccessToken + require.NoError(t, c.Get(context.Background(), client.ObjectKey{Namespace: "agentprimitives-system", Name: minted.TokenID}, &tok)) + assert.Equal(t, accesstoken.HashTokenValue(minted.Value), tok.Spec.TokenHash) + assert.Equal(t, testOwner().String(), tok.Spec.Owner) + assert.Equal(t, "demo tool", tok.Spec.ClientName) + assert.Equal(t, "client-abc", tok.Spec.ClientID) + assert.True(t, minted.ExpiresAt.Equal(tok.Spec.ExpiresAt.Time), "grant and CR expiry must be the same instant (minted=%v, cr=%v)", minted.ExpiresAt, tok.Spec.ExpiresAt.Time) +} + +func TestMintCompensatesWhenCRCreateFails(t *testing.T) { + writer := &fakeGrantWriter{} + createErr := errors.New("apiserver unavailable") + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithInterceptorFuncs(interceptor.Funcs{ + Create: func(ctx context.Context, cl client.WithWatch, obj client.Object, opts ...client.CreateOption) error { + return createErr + }, + }).Build() + + m := &Minter{ + SpiceDB: writer, + K8s: c, + Namespace: "agentprimitives-system", + Lifetime: 90 * 24 * time.Hour, + } + + _, err := m.MintAccessToken(context.Background(), MintParams{ + Owner: testOwner(), + Role: accesstoken.RoleRead, + ScopeClasses: []string{"default/demo-agent"}, + }) + require.Error(t, err) + assert.ErrorIs(t, err, createErr, "the CR-create error must be part of the joined error") + assert.Contains(t, err.Error(), "apiserver unavailable") + + require.Len(t, writer.calls, 2, "write then compensating delete") + assert.Equal(t, "write", writer.calls[0].method) + assert.Equal(t, "delete", writer.calls[1].method) + assert.Equal(t, writer.calls[0].tokenID, writer.calls[1].tokenID, "the compensation must target the SAME token id the grant was written for") + + var list spiceboxv1alpha1.AccessTokenList + require.NoError(t, c.List(context.Background(), &list)) + assert.Empty(t, list.Items, "a failed create must leave no CR behind") +} + +func TestMintCompensationFailureIsJoined(t *testing.T) { + createErr := errors.New("apiserver unavailable") + deleteErr := errors.New("spicedb unreachable") + writer := &fakeGrantWriter{deleteErr: deleteErr} + c := fake.NewClientBuilder(). + WithScheme(newMintScheme(t)). + WithInterceptorFuncs(interceptor.Funcs{ + Create: func(ctx context.Context, cl client.WithWatch, obj client.Object, opts ...client.CreateOption) error { + return createErr + }, + }).Build() + + m := &Minter{SpiceDB: writer, K8s: c, Namespace: "agentprimitives-system", Lifetime: time.Hour} + + _, err := m.MintAccessToken(context.Background(), MintParams{ + Owner: testOwner(), Role: accesstoken.RoleRead, Unfiltered: true, + }) + require.Error(t, err) + assert.ErrorIs(t, err, createErr) + assert.ErrorIs(t, err, deleteErr, "a failed compensation must never be silently dropped") +} + +func TestMintRejectsEmptyOwnerOrRole(t *testing.T) { + cases := []struct { + name string + params MintParams + }{ + {name: "empty owner", params: MintParams{Role: accesstoken.RoleRead, Unfiltered: true}}, + {name: "unknown role", params: MintParams{Owner: testOwner(), Role: "bogus", Unfiltered: true}}, + {name: "no scope and not unfiltered", params: MintParams{Owner: testOwner(), Role: accesstoken.RoleRead}}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + writer := &fakeGrantWriter{} + c := fake.NewClientBuilder().WithScheme(newMintScheme(t)).Build() + m := &Minter{SpiceDB: writer, K8s: c, Namespace: "ns", Lifetime: time.Hour} + + _, err := m.MintAccessToken(context.Background(), tc.params) + require.Error(t, err) + assert.Empty(t, writer.calls, "a rejected mint must never reach SpiceDB") + }) + } +} diff --git a/pkg/web/mcpfront/ops.go b/pkg/web/mcpfront/ops.go new file mode 100644 index 00000000..11bad0eb --- /dev/null +++ b/pkg/web/mcpfront/ops.go @@ -0,0 +1,737 @@ +package mcpfront + +import ( + "context" + "errors" + "fmt" + "sort" + "sync" + "time" + "unicode/utf8" + + "golang.org/x/sync/errgroup" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/memory" + "github.com/authzed/openagentprimitives/pkg/memory/httpclient" + "github.com/authzed/openagentprimitives/pkg/platform/artifacts" + "github.com/authzed/openagentprimitives/pkg/web/webui/livemirror" +) + +// The three $sameperm mirror names this file checks, pinned as constants so a +// typo can't silently check the wrong permission — and so +// TestRoleMatrixOnTools can assert on the exact string a tool uses without +// duplicating the literal everywhere. See accesstoken.MirrorPermissions for +// the full set these are drawn from. +const ( + permReadTranscript = "read_transcript" + permRead = "read" + permView = "view" +) + +// maxReadableSessionRefs is a defensive second cap on the refs this file fans +// K8s Gets out over, mirroring pkg/web/webui/sessions' maxListedSessions. +// d.LookupReadableSessions already bounds its own answer (webd's +// mcpReadableSessionsLimit), so this is belt-and-suspenders against a future +// Deps implementation that doesn't. +const maxReadableSessionRefs = 200 + +// readableSessionConcurrency bounds the parallel Gets fanned out over the +// lookup's refs, the same discipline pkg/web/webui/sessions/list.go uses +// (listGetConcurrency) for the identical reason: a direct, non-cache-backed +// K8s client turns every row into one apiserver round trip. +const readableSessionConcurrency = 8 + +// maxSearchDerivedScopes caps the number of session scopes search_memory +// derives when the caller names no explicit session — see opSearchMemory. +const maxSearchDerivedScopes = 20 + +// maxArtifactContentBytes caps get_artifact's inline content; beyond this the +// content is truncated and GetArtifactOut.ContentTruncated is set. +const maxArtifactContentBytes = 256 * 1024 + +// defaultListSessionsLimit / maxListSessionsLimit are list_sessions' Limit +// field defaults/cap. +const ( + defaultListSessionsLimit = 50 + maxListSessionsLimit = 200 +) + +// defaultTranscriptLimit is get_transcript's Limit field default. +const defaultTranscriptLimit = 100 + +// errSessionNotAccessible is the ONE denial text authorizeSessionOp ever +// returns, for BOTH "no such session" and "it exists but the three-legged +// check denied" — there is no existence oracle for a bearer that cannot read +// a session to learn whether it is even there. Every call site below returns +// this value verbatim rather than wrapping it, so the bytes a caller sees +// never vary by failure mode. +var errSessionNotAccessible = errors.New("session not found or not accessible") + +// ListSessionsIn / SessionSummary / ListSessionsOut, GetSessionIn/Out, +// GetTranscriptIn/TranscriptEntry/Out, SearchMemoryIn/SearchHit/Out, +// ListArtifactsIn/ArtifactSummary/Out and GetArtifactIn/Out are the shared +// typed contract the six /mcp tools expose — also the contract the Phase-2 +// CLI backend imports directly (see task-10-brief.md), so field names and +// JSON tags here are the wire format, not an implementation detail. +// Phase-1 narrowing, recorded: the spec's time-range filtering (e.g. +// startedAfter/startedBefore) is NOT implemented here — Agent/State/Limit +// are the only filters. A caller wanting a time window filters client-side +// over StartedAt on the returned SessionSummary rows. +type ListSessionsIn struct { + Agent string `json:"agent,omitempty"` // agentclass name filter + State string `json:"state,omitempty"` // "running"|"ended"|"" for all + Limit int `json:"limit,omitempty"` // default 50, cap 200 +} + +type SessionSummary struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + Class string `json:"class"` + Phase string `json:"phase"` + StartedAt time.Time `json:"startedAt"` + // Channel summarizes spec.inputChannel: kind, key, and the kind-specific + // External routing metadata, copied verbatim. Omitted for a kubectl-driven + // session with no input channel. + Channel map[string]string `json:"channel,omitempty"` +} + +type ListSessionsOut struct { + Sessions []SessionSummary `json:"sessions"` +} + +type GetSessionIn struct { + Namespace string `json:"namespace"` + Name string `json:"name"` +} + +type GetSessionOut struct { + Session SessionSummary `json:"session"` +} + +type GetTranscriptIn struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + Offset int `json:"offset,omitempty"` + Limit int `json:"limit,omitempty"` // entries; default 100 +} + +type TranscriptEntry struct { + Kind string `json:"kind"` + Role string `json:"role,omitempty"` + Text string `json:"text,omitempty"` + CreatedAt time.Time `json:"createdAt"` +} + +type GetTranscriptOut struct { + Entries []TranscriptEntry `json:"entries"` + Total int `json:"total"` +} + +type SearchMemoryIn struct { + Query string `json:"query"` + // Namespace+Name together name a single session to search; both empty + // searches every session this token's owner may read_transcript. + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` + Limit int `json:"limit,omitempty"` +} + +type SearchHit struct { + Session string `json:"session"` + Kind string `json:"kind"` + ID string `json:"id"` + Snippet string `json:"snippet"` + Score float64 `json:"score"` +} + +type SearchMemoryOut struct { + Hits []SearchHit `json:"hits"` + // SearchUnavailable is true when no search provider is configured + // (memory.ErrNoSearchProviders) — distinct from "no hits", so a caller + // doesn't read an empty result as "nothing matched". + SearchUnavailable bool `json:"searchUnavailable,omitempty"` +} + +type ListArtifactsIn struct { + Namespace string `json:"namespace"` + Name string `json:"name"` +} + +type ArtifactSummary struct { + ArtifactID string `json:"artifactId"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + RendererKind string `json:"rendererKind"` + CreatedAt time.Time `json:"createdAt"` +} + +type ListArtifactsOut struct { + Artifacts []ArtifactSummary `json:"artifacts"` +} + +type GetArtifactIn struct { + Namespace string `json:"namespace"` + Name string `json:"name"` + ArtifactID string `json:"artifactId"` +} + +type GetArtifactOut struct { + Artifact ArtifactSummary `json:"artifact"` + Content string `json:"content,omitempty"` + // ContentTruncated is true when the artifact's content exceeded + // maxArtifactContentBytes and was cut to fit. + ContentTruncated bool `json:"contentTruncated,omitempty"` +} + +// recordDecision is the ops layer's audit seam: every authorization decision +// an op makes — allowed, or denied naming which leg of the three-way check +// failed — is recorded through this ONE function. Today it only logs; Task +// 10b replaces the body with a write through the signed component-publisher +// path into a durable authzdecision entry (DecisionRecorder), without +// touching any call site below. +func recordDecision(d Deps, act acting, permission, resourceType, resourceID string, allowed bool, failedLeg string) { + d.Logger().Info("mcpfront: access-token op decision", + "tokenID", act.TokenID, "owner", act.Owner.String(), "permission", permission, + "resourceType", resourceType, "resourceID", resourceID, "allowed", allowed, "failedLeg", failedLeg) +} + +// failedLegOf names which leg of a denied AccessTokenDecision failed, for +// recordDecision's audit trail. "" for an allowed decision. +func failedLegOf(dec spicedb.AccessTokenDecision) string { + switch { + case !dec.TokenGrants: + return "token" + case !dec.ScopeCovers: + return "scope" + case !dec.OwnerHas: + return "owner" + default: + return "" + } +} + +// sessionResourcePermission maps a token mirror permission to the permission +// leg 3 checks on the agentsession itself (AccessTokenCheck.ResourcePermission). +// +// WHY the "read"/"view" mirrors map to read_transcript: agentsession defines +// no "read" or "view" permission, and the session-level truth each mirror +// stands on IS read_transcript — +// - memory_entry#read is literally defined as session->read_transcript in +// the schema, so for a whole-session memory read, read_transcript on the +// session is the same question asked one hop earlier; +// - artifact#view adds org-view (parent->artifact_org_view) and platform +// admin (platform->view_audit) arms on top of parent->interact. Gating a +// token's artifact reads on session-level read_transcript is deliberately +// NARROWER on those two arms: token holders do not get those side doors +// in v1 — an org-wide artifact audience or an admin's audit standing must +// not leak through a delegated bearer token. +// +// The mapping is NOT narrower in every direction, though. read_transcript +// also has a parent-chain arm (parent + parent->read_transcript) that +// artifact#view's own parent->interact arm does not mirror one-for-one, so a +// parent-session owner's token can fetch a CHILD session's artifacts here +// where the browser's artifact#view would require interact on the child +// directly — WIDER, on that one arm, than the permission being mirrored. +// Accepted trade, not an oversight: a holder who can read_transcript on the +// parent already sees everything the child did (transcript access already +// dominates artifact content for information purposes — an artifact is a +// rendering of something the transcript already narrates), and list_sessions +// never lists child sessions, so this widening cannot be used to discover a +// child session a caller does not already know the (namespace, name) of. +// +// Every other mirror ("read_transcript", "interact", "approve", ...) is +// literally the session permission of the same name; empty means "same as +// the mirror" (CheckAccessTokenOp's default), keeping this map a closed +// exception list rather than a parallel naming scheme. +func sessionResourcePermission(mirrorPerm string) string { + switch mirrorPerm { + case permRead, permView: + return permReadTranscript + default: + return "" + } +} + +// authorizeSessionOp loads the AgentSession CR and runs the three-legged +// CheckAccessTokenOp for perm against it. Denial and not-found BOTH return +// errSessionNotAccessible — verbatim, never wrapped — so a caller cannot tell +// "it doesn't exist" from "it exists but you may not touch it" (no existence +// oracle for a bearer token). +// +// fullyConsistent=true on every call: correctness over latency for a per-call +// authorization gate that denies by default. A future refinement could cache +// a ZedToken per (token id, class) and pass fullyConsistent only on this +// process's first check of it, trading a little staleness tolerance for one +// fewer round trip to the datastore's leader — not implemented here; see +// task-10-brief.md's binding design notes. +func authorizeSessionOp(ctx context.Context, d Deps, act acting, perm, ns, name string) (*spiceboxv1alpha1.AgentSession, error) { + var sess spiceboxv1alpha1.AgentSession + if err := d.K8s().Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, &sess); err != nil { + if !apierrors.IsNotFound(err) { + d.Logger().Info("mcpfront: authorizeSessionOp Get errored; treating as not accessible", + "ns", ns, "name", name, "tokenID", act.TokenID, "perm", perm, "err", err.Error()) + } + recordDecision(d, act, perm, "agentsession", ns+"/"+name, false, "") + return nil, errSessionNotAccessible + } + + classID := sess.Namespace + "/" + sess.Spec.Class + dec, err := d.AccessTokenAuthz().CheckAccessTokenOp(ctx, spicedb.AccessTokenCheck{ + TokenID: act.TokenID, + Owner: act.Owner, + Permission: perm, + ResourceType: "agentsession", + ResourceID: ns + "/" + name, + ClassID: classID, + ResourcePermission: sessionResourcePermission(perm), + }, true) + if err != nil { + d.Logger().Info("mcpfront: authorizeSessionOp check errored; denying", + "ns", ns, "name", name, "tokenID", act.TokenID, "perm", perm, "err", err.Error()) + recordDecision(d, act, perm, "agentsession", ns+"/"+name, false, "") + return nil, errSessionNotAccessible + } + if !dec.Allowed() { + recordDecision(d, act, perm, "agentsession", ns+"/"+name, false, failedLegOf(dec)) + return nil, errSessionNotAccessible + } + recordDecision(d, act, perm, "agentsession", ns+"/"+name, true, "") + return &sess, nil +} + +// readApprovedContext mints a memory.ForBearerToken capability approval for +// resource and attaches it to ctx, for the narrow case where d.Artifacts() +// reaches a memory.Local in-process (that facade's Query/Get enforce +// memory.EnsureApproval on every call). Called only AFTER authorizeSessionOp +// has already proven the three-legged check for this exact resource — exactly +// the contract ForBearerToken documents ("call only after the token-scope +// check has passed"). In production d.Artifacts() wraps an httpclient.Client +// instead (an HTTP call to the operator, authorized at that boundary), which +// never inspects ctx's approval set, so this is a no-op there and only +// matters for an in-process memory.Memory implementation. +func readApprovedContext(ctx context.Context, act acting, resource string) context.Context { + return memory.WithApproval(ctx, memory.ForBearerToken(memory.ReadMemory, resource, act.TokenID)) +} + +// coveredSessionRow is one session this token's owner may read AND whose +// agentclass the token's scope covers — the join list_sessions and +// search_memory's session-less derivation both need. +type coveredSessionRow struct { + Ref spicedb.SessionRef + Sess *spiceboxv1alpha1.AgentSession + Class string // ns/class +} + +// coveredSessions is the shared derivation behind list_sessions and +// search_memory's no-explicit-session branch: gate on the token's role +// mirror for mirrorPerm (leg 1 ALONE — cheaper than a full CheckAccessTokenOp +// per candidate), then join the owner's readable sessions against the K8s +// objects and filter to the classes the token's scope actually covers (leg +// 2, bulk, once). +// +// A false mirror check returns an EMPTY result with no error and, crucially, +// never calls LookupReadableSessions or touches K8s at all — the brief's +// "no lookup/K8s reads" requirement, so a read-role token asking a +// write-scoped question costs nothing beyond the one mirror check. +func coveredSessions(ctx context.Context, d Deps, act acting, mirrorPerm string) ([]coveredSessionRow, error) { + mirrorOK, err := d.AccessTokenAuthz().CheckAccessTokenMirror(ctx, act.TokenID, act.Owner, mirrorPerm, false) + if err != nil { + d.Logger().Info("mcpfront: coveredSessions mirror check errored; returning empty", + "tokenID", act.TokenID, "permission", mirrorPerm, "err", err.Error()) + recordDecision(d, act, mirrorPerm, "accesstoken", act.TokenID, false, "token") + return nil, nil + } + if !mirrorOK { + recordDecision(d, act, mirrorPerm, "accesstoken", act.TokenID, false, "token") + return nil, nil + } + recordDecision(d, act, mirrorPerm, "accesstoken", act.TokenID, true, "") + + lookup, err := d.LookupReadableSessions(ctx, act.Owner) + if err != nil { + return nil, fmt.Errorf("mcpfront: lookup readable sessions: %w", err) + } + + refs := lookup.Refs + if len(refs) > maxReadableSessionRefs { + refs = refs[:maxReadableSessionRefs] + } + + rows := make([]*coveredSessionRow, len(refs)) + var mu sync.Mutex + classSet := map[string]struct{}{} + + g, gctx := errgroup.WithContext(ctx) + g.SetLimit(readableSessionConcurrency) + for i, r := range refs { + g.Go(func() error { + var sess spiceboxv1alpha1.AgentSession + if err := d.K8s().Get(gctx, client.ObjectKey{Namespace: r.Namespace, Name: r.Name}, &sess); err != nil { + if apierrors.IsNotFound(err) { + d.Logger().Info("mcpfront: readable session has no Kubernetes object; dropping", + "ns", r.Namespace, "name", r.Name, "tokenID", act.TokenID) + } else { + d.Logger().Info("mcpfront: get AgentSession failed; dropping", + "ns", r.Namespace, "name", r.Name, "tokenID", act.TokenID, "err", err.Error()) + } + return nil // never abort the group: one unreadable session must not blank the rest + } + // A delegated child is machinery, not a conversation a token holder + // asked for — same exclusion pkg/web/webui/sessions/list.go applies. + if sess.Spec.Parent != nil { + return nil + } + classID := sess.Namespace + "/" + sess.Spec.Class + mu.Lock() + classSet[classID] = struct{}{} + mu.Unlock() + rows[i] = &coveredSessionRow{Ref: r, Sess: &sess, Class: classID} + return nil + }) + } + if err := g.Wait(); err != nil { + return nil, fmt.Errorf("mcpfront: fan out session gets: %w", err) + } + + classIDs := make([]string, 0, len(classSet)) + for c := range classSet { + classIDs = append(classIDs, c) + } + covered, err := d.AccessTokenAuthz().FilterAccessTokenCoveredClasses(ctx, act.TokenID, classIDs, false) + if err != nil { + return nil, fmt.Errorf("mcpfront: filter covered classes: %w", err) + } + + out := make([]coveredSessionRow, 0, len(rows)) + for _, row := range rows { + if row == nil { + continue + } + if !covered[row.Class] { + continue + } + out = append(out, *row) + } + return out, nil +} + +// opListSessions implements the list_sessions tool. +func opListSessions(ctx context.Context, d Deps, act acting, in ListSessionsIn) (ListSessionsOut, error) { + rows, err := coveredSessions(ctx, d, act, permReadTranscript) + if err != nil { + return ListSessionsOut{}, err + } + + limit := in.Limit + if limit <= 0 { + limit = defaultListSessionsLimit + } + if limit > maxListSessionsLimit { + limit = maxListSessionsLimit + } + + summaries := make([]SessionSummary, 0, len(rows)) + for _, row := range rows { + if in.Agent != "" && row.Sess.Spec.Class != in.Agent { + continue + } + if in.State != "" { + ended := sessionIsEnded(row.Sess.Status.Phase) + if (in.State == "ended") != ended { + continue + } + } + summaries = append(summaries, sessionSummaryFrom(row.Sess)) + } + + // Deterministic order: newest first, tiebroken by namespace/name — two + // identical calls must return the same order (sort.Slice is not stable and + // StartedAt alone collides at second granularity). + sort.Slice(summaries, func(i, j int) bool { + if !summaries[i].StartedAt.Equal(summaries[j].StartedAt) { + return summaries[i].StartedAt.After(summaries[j].StartedAt) + } + if summaries[i].Namespace != summaries[j].Namespace { + return summaries[i].Namespace < summaries[j].Namespace + } + return summaries[i].Name < summaries[j].Name + }) + + if len(summaries) > limit { + summaries = summaries[:limit] + } + return ListSessionsOut{Sessions: summaries}, nil +} + +// sessionIsEnded reports whether phase is one of the two terminal phases — +// the same two-bucket split ListSessionsIn.State offers ("running"|"ended"). +func sessionIsEnded(phase string) bool { + return phase == spiceboxv1alpha1.AgentSessionPhaseSucceeded || phase == spiceboxv1alpha1.AgentSessionPhaseFailed +} + +// sessionSummaryFrom maps an AgentSession CR to the wire SessionSummary. +func sessionSummaryFrom(sess *spiceboxv1alpha1.AgentSession) SessionSummary { + var startedAt time.Time + switch { + case sess.Status.StartedAt != nil: + startedAt = sess.Status.StartedAt.Time.UTC() + case !sess.CreationTimestamp.IsZero(): + startedAt = sess.CreationTimestamp.Time.UTC() + } + + var channel map[string]string + if ic := sess.Spec.InputChannel; ic != nil { + channel = make(map[string]string, len(ic.External)+2) + channel["kind"] = ic.Kind + channel["key"] = ic.Key + for k, v := range ic.External { + channel[k] = v + } + } + + return SessionSummary{ + Namespace: sess.Namespace, + Name: sess.Name, + Class: sess.Spec.Class, + Phase: sess.Status.Phase, + StartedAt: startedAt, + Channel: channel, + } +} + +// opGetSession implements the get_session tool. +func opGetSession(ctx context.Context, d Deps, act acting, in GetSessionIn) (GetSessionOut, error) { + sess, err := authorizeSessionOp(ctx, d, act, permReadTranscript, in.Namespace, in.Name) + if err != nil { + return GetSessionOut{}, err + } + return GetSessionOut{Session: sessionSummaryFrom(sess)}, nil +} + +// opGetTranscript implements the get_transcript tool. +func opGetTranscript(ctx context.Context, d Deps, act acting, in GetTranscriptIn) (GetTranscriptOut, error) { + if _, err := authorizeSessionOp(ctx, d, act, permReadTranscript, in.Namespace, in.Name); err != nil { + return GetTranscriptOut{}, err + } + + hist, err := livemirror.ReadHistory(ctx, d.OperatorURL(), d.MemoryToken(), in.Namespace, in.Name, d.Logger()) + if err != nil { + return GetTranscriptOut{}, fmt.Errorf("mcpfront: read transcript: %w", err) + } + + total := len(hist.Timeline) + limit := in.Limit + if limit <= 0 { + limit = defaultTranscriptLimit + } + offset := in.Offset + if offset < 0 { + offset = 0 + } + + var entries []TranscriptEntry + if offset < total { + end := offset + limit + if end > total { + end = total + } + slice := hist.Timeline[offset:end] + entries = make([]TranscriptEntry, 0, len(slice)) + for _, it := range slice { + entries = append(entries, TranscriptEntry{ + Kind: it.Kind, Role: it.Role, Text: it.Text, CreatedAt: it.CreatedAt, + }) + } + } + + return GetTranscriptOut{Entries: entries, Total: total}, nil +} + +// opSearchMemory implements the search_memory tool. +// +// Pre-authorization is mandatory here, not optional hardening: the webd +// memory token this process holds (d.MemoryToken()) can read ANY session's +// memory — it is an operator-wide credential, not scoped per caller. So the +// SCOPE LIST passed to the search request IS the entire authorization +// boundary for this call. Building that list from anything other than +// sessions this token has already been proven to cover (via authorizeSessionOp +// for an explicit session, or coveredSessions' leg-1+leg-2 join otherwise) +// would let a read-scoped token search sessions its owner — or its own +// class scope — was never granted. +func opSearchMemory(ctx context.Context, d Deps, act acting, in SearchMemoryIn) (SearchMemoryOut, error) { + var scopes []memory.Scope + + if in.Namespace != "" && in.Name != "" { + if _, err := authorizeSessionOp(ctx, d, act, permRead, in.Namespace, in.Name); err != nil { + return SearchMemoryOut{}, err + } + scopes = []memory.Scope{{Kind: "session", ID: in.Namespace + "/" + in.Name}} + } else { + rows, err := coveredSessions(ctx, d, act, permRead) + if err != nil { + return SearchMemoryOut{}, err + } + if len(rows) > maxSearchDerivedScopes { + rows = rows[:maxSearchDerivedScopes] + } + scopes = make([]memory.Scope, 0, len(rows)) + for _, row := range rows { + scopes = append(scopes, memory.Scope{Kind: "session", ID: row.Ref.Namespace + "/" + row.Ref.Name}) + } + } + + if len(scopes) == 0 { + return SearchMemoryOut{}, nil + } + + limit := in.Limit + if limit <= 0 { + limit = 20 + } + + mem := httpclient.New(d.OperatorURL(), d.MemoryToken()) + res, err := mem.Search(ctx, memory.SearchRequest{Scopes: scopes, Text: in.Query, Limit: limit}) + if err != nil { + if errors.Is(err, memory.ErrNoSearchProviders) { + return SearchMemoryOut{SearchUnavailable: true}, nil + } + return SearchMemoryOut{}, fmt.Errorf("mcpfront: search memory: %w", err) + } + + hits := make([]SearchHit, 0, len(res.Entries)) + for _, se := range res.Entries { + hits = append(hits, SearchHit{ + Session: se.Entry.Scope.ID, + Kind: se.Entry.Kind, + ID: se.Entry.ID, + Snippet: snippetFrom(se.Entry.Content), + Score: se.Score, + }) + } + return SearchMemoryOut{Hits: hits}, nil +} + +// maxSnippetRunes bounds search_memory's Snippet field — a preview, not the +// full stored content (search_memory the agent tool relays full content; +// this /mcp surface deliberately doesn't, since its caller is a remote client +// browsing results, not a model deciding whether to fetch more). +const maxSnippetRunes = 200 + +// snippetFrom renders a bounded preview of an entry's raw content for +// SearchHit.Snippet. content is the Kind's opaque ContentSchema payload (JSON +// bytes); this intentionally does not attempt to parse it by Kind — a tool +// generic across every memory Kind can't special-case each one's shape — so +// the preview is simply the content's own JSON text, truncated at a rune +// boundary so truncation can never split a multi-byte character. +func snippetFrom(content []byte) string { + s := string(content) + if utf8.RuneCountInString(s) <= maxSnippetRunes { + return s + } + runes := []rune(s) + return string(runes[:maxSnippetRunes]) +} + +// opListArtifacts implements the list_artifacts tool. +func opListArtifacts(ctx context.Context, d Deps, act acting, in ListArtifactsIn) (ListArtifactsOut, error) { + if _, err := authorizeSessionOp(ctx, d, act, permView, in.Namespace, in.Name); err != nil { + return ListArtifactsOut{}, err + } + + scope := memory.Scope{Kind: "session", ID: in.Namespace + "/" + in.Name} + views, err := d.Artifacts().ListArtifacts(readApprovedContext(ctx, act, scope.ID), scope) + if err != nil { + return ListArtifactsOut{}, fmt.Errorf("mcpfront: list artifacts: %w", err) + } + + out := make([]ArtifactSummary, 0, len(views)) + for _, v := range views { + out = append(out, artifactSummaryFrom(v)) + } + return ListArtifactsOut{Artifacts: out}, nil +} + +// artifactCreatedAtLayout matches artifacts.ArtifactView.CreatedAt's format +// (UTC, "2006-01-02T15:04:05Z") exactly — see pkg/platform/artifacts/resolve.go. +const artifactCreatedAtLayout = "2006-01-02T15:04:05Z" + +// artifactSummaryFrom maps an artifacts.ArtifactView to the wire +// ArtifactSummary. An unparsable CreatedAt (should not happen; the service +// always writes this exact layout) degrades to the zero time rather than +// failing the whole list — one malformed timestamp must not blank a page of +// otherwise-valid artifacts. +func artifactSummaryFrom(v artifacts.ArtifactView) ArtifactSummary { + createdAt, err := time.Parse(artifactCreatedAtLayout, v.CreatedAt) + if err != nil { + createdAt = time.Time{} + } + return ArtifactSummary{ + ArtifactID: v.ArtifactID, + Name: v.Name, + Description: v.Description, + RendererKind: v.RendererKind, + CreatedAt: createdAt, + } +} + +// opGetArtifact implements the get_artifact tool. +// +// Metadata is resolved via ListArtifacts rather than Artifacts().GetHead: +// GetHead's memartifact.Artifact carries no CreatedAt (that field lives on +// the memory.Entry wrapper, which ListArtifacts already reads and formats), +// so going through the same list the sibling tool uses keeps one artifact id +// reporting one CreatedAt everywhere, and doubles as the existence check. +func opGetArtifact(ctx context.Context, d Deps, act acting, in GetArtifactIn) (GetArtifactOut, error) { + if _, err := authorizeSessionOp(ctx, d, act, permView, in.Namespace, in.Name); err != nil { + return GetArtifactOut{}, err + } + + scope := memory.Scope{Kind: "session", ID: in.Namespace + "/" + in.Name} + views, err := d.Artifacts().ListArtifacts(readApprovedContext(ctx, act, scope.ID), scope) + if err != nil { + return GetArtifactOut{}, fmt.Errorf("mcpfront: list artifacts for get_artifact: %w", err) + } + var summary ArtifactSummary + found := false + for _, v := range views { + if v.ArtifactID == in.ArtifactID { + summary = artifactSummaryFrom(v) + found = true + break + } + } + if !found { + return GetArtifactOut{}, fmt.Errorf("mcpfront: artifact %q not found in session %s/%s", in.ArtifactID, in.Namespace, in.Name) + } + + raw, _, err := d.FetchArtifact(ctx, in.Namespace, in.Name, in.ArtifactID) + if err != nil { + return GetArtifactOut{}, fmt.Errorf("mcpfront: fetch artifact content: %w", err) + } + + out := GetArtifactOut{Artifact: summary} + if !utf8.Valid(raw) { + // Binary content (e.g. an image render) has no text representation + // this tool can return; Content stays empty rather than emitting + // mangled bytes. Logged so an operator can tell "empty on purpose" + // from "something broke". + d.Logger().Info("mcpfront: get_artifact content is not valid UTF-8; returning no inline content", + "ns", in.Namespace, "name", in.Name, "artifactID", in.ArtifactID) + return out, nil + } + if len(raw) <= maxArtifactContentBytes { + out.Content = string(raw) + return out, nil + } + cut := maxArtifactContentBytes + for cut > 0 && !utf8.RuneStart(raw[cut]) { + cut-- + } + out.Content = string(raw[:cut]) + out.ContentTruncated = true + return out, nil +} diff --git a/pkg/web/mcpfront/ops_test.go b/pkg/web/mcpfront/ops_test.go new file mode 100644 index 00000000..7b5ab2b2 --- /dev/null +++ b/pkg/web/mcpfront/ops_test.go @@ -0,0 +1,598 @@ +package mcpfront + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1" + "github.com/authzed/openagentprimitives/pkg/authz/spicedb" + "github.com/authzed/openagentprimitives/pkg/memory" + "github.com/authzed/openagentprimitives/pkg/memory/inmem" + memartifact "github.com/authzed/openagentprimitives/pkg/memory/kinds/artifact" + "github.com/authzed/openagentprimitives/pkg/memory/kinds/turn" + "github.com/authzed/openagentprimitives/pkg/platform/artifacts" + "github.com/authzed/openagentprimitives/pkg/platform/identity" +) + +// --- scripted AccessTokenAuthz fake ----------------------------------------- + +// scriptedAuthz is the ops tests' fake AccessTokenAuthz: a decision table a +// test can script per-call, plus a record of every call it received — the +// "pin the exact permission names" assertions read opCalls/mirrorCalls back. +type scriptedAuthz struct { + mu sync.Mutex + + opCalls []spicedb.AccessTokenCheck + opDecision func(chk spicedb.AccessTokenCheck) (spicedb.AccessTokenDecision, error) + mirrorCalls []string + mirrorDecision func(permission string) (bool, error) + coveredClasses map[string]bool + filterCalls [][]string +} + +func newScriptedAuthz() *scriptedAuthz { + return &scriptedAuthz{coveredClasses: map[string]bool{}} +} + +func (s *scriptedAuthz) CheckAccessTokenOp(_ context.Context, chk spicedb.AccessTokenCheck, _ bool) (spicedb.AccessTokenDecision, error) { + s.mu.Lock() + s.opCalls = append(s.opCalls, chk) + s.mu.Unlock() + if s.opDecision != nil { + return s.opDecision(chk) + } + return spicedb.AccessTokenDecision{TokenGrants: true, ScopeCovers: true, OwnerHas: true}, nil +} + +func (s *scriptedAuthz) CheckAccessTokenMirror(_ context.Context, _ string, _ identity.CanonicalUserID, permission string, _ bool) (bool, error) { + s.mu.Lock() + s.mirrorCalls = append(s.mirrorCalls, permission) + s.mu.Unlock() + if s.mirrorDecision != nil { + return s.mirrorDecision(permission) + } + return true, nil +} + +func (s *scriptedAuthz) FilterAccessTokenCoveredClasses(_ context.Context, _ string, classIDs []string, _ bool) (map[string]bool, error) { + s.mu.Lock() + s.filterCalls = append(s.filterCalls, append([]string(nil), classIDs...)) + s.mu.Unlock() + out := make(map[string]bool, len(classIDs)) + for _, c := range classIDs { + if s.coveredClasses[c] { + out[c] = true + } + } + return out, nil +} + +var _ AccessTokenAuthz = (*scriptedAuthz)(nil) + +// --- fake Deps --------------------------------------------------------------- + +// fakeOpsDeps is ops_test.go's Deps double: a real controller-runtime fake +// K8s client, a scripted AccessTokenAuthz, a real in-memory artifacts.Service +// (Artifacts() returns a concrete *artifacts.Service, never an interface — +// there is no faking it), and test-controlled operator URL/memory token so +// livemirror.ReadHistory / httpclient.Search can be pointed at an httptest +// server. +type fakeOpsDeps struct { + k8s client.Client + authz *scriptedAuthz + operatorURL string + memoryToken string + artifactSvc *artifacts.Service + artifactMem memory.Memory + fetchBytes []byte + fetchMIME string + fetchErr error + + lookupSessions spicedb.InteractableSessions + lookupErr error + lookupCalls int +} + +func newOpsFakeDeps(t *testing.T, c client.Client) *fakeOpsDeps { + t.Helper() + mem := memory.NewLocal(inmem.NewBackend()) + return &fakeOpsDeps{ + k8s: c, + authz: newScriptedAuthz(), + artifactSvc: artifacts.NewService(mem, nil), + artifactMem: mem, + } +} + +func (f *fakeOpsDeps) K8s() client.Client { return f.k8s } +func (f *fakeOpsDeps) AccessTokenAuthz() AccessTokenAuthz { return f.authz } +func (f *fakeOpsDeps) AccessTokenNamespace() string { return "agentprimitives-system" } +func (f *fakeOpsDeps) OperatorURL() string { return f.operatorURL } +func (f *fakeOpsDeps) MemoryToken() string { return f.memoryToken } +func (f *fakeOpsDeps) Artifacts() *artifacts.Service { return f.artifactSvc } +func (f *fakeOpsDeps) LookupReadableSessions(_ context.Context, _ identity.CanonicalUserID) (spicedb.InteractableSessions, error) { + f.lookupCalls++ + return f.lookupSessions, f.lookupErr +} +func (f *fakeOpsDeps) FetchArtifact(_ context.Context, _, _, _ string) ([]byte, string, error) { + return f.fetchBytes, f.fetchMIME, f.fetchErr +} +func (f *fakeOpsDeps) ExternalBaseURL() string { return "https://example.test" } +func (f *fakeOpsDeps) Logger() logr.Logger { return logr.Discard() } + +var _ Deps = (*fakeOpsDeps)(nil) + +// --- fixtures ---------------------------------------------------------------- + +func opsTestOwner() identity.CanonicalUserID { + return identity.CanonicalFromTrusted("alice", "test fixture") +} + +func opsTestActing() acting { + return acting{TokenID: "at-demo", Owner: opsTestOwner()} +} + +func newOpsTestClient(t *testing.T, objects ...client.Object) client.Client { + t.Helper() + return fake.NewClientBuilder().WithScheme(newMintScheme(t)).WithObjects(objects...).Build() +} + +// sessionFixture builds a minimal, non-delegated AgentSession CR: the +// "demo-agent"/"demo-session" shaped fixture the brief calls for, extended +// with a namespace/name/class so tests can build several at once. +func sessionFixture(ns, name, class string) *spiceboxv1alpha1.AgentSession { + startedAt := metav1.NewTime(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)) + return &spiceboxv1alpha1.AgentSession{ + ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}, + Spec: spiceboxv1alpha1.AgentSessionSpec{Class: class}, + Status: spiceboxv1alpha1.AgentSessionStatus{ + Phase: spiceboxv1alpha1.AgentSessionPhaseRunning, + StartedAt: &startedAt, + }, + } +} + +// opsTurnEntry builds a memory Entry in the wire shape turn.EntryToTurn +// decodes — the same shape pkg/web/webui/livemirror's own tests use +// (historyTestEntry), copied locally since that helper is unexported in +// another package. +func opsTurnEntry(t *testing.T, ns, name string, idx int, role, text string, at time.Time) memory.Entry { + t.Helper() + content, err := json.Marshal(map[string]any{ + "content": []map[string]string{{"type": "text", "text": text}}, + }) + require.NoError(t, err) + return memory.Entry{ + Scope: memory.Scope{Kind: "session", ID: ns + "/" + name}, + Kind: turn.KindName, + ID: turn.EntryID(idx, role), + CreatedAt: at, + Content: content, + } +} + +// --- tests --------------------------------------------------------------- + +func TestAuthorizeSessionOpDeniesWithoutLeakingExistence(t *testing.T) { + ctx := context.Background() + act := opsTestActing() + + t.Run("session absent: denied with the standard text, authz never consulted", func(t *testing.T) { + d := newOpsFakeDeps(t, newOpsTestClient(t)) + _, err := authorizeSessionOp(ctx, d, act, permReadTranscript, "demo-ns", "demo-session") + require.ErrorIs(t, err, errSessionNotAccessible) + assert.Empty(t, d.authz.opCalls, "a missing CR must never reach the three-legged check") + }) + + t.Run("session present but a leg denies: same text as absent", func(t *testing.T) { + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.authz.opDecision = func(spicedb.AccessTokenCheck) (spicedb.AccessTokenDecision, error) { + return spicedb.AccessTokenDecision{TokenGrants: true, ScopeCovers: false, OwnerHas: true}, nil + } + _, err := authorizeSessionOp(ctx, d, act, permReadTranscript, "demo-ns", "demo-session") + require.ErrorIs(t, err, errSessionNotAccessible) + }) + + t.Run("all three legs true: session returned", func(t *testing.T) { + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + got, err := authorizeSessionOp(ctx, d, act, permReadTranscript, "demo-ns", "demo-session") + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, "demo-session", got.Name) + require.Len(t, d.authz.opCalls, 1) + assert.Equal(t, permReadTranscript, d.authz.opCalls[0].Permission) + assert.Equal(t, "agentsession", d.authz.opCalls[0].ResourceType) + assert.Equal(t, "demo-ns/demo-session", d.authz.opCalls[0].ResourceID) + assert.Equal(t, "demo-ns/demo-agent", d.authz.opCalls[0].ClassID) + }) + + t.Run("absent vs denied are byte-identical", func(t *testing.T) { + _, errAbsent := authorizeSessionOp(ctx, newOpsFakeDeps(t, newOpsTestClient(t)), act, permReadTranscript, "demo-ns", "demo-session") + + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.authz.opDecision = func(spicedb.AccessTokenCheck) (spicedb.AccessTokenDecision, error) { + return spicedb.AccessTokenDecision{}, nil + } + _, errDenied := authorizeSessionOp(ctx, d, act, permReadTranscript, "demo-ns", "demo-session") + + require.Error(t, errAbsent) + require.Error(t, errDenied) + assert.Equal(t, errAbsent.Error(), errDenied.Error()) + }) +} + +func TestListSessionsFiltersByCoverageAndClass(t *testing.T) { + ctx := context.Background() + act := opsTestActing() + + t.Run("covered-class sessions only", func(t *testing.T) { + sessA1 := sessionFixture("demo-ns", "sess-a1", "class-a") + sessA2 := sessionFixture("demo-ns", "sess-a2", "class-a") + sessB1 := sessionFixture("demo-ns", "sess-b1", "class-b") + + d := newOpsFakeDeps(t, newOpsTestClient(t, sessA1, sessA2, sessB1)) + d.lookupSessions = spicedb.InteractableSessions{Refs: []spicedb.SessionRef{ + {Namespace: "demo-ns", Name: "sess-a1"}, + {Namespace: "demo-ns", Name: "sess-a2"}, + {Namespace: "demo-ns", Name: "sess-b1"}, + }} + d.authz.coveredClasses = map[string]bool{"demo-ns/class-a": true} + + out, err := opListSessions(ctx, d, act, ListSessionsIn{}) + require.NoError(t, err) + require.Len(t, out.Sessions, 2, "only the two class-a sessions are covered") + for _, s := range out.Sessions { + assert.Equal(t, "class-a", s.Class) + } + assert.Equal(t, 1, d.lookupCalls) + }) + + t.Run("mirror check false: empty result, no lookup, no K8s reads", func(t *testing.T) { + sess := sessionFixture("demo-ns", "sess-a1", "class-a") + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.authz.mirrorDecision = func(string) (bool, error) { return false, nil } + d.lookupSessions = spicedb.InteractableSessions{Refs: []spicedb.SessionRef{{Namespace: "demo-ns", Name: "sess-a1"}}} + + out, err := opListSessions(ctx, d, act, ListSessionsIn{}) + require.NoError(t, err) + assert.Empty(t, out.Sessions) + assert.Equal(t, 0, d.lookupCalls, "a false mirror check must never consult the lookup") + }) +} + +func TestGetTranscriptPaginates(t *testing.T) { + ctx := context.Background() + act := opsTestActing() + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + + at := time.Now().UTC().Truncate(time.Second) + entries := []memory.Entry{ + opsTurnEntry(t, "demo-ns", "demo-session", 0, "user", "m0", at), + opsTurnEntry(t, "demo-ns", "demo-session", 1, "assistant", "m1", at.Add(time.Second)), + opsTurnEntry(t, "demo-ns", "demo-session", 2, "user", "m2", at.Add(2*time.Second)), + opsTurnEntry(t, "demo-ns", "demo-session", 3, "assistant", "m3", at.Add(3*time.Second)), + opsTurnEntry(t, "demo-ns", "demo-session", 4, "user", "m4", at.Add(4*time.Second)), + } + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + require.NoError(t, json.NewEncoder(w).Encode(memory.QueryResult{Entries: entries})) + })) + defer srv.Close() + + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.operatorURL = srv.URL + d.memoryToken = "webd-token" + + t.Run("middle page", func(t *testing.T) { + out, err := opGetTranscript(ctx, d, act, GetTranscriptIn{Namespace: "demo-ns", Name: "demo-session", Offset: 2, Limit: 2}) + require.NoError(t, err) + assert.Equal(t, 5, out.Total) + require.Len(t, out.Entries, 2, "entries 3 and 4 of 5") + assert.Equal(t, "m2", out.Entries[0].Text) + assert.Equal(t, "m3", out.Entries[1].Text) + }) + + t.Run("offset past end: empty page, Total still correct", func(t *testing.T) { + out, err := opGetTranscript(ctx, d, act, GetTranscriptIn{Namespace: "demo-ns", Name: "demo-session", Offset: 50, Limit: 10}) + require.NoError(t, err) + assert.Equal(t, 5, out.Total) + assert.Empty(t, out.Entries) + }) + + t.Run("default limit when unset", func(t *testing.T) { + out, err := opGetTranscript(ctx, d, act, GetTranscriptIn{Namespace: "demo-ns", Name: "demo-session"}) + require.NoError(t, err) + assert.Equal(t, 5, out.Total) + assert.Len(t, out.Entries, 5, "fewer entries than the default limit returns them all") + }) +} + +func TestSearchMemoryScopesToAuthorizedSessionsOnly(t *testing.T) { + ctx := context.Background() + act := opsTestActing() + + sessA := sessionFixture("demo-ns", "sess-a", "class-a") + sessB := sessionFixture("demo-ns", "sess-b", "class-b") + + t.Run("derived scope list is exactly the covered sessions", func(t *testing.T) { + var gotScopes []memory.Scope + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req memory.SearchRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + gotScopes = req.Scopes + w.Header().Set("Content-Type", "application/json") + require.NoError(t, json.NewEncoder(w).Encode(memory.MergedSearchResult{})) + })) + defer srv.Close() + + d := newOpsFakeDeps(t, newOpsTestClient(t, sessA, sessB)) + d.operatorURL = srv.URL + d.memoryToken = "webd-token" + d.lookupSessions = spicedb.InteractableSessions{Refs: []spicedb.SessionRef{ + {Namespace: "demo-ns", Name: "sess-a"}, + {Namespace: "demo-ns", Name: "sess-b"}, + }} + d.authz.coveredClasses = map[string]bool{"demo-ns/class-a": true} + + out, err := opSearchMemory(ctx, d, act, SearchMemoryIn{Query: "hello"}) + require.NoError(t, err) + assert.False(t, out.SearchUnavailable) + require.Len(t, gotScopes, 1, "only the covered session's scope is ever sent") + assert.Equal(t, memory.Scope{Kind: "session", ID: "demo-ns/sess-a"}, gotScopes[0]) + assert.Contains(t, d.authz.mirrorCalls, permRead, "the derived branch mirrors on \"read\", not \"read_transcript\"") + }) + + t.Run("no search providers configured: SearchUnavailable, no error", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "no search providers configured", http.StatusNotFound) + })) + defer srv.Close() + + sess := sessionFixture("demo-ns", "sess-a", "class-a") + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.operatorURL = srv.URL + d.memoryToken = "webd-token" + d.lookupSessions = spicedb.InteractableSessions{Refs: []spicedb.SessionRef{{Namespace: "demo-ns", Name: "sess-a"}}} + d.authz.coveredClasses = map[string]bool{"demo-ns/class-a": true} + + out, err := opSearchMemory(ctx, d, act, SearchMemoryIn{Query: "x"}) + require.NoError(t, err) + assert.True(t, out.SearchUnavailable) + assert.Empty(t, out.Hits) + }) + + t.Run("explicit session uses authorizeSessionOp with perm read, single scope", func(t *testing.T) { + var gotScopes []memory.Scope + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req memory.SearchRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + gotScopes = req.Scopes + w.Header().Set("Content-Type", "application/json") + require.NoError(t, json.NewEncoder(w).Encode(memory.MergedSearchResult{})) + })) + defer srv.Close() + + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.operatorURL = srv.URL + d.memoryToken = "webd-token" + + _, err := opSearchMemory(ctx, d, act, SearchMemoryIn{Query: "x", Namespace: "demo-ns", Name: "demo-session"}) + require.NoError(t, err) + require.Len(t, gotScopes, 1) + assert.Equal(t, memory.Scope{Kind: "session", ID: "demo-ns/demo-session"}, gotScopes[0]) + require.Len(t, d.authz.opCalls, 1) + assert.Equal(t, permRead, d.authz.opCalls[0].Permission) + }) +} + +// TestRoleMatrixOnTools pins BOTH permission names each tool uses: the token +// mirror name leg 1 checks ($sameperm contract) and the resource permission +// leg 3 checks on the agentsession (the view/read → read_transcript mapping; +// "" means leg 3 defaults to the mirror name). A drift on either side of +// sessionResourcePermission's map is a regression this table catches by name. +func TestRoleMatrixOnTools(t *testing.T) { + ctx := context.Background() + act := opsTestActing() + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + require.NoError(t, json.NewEncoder(w).Encode(memory.QueryResult{})) + })) + defer srv.Close() + + type permPair struct{ mirror, resource string } + + cases := []struct { + tool string + run func(t *testing.T, d *fakeOpsDeps) error + wantOp *permPair // the CheckAccessTokenOp (mirror, ResourcePermission) pair; nil = no three-leg check + wantMirror string // the CheckAccessTokenMirror permission; "" = no mirror-only check + }{ + { + tool: "get_session", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opGetSession(ctx, d, act, GetSessionIn{Namespace: "demo-ns", Name: "demo-session"}) + return err + }, + wantOp: &permPair{mirror: permReadTranscript, resource: ""}, + }, + { + tool: "get_transcript", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opGetTranscript(ctx, d, act, GetTranscriptIn{Namespace: "demo-ns", Name: "demo-session"}) + return err + }, + wantOp: &permPair{mirror: permReadTranscript, resource: ""}, + }, + { + tool: "search_memory (explicit session)", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opSearchMemory(ctx, d, act, SearchMemoryIn{Query: "x", Namespace: "demo-ns", Name: "demo-session"}) + return err + }, + wantOp: &permPair{mirror: permRead, resource: permReadTranscript}, + }, + { + tool: "search_memory (derived)", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opSearchMemory(ctx, d, act, SearchMemoryIn{Query: "x"}) + return err + }, + wantMirror: permRead, + }, + { + tool: "list_artifacts", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opListArtifacts(ctx, d, act, ListArtifactsIn{Namespace: "demo-ns", Name: "demo-session"}) + return err + }, + wantOp: &permPair{mirror: permView, resource: permReadTranscript}, + }, + { + tool: "get_artifact", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opGetArtifact(ctx, d, act, GetArtifactIn{Namespace: "demo-ns", Name: "demo-session", ArtifactID: "artifact-1"}) + return err + }, + wantOp: &permPair{mirror: permView, resource: permReadTranscript}, + }, + { + tool: "list_sessions", + run: func(t *testing.T, d *fakeOpsDeps) error { + _, err := opListSessions(ctx, d, act, ListSessionsIn{}) + return err + }, + wantMirror: permReadTranscript, + }, + } + + for _, tc := range cases { + t.Run(tc.tool+": pins mirror + resource permission names", func(t *testing.T) { + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + d.operatorURL = srv.URL + d.memoryToken = "webd-token" + seedArtifact(t, d.artifactMem, "demo-ns", "demo-session", "artifact-1") + // A read-role token's mirror grants every read-surface permission + // but denies "interact" (a send-class permission never checked by + // this read-only tool set) — the scripted decision table the brief + // asks for. + d.authz.mirrorDecision = func(permission string) (bool, error) { return permission != "interact", nil } + + require.NoError(t, tc.run(t, d)) + + if tc.wantOp != nil { + require.Len(t, d.authz.opCalls, 1, "exactly one three-leg check per authorized op") + assert.Equal(t, tc.wantOp.mirror, d.authz.opCalls[0].Permission, "leg-1 mirror name") + assert.Equal(t, tc.wantOp.resource, d.authz.opCalls[0].ResourcePermission, "leg-3 resource permission") + } + if tc.wantMirror != "" { + require.Len(t, d.authz.mirrorCalls, 1, "exactly one mirror-only check per enumeration op") + assert.Equal(t, tc.wantMirror, d.authz.mirrorCalls[0]) + } + for _, c := range d.authz.opCalls { + assert.NotEqual(t, "interact", c.Permission, "interact is a send-class permission; no read-role tool may check it") + assert.NotEqual(t, "interact", c.ResourcePermission) + } + for _, p := range d.authz.mirrorCalls { + assert.NotEqual(t, "interact", p) + } + }) + } +} + +func TestGetArtifactContentTruncatesAndRejectsNonUTF8(t *testing.T) { + ctx := context.Background() + act := opsTestActing() + sess := sessionFixture("demo-ns", "demo-session", "demo-agent") + + newSeededDeps := func(t *testing.T) *fakeOpsDeps { + t.Helper() + d := newOpsFakeDeps(t, newOpsTestClient(t, sess)) + seedArtifact(t, d.artifactMem, "demo-ns", "demo-session", "artifact-1") + return d + } + + t.Run("content over the cap is truncated at a rune boundary", func(t *testing.T) { + d := newSeededDeps(t) + d.fetchBytes = make([]byte, maxArtifactContentBytes+10) + for i := range d.fetchBytes { + d.fetchBytes[i] = 'a' + } + + out, err := opGetArtifact(ctx, d, act, GetArtifactIn{Namespace: "demo-ns", Name: "demo-session", ArtifactID: "artifact-1"}) + require.NoError(t, err) + assert.True(t, out.ContentTruncated) + assert.LessOrEqual(t, len(out.Content), maxArtifactContentBytes) + assert.Equal(t, "artifact-1", out.Artifact.ArtifactID) + }) + + t.Run("non-UTF8 content returns no inline content", func(t *testing.T) { + d := newSeededDeps(t) + d.fetchBytes = []byte{0xff, 0xfe, 0xfd} + + out, err := opGetArtifact(ctx, d, act, GetArtifactIn{Namespace: "demo-ns", Name: "demo-session", ArtifactID: "artifact-1"}) + require.NoError(t, err) + assert.Empty(t, out.Content) + assert.False(t, out.ContentTruncated) + }) + + t.Run("small content round-trips untruncated", func(t *testing.T) { + d := newSeededDeps(t) + d.fetchBytes = []byte("hello artifact") + + out, err := opGetArtifact(ctx, d, act, GetArtifactIn{Namespace: "demo-ns", Name: "demo-session", ArtifactID: "artifact-1"}) + require.NoError(t, err) + assert.Equal(t, "hello artifact", out.Content) + assert.False(t, out.ContentTruncated) + assert.Equal(t, "html", out.Artifact.RendererKind) + }) + + t.Run("unknown artifact id errors", func(t *testing.T) { + d := newSeededDeps(t) + _, err := opGetArtifact(ctx, d, act, GetArtifactIn{Namespace: "demo-ns", Name: "demo-session", ArtifactID: "nope"}) + require.Error(t, err) + }) +} + +// seedArtifact writes a single artifact head directly into mem, bypassing the +// full FinalizeRevision flow (CRs, renders, revisions) this test has no need +// for — only ListArtifacts/get_artifact's metadata read is exercised here; +// content always comes from the (faked) FetchArtifact, never from this entry. +func seedArtifact(t *testing.T, mem memory.Memory, ns, name, artifactID string) { + t.Helper() + content, err := json.Marshal(memartifact.Artifact{Name: "Report", Description: "a test artifact", RendererKind: "html"}) + require.NoError(t, err) + ctx := memory.WithSystemApproval(context.Background(), "test") + _, err = mem.Put(ctx, memory.Entry{ + Scope: memory.Scope{Kind: "session", ID: ns + "/" + name}, + Kind: memartifact.KindName, + ID: artifactID, + CreatedAt: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), + Content: content, + }) + require.NoError(t, err) +} + +func TestSessionIsEnded(t *testing.T) { + assert.False(t, sessionIsEnded(spiceboxv1alpha1.AgentSessionPhaseRunning)) + assert.False(t, sessionIsEnded("")) + assert.True(t, sessionIsEnded(spiceboxv1alpha1.AgentSessionPhaseSucceeded)) + assert.True(t, sessionIsEnded(spiceboxv1alpha1.AgentSessionPhaseFailed)) +} diff --git a/pkg/web/mcpfront/plugin.go b/pkg/web/mcpfront/plugin.go new file mode 100644 index 00000000..860e0966 --- /dev/null +++ b/pkg/web/mcpfront/plugin.go @@ -0,0 +1,81 @@ +package mcpfront + +import ( + "encoding/json" + "net/http" + "strings" + "time" + + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/authzed/openagentprimitives/pkg/web/webui" + webuiregistry "github.com/authzed/openagentprimitives/pkg/web/webui/registry" +) + +// Version is the MCP Implementation.Version this server reports to a +// connecting client. Not currently wired to -ldflags, matching every other +// in-repo MCP server's own buildVersion var (internal/cmd/workshop, +// internal/cmd/websearchd, internal/cmd/apiadapter) — a fixed string is +// enough until this surface has its own release story. +var Version = "dev" + +// ui is the /mcp webui plugin. Stateless: the mcp.Server and bearer +// middleware are built fresh inside Routes(). +type ui struct{} + +func init() { webuiregistry.Register(ui{}) } + +func (ui) Name() string { return "mcpfront" } + +// Routes mounts the /mcp plugin. It is INERT — returns no routes at all — +// when deps doesn't implement Deps, or AccessTokenAuthz() is nil: an +// unconfigured SpiceDB means no MCP surface exists, fail closed by absence +// rather than by every request refusing one at a time. +func (ui) Routes(deps webui.Deps) []webui.Route { + d, ok := deps.(Deps) + if !ok || d.AccessTokenAuthz() == nil { + return nil + } + + srv := mcp.NewServer(&mcp.Implementation{Name: "oap", Version: Version}, nil) + registerTools(srv, d) + handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return srv }, nil) + authed := newBearerMiddleware(d, time.Now)(handler) + + return []webui.Route{ + { + Origin: webui.OriginTrusted, + Pattern: "/mcp", + Methods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, + // AuthHandlerManaged: the bearer middleware above authenticates by + // its own means (the AccessToken's hash, not the session cookie), + // and the MCP protocol itself needs GET/POST/DELETE, which + // AuthNone's GET/HEAD-only restriction would refuse. + Auth: webui.AuthHandlerManaged, + Handler: authed, + }, + { + Origin: webui.OriginTrusted, + Pattern: "/.well-known/oauth-protected-resource", + Methods: []string{http.MethodGet}, + Auth: webui.AuthNone, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + base := strings.TrimSuffix(d.ExternalBaseURL(), "/") + writeJSON(w, http.StatusOK, map[string]any{ + "resource": base + "/mcp", + "authorization_servers": []string{base}, + }) + }), + }, + } +} + +// writeJSON is the standard tiny json-response helper, duplicated per +// package to match this repo's existing convention (pkg/web/webui/chat, +// pkg/web/webui/agentui, pkg/web/webui/interact and pkg/web/admind each keep +// their own copy rather than share one three-line helper). +func writeJSON(w http.ResponseWriter, status int, v any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(v) +} diff --git a/pkg/web/mcpfront/tools.go b/pkg/web/mcpfront/tools.go new file mode 100644 index 00000000..9e3d98dd --- /dev/null +++ b/pkg/web/mcpfront/tools.go @@ -0,0 +1,290 @@ +package mcpfront + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// toolErr/jsonResult/decodeArgs are local copies of the same three helpers +// pkg/tools/workshopmcp/server.go:225-293 defines for its own MCP server — +// the shared shape every leaf MCP server in this repo uses for a +// single-text-block JSON result, duplicated per package rather than factored +// out (see pkg/web/webui/chat, pkg/web/webui/agentui, pkg/web/admind for the +// same convention with writeJSON). + +// toolErr builds a structured IsError CallToolResult carrying +// {"error": ""}. +func toolErr(format string, a ...any) *mcp.CallToolResult { + b, err := json.Marshal(map[string]any{"error": fmt.Sprintf(format, a...)}) + if err != nil { + b = fmt.Appendf(nil, "%q", format) + } + return &mcp.CallToolResult{IsError: true, Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}} +} + +// jsonResult marshals body into a single-text-block SUCCESSFUL CallToolResult. +// body is always a plain struct/slice/map of JSON-safe values at every call +// site in this file, so json.Marshal cannot fail in practice — but per +// AGENTS.md's never-silently-drop-an-error rule, a hypothetical failure still +// surfaces as a structured tool error rather than an empty or malformed result. +func jsonResult(body any) (*mcp.CallToolResult, error) { + b, err := json.Marshal(body) + if err != nil { + return toolErr("marshaling result: %v", err), nil + } + return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, nil +} + +// decodeArgs decodes req's raw MCP tool-call arguments into out. A nil/empty +// Arguments (a tool called with no args at all) leaves out at its zero value +// rather than erroring, mirroring encoding/json's own treatment of a missing +// field. +func decodeArgs(req *mcp.CallToolRequest, out any) error { + if req == nil || req.Params == nil || len(req.Params.Arguments) == 0 { + return nil + } + return json.Unmarshal(req.Params.Arguments, out) +} + +// readOnly is the Annotations every /mcp tool in this file carries: this +// surface is read-role only (Task 10 — a write/interact role is out of +// Phase-1 scope), and ReadOnlyHint tells a connecting client that up front. +var readOnly = &mcp.ToolAnnotations{ReadOnlyHint: true} + +// unauthenticatedErr is returned by a tool handler when actingFrom finds no +// acting identity on the request context. Unreachable in production — every +// /mcp request passes through newBearerMiddleware first, which either attaches +// one or never reaches a tool handler at all — but a handler must still fail +// closed rather than panic if ever called directly (e.g. from a future +// in-process test harness that forgets the middleware). +func unauthenticatedErr() *mcp.CallToolResult { + return toolErr("unauthenticated: no acting identity on request context") +} + +// registerTools registers the /mcp server's read-role tool set against d. +// Every handler: resolve the acting identity the bearer middleware attached +// to the request context, decode args, run the op, and translate its result +// (or its denial) into a CallToolResult. The ops themselves (pkg/web/mcpfront +// ops.go) own every authorization decision; nothing here second-guesses them. +func registerTools(srv *mcp.Server, d Deps) { + srv.AddTool(&mcp.Tool{ + Name: "list_sessions", + Description: "List agent sessions this token's owner may read, optionally filtered by " + + "agent (agentclass name) and/or state (\"running\" or \"ended\"). Each row reports its " + + "channel origin (kind, key, and any kind-specific routing metadata) — combine the agent " + + "filter with the channel fields to answer questions like \"find reviewbot's session for " + + "PR #123\" by filtering to agent=reviewbot and matching the channel key/metadata yourself, " + + "rather than needing a dedicated per-channel tool. Only sessions whose agentclass this " + + "token's scope covers are returned; everything else is silently absent, not denied.", + InputSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "agent": map[string]any{"type": "string", "description": "agentclass name filter"}, + "state": map[string]any{"type": "string", "enum": []any{"running", "ended"}, "description": "filter by lifecycle bucket; omit for all"}, + "limit": map[string]any{"type": "integer", "minimum": 1, "maximum": maxListSessionsLimit, "default": defaultListSessionsLimit, "description": "max rows to return"}, + }, + }, + Annotations: readOnly, + }, handleListSessions(d)) + + srv.AddTool(&mcp.Tool{ + Name: "get_session", + Description: "Get one session's summary (class, phase, start time, channel origin) by namespace and name. Use list_sessions first to find the (namespace, name) pair.", + InputSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "namespace": map[string]any{"type": "string"}, + "name": map[string]any{"type": "string"}, + }, + "required": []any{"namespace", "name"}, + }, + Annotations: readOnly, + }, handleGetSession(d)) + + // Phase-1 narrowing, recorded: the spec's tail=true live-streaming mode + // (follow a running session's transcript via MCP progress notifications) + // is NOT implemented here — this tool is pagination-only (offset/limit), + // same as every other /mcp read tool in this file. A caller that wants + // "what's new" polls get_transcript again with a larger offset. + srv.AddTool(&mcp.Tool{ + Name: "get_transcript", + Description: "Read a session's conversation transcript (messages and plan cards, in order), " + + "paginated with offset/limit. total is the full entry count regardless of the page " + + "requested, so a caller can tell whether more remains.", + InputSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "namespace": map[string]any{"type": "string"}, + "name": map[string]any{"type": "string"}, + "offset": map[string]any{"type": "integer", "minimum": 0, "default": 0}, + "limit": map[string]any{"type": "integer", "minimum": 1, "default": defaultTranscriptLimit, "description": "max entries to return"}, + }, + "required": []any{"namespace", "name"}, + }, + Annotations: readOnly, + }, handleGetTranscript(d)) + + srv.AddTool(&mcp.Tool{ + Name: "search_memory", + Description: "Search a session's (or, with no namespace/name given, every session this " + + "token's owner may read) stored memory by free text. Combine with list_sessions/" + + "get_session when you need to narrow to one agent or channel first. Each hit names the " + + "session it came from, so a cross-session search stays attributable. When no search " + + "provider is configured, searchUnavailable is true and hits is empty — that is not the " + + "same as \"nothing matched\".", + InputSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "query": map[string]any{"type": "string", "description": "free-text query"}, + "namespace": map[string]any{"type": "string", "description": "with name, restrict to one session"}, + "name": map[string]any{"type": "string"}, + "limit": map[string]any{"type": "integer", "minimum": 1, "default": 20}, + }, + "required": []any{"query"}, + }, + Annotations: readOnly, + }, handleSearchMemory(d)) + + srv.AddTool(&mcp.Tool{ + Name: "list_artifacts", + Description: "List the artifacts (generated files/renders) a session has produced, by namespace and name. Use get_artifact to fetch one's content.", + InputSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "namespace": map[string]any{"type": "string"}, + "name": map[string]any{"type": "string"}, + }, + "required": []any{"namespace", "name"}, + }, + Annotations: readOnly, + }, handleListArtifacts(d)) + + srv.AddTool(&mcp.Tool{ + Name: "get_artifact", + Description: "Fetch one artifact's content by namespace, name, and artifactId (from " + + "list_artifacts). content is populated only when the artifact's bytes are valid UTF-8 " + + "text; a binary artifact (e.g. an image) reports empty content. Content over 256KiB is " + + "truncated, with contentTruncated set to true.", + InputSchema: map[string]any{ + "type": "object", + "properties": map[string]any{ + "namespace": map[string]any{"type": "string"}, + "name": map[string]any{"type": "string"}, + "artifactId": map[string]any{"type": "string"}, + }, + "required": []any{"namespace", "name", "artifactId"}, + }, + Annotations: readOnly, + }, handleGetArtifact(d)) +} + +func handleListSessions(d Deps) mcp.ToolHandler { + return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + act, ok := actingFrom(ctx) + if !ok { + return unauthenticatedErr(), nil + } + var in ListSessionsIn + if err := decodeArgs(req, &in); err != nil { + return toolErr("list_sessions: decode arguments: %v", err), nil + } + out, err := opListSessions(ctx, d, act, in) + if err != nil { + return toolErr("list_sessions: %v", err), nil + } + return jsonResult(out) + } +} + +func handleGetSession(d Deps) mcp.ToolHandler { + return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + act, ok := actingFrom(ctx) + if !ok { + return unauthenticatedErr(), nil + } + var in GetSessionIn + if err := decodeArgs(req, &in); err != nil { + return toolErr("get_session: decode arguments: %v", err), nil + } + out, err := opGetSession(ctx, d, act, in) + if err != nil { + return toolErr("get_session: %v", err), nil + } + return jsonResult(out) + } +} + +func handleGetTranscript(d Deps) mcp.ToolHandler { + return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + act, ok := actingFrom(ctx) + if !ok { + return unauthenticatedErr(), nil + } + var in GetTranscriptIn + if err := decodeArgs(req, &in); err != nil { + return toolErr("get_transcript: decode arguments: %v", err), nil + } + out, err := opGetTranscript(ctx, d, act, in) + if err != nil { + return toolErr("get_transcript: %v", err), nil + } + return jsonResult(out) + } +} + +func handleSearchMemory(d Deps) mcp.ToolHandler { + return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + act, ok := actingFrom(ctx) + if !ok { + return unauthenticatedErr(), nil + } + var in SearchMemoryIn + if err := decodeArgs(req, &in); err != nil { + return toolErr("search_memory: decode arguments: %v", err), nil + } + out, err := opSearchMemory(ctx, d, act, in) + if err != nil { + return toolErr("search_memory: %v", err), nil + } + return jsonResult(out) + } +} + +func handleListArtifacts(d Deps) mcp.ToolHandler { + return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + act, ok := actingFrom(ctx) + if !ok { + return unauthenticatedErr(), nil + } + var in ListArtifactsIn + if err := decodeArgs(req, &in); err != nil { + return toolErr("list_artifacts: decode arguments: %v", err), nil + } + out, err := opListArtifacts(ctx, d, act, in) + if err != nil { + return toolErr("list_artifacts: %v", err), nil + } + return jsonResult(out) + } +} + +func handleGetArtifact(d Deps) mcp.ToolHandler { + return func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + act, ok := actingFrom(ctx) + if !ok { + return unauthenticatedErr(), nil + } + var in GetArtifactIn + if err := decodeArgs(req, &in); err != nil { + return toolErr("get_artifact: decode arguments: %v", err), nil + } + out, err := opGetArtifact(ctx, d, act, in) + if err != nil { + return toolErr("get_artifact: %v", err), nil + } + return jsonResult(out) + } +} diff --git a/pkg/web/webui/webassets/dist/IdentityShell.D-zSSgc9.js b/pkg/web/webui/webassets/dist/IdentityShell.BTN6Qj3t.js similarity index 73% rename from pkg/web/webui/webassets/dist/IdentityShell.D-zSSgc9.js rename to pkg/web/webui/webassets/dist/IdentityShell.BTN6Qj3t.js index 2db5e899..ead7c967 100644 --- a/pkg/web/webui/webassets/dist/IdentityShell.D-zSSgc9.js +++ b/pkg/web/webui/webassets/dist/IdentityShell.BTN6Qj3t.js @@ -1 +1 @@ -import{j as e}from"./vendor.C5MRNvUt.js";import{C as a,b as n,d,e as m}from"./mount.BOEH1oNx.js";function l({title:s,subtitle:r,children:t}){return e.jsx("div",{className:"min-h-screen flex items-start justify-center p-6 bg-background text-foreground",children:e.jsxs(a,{className:"max-w-xl w-full mt-[8vh]",children:[e.jsxs(n,{children:[e.jsx(d,{children:s}),r&&e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:r})]}),e.jsx(m,{children:t})]})})}export{l as I}; +import{j as e}from"./vendor.BHG48rjj.js";import{C as a,b as n,d,e as m}from"./mount.Bxdfy1H2.js";function l({title:s,subtitle:r,children:t}){return e.jsx("div",{className:"min-h-screen flex items-start justify-center p-6 bg-background text-foreground",children:e.jsxs(a,{className:"max-w-xl w-full mt-[8vh]",children:[e.jsxs(n,{children:[e.jsx(d,{children:s}),r&&e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:r})]}),e.jsx(m,{children:t})]})})}export{l as I}; diff --git a/pkg/web/webui/webassets/dist/NoticeBanner.lKrMgKCy.js b/pkg/web/webui/webassets/dist/NoticeBanner.D2SnK6rv.js similarity index 83% rename from pkg/web/webui/webassets/dist/NoticeBanner.lKrMgKCy.js rename to pkg/web/webui/webassets/dist/NoticeBanner.D2SnK6rv.js index 8149e316..69230ada 100644 --- a/pkg/web/webui/webassets/dist/NoticeBanner.lKrMgKCy.js +++ b/pkg/web/webui/webassets/dist/NoticeBanner.D2SnK6rv.js @@ -1 +1 @@ -import{r as o,j as c}from"./vendor.C5MRNvUt.js";import{A as u,a as d,c as a}from"./mount.BOEH1oNx.js";function k(t){const e=new URLSearchParams(t),i=e.get("error");if(i)return{kind:"error",text:i};const n=e.get("notice");if(n){const[s,r]=n.split(":");return s==="linked"&&r?{kind:"success",text:`Linked ${r}.`}:s==="revoked"&&r?{kind:"success",text:`Revoked ${r}.`}:s==="linkedunverified"&&r?{kind:"error",text:`Linked ${r} — but the token could not be verified. It may not work.`}:{kind:"success",text:n}}return null}function f({search:t}){const[e]=o.useState(()=>k(t));return e?c.jsx(u,{className:a("mb-4",e.kind==="error"?"border-destructive text-destructive":"border-success text-success"),children:c.jsx(d,{children:e.text})}):null}export{f as N}; +import{r as o,j as c}from"./vendor.BHG48rjj.js";import{A as u,a as d,c as a}from"./mount.Bxdfy1H2.js";function k(t){const e=new URLSearchParams(t),i=e.get("error");if(i)return{kind:"error",text:i};const n=e.get("notice");if(n){const[s,r]=n.split(":");return s==="linked"&&r?{kind:"success",text:`Linked ${r}.`}:s==="revoked"&&r?{kind:"success",text:`Revoked ${r}.`}:s==="linkedunverified"&&r?{kind:"error",text:`Linked ${r} — but the token could not be verified. It may not work.`}:{kind:"success",text:n}}return null}function f({search:t}){const[e]=o.useState(()=>k(t));return e?c.jsx(u,{className:a("mb-4",e.kind==="error"?"border-destructive text-destructive":"border-success text-success"),children:c.jsx(d,{children:e.text})}):null}export{f as N}; diff --git a/pkg/web/webui/webassets/dist/PatForm.BuKUBhkS.js b/pkg/web/webui/webassets/dist/PatForm.3E7vJM76.js similarity index 85% rename from pkg/web/webui/webassets/dist/PatForm.BuKUBhkS.js rename to pkg/web/webui/webassets/dist/PatForm.3E7vJM76.js index c5ede216..b3ad22ec 100644 --- a/pkg/web/webui/webassets/dist/PatForm.BuKUBhkS.js +++ b/pkg/web/webui/webassets/dist/PatForm.3E7vJM76.js @@ -1 +1 @@ -import{j as e}from"./vendor.C5MRNvUt.js";import{B as m}from"./mount.BOEH1oNx.js";function p({action:o,hidden:r,submitLabel:a,instructions:t,docsUrl:n}){return e.jsxs(e.Fragment,{children:[t&&e.jsxs("div",{className:"text-xs text-muted-foreground mb-3 whitespace-pre-line",children:[t,n&&e.jsxs(e.Fragment,{children:[" ",e.jsx("a",{href:n,target:"_blank",rel:"noreferrer",className:"underline",children:"How to get this token"})]})]}),e.jsxs("form",{method:"POST",action:o,autoComplete:"off",className:"flex gap-2 items-center",children:[r&&Object.entries(r).map(([s,i])=>e.jsx("input",{type:"hidden",name:s,value:i},s)),e.jsx("input",{type:"password",name:"token",placeholder:"Token",required:!0,className:"flex-1 rounded-md bg-background border border-input px-2.5 py-1.5 text-sm outline-none focus:ring-2 focus:ring-ring"}),e.jsx(m,{type:"submit",size:"sm",children:a})]})]})}export{p as P}; +import{j as e}from"./vendor.BHG48rjj.js";import{B as m}from"./mount.Bxdfy1H2.js";function p({action:o,hidden:r,submitLabel:a,instructions:t,docsUrl:n}){return e.jsxs(e.Fragment,{children:[t&&e.jsxs("div",{className:"text-xs text-muted-foreground mb-3 whitespace-pre-line",children:[t,n&&e.jsxs(e.Fragment,{children:[" ",e.jsx("a",{href:n,target:"_blank",rel:"noreferrer",className:"underline",children:"How to get this token"})]})]}),e.jsxs("form",{method:"POST",action:o,autoComplete:"off",className:"flex gap-2 items-center",children:[r&&Object.entries(r).map(([s,i])=>e.jsx("input",{type:"hidden",name:s,value:i},s)),e.jsx("input",{type:"password",name:"token",placeholder:"Token",required:!0,className:"flex-1 rounded-md bg-background border border-input px-2.5 py-1.5 text-sm outline-none focus:ring-2 focus:ring-ring"}),e.jsx(m,{type:"submit",size:"sm",children:a})]})]})}export{p as P}; diff --git a/pkg/web/webui/webassets/dist/StartupLine.D55RHpak.js b/pkg/web/webui/webassets/dist/StartupLine.DKqmGMaA.js similarity index 95% rename from pkg/web/webui/webassets/dist/StartupLine.D55RHpak.js rename to pkg/web/webui/webassets/dist/StartupLine.DKqmGMaA.js index 381780d9..892c9bec 100644 --- a/pkg/web/webui/webassets/dist/StartupLine.D55RHpak.js +++ b/pkg/web/webui/webassets/dist/StartupLine.DKqmGMaA.js @@ -1,3 +1,3 @@ -import{ad as nt,r as l,j as n,x as rt,y as J,z as st,ae as ue,K as at,af as fe,ag as xe,ah as Se,ai as ot,aj as it,ak as lt,al as ct,am as Te,an as ce,ao as _e,c as dt,ap as ut,C as ft,O as xt,T as pt,aa as mt,aq as ht,E as gt,ar as yt,as as bt,at as jt}from"./vendor.C5MRNvUt.js";import{I as vt,J as wt,K as Nt,c as b,B as X,H as kt}from"./mount.BOEH1oNx.js";import{M as Ct}from"./markdown.FOTo-3Mz.js";function ye(e){return e.toFixed(1).replace(/\.0$/,"")}function be(e){return(!Number.isFinite(e)||e<0)&&(e=0),e<1e3?String(Math.round(e)):e<1e6?`${ye(e/1e3)}K`:`${ye(e/1e6)}M`}function pe(e){if((!Number.isFinite(e)||e<0)&&(e=0),e=Math.floor(e),e<60)return`${e}s`;const t=Math.floor(e/60),r=e%60;return r===0?`${t}m`:`${t}m${r}s`}function It(e){return e?`working · ${pe(e.elapsedSeconds)} · ${be(e.inputTokens)} in · ${be(e.outputTokens)} out`:"working"}const je=500,Z=new Uint8Array(0);function Rt(e){return{ref:e,outerTool:"",reason:"",lines:[],elided:0,pending:{stdout:Z,stderr:Z},done:!1,ok:!1}}function St(e){try{const t=atob(e),r=new Uint8Array(t.length);for(let s=0;s{const r={text:t.content};t.fg&&(r.fg=`rgb(${t.fg})`),t.bg&&(r.bg=`rgb(${t.bg})`);const s=t.decoration??void 0,d=Array.isArray(t.decorations)?t.decorations:s?[s]:[];return d.includes("bold")&&(r.bold=!0),d.includes("italic")&&(r.italic=!0),d.includes("underline")&&(r.underline=!0),d.includes("dim")&&(r.dim=!0),r})}function F(e,t){if(e.lines.push(t),e.lines.length>je){const r=e.lines.length-je;e.lines.splice(0,r),e.elided+=r}}function _t(e,t,r){let s=Tt(e.pending[t],r),d=0;for(let u=0;ud&&s[c-1]===13&&c--,F(e,{kind:t,spans:M($e.decode(s.subarray(d,c)))}),d=u+1}e.pending[t]=d===0?s:s.subarray(d)}function qe(e){for(const t of["stdout","stderr"]){const r=e.pending[t];r.length!==0&&(F(e,{kind:t,spans:M($e.decode(r))}),e.pending[t]=Z)}}function ve(e,t){const r=e??Rt(t);return{...r,lines:r.lines.slice(),pending:{...r.pending}}}function we(e){const t=e.payload;return t==null?void 0:t.payload}function $t(e,t){switch(t.outerTool&&(e.outerTool=t.outerTool),t.reason&&(e.reason=t.reason),t.eventType){case"result":qe(e),e.done=!0,e.ok=t.ok??!1,t.durationMs!==void 0&&(e.durationMs=t.durationMs),t.costUsd!==void 0&&(e.costUsd=t.costUsd),t.summary&&F(e,{kind:"event",spans:M(t.summary)});return;case"tool_use_start":F(e,{kind:"event",spans:M(t.summary??""),toolName:t.toolName});return;case"tool_use_stop":F(e,{kind:"event",spans:M(" "+[t.ok?"✓":"✗",t.summary].filter(Boolean).join(" "))});return;default:t.text&&F(e,{kind:"event",spans:M(t.text)})}}function qt(e,t){const r=t.stream==="stderr"?"stderr":"stdout";t.data&&_t(e,r,St(t.data)),t.terminal&&(qe(e),e.done=!0,e.ok=(t.exitCode??0)===0&&t.exitReason!=="failed",t.exitCode!==void 0&&(e.exitCode=t.exitCode),t.exitReason&&(e.exitReason=t.exitReason))}function Et(e,t){if(t.type==="tool_session_event"){const r=we(t);if(!(r!=null&&r.toolCallRef))return e;const s=ve(e[r.toolCallRef],r.toolCallRef);return $t(s,r),{...e,[r.toolCallRef]:s}}if(t.type==="tool_session_delta"){const r=we(t);if(!(r!=null&&r.toolCallRef))return e;const s=ve(e[r.toolCallRef],r.toolCallRef);return qt(s,r),{...e,[r.toolCallRef]:s}}return e}function At(e){try{const t=JSON.parse(e);return!t||typeof t.type!="string"?null:t}catch{return null}}function Ot(e,t,r){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/sessions/api/${encodeURIComponent(t)}/${encodeURIComponent(r)}/ws`}const oe=3;function Lt(e,t,r){const[s,d]=l.useState("connecting"),u=l.useRef(r);return u.current=r,l.useEffect(()=>{if(!e||!t){console.error("chat: no live socket opened; this view was given no session to address",{ns:e,name:t}),d("offline");return}let c=null,a,m=!1,p=0;const N=()=>{m||(p+=1,d(p>=oe?"offline":"reconnecting"),a=window.setTimeout(j,2e3))},j=()=>{if(!m){d(p===0?"connecting":p>=oe?"offline":"reconnecting");try{c=new WebSocket(Ot(window.location,e,t))}catch{N();return}c.onopen=()=>{p=0,d("connected")},c.onmessage=S=>{const _=At(S.data);_&&u.current(_)},c.onerror=()=>{m||d(p>=oe?"offline":"reconnecting")},c.onclose=N}};return j(),()=>{m=!0,a&&window.clearTimeout(a),c&&c.close()}},[e,t]),{state:s}}const Ee={turnActive:!1,awaitingReply:!1,pausedIdle:!1,lastAgentReply:null,replySeq:0,composingUpdate:!1,activity:null,plan:null,notice:null,ended:!1,startup:null,pendingInteractions:[]};function Ut(e,t){return e===t?!0:e===null||t===null?!1:e.text===t.text&&e.seq===t.seq}function ie(e,t){return e===t?!0:e===null||t===null?!1:JSON.stringify(e)===JSON.stringify(t)}function Dt(e,t){return e===t?!0:e.length!==t.length?!1:e.every((r,s)=>r===t[s])}function Pt(e,t){return e.turnActive===t.turnActive&&e.awaitingReply===t.awaitingReply&&e.pausedIdle===t.pausedIdle&&Ut(e.lastAgentReply,t.lastAgentReply)&&e.replySeq===t.replySeq&&e.composingUpdate===t.composingUpdate&&ie(e.activity,t.activity)&&ie(e.plan,t.plan)&&e.notice===t.notice&&e.ended===t.ended&&ie(e.startup,t.startup)&&Dt(e.pendingInteractions,t.pendingInteractions)}const Mt={type:"__reconnect"};function Ft(e,t){const r=Bt(e,t);return r===e||Pt(e,r)?e:r}function Bt(e,t){var r,s,d,u,c;switch(t.type){case"turn_activity":{const a=t.payload;return a!=null&&a.active?{...e,turnActive:!0,awaitingReply:!1,pausedIdle:!1,composingUpdate:!1,lastAgentReply:null,notice:null}:{...e,turnActive:!1,awaitingReply:(a==null?void 0:a.cause)==="awaiting_reply",pausedIdle:(a==null?void 0:a.cause)==="idle",composingUpdate:!1}}case"user_message":{const a=t.payload,m=e.replySeq+1;return{...e,lastAgentReply:{text:(a==null?void 0:a.text)??"",seq:m},replySeq:m}}case"user_echo":return{...e,lastAgentReply:null,awaitingReply:!1};case"stream_delta":{const a=(r=t.payload)==null?void 0:r.payload;return(a==null?void 0:a.eventType)==="tool_use_start"&&a.toolName==="update_view"?{...e,composingUpdate:!0}:(a==null?void 0:a.eventType)==="tool_use_stop"||(a==null?void 0:a.eventType)==="stop"?{...e,composingUpdate:!1}:e}case"operation_activity":{const a=(s=t.payload)==null?void 0:s.payload;return{...e,activity:a!=null&&a.cleared?null:a??null}}case"plan_update":{const a=(d=t.payload)==null?void 0:d.payload;return{...e,plan:a??null}}case"notification":{const a=t.payload;return{...e,notice:(a==null?void 0:a.text)??null}}case"interaction_request":{const a=(u=t.payload)==null?void 0:u.payload;if(!(a!=null&&a.requestRef))return e;const m=e.pendingInteractions.findIndex(N=>N.requestRef===a.requestRef),p=m>=0?e.pendingInteractions.map((N,j)=>j===m?a:N):[...e.pendingInteractions,a];return{...e,pendingInteractions:p}}case"interaction_applied":{const a=(c=t.payload)==null?void 0:c.payload;return a!=null&&a.requestRef?{...e,pendingInteractions:e.pendingInteractions.filter(m=>m.requestRef!==a.requestRef)}:e}case"__reconnect":return{...e,turnActive:!1,pausedIdle:!1,composingUpdate:!1,activity:null,startup:null,pendingInteractions:[]};case"session_startup":{const a=t.payload;return!a||a.started||!a.text?{...e,startup:null}:{...e,startup:{text:a.text??"",short:a.short??null,stillTrying:!!a.stillTrying}}}case"session_ended":return{...e,ended:!0,turnActive:!1,awaitingReply:!1,pausedIdle:!1,composingUpdate:!1,startup:null,pendingInteractions:[]};default:return e}}const Ae=l.createContext(Ee);function Oe(){return l.useContext(Ae)}const me=l.createContext({state:"offline",subscribe:()=>(console.error("chat: useSessionFrames outside a SessionSocketProvider; no frames will arrive"),()=>{})});function Ln({ns:e,name:t,children:r}){const s=l.useRef(null);s.current===null&&(s.current=new Set);const[d,u]=l.useReducer(Ft,Ee),c=l.useCallback(j=>{u(j);for(const S of s.current)S(j)},[]),{state:a}=Lt(e,t,c),m=l.useCallback(j=>(s.current.add(j),()=>{s.current.delete(j)}),[]),p=l.useRef(a);l.useEffect(()=>{const j=p.current;p.current=a,a==="connected"&&(j==="reconnecting"||j==="offline")&&u(Mt)},[a]);const N=l.useMemo(()=>({state:a,subscribe:m}),[a,m]);return n.jsx(me.Provider,{value:N,children:n.jsx(Ae.Provider,{value:d,children:r})})}function Ht(e){const{subscribe:t}=l.useContext(me),r=l.useRef(e);r.current=e,l.useEffect(()=>t(s=>r.current(s)),[t])}function Jt(){return l.useContext(me).state}const Vt={connected:{Icon:st,color:"text-success",spin:!1,tip:"Live — connected"},connecting:{Icon:J,color:"text-warning",spin:!0,tip:"Connecting…"},reconnecting:{Icon:J,color:"text-warning",spin:!0,tip:"Reconnecting…"},offline:{Icon:rt,color:"text-destructive",spin:!1,tip:"Offline — retrying"}};function Yt({state:e}){const t=Vt[e],{Icon:r}=t;return n.jsxs(vt,{children:[n.jsx(wt,{asChild:!0,children:n.jsx("div",{className:`flex h-7 w-7 items-center justify-center rounded-md cursor-default ${t.color}`,children:n.jsx(r,{className:`h-[18px] w-[18px] ${t.spin?"animate-spin":""}`,"aria-hidden":"true"})})}),n.jsx(Nt,{children:t.tip})]})}function Kt({call:e}){return n.jsxs("div",{className:b("flex items-center gap-1.5 pl-4 text-[11px]",e.active?"text-muted-foreground animate-pulse":"text-muted-foreground/60"),children:[n.jsx("span",{className:"truncate",children:e.reason}),n.jsxs("span",{className:"shrink-0",children:["· ",pe(e.elapsedSeconds)]})]})}function zt({op:e}){return n.jsxs("div",{className:"flex flex-col gap-0.5",children:[n.jsxs("div",{className:b("flex items-center gap-1.5 text-[11px]",e.active?"text-muted-foreground animate-pulse":"text-muted-foreground/60"),children:[n.jsx("span",{className:"truncate",children:e.description}),n.jsxs("span",{className:"shrink-0",children:["· ",pe(e.elapsedSeconds)]})]}),(e.calls??[]).map((t,r)=>n.jsx(Kt,{call:t},r))]})}function Wt({nodes:e}){return e.length===0?null:n.jsx("div",{className:"flex flex-col gap-1 items-start","data-testid":"operation-tree",children:e.map(t=>n.jsx(zt,{op:t},t.id))})}function Gt({fields:e}){return!e||e.length===0?null:n.jsx("div",{className:"flex flex-col gap-0.5",children:e.map((t,r)=>n.jsxs("div",{className:"text-xs leading-snug",children:[n.jsxs("span",{className:"font-medium text-card-foreground",children:[t.label,":"]})," ",n.jsx("span",{className:"text-muted-foreground",children:t.value})]},`${t.label}:${r}`))})}function Le({excerpt:e}){return e?n.jsx("pre",{className:"max-h-48 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-background px-2 py-1.5 text-[11px] leading-snug text-card-foreground",children:n.jsxs("code",{children:[e.label?`${e.label}: +import{ae as nt,r as l,j as n,y as rt,z as J,E as st,af as ue,K as at,ag as fe,ah as xe,ai as Se,aj as ot,ak as it,al as lt,am as ct,an as Te,ao as ce,ap as _e,c as dt,aq as ut,C as ft,P as xt,T as pt,ab as mt,ar as ht,I as gt,as as yt,at as bt,au as jt}from"./vendor.BHG48rjj.js";import{I as vt,J as wt,K as Nt,c as b,B as X,H as kt}from"./mount.Bxdfy1H2.js";import{M as Ct}from"./markdown.CZgLwgiZ.js";function ye(e){return e.toFixed(1).replace(/\.0$/,"")}function be(e){return(!Number.isFinite(e)||e<0)&&(e=0),e<1e3?String(Math.round(e)):e<1e6?`${ye(e/1e3)}K`:`${ye(e/1e6)}M`}function pe(e){if((!Number.isFinite(e)||e<0)&&(e=0),e=Math.floor(e),e<60)return`${e}s`;const t=Math.floor(e/60),r=e%60;return r===0?`${t}m`:`${t}m${r}s`}function It(e){return e?`working · ${pe(e.elapsedSeconds)} · ${be(e.inputTokens)} in · ${be(e.outputTokens)} out`:"working"}const je=500,Z=new Uint8Array(0);function Rt(e){return{ref:e,outerTool:"",reason:"",lines:[],elided:0,pending:{stdout:Z,stderr:Z},done:!1,ok:!1}}function St(e){try{const t=atob(e),r=new Uint8Array(t.length);for(let s=0;s{const r={text:t.content};t.fg&&(r.fg=`rgb(${t.fg})`),t.bg&&(r.bg=`rgb(${t.bg})`);const s=t.decoration??void 0,d=Array.isArray(t.decorations)?t.decorations:s?[s]:[];return d.includes("bold")&&(r.bold=!0),d.includes("italic")&&(r.italic=!0),d.includes("underline")&&(r.underline=!0),d.includes("dim")&&(r.dim=!0),r})}function F(e,t){if(e.lines.push(t),e.lines.length>je){const r=e.lines.length-je;e.lines.splice(0,r),e.elided+=r}}function _t(e,t,r){let s=Tt(e.pending[t],r),d=0;for(let u=0;ud&&s[c-1]===13&&c--,F(e,{kind:t,spans:M($e.decode(s.subarray(d,c)))}),d=u+1}e.pending[t]=d===0?s:s.subarray(d)}function qe(e){for(const t of["stdout","stderr"]){const r=e.pending[t];r.length!==0&&(F(e,{kind:t,spans:M($e.decode(r))}),e.pending[t]=Z)}}function ve(e,t){const r=e??Rt(t);return{...r,lines:r.lines.slice(),pending:{...r.pending}}}function we(e){const t=e.payload;return t==null?void 0:t.payload}function $t(e,t){switch(t.outerTool&&(e.outerTool=t.outerTool),t.reason&&(e.reason=t.reason),t.eventType){case"result":qe(e),e.done=!0,e.ok=t.ok??!1,t.durationMs!==void 0&&(e.durationMs=t.durationMs),t.costUsd!==void 0&&(e.costUsd=t.costUsd),t.summary&&F(e,{kind:"event",spans:M(t.summary)});return;case"tool_use_start":F(e,{kind:"event",spans:M(t.summary??""),toolName:t.toolName});return;case"tool_use_stop":F(e,{kind:"event",spans:M(" "+[t.ok?"✓":"✗",t.summary].filter(Boolean).join(" "))});return;default:t.text&&F(e,{kind:"event",spans:M(t.text)})}}function qt(e,t){const r=t.stream==="stderr"?"stderr":"stdout";t.data&&_t(e,r,St(t.data)),t.terminal&&(qe(e),e.done=!0,e.ok=(t.exitCode??0)===0&&t.exitReason!=="failed",t.exitCode!==void 0&&(e.exitCode=t.exitCode),t.exitReason&&(e.exitReason=t.exitReason))}function Et(e,t){if(t.type==="tool_session_event"){const r=we(t);if(!(r!=null&&r.toolCallRef))return e;const s=ve(e[r.toolCallRef],r.toolCallRef);return $t(s,r),{...e,[r.toolCallRef]:s}}if(t.type==="tool_session_delta"){const r=we(t);if(!(r!=null&&r.toolCallRef))return e;const s=ve(e[r.toolCallRef],r.toolCallRef);return qt(s,r),{...e,[r.toolCallRef]:s}}return e}function At(e){try{const t=JSON.parse(e);return!t||typeof t.type!="string"?null:t}catch{return null}}function Lt(e,t,r){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/sessions/api/${encodeURIComponent(t)}/${encodeURIComponent(r)}/ws`}const oe=3;function Ot(e,t,r){const[s,d]=l.useState("connecting"),u=l.useRef(r);return u.current=r,l.useEffect(()=>{if(!e||!t){console.error("chat: no live socket opened; this view was given no session to address",{ns:e,name:t}),d("offline");return}let c=null,a,m=!1,p=0;const N=()=>{m||(p+=1,d(p>=oe?"offline":"reconnecting"),a=window.setTimeout(j,2e3))},j=()=>{if(!m){d(p===0?"connecting":p>=oe?"offline":"reconnecting");try{c=new WebSocket(Lt(window.location,e,t))}catch{N();return}c.onopen=()=>{p=0,d("connected")},c.onmessage=S=>{const _=At(S.data);_&&u.current(_)},c.onerror=()=>{m||d(p>=oe?"offline":"reconnecting")},c.onclose=N}};return j(),()=>{m=!0,a&&window.clearTimeout(a),c&&c.close()}},[e,t]),{state:s}}const Ee={turnActive:!1,awaitingReply:!1,pausedIdle:!1,lastAgentReply:null,replySeq:0,composingUpdate:!1,activity:null,plan:null,notice:null,ended:!1,startup:null,pendingInteractions:[]};function Ut(e,t){return e===t?!0:e===null||t===null?!1:e.text===t.text&&e.seq===t.seq}function ie(e,t){return e===t?!0:e===null||t===null?!1:JSON.stringify(e)===JSON.stringify(t)}function Dt(e,t){return e===t?!0:e.length!==t.length?!1:e.every((r,s)=>r===t[s])}function Pt(e,t){return e.turnActive===t.turnActive&&e.awaitingReply===t.awaitingReply&&e.pausedIdle===t.pausedIdle&&Ut(e.lastAgentReply,t.lastAgentReply)&&e.replySeq===t.replySeq&&e.composingUpdate===t.composingUpdate&&ie(e.activity,t.activity)&&ie(e.plan,t.plan)&&e.notice===t.notice&&e.ended===t.ended&&ie(e.startup,t.startup)&&Dt(e.pendingInteractions,t.pendingInteractions)}const Mt={type:"__reconnect"};function Ft(e,t){const r=Bt(e,t);return r===e||Pt(e,r)?e:r}function Bt(e,t){var r,s,d,u,c;switch(t.type){case"turn_activity":{const a=t.payload;return a!=null&&a.active?{...e,turnActive:!0,awaitingReply:!1,pausedIdle:!1,composingUpdate:!1,lastAgentReply:null,notice:null}:{...e,turnActive:!1,awaitingReply:(a==null?void 0:a.cause)==="awaiting_reply",pausedIdle:(a==null?void 0:a.cause)==="idle",composingUpdate:!1}}case"user_message":{const a=t.payload,m=e.replySeq+1;return{...e,lastAgentReply:{text:(a==null?void 0:a.text)??"",seq:m},replySeq:m}}case"user_echo":return{...e,lastAgentReply:null,awaitingReply:!1};case"stream_delta":{const a=(r=t.payload)==null?void 0:r.payload;return(a==null?void 0:a.eventType)==="tool_use_start"&&a.toolName==="update_view"?{...e,composingUpdate:!0}:(a==null?void 0:a.eventType)==="tool_use_stop"||(a==null?void 0:a.eventType)==="stop"?{...e,composingUpdate:!1}:e}case"operation_activity":{const a=(s=t.payload)==null?void 0:s.payload;return{...e,activity:a!=null&&a.cleared?null:a??null}}case"plan_update":{const a=(d=t.payload)==null?void 0:d.payload;return{...e,plan:a??null}}case"notification":{const a=t.payload;return{...e,notice:(a==null?void 0:a.text)??null}}case"interaction_request":{const a=(u=t.payload)==null?void 0:u.payload;if(!(a!=null&&a.requestRef))return e;const m=e.pendingInteractions.findIndex(N=>N.requestRef===a.requestRef),p=m>=0?e.pendingInteractions.map((N,j)=>j===m?a:N):[...e.pendingInteractions,a];return{...e,pendingInteractions:p}}case"interaction_applied":{const a=(c=t.payload)==null?void 0:c.payload;return a!=null&&a.requestRef?{...e,pendingInteractions:e.pendingInteractions.filter(m=>m.requestRef!==a.requestRef)}:e}case"__reconnect":return{...e,turnActive:!1,pausedIdle:!1,composingUpdate:!1,activity:null,startup:null,pendingInteractions:[]};case"session_startup":{const a=t.payload;return!a||a.started||!a.text?{...e,startup:null}:{...e,startup:{text:a.text??"",short:a.short??null,stillTrying:!!a.stillTrying}}}case"session_ended":return{...e,ended:!0,turnActive:!1,awaitingReply:!1,pausedIdle:!1,composingUpdate:!1,startup:null,pendingInteractions:[]};default:return e}}const Ae=l.createContext(Ee);function Le(){return l.useContext(Ae)}const me=l.createContext({state:"offline",subscribe:()=>(console.error("chat: useSessionFrames outside a SessionSocketProvider; no frames will arrive"),()=>{})});function On({ns:e,name:t,children:r}){const s=l.useRef(null);s.current===null&&(s.current=new Set);const[d,u]=l.useReducer(Ft,Ee),c=l.useCallback(j=>{u(j);for(const S of s.current)S(j)},[]),{state:a}=Ot(e,t,c),m=l.useCallback(j=>(s.current.add(j),()=>{s.current.delete(j)}),[]),p=l.useRef(a);l.useEffect(()=>{const j=p.current;p.current=a,a==="connected"&&(j==="reconnecting"||j==="offline")&&u(Mt)},[a]);const N=l.useMemo(()=>({state:a,subscribe:m}),[a,m]);return n.jsx(me.Provider,{value:N,children:n.jsx(Ae.Provider,{value:d,children:r})})}function Ht(e){const{subscribe:t}=l.useContext(me),r=l.useRef(e);r.current=e,l.useEffect(()=>t(s=>r.current(s)),[t])}function Jt(){return l.useContext(me).state}const Vt={connected:{Icon:st,color:"text-success",spin:!1,tip:"Live — connected"},connecting:{Icon:J,color:"text-warning",spin:!0,tip:"Connecting…"},reconnecting:{Icon:J,color:"text-warning",spin:!0,tip:"Reconnecting…"},offline:{Icon:rt,color:"text-destructive",spin:!1,tip:"Offline — retrying"}};function Yt({state:e}){const t=Vt[e],{Icon:r}=t;return n.jsxs(vt,{children:[n.jsx(wt,{asChild:!0,children:n.jsx("div",{className:`flex h-7 w-7 items-center justify-center rounded-md cursor-default ${t.color}`,children:n.jsx(r,{className:`h-[18px] w-[18px] ${t.spin?"animate-spin":""}`,"aria-hidden":"true"})})}),n.jsx(Nt,{children:t.tip})]})}function Kt({call:e}){return n.jsxs("div",{className:b("flex items-center gap-1.5 pl-4 text-[11px]",e.active?"text-muted-foreground animate-pulse":"text-muted-foreground/60"),children:[n.jsx("span",{className:"truncate",children:e.reason}),n.jsxs("span",{className:"shrink-0",children:["· ",pe(e.elapsedSeconds)]})]})}function zt({op:e}){return n.jsxs("div",{className:"flex flex-col gap-0.5",children:[n.jsxs("div",{className:b("flex items-center gap-1.5 text-[11px]",e.active?"text-muted-foreground animate-pulse":"text-muted-foreground/60"),children:[n.jsx("span",{className:"truncate",children:e.description}),n.jsxs("span",{className:"shrink-0",children:["· ",pe(e.elapsedSeconds)]})]}),(e.calls??[]).map((t,r)=>n.jsx(Kt,{call:t},r))]})}function Wt({nodes:e}){return e.length===0?null:n.jsx("div",{className:"flex flex-col gap-1 items-start","data-testid":"operation-tree",children:e.map(t=>n.jsx(zt,{op:t},t.id))})}function Gt({fields:e}){return!e||e.length===0?null:n.jsx("div",{className:"flex flex-col gap-0.5",children:e.map((t,r)=>n.jsxs("div",{className:"text-xs leading-snug",children:[n.jsxs("span",{className:"font-medium text-card-foreground",children:[t.label,":"]})," ",n.jsx("span",{className:"text-muted-foreground",children:t.value})]},`${t.label}:${r}`))})}function Oe({excerpt:e}){return e?n.jsx("pre",{className:"max-h-48 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-background px-2 py-1.5 text-[11px] leading-snug text-card-foreground",children:n.jsxs("code",{children:[e.label?`${e.label}: `:"",e.content]})}):null}function Qt(e){switch(e){case"denied":return n.jsx(Se,{className:"h-3.5 w-3.5 shrink-0 text-destructive","aria-hidden":"true"});case"expired":return n.jsx(xe,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground","aria-hidden":"true"});default:return n.jsx(fe,{className:"h-3.5 w-3.5 shrink-0 text-success","aria-hidden":"true"})}}const Xt={agent:"Running as the agent",userPassthrough:"Running as you",cancel:"Cancelled"},Zt={approved:"Approved",denied:"Denied",expired:"Expired",resolved:"Resolved"};function en(e){if(e.category==="identity_choice"){const r=Xt[e.outcomeText??""];if(r)return r}const t=(e.outcomeText??"").trim();return t!==""?t:Zt[e.outcome]??e.outcome}function tn(e){return e==="primary"?"default":e==="danger"?"destructive":"secondary"}function nn({action:e,pending:t,onClick:r}){const s=tn(e.style);return e.kind==="link"||e.kind==="link_mint"?e.url?n.jsx(X,{asChild:!0,variant:s,size:"sm",children:n.jsxs("a",{href:e.url,target:"_blank",rel:"noopener noreferrer",children:[e.label,n.jsx(Te,{className:"h-3.5 w-3.5","aria-hidden":"true"})]})}):null:n.jsx(X,{variant:s,size:"sm",disabled:t,onClick:r,children:e.label})}const rn={readonly:{Icon:it,textClass:"text-success"},readwrite:{Icon:ot,textClass:"text-primary"},external:{Icon:ue,textClass:"text-warning"}},sn={repository:ct,github:lt};function de({items:e,depth:t=0}){let r=0;return n.jsx("div",{className:b("flex flex-col",t===0?"gap-1.5":t===1?"gap-1":"gap-0.5 pl-2"),children:e.map((s,d)=>{var m;const u=t===0&&!!((m=s.items)!=null&&m.length);u&&(r+=1);const c=s.tone?rn[s.tone]:void 0,a=s.icon?sn[s.icon]:void 0;return n.jsxs("div",{className:b(u&&"rounded-md border border-border/60 bg-muted/30 px-3 py-2.5"),children:[n.jsxs("div",{className:"flex flex-wrap items-baseline gap-x-1.5",children:[u&&n.jsx("span",{className:"rounded bg-state/15 px-1 py-px font-mono text-[10px] font-medium text-state",children:r}),c&&n.jsx(c.Icon,{"data-testid":`tier-${s.tone}`,className:b("h-3 w-3 shrink-0",c.textClass),"aria-hidden":"true"}),a&&n.jsx(a,{"data-testid":`resource-icon-${s.icon}`,className:"h-3 w-3 shrink-0 text-muted-foreground","aria-hidden":"true"}),n.jsx("span",{title:s.hint||void 0,className:b(u&&"font-medium",s.hint&&"cursor-help underline decoration-dotted decoration-muted-foreground/60 underline-offset-2",s.tone==="muted"?"text-muted-foreground":c?b("font-medium",c.textClass):"text-card-foreground"),children:s.text}),s.detail&&s.href?n.jsx("a",{href:s.href,target:"_blank",rel:"noopener noreferrer",className:"break-all font-mono text-[11px] text-link underline decoration-link/40 underline-offset-2 hover:decoration-link",children:s.href}):s.detail&&n.jsx("span",{className:b("break-all",s.tone==="external"?"rounded bg-warning/15 px-1.5 py-px text-[10px] font-semibold uppercase tracking-wide text-warning ring-1 ring-inset ring-warning/30":"font-mono text-[11px] text-muted-foreground"),children:s.detail})]}),s.items&&s.items.length>0&&n.jsx("div",{className:b(t===0&&"mt-2"),children:n.jsx(de,{items:s.items,depth:t+1})})]},`${s.text}:${d}`)})})}function an(e){const t=r=>(r??[]).some(s=>s.tone==="external"||t(s.items));return(e??[]).some(r=>t(r.items))}function on(e){const{request:t,applied:r,onDecision:s,footer:d}=e,u=an(t.fields),[c,a]=l.useState(!1),m=!!r;return n.jsxs("div",{className:b("mx-auto flex w-full max-w-[85%] flex-col gap-2 rounded-lg border bg-card/60 px-3 py-2.5",m?"border-border":u?"border-l-2 border-border border-l-warning":"border-l-2 border-border border-l-primary"),"data-testid":"interaction-card","data-request-ref":t.requestRef,"data-external":u?"true":void 0,children:[n.jsxs("div",{className:"flex items-start gap-1.5",children:[n.jsx("span",{className:"mt-0.5",children:m?Qt(r.outcome):u?n.jsx(ue,{className:"h-3.5 w-3.5 shrink-0 text-warning","aria-hidden":"true"}):n.jsx(at,{className:"h-3.5 w-3.5 shrink-0 text-primary","aria-hidden":"true"})}),n.jsx("span",{className:b("break-words text-sm font-semibold leading-snug",m?"text-muted-foreground":"text-card-foreground"),children:t.lead})]}),t.body&&n.jsx("div",{className:"text-xs leading-snug text-muted-foreground",children:t.body}),d,t.fields&&t.fields.length>0&&n.jsx("div",{className:"flex flex-col gap-2.5",children:t.fields.map((p,N)=>{if(p.items&&p.items.length>0){const S="text-[10px] font-medium uppercase tracking-wide text-muted-foreground";return m?n.jsxs("details",{className:"text-xs leading-snug",children:[n.jsxs("summary",{className:b(S,"cursor-pointer hover:text-foreground"),children:[p.label," ",n.jsxs("span",{className:"font-normal normal-case tracking-normal text-muted-foreground/70",children:["(",p.items.length,")"]})]}),n.jsx("div",{className:"mt-2",children:n.jsx(de,{items:p.items})})]},`${p.label}:${N}`):n.jsxs("div",{className:"flex flex-col gap-1 text-xs leading-snug",children:[n.jsx("div",{className:S,children:p.label}),n.jsx(de,{items:p.items})]},`${p.label}:${N}`)}const j=p.value.includes(` -`);return n.jsxs("div",{className:"flex flex-col gap-0.5 text-xs leading-snug",children:[n.jsx("div",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:p.label}),n.jsx("span",{className:b("text-card-foreground",j&&"whitespace-pre-wrap break-words"),children:p.value})]},`${p.label}:${N}`)})}),n.jsx(Le,{excerpt:t.excerpt}),m?n.jsx("div",{className:b("text-xs",r.outcome==="denied"?"text-destructive":"text-muted-foreground"),children:en(r)}):t.actions&&t.actions.length>0&&n.jsx("div",{className:"mt-0.5 flex flex-wrap justify-end gap-1.5 border-t border-border/60 pt-2",children:t.actions.map(p=>n.jsx(nn,{action:p,pending:c,onClick:()=>{a(!0),s(t.requestRef,t.category,p.id)}},p.id))})]})}const ln={critical:{Icon:ue,color:"text-destructive"},privacy:{Icon:dt,color:"text-warning"},degraded:{Icon:_e,color:"text-warning"},waiting:{Icon:xe,color:"text-muted-foreground"},routine:{Icon:ce,color:"text-muted-foreground"},housekeeping:{Icon:ce,color:"text-muted-foreground"},resolved:{Icon:fe,color:"text-success"}},cn={Icon:ce,color:"text-muted-foreground"},dn={money:ut,clock:xe};function un({notice:e}){const t=ln[e.tone??""]??cn,r=dn[e.glyph??""]??t.Icon;return n.jsxs("div",{className:"mx-auto flex w-full max-w-[85%] flex-col gap-2 rounded-lg border border-border bg-card/60 px-3 py-2.5","data-testid":"notice-card","data-tone":e.tone||"",children:[n.jsxs("div",{className:b("flex items-center gap-1.5 text-xs font-medium",t.color),children:[n.jsx(r,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),n.jsx("span",{className:"break-words text-card-foreground",children:e.lead})]}),e.body&&n.jsx("div",{className:"text-xs text-muted-foreground",children:e.body}),n.jsx(Gt,{fields:e.fields}),n.jsx(Le,{excerpt:e.excerpt}),e.nextStep&&n.jsx("div",{className:"text-xs font-medium text-card-foreground",children:e.nextStep})]})}function fn({spans:e}){return n.jsx(n.Fragment,{children:e.map((t,r)=>{const s={};return t.fg&&(s.color=t.fg),t.bg&&(s.backgroundColor=t.bg),t.dim&&(s.opacity=.6),n.jsx("span",{style:s,className:b(t.bold&&"font-semibold",t.italic&&"italic",t.underline&&"underline"),children:t.text},r)})})}function xn({line:e}){return n.jsxs("div",{"data-stream":e.kind,className:b("whitespace-pre-wrap break-all font-mono text-[11px] leading-[1.45]",e.kind==="stderr"&&"text-red-300/90",e.kind==="event"&&"text-foreground/80"),children:[e.toolName&&n.jsxs("span",{className:"mr-1.5 inline-flex items-center gap-1 text-[10px] text-muted-foreground",children:[n.jsx(mt,{className:"h-2.5 w-2.5"}),e.toolName]}),n.jsx(fn,{spans:e.spans})]})}function pn(e){const t=e/1e3;if(t<60)return`${t.toFixed(1)}s`;const r=Math.floor(t/60);return`${r}m${Math.round(t-r*60)}s`}function mn(e){const t=[e.ok?"done":"failed"];return!e.ok&&e.exitCode!==void 0&&t.push(`exit ${e.exitCode}`),!e.ok&&e.exitReason&&e.exitReason!=="failed"&&t.push(e.exitReason),e.durationMs!==void 0&&t.push(pn(e.durationMs)),e.costUsd!==void 0&&t.push(`$${e.costUsd.toFixed(2)}`),t.join(" · ")}function hn({state:e}){const[t,r]=l.useState(void 0),s=t??!(e.done&&e.ok),d=[e.outerTool||"tool",e.reason].filter(Boolean).join(" · ");return n.jsxs("div",{className:"my-1 w-full overflow-hidden rounded-md border border-border/60 bg-background/40",children:[n.jsxs("button",{type:"button","data-testid":"tool-session-toggle",onClick:()=>r(!s),className:"flex w-full items-center gap-1.5 px-2 py-1.5 text-left text-[11px] text-muted-foreground hover:bg-foreground/5",children:[s?n.jsx(ft,{className:"h-3 w-3 shrink-0"}):n.jsx(xt,{className:"h-3 w-3 shrink-0"}),n.jsx(pt,{className:"h-3 w-3 shrink-0"}),n.jsx("span",{className:b("truncate",!e.done&&"animate-pulse"),children:d})]}),s&&n.jsxs("div",{"data-testid":"tool-session-body",className:"max-h-80 overflow-auto border-t border-border/60 px-2 py-1.5",children:[e.elided>0&&n.jsxs("div",{className:"mb-1 text-[10px] italic text-muted-foreground/70",children:["… ",e.elided," earlier ",e.elided===1?"line":"lines"," elided"]}),e.lines.map((u,c)=>n.jsx(xn,{line:u},c))]}),e.done&&n.jsx("div",{"data-testid":"tool-session-footer",className:b("border-t border-border/60 px-2 py-1 text-[10px]",e.ok?"text-muted-foreground":"text-red-300/90"),children:mn(e)})]})}const G=l.memo(function({text:t}){return n.jsx(Ct,{children:t})});function Ue({text:e}){return n.jsx("span",{className:"whitespace-pre-wrap break-words",children:e})}function De(e){return e==="done"||e==="complete"||e==="completed"||e==="skipped"}function gn(e,t){if(De(e))return n.jsx(fe,{className:"h-3.5 w-3.5 shrink-0 text-success","aria-hidden":"true"});switch(e){case"in_progress":case"active":case"running":return n.jsx(J,{className:b("h-3.5 w-3.5 shrink-0 text-primary",t&&"animate-spin"),"aria-hidden":"true"});case"blocked":case"failed":case"error":return n.jsx(Se,{className:"h-3.5 w-3.5 shrink-0 text-destructive","aria-hidden":"true"});default:return n.jsx(bt,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground","aria-hidden":"true"})}}function le({items:e,live:t}){return n.jsx("ul",{className:"flex flex-col gap-1",children:e.map(r=>{const s=De(r.status);return n.jsxs("li",{className:"flex items-start gap-1.5 text-xs leading-snug",children:[n.jsx("span",{className:"mt-0.5",children:gn(r.status,t)}),n.jsx("span",{className:b("break-words",s?"text-muted-foreground":"text-card-foreground"),children:r.label})]},r.id)})})}function Pe({plan:e,live:t}){const r=e.items??[],s=e.phases??[];if(s.length===0)return n.jsx(le,{items:r,live:t});const d=s.map(a=>({phase:a,steps:r.filter(m=>m.phase===a.id)})).filter(a=>a.steps.length>0),u=new Set(s.map(a=>a.id)),c=r.filter(a=>!a.phase||!u.has(a.phase));return n.jsxs("div",{className:"flex flex-col gap-2",children:[d.map((a,m)=>n.jsxs("div",{className:"flex flex-col gap-1",children:[n.jsx("div",{className:"break-words text-xs font-medium text-card-foreground",children:a.phase.label}),n.jsx("div",{className:"border-l border-border/60 pl-2",children:n.jsx(le,{items:a.steps,live:t})})]},a.phase.id||m)),c.length>0&&n.jsxs("div",{className:"flex flex-col gap-1",children:[d.length>0&&n.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:"Other steps"}),n.jsx("div",{className:b(d.length>0&&"border-l border-border/60 pl-2"),children:n.jsx(le,{items:c,live:t})})]})]})}function yn({plan:e,live:t}){const r=e.items??[];return n.jsxs("div",{className:"mx-auto w-full max-w-[85%] rounded-lg border border-border bg-card/60 px-3 py-2",children:[n.jsxs("div",{className:"flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1.5",children:[n.jsx(yt,{className:"h-3.5 w-3.5","aria-hidden":"true"}),n.jsxs("span",{children:["Plan",r.length>0?` · ${r.length} step${r.length===1?"":"s"}`:""]})]}),r.length===0?n.jsx("div",{className:"text-xs text-muted-foreground",children:"Plan updated."}):n.jsx(Pe,{plan:e,live:t})]})}function D({isUser:e,className:t,children:r}){return n.jsx("div",{className:b("flex",e?"justify-end":"justify-start"),children:n.jsx("div",{className:b("max-w-[75%] rounded-2xl px-3.5 py-2 text-sm leading-relaxed",e?"bg-primary text-primary-foreground rounded-br-sm":"bg-card border border-border text-card-foreground rounded-bl-sm",t),children:r})})}const bn=l.memo(function({line:t,toolSession:r,onRetry:s,onDecision:d,live:u,absorbedPlan:c}){return t.role==="toolSession"?r?n.jsx(hn,{state:r}):null:t.liveViewUrl?n.jsx("div",{className:"flex justify-center py-1",children:n.jsxs("a",{href:t.liveViewUrl,target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-xs font-medium text-card-foreground hover:bg-accent hover:text-accent-foreground",children:[n.jsx(Te,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),"View live"]})}):t.role==="plan"&&t.plan?n.jsx(yn,{plan:t.plan,live:u}):t.role==="interaction"&&t.interactionRequest?n.jsx(on,{request:t.interactionRequest,applied:t.interactionApplied,onDecision:d??(()=>{}),footer:c?n.jsxs("div",{className:"flex flex-col gap-1 border-t border-border/60 pt-2",children:[n.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:"The plan this covers"}),n.jsx(Pe,{plan:c,live:u})]}):void 0}):t.role==="notice"&&t.notice?n.jsx(un,{notice:t.notice}):t.role==="system"||t.role==="error"?n.jsx("div",{className:b("mx-auto max-w-[85%] text-center text-xs py-1",t.role==="error"?"text-destructive":"text-muted-foreground"),children:n.jsx(Ue,{text:t.text})}):t.role==="user"&&t.sendStatus==="failed"?n.jsxs("div",{className:"flex flex-col items-end gap-1","data-testid":"failed-message",children:[n.jsx(D,{isUser:!0,className:"border border-destructive bg-destructive/10 text-foreground",children:n.jsx(G,{text:t.text})}),n.jsxs("div",{className:"flex items-center gap-2 text-[11px] text-destructive",children:[n.jsx(_e,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),n.jsx("span",{className:"break-words",children:t.sendError||"Failed to send."}),s&&n.jsxs("button",{type:"button",onClick:()=>s(t.id,t.text,t.requestId??""),className:"inline-flex items-center gap-1 font-medium underline underline-offset-2 hover:no-underline",children:[n.jsx(ht,{className:"h-3 w-3","aria-hidden":"true"}),"Retry"]})]})]}):t.role==="user"&&t.sendStatus==="pending"?n.jsx("div",{"data-testid":"pending-message",children:n.jsx(D,{isUser:!0,className:"opacity-60 animate-pulse",children:n.jsx(G,{text:t.text})})}):t.role==="user"&&t.queued?n.jsx("div",{"data-testid":"queued-message",children:n.jsxs(D,{isUser:!0,children:[n.jsx("div",{className:"mb-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:"Queued"}),n.jsx("span",{className:"whitespace-pre-wrap break-words text-muted-foreground",children:t.text})]})}):t.attachments&&t.attachments.length>0?n.jsxs("div",{className:b("flex flex-col gap-1",t.role==="user"?"items-end":"items-start"),children:[n.jsx(D,{isUser:t.role==="user",children:n.jsx(G,{text:t.text})}),n.jsx(jn,{attachments:t.attachments})]}):n.jsx(D,{isUser:t.role==="user",children:n.jsx(G,{text:t.text})})});function jn({attachments:e}){return n.jsx("div",{className:"flex flex-wrap gap-1.5","data-testid":"attachment-chips",children:e.map((t,r)=>n.jsxs("a",{href:t.url,download:t.filename,className:"inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-2.5 py-1 text-xs text-card-foreground hover:bg-accent hover:text-accent-foreground",children:[n.jsx(gt,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),n.jsx("span",{className:"break-all",children:t.filename})]},r))})}function vn({text:e}){return n.jsxs(D,{isUser:!1,children:[n.jsx(Ue,{text:e}),n.jsx("span",{className:"ml-0.5 inline-block h-4 w-[2px] translate-y-0.5 bg-current animate-pulse","aria-hidden":"true"})]})}function wn(){return n.jsx("div",{className:"flex justify-start",role:"status","aria-label":"Agent is working",children:n.jsx("div",{className:"flex items-center gap-2 rounded-2xl rounded-bl-sm border border-border bg-card px-3.5 py-2.5",children:n.jsx("div",{className:"flex items-center gap-1",children:[0,150,300].map(e=>n.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:`${e}ms`}},e))})})})}function Nn({text:e}){return n.jsx("div",{className:"flex items-center justify-center text-[11px] text-muted-foreground py-0.5",children:n.jsx("span",{className:"animate-pulse",children:e})})}function kn({text:e}){return n.jsxs("div",{className:"flex items-center justify-center gap-1.5 text-[11px] text-muted-foreground py-0.5",children:[n.jsx(J,{className:"h-3 w-3 animate-spin","aria-hidden":"true"}),n.jsx("span",{children:e})]})}function Cn({lines:e,streamingText:t,working:r,statusText:s,notice:d,opTree:u,toolSessions:c,onRetry:a,onDecision:m}){const p=l.useRef(null),N=l.useRef(null),j=l.useRef(null);l.useEffect(()=>{var w,O;const g=N.current;if(!(!g||!(g.scrollHeight-g.scrollTop-g.clientHeight<120))){if(typeof requestAnimationFrame!="function"){(O=(w=p.current)==null?void 0:w.scrollIntoView)==null||O.call(w,{block:"end"});return}return j.current!=null&&cancelAnimationFrame(j.current),j.current=requestAnimationFrame(()=>{var k,L;j.current=null,(L=(k=p.current)==null?void 0:k.scrollIntoView)==null||L.call(k,{block:"end"})}),()=>{j.current!=null&&(cancelAnimationFrame(j.current),j.current=null)}}},[e.length,t,r,s,d,u]);const{absorbedPlanByLine:S,suppressedPlanLines:_}=l.useMemo(()=>{var O;const g={},$=new Set;let w;for(const k of e){if(k.role==="plan"&&k.plan){w=k;continue}const L=(O=k.interactionRequest)==null?void 0:O.category;k.role==="interaction"&&(L==="plan_phase"||L==="plan_amendment")&&w!=null&&w.plan&&(g[k.id]=w.plan,$.add(w.id))}return{absorbedPlanByLine:g,suppressedPlanLines:$}},[e]),A=t.length>0,V=r&&!A;return n.jsx("div",{ref:N,className:"flex-1 min-h-0 overflow-y-auto px-4 py-4",children:n.jsxs("div",{className:"flex flex-col gap-2.5 max-w-3xl mx-auto",children:[e.map(g=>_.has(g.id)?null:n.jsx("div",{"data-testid":"chat-line","data-role":g.role,children:n.jsx(bn,{line:g,toolSession:g.toolSessionRef?c[g.toolSessionRef]:void 0,onRetry:a,onDecision:m,live:r,absorbedPlan:S[g.id]})},g.id)),A&&n.jsx(vn,{text:t}),V&&n.jsx(wn,{}),r&&d&&n.jsx(Nn,{text:d}),r&&u.length>0&&n.jsx(Wt,{nodes:u}),r&&s&&n.jsx(kn,{text:s}),n.jsx("div",{ref:p})]})})}function In({disabled:e,placeholder:t,onSend:r}){const[s,d]=l.useState(""),u=()=>{const a=s.trim();!a||e||(r(a),d(""))},c=a=>{a.key==="Enter"&&!a.shiftKey&&(a.preventDefault(),u())};return n.jsxs("div",{className:"flex items-end gap-2 px-4 py-3 border-t border-border bg-card",children:[n.jsx("textarea",{rows:1,value:s,disabled:e,placeholder:t,onChange:a=>d(a.target.value),onKeyDown:c,className:b("flex-1 resize-none max-h-40 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm","placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring","disabled:cursor-not-allowed disabled:opacity-50")}),n.jsx(X,{size:"icon",className:"h-9 w-9 shrink-0",disabled:e||!s.trim(),onClick:u,title:"Send",children:n.jsx(jt,{className:"h-4 w-4"})})]})}function P(e,t){return`/sessions/api/${encodeURIComponent(e)}/${encodeURIComponent(t)}`}async function Q(e,t){if((e.headers.get("content-type")??"").includes("application/json"))try{const r=await e.json(),s=r==null?void 0:r.error;if(typeof s=="string"&&s.trim()!=="")return s}catch{}switch(e.status){case 401:return"You are no longer signed in. Reload the page to continue.";case 403:return"You do not have access to this conversation.";case 503:return"That could not be checked right now. Try again in a moment.";default:return t}}async function Un(e,t,r){const s=await fetch(`/sessions/api/${encodeURIComponent(e)}/${encodeURIComponent(t)}/message`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:r})});if(!s.ok)throw new Error(`send session message: request failed (${s.status})`)}function Rn(e,t,r){const s=l.useCallback(async(d,u,c)=>{try{const a=await fetch(`${P(e,t)}/decision`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({category:u,requestRef:d,actionId:c})});if(!a.ok){console.error("interaction decision: request failed",{ns:e,name:t,status:a.status});const m=await Q(a,`Failed to submit decision (${a.status}).`);r(m)}}catch(a){console.error("interaction decision: request errored",{ns:e,name:t,err:a}),r("Network error while trying to submit decision.")}},[e,t,r]);return l.useCallback((d,u,c)=>{s(d,u,c)},[s])}function Sn(e){if(e.class==="already_resolved"){let r="someone else";e.originalDecider&&(r=e.originalDecider.email||e.originalDecider.externalId||r);let s=`This request was already resolved by ${r}`;return e.originalOutcome&&(s+=` (${e.originalOutcome})`),s+"."}return((e.reason||"").trim()||"your click could not be applied")+"."}function Tn(e){switch(e.reason){case"succeeded":return"Conversation ended.";case"failed":return`Conversation ended: ${e.failureMessage||e.failureReason||"the agent failed"}.`;case"idle_timeout":return"Conversation ended after being idle too long.";case"server_shutdown":return"Conversation ended: the server is restarting.";default:return"Conversation ended."}}function Ne(e,t){return e.map(r=>r.kind==="plan"?{id:`plan:${r.plan.planName}`,role:"plan",text:"",plan:r.plan}:{id:t(),role:r.role,text:r.text})}const ke="Earlier messages could not be loaded. The live conversation still works.",_n="Starting…",Ce="Working…",Ie={text:"",ephemeral:!1};function Re(e){return e.ephemeral&&e.text!==Ce?{text:Ce,ephemeral:!0}:e}function $n(e,t){return`/artifact-download?${new URLSearchParams({artifactId:t.artifactId,sessionRef:`${e.namespace}/${e.name}`,fn:t.filename||t.artifactId}).toString()}`}const qn=new Set(["startup","scheduling"]);function Dn({ns:e,name:t,readOnly:r,onUserSend:s,onSessionEnded:d}){const[u,c]=l.useState([]),[a,m]=l.useState(!1),[p,N]=l.useState([]),{startup:j}=Oe(),S=j===null?p:p.filter(o=>!qn.has(o.kind)),_=r||a,A=l.useCallback(()=>{m(!0),d==null||d()},[d]),V=l.useRef(`${e}/${t}`),g=l.useCallback(o=>o===V.current,[]),[$,w]=l.useState(!1),[O,k]=l.useState(Ie),[L,Me]=l.useState(""),[Fe,ee]=l.useState(null),[Be,te]=l.useState([]),[He,Je]=l.useState({}),ne=l.useRef(""),U=l.useCallback(o=>{const f=o(ne.current);ne.current=f,Me(f)},[]),[Ve,Ye]=l.useState(null),re=l.useRef(null),q=l.useCallback(o=>{re.current=o,Ye(o)},[]),Ke=l.useRef(0),I=l.useCallback(()=>`l${Ke.current++}`,[]),C=l.useCallback((o,f,h)=>{c(i=>[...i,{id:I(),role:o,text:f,queued:h==null?void 0:h.queued,attachments:h==null?void 0:h.attachments}])},[I]),he=l.useCallback(o=>{o.lead&&c(f=>[...f,{id:I(),role:"notice",text:"",notice:o}])},[I]),Y=l.useRef(!1),E=l.useRef(!1),K=l.useCallback(()=>{E.current=!0,w(!0),ee(null),k({text:_n,ephemeral:!0}),U(()=>"")},[U]),R=l.useCallback(()=>{E.current=!1,w(!1),ee(null),te([]),k(Ie),U(()=>"")},[U]),se=l.useCallback(()=>{const o=ne.current;o&&C("agent",o)},[C]),ae=l.useCallback(()=>{c(o=>o.map(f=>f.queued?{...f,queued:!1}:f))},[]),ge=l.useCallback(async()=>{const o=P(e,t),f=`${e}/${t}`;try{const[h,i]=await Promise.all([fetch(`${o}/messages`),fetch(`${o}/detail`)]);if(!h.ok)console.error("chat: resync transcript failed",{ns:e,name:t,status:h.status});else{const y=await h.json();if(!g(f))return;c(Ne(y.timeline??[],I))}if(!i.ok){console.error("chat: resync session detail failed",{ns:e,name:t,status:i.status});return}const x=await i.json();if(!g(f))return;x.phase!=="Running"&&R(),N(x.notices??[])}catch(h){console.error("chat: resync after reconnect errored",{ns:e,name:t,err:h})}},[e,t,I,R,g]);l.useEffect(()=>{let o=!1;return V.current=`${e}/${t}`,c([]),m(!1),R(),q(null),Y.current=!1,(async()=>{try{(async()=>{try{const i=await fetch(`${P(e,t)}/detail`);if(o||!i.ok)return;const x=await i.json();o||N(x.notices??[])}catch(i){console.error("chat: load session detail errored",{ns:e,name:t,err:i})}})();const f=await fetch(`${P(e,t)}/messages`);if(o)return;if(f.ok){const i=await f.json();if(o)return;c(Ne(i.timeline??[],I));return}console.error("chat: load transcript failed",{ns:e,name:t,status:f.status});const h=f.status===501?"History replay is unavailable here; resuming live.":await Q(f,ke);if(o)return;c([{id:I(),role:f.status===501?"system":"error",text:h}])}catch(f){if(console.error("chat: load transcript errored",{ns:e,name:t,err:f}),o)return;c([{id:I(),role:"error",text:ke}])}})(),()=>{o=!0}},[e,t,I,R,q]);const ze=l.useCallback(o=>{var f,h;switch(o.type){case"user_message":{const i=o.payload,x=i.text;U(()=>"");const y=(i.attachments??[]).filter(v=>v.artifactId).map(v=>({filename:v.filename||v.artifactId,mime:v.mime,url:$n(i.session,v)}));C("agent",x,y.length?{attachments:y}:void 0);break}case"user_echo":{const i=o.payload;c(x=>i.requestID&&x.some(y=>y.requestId===i.requestID)?x:[...x,{id:I(),role:"user",text:i.text}]);break}case"notification":{if(!E.current)break;const i=o.payload;w(!0),k({text:i.text,ephemeral:i.ephemeral===!0});break}case"plan_update":{const i=o.payload.payload;E.current&&w(!0);const x=`plan:${i.planName}`;c(y=>{const v=y.findIndex(T=>T.role==="plan"&&T.id===x);if(v>=0){const T=y.slice();return T[v]={...y[v],plan:i},T}return[...y,{id:x,role:"plan",text:"",plan:i}]});break}case"stream_delta":{if(!E.current)break;const i=o.payload.payload;(i==null?void 0:i.eventType)==="text_delta"&&i.text&&(w(!0),k(Re),U(x=>x+i.text));break}case"turn_progress":if(!E.current)break;w(!0),k(Re),ee(o.payload.payload);break;case"operation_activity":{if(!E.current){te([]);break}const i=o.payload.payload;te(i.cleared?[]:i.operations??[]);break}case"turn_activity":{o.payload.active?(E.current=!0,w(!0)):(se(),R(),ae());break}case"interrupt_applied":{const i=o.payload;if(i.requestID!==re.current)break;q(null),i.outcome==="interrupted"?(C("system","Interrupting — sending your message(s) now."),ae()):C("system","Couldn't interrupt — "+(i.reason||"the runner declined."));break}case"send_error":R(),C("error",o.payload.err||"The agent failed to process a message.");break;case"session_ended":{A(),se(),R(),C("system",Tn(o.payload));break}case"live_view_offer":{const i=o.payload.url;i&&c(x=>[...x,{id:I(),role:"system",text:"",liveViewUrl:i}]);break}case"tool_session_event":case"tool_session_delta":{Je(y=>Et(y,o));const i=(h=(f=o.payload)==null?void 0:f.payload)==null?void 0:h.toolCallRef;if(!i)break;const x=`toolsession:${i}`;c(y=>y.some(v=>v.id===x)?y:[...y,{id:x,role:"toolSession",text:"",toolSessionRef:i}]);break}case"interaction_request":{const i=o.payload.payload,x=`interaction:${i.requestRef}`;c(y=>{const v=y.findIndex(T=>T.role==="interaction"&&T.id===x);if(v>=0){const T=y.slice();return T[v]={...y[v],interactionRequest:i},T}return[...y,{id:x,role:"interaction",text:"",interactionRequest:i}]});break}case"interaction_applied":{const i=o.payload.payload,x=`interaction:${i.requestRef}`;c(y=>y.map(v=>v.id===x?{...v,interactionApplied:i}:v));break}case"interaction_decision_rejected":{const i=o.payload.payload;C(i.class==="already_resolved"?"system":"error",Sn(i));break}}},[C,I,U,q,A,R,se,ae]);Ht(ze);const B=Jt(),z=l.useRef(!1);l.useEffect(()=>{if(B==="connected"){z.current=!1,Y.current&&(Y.current=!1,ge());return}if(B==="connecting"){z.current=!1;return}Y.current=!0,B==="offline"&&!_&&!z.current&&(z.current=!0,R(),C("error","Connection lost — reload the page to reconnect."))},[B,_,C,ge,R]);const H=l.useCallback((o,f,h)=>{c(i=>i.map(x=>x.id===o?{...x,sendStatus:f,sendError:h}:x))},[]),W=l.useCallback(async(o,f,h)=>{const i=`${e}/${t}`;H(o,"pending");try{const x=await fetch(`${P(e,t)}/message`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:f,requestId:h})});if(!g(i))return;if(!x.ok){console.error("chat: send failed",{ns:e,name:t,status:x.status});const v=await Q(x,`Failed to send (${x.status}).`);if(!g(i))return;H(o,"failed",v),R();return}const y=await x.json();if(!g(i))return;H(o,"sent"),y.notice&&he(y.notice),y.ended&&(A(),R())}catch(x){if(console.error("chat: send errored",{ns:e,name:t,err:x}),!g(i))return;H(o,"failed","Network error while sending your message."),R()}},[e,t,H,he,A,R,g]),We=l.useCallback(o=>{s==null||s();const f=I(),h=crypto.randomUUID();c(i=>[...i,{id:f,role:"user",text:o,queued:$,sendStatus:"pending",requestId:h}]),K(),W(f,o,h)},[$,I,K,s,W]),Ge=l.useCallback((o,f,h)=>{K(),W(o,f,h)},[K,W]),Qe=l.useCallback(async()=>{if(re.current)return;const o=`${e}/${t}`,f=crypto.randomUUID();q(f);try{const h=await fetch(`${P(e,t)}/interrupt`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:f})});if(!h.ok){console.error("chat: interrupt failed",{ns:e,name:t,status:h.status});const i=await Q(h,`Failed to interrupt (${h.status}).`);if(!g(o))return;C("error",i),q(null)}}catch(h){if(console.error("chat: interrupt errored",{ns:e,name:t,err:h}),!g(o))return;C("error","Network error while trying to interrupt."),q(null)}},[e,t,C,q,g]),Xe=l.useCallback(o=>{g(`${e}/${t}`)&&C("error",o)},[e,t,g,C]),Ze=Rn(e,t,Xe),et=$?It(Fe):null,tt=_?"This conversation has ended.":"Message the agent…";return n.jsx(kt,{delayDuration:200,children:n.jsxs("div",{className:"flex min-h-0 flex-1 flex-col bg-background text-foreground","data-testid":"chat-view",children:[n.jsx("div",{className:"flex items-center justify-end px-3 py-1 border-b border-border bg-card/40",children:n.jsx(Yt,{state:B})}),n.jsx(Cn,{lines:u,streamingText:L,working:$,statusText:et,notice:O.text,opTree:Be,toolSessions:He,onRetry:Ge,onDecision:Ze}),$&&u.some(o=>o.queued)&&n.jsx("div",{className:"flex justify-center px-4 pt-2 bg-card",children:n.jsx(X,{variant:"secondary",size:"sm",disabled:Ve!==null,onClick:Qe,children:"Interrupt & Send Now"})}),S.length>0&&n.jsx("div",{className:"mx-4 mb-2 flex flex-col gap-1.5",children:S.map((o,f)=>n.jsxs("div",{"data-testid":`session-notice-${o.kind}`,className:b("rounded-md border px-3 py-2 text-xs leading-snug",o.tone==="degraded"?"border-warning/30 bg-warning/10 text-warning":"border-border/60 bg-muted/40 text-muted-foreground"),children:[n.jsx("div",{children:o.lead}),o.nextStep&&n.jsx("div",{className:"mt-0.5 font-medium",children:o.nextStep}),o.body&&n.jsx("div",{className:"mt-1 break-all font-mono text-[10px] opacity-70",children:o.body})]},`${o.kind}:${f}`))}),n.jsx(In,{disabled:_,placeholder:tt,onSend:We})]})})}function Pn(){const{startup:e}=Oe();return e===null?null:n.jsxs("div",{"data-testid":"session-shell-startup",role:"status",className:"flex items-center gap-2 border-b border-border bg-card/60 px-4 py-2 text-sm text-muted-foreground",children:[n.jsx(J,{className:"h-4 w-4 shrink-0 animate-spin","aria-hidden":"true"}),n.jsxs("span",{children:[e.short!==null?n.jsxs(n.Fragment,{children:[n.jsx("span",{className:"hidden sm:inline",children:e.text}),n.jsx("span",{className:"sm:hidden",children:e.short})]}):e.text,e.stillTrying&&n.jsx("span",{"data-testid":"session-shell-startup-still-trying",children:" — still trying"})]})]})}export{Dn as C,on as I,Ln as S,Pn as a,Rn as b,Ht as c,Sn as i,Un as s,Oe as u}; +`);return n.jsxs("div",{className:"flex flex-col gap-0.5 text-xs leading-snug",children:[n.jsx("div",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:p.label}),n.jsx("span",{className:b("text-card-foreground",j&&"whitespace-pre-wrap break-words"),children:p.value})]},`${p.label}:${N}`)})}),n.jsx(Oe,{excerpt:t.excerpt}),m?n.jsx("div",{className:b("text-xs",r.outcome==="denied"?"text-destructive":"text-muted-foreground"),children:en(r)}):t.actions&&t.actions.length>0&&n.jsx("div",{className:"mt-0.5 flex flex-wrap justify-end gap-1.5 border-t border-border/60 pt-2",children:t.actions.map(p=>n.jsx(nn,{action:p,pending:c,onClick:()=>{a(!0),s(t.requestRef,t.category,p.id)}},p.id))})]})}const ln={critical:{Icon:ue,color:"text-destructive"},privacy:{Icon:dt,color:"text-warning"},degraded:{Icon:_e,color:"text-warning"},waiting:{Icon:xe,color:"text-muted-foreground"},routine:{Icon:ce,color:"text-muted-foreground"},housekeeping:{Icon:ce,color:"text-muted-foreground"},resolved:{Icon:fe,color:"text-success"}},cn={Icon:ce,color:"text-muted-foreground"},dn={money:ut,clock:xe};function un({notice:e}){const t=ln[e.tone??""]??cn,r=dn[e.glyph??""]??t.Icon;return n.jsxs("div",{className:"mx-auto flex w-full max-w-[85%] flex-col gap-2 rounded-lg border border-border bg-card/60 px-3 py-2.5","data-testid":"notice-card","data-tone":e.tone||"",children:[n.jsxs("div",{className:b("flex items-center gap-1.5 text-xs font-medium",t.color),children:[n.jsx(r,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),n.jsx("span",{className:"break-words text-card-foreground",children:e.lead})]}),e.body&&n.jsx("div",{className:"text-xs text-muted-foreground",children:e.body}),n.jsx(Gt,{fields:e.fields}),n.jsx(Oe,{excerpt:e.excerpt}),e.nextStep&&n.jsx("div",{className:"text-xs font-medium text-card-foreground",children:e.nextStep})]})}function fn({spans:e}){return n.jsx(n.Fragment,{children:e.map((t,r)=>{const s={};return t.fg&&(s.color=t.fg),t.bg&&(s.backgroundColor=t.bg),t.dim&&(s.opacity=.6),n.jsx("span",{style:s,className:b(t.bold&&"font-semibold",t.italic&&"italic",t.underline&&"underline"),children:t.text},r)})})}function xn({line:e}){return n.jsxs("div",{"data-stream":e.kind,className:b("whitespace-pre-wrap break-all font-mono text-[11px] leading-[1.45]",e.kind==="stderr"&&"text-red-300/90",e.kind==="event"&&"text-foreground/80"),children:[e.toolName&&n.jsxs("span",{className:"mr-1.5 inline-flex items-center gap-1 text-[10px] text-muted-foreground",children:[n.jsx(mt,{className:"h-2.5 w-2.5"}),e.toolName]}),n.jsx(fn,{spans:e.spans})]})}function pn(e){const t=e/1e3;if(t<60)return`${t.toFixed(1)}s`;const r=Math.floor(t/60);return`${r}m${Math.round(t-r*60)}s`}function mn(e){const t=[e.ok?"done":"failed"];return!e.ok&&e.exitCode!==void 0&&t.push(`exit ${e.exitCode}`),!e.ok&&e.exitReason&&e.exitReason!=="failed"&&t.push(e.exitReason),e.durationMs!==void 0&&t.push(pn(e.durationMs)),e.costUsd!==void 0&&t.push(`$${e.costUsd.toFixed(2)}`),t.join(" · ")}function hn({state:e}){const[t,r]=l.useState(void 0),s=t??!(e.done&&e.ok),d=[e.outerTool||"tool",e.reason].filter(Boolean).join(" · ");return n.jsxs("div",{className:"my-1 w-full overflow-hidden rounded-md border border-border/60 bg-background/40",children:[n.jsxs("button",{type:"button","data-testid":"tool-session-toggle",onClick:()=>r(!s),className:"flex w-full items-center gap-1.5 px-2 py-1.5 text-left text-[11px] text-muted-foreground hover:bg-foreground/5",children:[s?n.jsx(ft,{className:"h-3 w-3 shrink-0"}):n.jsx(xt,{className:"h-3 w-3 shrink-0"}),n.jsx(pt,{className:"h-3 w-3 shrink-0"}),n.jsx("span",{className:b("truncate",!e.done&&"animate-pulse"),children:d})]}),s&&n.jsxs("div",{"data-testid":"tool-session-body",className:"max-h-80 overflow-auto border-t border-border/60 px-2 py-1.5",children:[e.elided>0&&n.jsxs("div",{className:"mb-1 text-[10px] italic text-muted-foreground/70",children:["… ",e.elided," earlier ",e.elided===1?"line":"lines"," elided"]}),e.lines.map((u,c)=>n.jsx(xn,{line:u},c))]}),e.done&&n.jsx("div",{"data-testid":"tool-session-footer",className:b("border-t border-border/60 px-2 py-1 text-[10px]",e.ok?"text-muted-foreground":"text-red-300/90"),children:mn(e)})]})}const G=l.memo(function({text:t}){return n.jsx(Ct,{children:t})});function Ue({text:e}){return n.jsx("span",{className:"whitespace-pre-wrap break-words",children:e})}function De(e){return e==="done"||e==="complete"||e==="completed"||e==="skipped"}function gn(e,t){if(De(e))return n.jsx(fe,{className:"h-3.5 w-3.5 shrink-0 text-success","aria-hidden":"true"});switch(e){case"in_progress":case"active":case"running":return n.jsx(J,{className:b("h-3.5 w-3.5 shrink-0 text-primary",t&&"animate-spin"),"aria-hidden":"true"});case"blocked":case"failed":case"error":return n.jsx(Se,{className:"h-3.5 w-3.5 shrink-0 text-destructive","aria-hidden":"true"});default:return n.jsx(bt,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground","aria-hidden":"true"})}}function le({items:e,live:t}){return n.jsx("ul",{className:"flex flex-col gap-1",children:e.map(r=>{const s=De(r.status);return n.jsxs("li",{className:"flex items-start gap-1.5 text-xs leading-snug",children:[n.jsx("span",{className:"mt-0.5",children:gn(r.status,t)}),n.jsx("span",{className:b("break-words",s?"text-muted-foreground":"text-card-foreground"),children:r.label})]},r.id)})})}function Pe({plan:e,live:t}){const r=e.items??[],s=e.phases??[];if(s.length===0)return n.jsx(le,{items:r,live:t});const d=s.map(a=>({phase:a,steps:r.filter(m=>m.phase===a.id)})).filter(a=>a.steps.length>0),u=new Set(s.map(a=>a.id)),c=r.filter(a=>!a.phase||!u.has(a.phase));return n.jsxs("div",{className:"flex flex-col gap-2",children:[d.map((a,m)=>n.jsxs("div",{className:"flex flex-col gap-1",children:[n.jsx("div",{className:"break-words text-xs font-medium text-card-foreground",children:a.phase.label}),n.jsx("div",{className:"border-l border-border/60 pl-2",children:n.jsx(le,{items:a.steps,live:t})})]},a.phase.id||m)),c.length>0&&n.jsxs("div",{className:"flex flex-col gap-1",children:[d.length>0&&n.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:"Other steps"}),n.jsx("div",{className:b(d.length>0&&"border-l border-border/60 pl-2"),children:n.jsx(le,{items:c,live:t})})]})]})}function yn({plan:e,live:t}){const r=e.items??[];return n.jsxs("div",{className:"mx-auto w-full max-w-[85%] rounded-lg border border-border bg-card/60 px-3 py-2",children:[n.jsxs("div",{className:"flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1.5",children:[n.jsx(yt,{className:"h-3.5 w-3.5","aria-hidden":"true"}),n.jsxs("span",{children:["Plan",r.length>0?` · ${r.length} step${r.length===1?"":"s"}`:""]})]}),r.length===0?n.jsx("div",{className:"text-xs text-muted-foreground",children:"Plan updated."}):n.jsx(Pe,{plan:e,live:t})]})}function D({isUser:e,className:t,children:r}){return n.jsx("div",{className:b("flex",e?"justify-end":"justify-start"),children:n.jsx("div",{className:b("max-w-[75%] rounded-2xl px-3.5 py-2 text-sm leading-relaxed",e?"bg-primary text-primary-foreground rounded-br-sm":"bg-card border border-border text-card-foreground rounded-bl-sm",t),children:r})})}const bn=l.memo(function({line:t,toolSession:r,onRetry:s,onDecision:d,live:u,absorbedPlan:c}){return t.role==="toolSession"?r?n.jsx(hn,{state:r}):null:t.liveViewUrl?n.jsx("div",{className:"flex justify-center py-1",children:n.jsxs("a",{href:t.liveViewUrl,target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-xs font-medium text-card-foreground hover:bg-accent hover:text-accent-foreground",children:[n.jsx(Te,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),"View live"]})}):t.role==="plan"&&t.plan?n.jsx(yn,{plan:t.plan,live:u}):t.role==="interaction"&&t.interactionRequest?n.jsx(on,{request:t.interactionRequest,applied:t.interactionApplied,onDecision:d??(()=>{}),footer:c?n.jsxs("div",{className:"flex flex-col gap-1 border-t border-border/60 pt-2",children:[n.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:"The plan this covers"}),n.jsx(Pe,{plan:c,live:u})]}):void 0}):t.role==="notice"&&t.notice?n.jsx(un,{notice:t.notice}):t.role==="system"||t.role==="error"?n.jsx("div",{className:b("mx-auto max-w-[85%] text-center text-xs py-1",t.role==="error"?"text-destructive":"text-muted-foreground"),children:n.jsx(Ue,{text:t.text})}):t.role==="user"&&t.sendStatus==="failed"?n.jsxs("div",{className:"flex flex-col items-end gap-1","data-testid":"failed-message",children:[n.jsx(D,{isUser:!0,className:"border border-destructive bg-destructive/10 text-foreground",children:n.jsx(G,{text:t.text})}),n.jsxs("div",{className:"flex items-center gap-2 text-[11px] text-destructive",children:[n.jsx(_e,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),n.jsx("span",{className:"break-words",children:t.sendError||"Failed to send."}),s&&n.jsxs("button",{type:"button",onClick:()=>s(t.id,t.text,t.requestId??""),className:"inline-flex items-center gap-1 font-medium underline underline-offset-2 hover:no-underline",children:[n.jsx(ht,{className:"h-3 w-3","aria-hidden":"true"}),"Retry"]})]})]}):t.role==="user"&&t.sendStatus==="pending"?n.jsx("div",{"data-testid":"pending-message",children:n.jsx(D,{isUser:!0,className:"opacity-60 animate-pulse",children:n.jsx(G,{text:t.text})})}):t.role==="user"&&t.queued?n.jsx("div",{"data-testid":"queued-message",children:n.jsxs(D,{isUser:!0,children:[n.jsx("div",{className:"mb-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:"Queued"}),n.jsx("span",{className:"whitespace-pre-wrap break-words text-muted-foreground",children:t.text})]})}):t.attachments&&t.attachments.length>0?n.jsxs("div",{className:b("flex flex-col gap-1",t.role==="user"?"items-end":"items-start"),children:[n.jsx(D,{isUser:t.role==="user",children:n.jsx(G,{text:t.text})}),n.jsx(jn,{attachments:t.attachments})]}):n.jsx(D,{isUser:t.role==="user",children:n.jsx(G,{text:t.text})})});function jn({attachments:e}){return n.jsx("div",{className:"flex flex-wrap gap-1.5","data-testid":"attachment-chips",children:e.map((t,r)=>n.jsxs("a",{href:t.url,download:t.filename,className:"inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-2.5 py-1 text-xs text-card-foreground hover:bg-accent hover:text-accent-foreground",children:[n.jsx(gt,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),n.jsx("span",{className:"break-all",children:t.filename})]},r))})}function vn({text:e}){return n.jsxs(D,{isUser:!1,children:[n.jsx(Ue,{text:e}),n.jsx("span",{className:"ml-0.5 inline-block h-4 w-[2px] translate-y-0.5 bg-current animate-pulse","aria-hidden":"true"})]})}function wn(){return n.jsx("div",{className:"flex justify-start",role:"status","aria-label":"Agent is working",children:n.jsx("div",{className:"flex items-center gap-2 rounded-2xl rounded-bl-sm border border-border bg-card px-3.5 py-2.5",children:n.jsx("div",{className:"flex items-center gap-1",children:[0,150,300].map(e=>n.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:`${e}ms`}},e))})})})}function Nn({text:e}){return n.jsx("div",{className:"flex items-center justify-center text-[11px] text-muted-foreground py-0.5",children:n.jsx("span",{className:"animate-pulse",children:e})})}function kn({text:e}){return n.jsxs("div",{className:"flex items-center justify-center gap-1.5 text-[11px] text-muted-foreground py-0.5",children:[n.jsx(J,{className:"h-3 w-3 animate-spin","aria-hidden":"true"}),n.jsx("span",{children:e})]})}function Cn({lines:e,streamingText:t,working:r,statusText:s,notice:d,opTree:u,toolSessions:c,onRetry:a,onDecision:m}){const p=l.useRef(null),N=l.useRef(null),j=l.useRef(null);l.useEffect(()=>{var w,L;const g=N.current;if(!(!g||!(g.scrollHeight-g.scrollTop-g.clientHeight<120))){if(typeof requestAnimationFrame!="function"){(L=(w=p.current)==null?void 0:w.scrollIntoView)==null||L.call(w,{block:"end"});return}return j.current!=null&&cancelAnimationFrame(j.current),j.current=requestAnimationFrame(()=>{var k,O;j.current=null,(O=(k=p.current)==null?void 0:k.scrollIntoView)==null||O.call(k,{block:"end"})}),()=>{j.current!=null&&(cancelAnimationFrame(j.current),j.current=null)}}},[e.length,t,r,s,d,u]);const{absorbedPlanByLine:S,suppressedPlanLines:_}=l.useMemo(()=>{var L;const g={},$=new Set;let w;for(const k of e){if(k.role==="plan"&&k.plan){w=k;continue}const O=(L=k.interactionRequest)==null?void 0:L.category;k.role==="interaction"&&(O==="plan_phase"||O==="plan_amendment")&&w!=null&&w.plan&&(g[k.id]=w.plan,$.add(w.id))}return{absorbedPlanByLine:g,suppressedPlanLines:$}},[e]),A=t.length>0,V=r&&!A;return n.jsx("div",{ref:N,className:"flex-1 min-h-0 overflow-y-auto px-4 py-4",children:n.jsxs("div",{className:"flex flex-col gap-2.5 max-w-3xl mx-auto",children:[e.map(g=>_.has(g.id)?null:n.jsx("div",{"data-testid":"chat-line","data-role":g.role,children:n.jsx(bn,{line:g,toolSession:g.toolSessionRef?c[g.toolSessionRef]:void 0,onRetry:a,onDecision:m,live:r,absorbedPlan:S[g.id]})},g.id)),A&&n.jsx(vn,{text:t}),V&&n.jsx(wn,{}),r&&d&&n.jsx(Nn,{text:d}),r&&u.length>0&&n.jsx(Wt,{nodes:u}),r&&s&&n.jsx(kn,{text:s}),n.jsx("div",{ref:p})]})})}function In({disabled:e,placeholder:t,onSend:r}){const[s,d]=l.useState(""),u=()=>{const a=s.trim();!a||e||(r(a),d(""))},c=a=>{a.key==="Enter"&&!a.shiftKey&&(a.preventDefault(),u())};return n.jsxs("div",{className:"flex items-end gap-2 px-4 py-3 border-t border-border bg-card",children:[n.jsx("textarea",{rows:1,value:s,disabled:e,placeholder:t,onChange:a=>d(a.target.value),onKeyDown:c,className:b("flex-1 resize-none max-h-40 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm","placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring","disabled:cursor-not-allowed disabled:opacity-50")}),n.jsx(X,{size:"icon",className:"h-9 w-9 shrink-0",disabled:e||!s.trim(),onClick:u,title:"Send",children:n.jsx(jt,{className:"h-4 w-4"})})]})}function P(e,t){return`/sessions/api/${encodeURIComponent(e)}/${encodeURIComponent(t)}`}async function Q(e,t){if((e.headers.get("content-type")??"").includes("application/json"))try{const r=await e.json(),s=r==null?void 0:r.error;if(typeof s=="string"&&s.trim()!=="")return s}catch{}switch(e.status){case 401:return"You are no longer signed in. Reload the page to continue.";case 403:return"You do not have access to this conversation.";case 503:return"That could not be checked right now. Try again in a moment.";default:return t}}async function Un(e,t,r){const s=await fetch(`/sessions/api/${encodeURIComponent(e)}/${encodeURIComponent(t)}/message`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:r})});if(!s.ok)throw new Error(`send session message: request failed (${s.status})`)}function Rn(e,t,r){const s=l.useCallback(async(d,u,c)=>{try{const a=await fetch(`${P(e,t)}/decision`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({category:u,requestRef:d,actionId:c})});if(!a.ok){console.error("interaction decision: request failed",{ns:e,name:t,status:a.status});const m=await Q(a,`Failed to submit decision (${a.status}).`);r(m)}}catch(a){console.error("interaction decision: request errored",{ns:e,name:t,err:a}),r("Network error while trying to submit decision.")}},[e,t,r]);return l.useCallback((d,u,c)=>{s(d,u,c)},[s])}function Sn(e){if(e.class==="already_resolved"){let r="someone else";e.originalDecider&&(r=e.originalDecider.email||e.originalDecider.externalId||r);let s=`This request was already resolved by ${r}`;return e.originalOutcome&&(s+=` (${e.originalOutcome})`),s+"."}return((e.reason||"").trim()||"your click could not be applied")+"."}function Tn(e){switch(e.reason){case"succeeded":return"Conversation ended.";case"failed":return`Conversation ended: ${e.failureMessage||e.failureReason||"the agent failed"}.`;case"idle_timeout":return"Conversation ended after being idle too long.";case"server_shutdown":return"Conversation ended: the server is restarting.";default:return"Conversation ended."}}function Ne(e,t){return e.map(r=>r.kind==="plan"?{id:`plan:${r.plan.planName}`,role:"plan",text:"",plan:r.plan}:{id:t(),role:r.role,text:r.text})}const ke="Earlier messages could not be loaded. The live conversation still works.",_n="Starting…",Ce="Working…",Ie={text:"",ephemeral:!1};function Re(e){return e.ephemeral&&e.text!==Ce?{text:Ce,ephemeral:!0}:e}function $n(e,t){return`/artifact-download?${new URLSearchParams({artifactId:t.artifactId,sessionRef:`${e.namespace}/${e.name}`,fn:t.filename||t.artifactId}).toString()}`}const qn=new Set(["startup","scheduling"]);function Dn({ns:e,name:t,readOnly:r,onUserSend:s,onSessionEnded:d}){const[u,c]=l.useState([]),[a,m]=l.useState(!1),[p,N]=l.useState([]),{startup:j}=Le(),S=j===null?p:p.filter(o=>!qn.has(o.kind)),_=r||a,A=l.useCallback(()=>{m(!0),d==null||d()},[d]),V=l.useRef(`${e}/${t}`),g=l.useCallback(o=>o===V.current,[]),[$,w]=l.useState(!1),[L,k]=l.useState(Ie),[O,Me]=l.useState(""),[Fe,ee]=l.useState(null),[Be,te]=l.useState([]),[He,Je]=l.useState({}),ne=l.useRef(""),U=l.useCallback(o=>{const f=o(ne.current);ne.current=f,Me(f)},[]),[Ve,Ye]=l.useState(null),re=l.useRef(null),q=l.useCallback(o=>{re.current=o,Ye(o)},[]),Ke=l.useRef(0),I=l.useCallback(()=>`l${Ke.current++}`,[]),C=l.useCallback((o,f,h)=>{c(i=>[...i,{id:I(),role:o,text:f,queued:h==null?void 0:h.queued,attachments:h==null?void 0:h.attachments}])},[I]),he=l.useCallback(o=>{o.lead&&c(f=>[...f,{id:I(),role:"notice",text:"",notice:o}])},[I]),Y=l.useRef(!1),E=l.useRef(!1),K=l.useCallback(()=>{E.current=!0,w(!0),ee(null),k({text:_n,ephemeral:!0}),U(()=>"")},[U]),R=l.useCallback(()=>{E.current=!1,w(!1),ee(null),te([]),k(Ie),U(()=>"")},[U]),se=l.useCallback(()=>{const o=ne.current;o&&C("agent",o)},[C]),ae=l.useCallback(()=>{c(o=>o.map(f=>f.queued?{...f,queued:!1}:f))},[]),ge=l.useCallback(async()=>{const o=P(e,t),f=`${e}/${t}`;try{const[h,i]=await Promise.all([fetch(`${o}/messages`),fetch(`${o}/detail`)]);if(!h.ok)console.error("chat: resync transcript failed",{ns:e,name:t,status:h.status});else{const y=await h.json();if(!g(f))return;c(Ne(y.timeline??[],I))}if(!i.ok){console.error("chat: resync session detail failed",{ns:e,name:t,status:i.status});return}const x=await i.json();if(!g(f))return;x.phase!=="Running"&&R(),N(x.notices??[])}catch(h){console.error("chat: resync after reconnect errored",{ns:e,name:t,err:h})}},[e,t,I,R,g]);l.useEffect(()=>{let o=!1;return V.current=`${e}/${t}`,c([]),m(!1),R(),q(null),Y.current=!1,(async()=>{try{(async()=>{try{const i=await fetch(`${P(e,t)}/detail`);if(o||!i.ok)return;const x=await i.json();o||N(x.notices??[])}catch(i){console.error("chat: load session detail errored",{ns:e,name:t,err:i})}})();const f=await fetch(`${P(e,t)}/messages`);if(o)return;if(f.ok){const i=await f.json();if(o)return;c(Ne(i.timeline??[],I));return}console.error("chat: load transcript failed",{ns:e,name:t,status:f.status});const h=f.status===501?"History replay is unavailable here; resuming live.":await Q(f,ke);if(o)return;c([{id:I(),role:f.status===501?"system":"error",text:h}])}catch(f){if(console.error("chat: load transcript errored",{ns:e,name:t,err:f}),o)return;c([{id:I(),role:"error",text:ke}])}})(),()=>{o=!0}},[e,t,I,R,q]);const ze=l.useCallback(o=>{var f,h;switch(o.type){case"user_message":{const i=o.payload,x=i.text;U(()=>"");const y=(i.attachments??[]).filter(v=>v.artifactId).map(v=>({filename:v.filename||v.artifactId,mime:v.mime,url:$n(i.session,v)}));C("agent",x,y.length?{attachments:y}:void 0);break}case"user_echo":{const i=o.payload;c(x=>i.requestID&&x.some(y=>y.requestId===i.requestID)?x:[...x,{id:I(),role:"user",text:i.text}]);break}case"notification":{if(!E.current)break;const i=o.payload;w(!0),k({text:i.text,ephemeral:i.ephemeral===!0});break}case"plan_update":{const i=o.payload.payload;E.current&&w(!0);const x=`plan:${i.planName}`;c(y=>{const v=y.findIndex(T=>T.role==="plan"&&T.id===x);if(v>=0){const T=y.slice();return T[v]={...y[v],plan:i},T}return[...y,{id:x,role:"plan",text:"",plan:i}]});break}case"stream_delta":{if(!E.current)break;const i=o.payload.payload;(i==null?void 0:i.eventType)==="text_delta"&&i.text&&(w(!0),k(Re),U(x=>x+i.text));break}case"turn_progress":if(!E.current)break;w(!0),k(Re),ee(o.payload.payload);break;case"operation_activity":{if(!E.current){te([]);break}const i=o.payload.payload;te(i.cleared?[]:i.operations??[]);break}case"turn_activity":{o.payload.active?(E.current=!0,w(!0)):(se(),R(),ae());break}case"interrupt_applied":{const i=o.payload;if(i.requestID!==re.current)break;q(null),i.outcome==="interrupted"?(C("system","Interrupting — sending your message(s) now."),ae()):C("system","Couldn't interrupt — "+(i.reason||"the runner declined."));break}case"send_error":R(),C("error",o.payload.err||"The agent failed to process a message.");break;case"session_ended":{A(),se(),R(),C("system",Tn(o.payload));break}case"live_view_offer":{const i=o.payload.url;i&&c(x=>[...x,{id:I(),role:"system",text:"",liveViewUrl:i}]);break}case"tool_session_event":case"tool_session_delta":{Je(y=>Et(y,o));const i=(h=(f=o.payload)==null?void 0:f.payload)==null?void 0:h.toolCallRef;if(!i)break;const x=`toolsession:${i}`;c(y=>y.some(v=>v.id===x)?y:[...y,{id:x,role:"toolSession",text:"",toolSessionRef:i}]);break}case"interaction_request":{const i=o.payload.payload,x=`interaction:${i.requestRef}`;c(y=>{const v=y.findIndex(T=>T.role==="interaction"&&T.id===x);if(v>=0){const T=y.slice();return T[v]={...y[v],interactionRequest:i},T}return[...y,{id:x,role:"interaction",text:"",interactionRequest:i}]});break}case"interaction_applied":{const i=o.payload.payload,x=`interaction:${i.requestRef}`;c(y=>y.map(v=>v.id===x?{...v,interactionApplied:i}:v));break}case"interaction_decision_rejected":{const i=o.payload.payload;C(i.class==="already_resolved"?"system":"error",Sn(i));break}}},[C,I,U,q,A,R,se,ae]);Ht(ze);const B=Jt(),z=l.useRef(!1);l.useEffect(()=>{if(B==="connected"){z.current=!1,Y.current&&(Y.current=!1,ge());return}if(B==="connecting"){z.current=!1;return}Y.current=!0,B==="offline"&&!_&&!z.current&&(z.current=!0,R(),C("error","Connection lost — reload the page to reconnect."))},[B,_,C,ge,R]);const H=l.useCallback((o,f,h)=>{c(i=>i.map(x=>x.id===o?{...x,sendStatus:f,sendError:h}:x))},[]),W=l.useCallback(async(o,f,h)=>{const i=`${e}/${t}`;H(o,"pending");try{const x=await fetch(`${P(e,t)}/message`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text:f,requestId:h})});if(!g(i))return;if(!x.ok){console.error("chat: send failed",{ns:e,name:t,status:x.status});const v=await Q(x,`Failed to send (${x.status}).`);if(!g(i))return;H(o,"failed",v),R();return}const y=await x.json();if(!g(i))return;H(o,"sent"),y.notice&&he(y.notice),y.ended&&(A(),R())}catch(x){if(console.error("chat: send errored",{ns:e,name:t,err:x}),!g(i))return;H(o,"failed","Network error while sending your message."),R()}},[e,t,H,he,A,R,g]),We=l.useCallback(o=>{s==null||s();const f=I(),h=crypto.randomUUID();c(i=>[...i,{id:f,role:"user",text:o,queued:$,sendStatus:"pending",requestId:h}]),K(),W(f,o,h)},[$,I,K,s,W]),Ge=l.useCallback((o,f,h)=>{K(),W(o,f,h)},[K,W]),Qe=l.useCallback(async()=>{if(re.current)return;const o=`${e}/${t}`,f=crypto.randomUUID();q(f);try{const h=await fetch(`${P(e,t)}/interrupt`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:f})});if(!h.ok){console.error("chat: interrupt failed",{ns:e,name:t,status:h.status});const i=await Q(h,`Failed to interrupt (${h.status}).`);if(!g(o))return;C("error",i),q(null)}}catch(h){if(console.error("chat: interrupt errored",{ns:e,name:t,err:h}),!g(o))return;C("error","Network error while trying to interrupt."),q(null)}},[e,t,C,q,g]),Xe=l.useCallback(o=>{g(`${e}/${t}`)&&C("error",o)},[e,t,g,C]),Ze=Rn(e,t,Xe),et=$?It(Fe):null,tt=_?"This conversation has ended.":"Message the agent…";return n.jsx(kt,{delayDuration:200,children:n.jsxs("div",{className:"flex min-h-0 flex-1 flex-col bg-background text-foreground","data-testid":"chat-view",children:[n.jsx("div",{className:"flex items-center justify-end px-3 py-1 border-b border-border bg-card/40",children:n.jsx(Yt,{state:B})}),n.jsx(Cn,{lines:u,streamingText:O,working:$,statusText:et,notice:L.text,opTree:Be,toolSessions:He,onRetry:Ge,onDecision:Ze}),$&&u.some(o=>o.queued)&&n.jsx("div",{className:"flex justify-center px-4 pt-2 bg-card",children:n.jsx(X,{variant:"secondary",size:"sm",disabled:Ve!==null,onClick:Qe,children:"Interrupt & Send Now"})}),S.length>0&&n.jsx("div",{className:"mx-4 mb-2 flex flex-col gap-1.5",children:S.map((o,f)=>n.jsxs("div",{"data-testid":`session-notice-${o.kind}`,className:b("rounded-md border px-3 py-2 text-xs leading-snug",o.tone==="degraded"?"border-warning/30 bg-warning/10 text-warning":"border-border/60 bg-muted/40 text-muted-foreground"),children:[n.jsx("div",{children:o.lead}),o.nextStep&&n.jsx("div",{className:"mt-0.5 font-medium",children:o.nextStep}),o.body&&n.jsx("div",{className:"mt-1 break-all font-mono text-[10px] opacity-70",children:o.body})]},`${o.kind}:${f}`))}),n.jsx(In,{disabled:_,placeholder:tt,onSend:We})]})})}function Pn(){const{startup:e}=Le();return e===null?null:n.jsxs("div",{"data-testid":"session-shell-startup",role:"status",className:"flex items-center gap-2 border-b border-border bg-card/60 px-4 py-2 text-sm text-muted-foreground",children:[n.jsx(J,{className:"h-4 w-4 shrink-0 animate-spin","aria-hidden":"true"}),n.jsxs("span",{children:[e.short!==null?n.jsxs(n.Fragment,{children:[n.jsx("span",{className:"hidden sm:inline",children:e.text}),n.jsx("span",{className:"sm:hidden",children:e.short})]}):e.text,e.stillTrying&&n.jsx("span",{"data-testid":"session-shell-startup-still-trying",children:" — still trying"})]})]})}export{Dn as C,on as I,On as S,Pn as a,Rn as b,Ht as c,Sn as i,Un as s,Le as u}; diff --git a/pkg/web/webui/webassets/dist/admin.C9jbAoMt.js b/pkg/web/webui/webassets/dist/admin.C9jbAoMt.js deleted file mode 100644 index aa4ce28a..00000000 --- a/pkg/web/webui/webassets/dist/admin.C9jbAoMt.js +++ /dev/null @@ -1 +0,0 @@ -import{c as Ge,r as en,f as Pe,s as _t,C as me,b as he,d as fe,e as pe,T as tn,g as sn,h as nn,i as rn,A as I,a as F,j as Y,k as Q,l as M,n as y,o as X,p as v,B as U,D as Je,q as Ye,t as Qe,u as Xe,v as Ze,w as Be,x as et,y as wt,S as an,z as on,E as ln,F as cn,G as dn,H as un,I as xn,J as mn,K as hn,L as fn,m as pn}from"./mount.BOEH1oNx.js";import{r as d,j as e,L as gn,A as jn,T as bn,S as vn,H as yn,B as Nn,W as wn,a as kn,G as Sn,F as Cn,b as $n,K as Tn,U as ms,c as Ln,d as An,e as En,f as Dn,g as Rn,h as On,D as In,N as hs,i as Fn,k as Mn,l as Pn,M as Kn,C as _n,m as Un,n as Vn,o as Wn,p as Hn,q as fs,X as zn,s as qn,t as Gn,u as Jn}from"./vendor.C5MRNvUt.js";import{B as z}from"./badge.CIlGQKPh.js";import{M as kt}from"./model-name.DtAiu0oG.js";const Ke="M159.186 7.59677C164.993 -2.53226 179.604 -2.53226 185.411 7.59677L342.572 281.702C348.349 291.779 341.075 304.335 329.459 304.335H15.138C3.52262 304.335 -3.75273 291.779 2.02468 281.702L159.186 7.59677Z",Yn="M178.855 133.226C175.951 128.161 168.646 128.161 165.742 133.226L117.254 217.794C114.366 222.832 118.003 229.11 123.811 229.11H220.786C226.594 229.11 230.232 222.832 227.343 217.794L178.855 133.226Z",Te=[1,2,3,4,5].map(t=>`hsl(var(--chart-${t}))`),_e=12,Qn=3.2,Xn=.55,Zn=.35;function Bn(){return typeof window<"u"&&typeof window.matchMedia=="function"?window.matchMedia("(prefers-reduced-motion: reduce)").matches:!1}function Le(t,s,n,r){if(t){if(n<=.05){t.style.opacity="0";return}t.style.opacity="1",t.style.stroke=r,t.style.strokeDasharray=`${n} ${100-n}`,t.style.strokeDashoffset=`${-s}`}}function er({className:t,title:s,active:n=!1,lapMs:r=2400,band:a=12,bandWidth:o=12,handoverEvery:i=2,...l}){const c=d.useRef(null),u=d.useRef(null),h=d.useRef([]),N=n&&!Bn();d.useEffect(()=>{if(!N)return;const b=performance.now();let x=0;const j=$=>{const C=($-b)/r,E=C*100%100,m=(E-a+100)%100,w=Math.floor(C*3),R=Math.floor(w/i),O=Te[R%Te.length],L=Te[(R-1+Te.length)%Te.length],k=w/3*100%100,g=(E-k+100)%100,T=w%i===0&&gcancelAnimationFrame(x)},[N,r,a,i]);const p={opacity:0};return e.jsxs("svg",{viewBox:"0 0 345 305",fill:"currentColor","aria-hidden":s?void 0:!0,role:s?"img":void 0,className:Ge("shrink-0 overflow-visible",t),"data-relay":N?"on":"off",...l,children:[s?e.jsx("title",{children:s}):null,e.jsx("path",{d:Ke+Yn,style:{opacity:N?Zn:1,transition:"opacity 240ms ease-out"},"data-ink":!0}),e.jsxs("g",{fill:"none",strokeWidth:o,strokeLinecap:"round",strokeLinejoin:"round",style:{opacity:N?1:0,transition:"opacity 240ms ease-out"},children:[Array.from({length:_e},(b,x)=>e.jsx("path",{ref:j=>{h.current[x]=j},d:Ke,pathLength:100,style:p,strokeLinecap:"butt","data-seg":"tail"},x)),e.jsx("path",{ref:c,d:Ke,pathLength:100,style:p,"data-seg":"a"}),e.jsx("path",{ref:u,d:Ke,pathLength:100,style:p,"data-seg":"b"})]})]})}const ps=[{group:"",items:[{id:"overview",label:"Overview",icon:gn}]},{group:"Live",items:[{id:"sessions",label:"Agent sessions",icon:jn},{id:"toolcalls",label:"Live tool calls",icon:bn},{id:"approvals",label:"Approvals",icon:vn},{id:"workshops",label:"Workshops",icon:yn}]},{group:"Config",items:[{id:"agents",label:"Agents",icon:Nn},{id:"tools",label:"Tools",icon:wn},{id:"skills",label:"Skills",icon:kn},{id:"sources",label:"Sources",icon:Sn},{id:"directory",label:"Directory",icon:Cn},{id:"channels",label:"Channels",icon:$n},{id:"identity",label:"Identity",icon:Tn},{id:"users",label:"Users",icon:ms},{id:"access",label:"Access",icon:Ln},{id:"settings",label:"Settings",icon:An}]},{group:"Audit",items:[{id:"logs",label:"Logs",icon:En},{id:"sessionsAudit",label:"Sessions",icon:Dn},{id:"budget",label:"Budget",icon:Rn},{id:"artifacts",label:"Artifacts",icon:On},{id:"memory",label:"Memory",icon:In},{id:"knowledge",label:"Knowledge",icon:hs}]}],gs={overview:{title:"Overview",sub:"Platform at a glance — usage, spend, and health"},sessions:{title:"Agent sessions",sub:"Live agent sessions across the platform"},toolcalls:{title:"Live tool calls",sub:"Tool invocations as they happen, across every session"},approvals:{title:"Approvals",sub:"Tool calls & leakage gates awaiting a decision"},workshops:{title:"Workshops",sub:"Agent-builder sessions' drafted bundles — install, decline, or kill"},agents:{title:"Agents",sub:"Configured AgentClasses and their readiness · oap class / kubectl agentclass"},tools:{title:"Tools",sub:"MCP servers, sandboxed CLIs, sidecars, toolkits · oap tools"},skills:{title:"Skills",sub:"Instruction & executable skills available to agents · oap skill"},sources:{title:"Sources",sub:"Skill sources synced from git · oap skill source"},directory:{title:"Directory",sub:"Directory syncs feeding SpiceDB — Slack, GitHub, 1Password · oap directory"},channels:{title:"Channels",sub:"Where agents are reachable · oap channel / kubectl channel"},identity:{title:"Identity",sub:"Agent identities, credentials, and login providers · oap identity / oap idp"},users:{title:"Users",sub:"People linked to the platform · oap user-identity"},access:{title:"Access",sub:"Platform admins & the SpiceDB authorization schema · oap platform / oap spicedb"},settings:{title:"Settings",sub:"Cluster configuration — defaults, ceilings, security · oap settings"},logs:{title:"Logs",sub:"Cross-session audit — approvals, authz, tool calls, scope changes · oap audit"},sessionsAudit:{title:"Sessions",sub:"Every agent session, current and historical"},budget:{title:"Budget",sub:"Estimated spend by model, agent, session, user"},artifacts:{title:"Artifacts",sub:"Rendered outputs agents produced · oap artifact"},memory:{title:"Memory",sub:"The searchable, authorized memory store · oap memory"},knowledge:{title:"Knowledge",sub:"The knowledge graph — entities, facts, communities · oap kg"}};class J extends Error{constructor(s,n){super(s),this.status=n}}class tt extends J{constructor(s,n,r=[],a=[],o=[],i=[]){super(s,400),this.questions=n,this.skillClones=r,this.warnings=a,this.channels=o,this.conflicts=i}}class st extends J{constructor(s,n,r=[],a=[],o=[],i=[]){super(s,409),this.conflicts=n,this.skillClones=r,this.channels=a,this.warnings=o,this.questions=i}}async function js(t,s){var a,o,i;const n=await fetch(`${t}/agents/oap-install`,s);if(n.ok)return await n.json();let r={};try{r=await n.json()}catch{}throw n.status===400&&((((a=r.questions)==null?void 0:a.length)??0)>0||(((o=r.channels)==null?void 0:o.length)??0)>0||(((i=r.conflicts)==null?void 0:i.length)??0)>0)?new tt(r.error||"missing required question(s)",r.questions??[],r.skillClones??[],r.warnings??[],r.channels??[],r.conflicts??[]):n.status===409&&r.conflicts&&r.conflicts.length>0?new st(r.error||"install would overwrite pre-existing object(s)",r.conflicts,r.skillClones??[],r.channels??[],r.warnings??[],r.questions??[]):new J(r.error||`request failed (${n.status})`,n.status)}class ht extends J{constructor(s,n){super(s,400),this.questions=n}}async function bs(t,s,n){var o;const r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({setupToken:s,answers:n})});if(r.ok)return await r.json();let a={};try{a=await r.json()}catch{}throw r.status===400&&(((o=a.questions)==null?void 0:o.length)??0)>0?new ht(a.error||"missing required channel answer(s)",a.questions??[]):new J(a.error||`request failed (${r.status})`,r.status)}const Ut=(t,s,n)=>bs(`${t}/agents/channel-setup`,s,n),tr=(t,s,n)=>bs(`${t}/agents/channel-handoff`,s,n),sr=(t,s)=>js(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),nr=(t,s,n)=>{var a;const r=new FormData;return r.append("file",s),r.append("namespace",n.namespace),n.name&&r.append("name",n.name),n.values&&r.append("values",JSON.stringify(n.values)),(a=n.adopt)!=null&&a.length&&r.append("adopt",JSON.stringify(n.adopt)),js(t,{method:"POST",body:r})};async function rr(t,s){const n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(n.ok)return await n.json();let r={};try{r=await n.json()}catch{}throw n.status===400&&r.questions&&r.questions.length>0?new tt(r.error||"missing required question(s)",r.questions,r.skillClones??[],r.warnings??[]):n.status===409&&r.conflicts&&r.conflicts.length>0?new st(r.error||"install would overwrite pre-existing object(s)",r.conflicts,r.skillClones??[]):new J(r.error||`request failed (${n.status})`,n.status)}const ar=(t,s,n,r)=>rr(`${t}/workshops/${s}/${n}/install`,r);async function St(t){if(!t.ok){let s=`request failed (${t.status})`;try{const n=await t.json();n.error&&(s=n.error)}catch{}throw new J(s,t.status)}return await t.json()}const _=async t=>St(await fetch(t)),or=t=>_(`${t}/overview`),lr=t=>_(`${t}/health`),ir=t=>_(`${t}/cluster`),cr=t=>_(`${t}/budget`),dr=t=>_(`${t}/toolcalls`),ur=(t,s,n)=>_(`${t}/sessions/${s}/${n}/logs`),xr=t=>_(`${t}/approvals`),Ct=async(t,s)=>await _(`${t}/config/${s}`)??[],Vt=(t,s,n)=>_(`${t}/config/${s}/${n}`),mr=t=>_(`${t}/access`),hr=t=>_(`${t}/artifacts`),fr=(t,s,n)=>_(`${t}/artifacts/${s}/${n}`),pr=t=>_(`${t}/workshops`),gr=(t,s={})=>{const n=new URLSearchParams;s.q&&n.set("q",s.q),s.kind&&n.set("kind",s.kind);const r=n.toString();return _(`${t}/memory${r?`?${r}`:""}`)},jr=t=>_(`${t}/config/settings`),Fe=(t,s,n={})=>{const r=new URLSearchParams(n).toString();return`${t}/kg/${s}${r?`?${r}`:""}`},br=(t,s)=>_(Fe(t,"search",s?{q:s}:{})),vr=(t,s)=>_(Fe(t,"entity",{uuid:s})),yr=(t,s)=>_(Fe(t,"facts",{uuid:s})),Nr=(t,s)=>_(Fe(t,"related",{uuid:s})),wr=t=>_(Fe(t,"communities")),ft=async(t,s)=>St(await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})),vs=async t=>{const s=await fetch(t,{method:"DELETE"});!s.ok&&s.status!==404&&await St(s)};function kr(t,s){const n=new EventSource(`${t}/sessions/stream`);let r;const a=()=>{r&&clearTimeout(r),s.onStale(!1),r=setTimeout(()=>s.onStale(!0),15e3)};return n.addEventListener("session",o=>{a(),s.onSession(JSON.parse(o.data))}),n.addEventListener("remove",o=>{a(),s.onRemove(JSON.parse(o.data))}),n.addEventListener("heartbeat",a),n.onerror=()=>s.onStale(!0),a(),()=>{r&&clearTimeout(r),n.close()}}const ee=t=>`${t.namespace}/${t.name}`;function Se(t){try{const s=new URL(t);return s.protocol==="http:"||s.protocol==="https:"?t:null}catch{return null}}const Sr={gke:"GKE",eks:"EKS",aks:"AKS",local:"Local",unknown:"Cluster"},Ie="h-3.5 w-3.5 shrink-0";function Cr(){return e.jsxs("svg",{viewBox:"0 0 24 24",className:Ie,"aria-hidden":"true",fill:"none",children:[e.jsx("path",{d:"M12 2.5 L20.16 7.25 V16.75 L12 21.5 L3.84 16.75 V7.25 Z",fill:"#4285F4",fillOpacity:"0.16",stroke:"#4285F4",strokeWidth:"1.5",strokeLinejoin:"round"}),e.jsx("circle",{cx:"12",cy:"12",r:"3",stroke:"#4285F4",strokeWidth:"1.5"})]})}function $r(){return e.jsxs("svg",{viewBox:"0 0 24 24",className:Ie,"aria-hidden":"true",fill:"none",children:[e.jsx("path",{d:"M4 12.5 C 8 16.2, 16 16.2, 20 12.5",stroke:"#FF9900",strokeWidth:"2",strokeLinecap:"round"}),e.jsx("path",{d:"M16.6 12.2 L20.4 12.4 L18.9 15.6 Z",fill:"#FF9900"})]})}function Tr(){return e.jsxs("svg",{viewBox:"0 0 24 24",className:Ie,"aria-hidden":"true",children:[e.jsx("path",{d:"M12.8 3 L21 20 H14.8 L12.4 13.6 Z",fill:"#0078D4"}),e.jsx("path",{d:"M10.8 8 L3.2 20 H15 L12.8 15.4 H9 Z",fill:"#0078D4",fillOpacity:"0.7"})]})}function Lr({type:t}){switch(t){case"gke":return e.jsx(Cr,{});case"eks":return e.jsx($r,{});case"aks":return e.jsx(Tr,{});case"local":return e.jsx(Mn,{"aria-hidden":"true",className:`${Ie} text-muted-foreground`});default:return e.jsx(Fn,{"aria-hidden":"true",className:`${Ie} text-muted-foreground`})}}function Ar({apiBase:t}){const[s,n]=d.useState(null);if(d.useEffect(()=>{let u=!0;return ir(t).then(h=>{u&&n(h)}).catch(h=>{console.warn("cluster info fetch failed",h)}),()=>{u=!1}},[t]),!s)return null;const r=Sr[s.type]??"Cluster",a=s.name||r||"cluster",o=!!s.name,i=s.consoleURL?Se(s.consoleURL):null,l=e.jsxs(e.Fragment,{children:[e.jsx(Lr,{type:s.type}),e.jsx("span",{className:"max-w-[16rem] truncate font-medium text-foreground",children:a}),o&&e.jsx("span",{className:"font-mono text-[10px] uppercase tracking-wide text-muted-foreground",children:r})]}),c="flex items-center gap-1.5 rounded-full border bg-card/60 px-2 py-0.5 text-xs";return i?e.jsx("a",{href:i,target:"_blank",rel:"noopener noreferrer",title:`Open ${a} in the cloud console`,className:`${c} transition-colors hover:border-primary/50 hover:bg-accent/40`,children:l}):e.jsx("span",{className:c,title:a,children:l})}const Er={email:"admin@example.com"};function Dr(){typeof window>"u"||window.location.assign("/admin/logout")}function Rr({view:t,onNavigate:s,currentUser:n=Er,apiBase:r="/admin/api",onSignOut:a=Dr,children:o}){const i=gs[t],[l,c]=d.useState(!1),[u,h]=d.useState(!1),[N,p]=d.useState(()=>en()),b=d.useRef(null);return d.useEffect(()=>{if(!l)return;function x($){b.current&&!b.current.contains($.target)&&c(!1)}function j($){$.key==="Escape"&&c(!1)}return document.addEventListener("mousedown",x),document.addEventListener("keydown",j),()=>{document.removeEventListener("mousedown",x),document.removeEventListener("keydown",j)}},[l]),e.jsxs("div",{className:"flex h-screen flex-col bg-background text-foreground",children:[e.jsxs("header",{className:"flex shrink-0 items-center border-b bg-card/30 px-5 py-2.5",children:[e.jsxs("div",{className:"mr-auto flex items-center gap-2.5",onPointerEnter:()=>h(!0),onPointerLeave:()=>h(!1),children:[e.jsx(er,{active:u,bandWidth:32,band:14,className:"h-5 w-5 text-foreground"}),e.jsx("span",{className:"text-sm font-semibold",children:"Open Agent Primitives"}),e.jsx(Ar,{apiBase:r})]}),e.jsx("button",{type:"button","aria-label":Pe(N)==="dark"?"Switch to light theme":"Switch to dark theme",title:Pe(N)==="dark"?"Light theme":"Dark theme",onClick:()=>{const x=Pe(N)==="dark"?"light":"dark";_t(x),p(x)},className:"mr-2 grid h-7 w-7 place-items-center rounded-md border text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground",children:Pe(N)==="dark"?e.jsx(Pn,{className:"h-3.5 w-3.5","aria-hidden":"true"}):e.jsx(Kn,{className:"h-3.5 w-3.5","aria-hidden":"true"})}),e.jsxs("div",{ref:b,className:"relative",children:[e.jsxs("button",{type:"button","aria-haspopup":"menu","aria-expanded":l,"aria-label":"User menu",onClick:()=>c(x=>!x),className:"flex items-center gap-2 rounded-md border bg-card px-2 py-1 transition-colors hover:bg-accent/50",children:[e.jsx("div",{"aria-hidden":"true",className:"grid h-6 w-6 shrink-0 place-items-center rounded-full border border-border bg-surface-3 font-mono text-[11px] font-medium uppercase leading-none text-foreground",children:n.email.trim()[0]||"?"}),e.jsx("div",{className:"text-left leading-tight",children:e.jsx("div",{className:"font-mono text-xs",children:n.email})}),e.jsx(_n,{className:`h-3.5 w-3.5 text-muted-foreground transition-transform ${l?"rotate-180":""}`})]}),l&&e.jsxs("div",{role:"menu",className:"absolute right-0 z-50 mt-1 w-44 overflow-hidden rounded-md border bg-card shadow-lg",children:[e.jsx("div",{className:"px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",children:"Appearance"}),e.jsx("div",{role:"group","aria-label":"Appearance",className:"flex gap-1 px-2 pb-2",children:["dark","light","system"].map(x=>e.jsx("button",{type:"button",role:"menuitemradio","aria-checked":N===x,onClick:()=>{_t(x),p(x)},className:["flex-1 rounded px-2 py-1 text-xs capitalize transition-colors",N===x?"bg-surface-active text-foreground":"text-muted-foreground hover:bg-surface-2 hover:text-foreground"].join(" "),children:x},x))}),e.jsx("div",{className:"border-t"}),e.jsxs("button",{type:"button",role:"menuitem",onClick:()=>{c(!1),a()},className:"flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-accent",children:[e.jsx(Un,{"aria-hidden":"true",className:"h-4 w-4 text-muted-foreground"}),"Sign Out"]})]})]})]}),e.jsxs("div",{className:"flex min-h-0 flex-1",children:[e.jsx("aside",{className:"flex w-48 shrink-0 flex-col border-r bg-card/20 lg:w-60",children:e.jsx("nav",{className:"flex-1 overflow-y-auto px-2 py-1",children:ps.map(x=>e.jsxs("div",{children:[x.group&&e.jsx("p",{className:"mt-3 mb-0.5 px-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",children:x.group}),x.items.map(j=>{const $=j.icon,C=t===j.id;return e.jsxs("button",{type:"button","aria-current":C?"page":void 0,onClick:()=>s(j.id),className:["flex w-full items-center gap-2 py-1.5 text-left text-sm transition-colors",C?"-ml-2 rounded-r-md bg-surface-active pl-4 pr-2 font-medium text-foreground shadow-[inset_3px_0_0_hsl(var(--state))]":"rounded-md px-2 text-foreground/80 hover:bg-surface-2 hover:text-foreground"].join(" "),children:[e.jsx($,{className:"h-4 w-4 shrink-0"}),j.label]},j.id)})]},x.group||"__root__"))})}),e.jsxs("div",{className:"flex min-w-0 flex-1 flex-col",children:[e.jsx("header",{className:"flex shrink-0 items-center justify-between border-b bg-card/10 px-6 py-3.5",children:e.jsxs("div",{children:[e.jsx("h1",{className:"text-base font-semibold",children:i.title}),e.jsx("p",{className:"text-xs text-muted-foreground",children:i.sub})]})}),e.jsx("main",{className:"flex-1 overflow-y-auto px-4 py-4 lg:px-6 lg:py-5",children:o})]})]})]})}function Or({view:t}){const s=gs[t];return e.jsxs(me,{className:"mt-2 border-dashed",children:[e.jsx(he,{children:e.jsx(fe,{className:"text-sm font-medium text-muted-foreground",children:s.title})}),e.jsx(pe,{children:e.jsx("p",{className:"text-xs text-muted-foreground",children:"This surface lands in a later Part 7 task."})})]})}const ve="/admin",Ir=new Set(ps.flatMap(t=>t.items.map(s=>s.id))),ys={session:"sessions",agent:"agents",tool:"tools",skill:"skills",source:"sources",directory:"directory",channel:"channels",identity:"identity",provider:"identity",artifact:"artifacts",user:"users"},Fr=Object.keys(ys),Mr=new Set(Fr);function Ns(t){return Mr.has(t)}const Wt={type:"view",view:"overview"};function Pr(t,s=""){const r=(t.startsWith(ve)?t.slice(ve.length):t).split("/").filter(Boolean);if(r.length===0)return Wt;const[a,...o]=r;if(r.length===1&&Ir.has(a)){const i=a,l=new URLSearchParams(s);return[...l].length>0?{type:"view",view:i,query:l}:{type:"view",view:i}}if(Ns(a)&&o.length>0){const i=o.join("/"),l=new URLSearchParams(s).get("tab")??void 0;return l?{type:"detail",entity:a,id:i,tab:l}:{type:"detail",entity:a,id:i}}return Wt}function ws(t){if(t.type==="detail"){const r=`${ve}/${t.entity}/${t.id}`;return t.tab?`${r}?tab=${encodeURIComponent(t.tab)}`:r}const s=t.query?t.query.toString():"";if(t.view==="overview"&&s==="")return ve;const n=`${ve}/${t.view}`;return s?`${n}?${s}`:n}function Kr(t){return t.type==="view"?t.view:ys[t.entity]}const pt=new Set;function _r(){for(const t of pt)t()}function Ur(t){return pt.add(t),typeof window<"u"&&window.addEventListener("popstate",t),()=>{pt.delete(t),typeof window<"u"&&window.removeEventListener("popstate",t)}}function Vr(){return typeof window>"u"?ve:window.location.pathname+window.location.search}function Wr(){return ve}function te(t){typeof window>"u"||(window.history.pushState({},"",ws(t)),_r())}function ks(){const t=d.useSyncExternalStore(Ur,Vr,Wr),s=t.indexOf("?"),n=s>=0?t.slice(0,s):t,r=s>=0?t.slice(s):"";return Pr(n,r)}function nt({title:t,subtitle:s,backLabel:n="Back",backRoute:r,tabs:a,activeTab:o,onTab:i,children:l,headerRight:c}){return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{children:[e.jsxs("button",{type:"button",onClick:()=>{r?te(r):typeof window<"u"&&window.history.back()},className:"mb-2 inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground",children:[e.jsx(Vn,{className:"h-3.5 w-3.5"}),n]}),e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("h2",{className:"truncate text-lg font-semibold",children:t}),s&&e.jsx("p",{className:"text-sm text-muted-foreground",children:s})]}),c&&e.jsx("div",{className:"shrink-0",children:c})]})]}),e.jsxs(tn,{value:o,onValueChange:i,children:[e.jsx(sn,{children:a.map(u=>e.jsx(nn,{value:u.id,children:u.label},u.id))}),a.map(u=>e.jsx(rn,{value:u.id,className:"mt-4",forceMount:!0,hidden:u.id!==o,children:u.id===o?l:null},u.id))]})]})}const Ss="ap_admin_";function Cs(){return typeof document<"u"}function Hr(t){if(!Cs())return null;const s=Ss+t+"=";for(const n of document.cookie.split(";")){const r=n.trimStart();if(r.startsWith(s))return decodeURIComponent(r.slice(s.length))}return null}function zr(t,s,n){if(!Cs())return;let r=`${Ss+t}=${encodeURIComponent(s)}; path=/; SameSite=Lax`;const a=365;r+=`; Expires=${new Date(Date.now()+a*864e5).toUTCString()}`,document.cookie=r}function qr(t){if(t.length>0&&t[0]==='"')try{const s=JSON.parse(t);if(typeof s=="string")return s}catch{}return t}function $t(t,s){const[n,r]=d.useState(()=>{const o=Hr(t);return o===null?s:qr(o)}),a=d.useCallback(o=>{r(o),zr(t,o)},[t]);return[n,a]}function $s({value:t,onChange:s}){return e.jsxs("div",{className:"inline-flex items-center rounded-md border bg-background p-0.5",children:[e.jsx(Ht,{label:"Grid view",active:t==="grid",onClick:()=>s("grid"),children:e.jsx(Wn,{className:"h-3.5 w-3.5"})}),e.jsx(Ht,{label:"Table view",active:t==="table",onClick:()=>s("table"),children:e.jsx(Hn,{className:"h-3.5 w-3.5"})})]})}function Ht({label:t,active:s,onClick:n,children:r}){return e.jsx("button",{type:"button","aria-label":t,"aria-pressed":s,onClick:n,className:`inline-flex h-6 w-6 items-center justify-center rounded ${s?"bg-accent text-primary":"text-muted-foreground hover:text-foreground"}`,children:r})}function Gr(t){const s=t.indexOf("/");return s<0?{ns:"",name:t}:{ns:t.slice(0,s),name:t.slice(s+1)}}function Jr(t){return t===""||t==="default"}function Z({id:t,ns:s,name:n,className:r}){const a=t!=null?Gr(t):{ns:s??"",name:n??""};return e.jsxs("span",{className:r,children:[!Jr(a.ns)&&e.jsx("span",{className:"text-muted-foreground",children:`${a.ns}/`}),a.name]})}function Yr(t){return t.button===0&&!t.metaKey&&!t.ctrlKey&&!t.shiftKey&&!t.altKey}function Ts({route:t,className:s,onClick:n,children:r}){const a=ws(t);return e.jsx("a",{href:a,className:s,onClick:o=>{n==null||n(o),Yr(o)&&(o.preventDefault(),te(t))},children:r})}function W({entity:t,id:s,tab:n,className:r,onClick:a,children:o}){return e.jsx(Ts,{route:{type:"detail",entity:t,id:s,...n?{tab:n}:{}},className:r,onClick:a,children:o})}function zt({view:t,query:s,className:n,children:r}){return e.jsx(Ts,{route:{type:"view",view:t,...s?{query:s}:{}},className:n,children:r})}const He=["hsl(var(--chart-1))","hsl(var(--chart-2))","hsl(var(--chart-3))","hsl(var(--chart-4))","hsl(var(--chart-5))"];function Ls(t){const s=Array.from(new Set(t.filter(r=>r!==""))).sort(),n=new Map;return s.forEach((r,a)=>n.set(r,He[a%He.length])),n}function As(t){if(t==="")return;let s=0;for(let n=0;n>>0)%He.length]}function rt({kind:t,className:s}){return t?e.jsxs("span",{className:`inline-flex items-center gap-1.5 rounded-full border bg-background px-2 py-0.5 font-mono text-[10px] text-muted-foreground${s?` ${s}`:""}`,children:[e.jsx(Tt,{kind:t}),t]}):null}function Tt({kind:t}){const s=As(t);return s?e.jsx("span",{"aria-hidden":"true",className:"inline-block h-2 w-2 shrink-0 rounded-full",style:{backgroundColor:s}}):null}function Qr(t){const s=t.replace(/-/g,"+").replace(/_/g,"/"),n=s.length%4===0?"":"=".repeat(4-s.length%4),r=atob(s+n),a=new Uint8Array(r.length);for(let o=0;o=1e3?`${(t/1e3).toFixed(1)}K`:`${t}`}function xe(t){return t>=1e6?`${(t/1e6).toFixed(2)}M`:t>=1e3?`${Math.round(t/1e3)}K`:`${t}`}function Lt(t,s=Date.now()){if(!t)return"—";const n=new Date(t).getTime();if(Number.isNaN(n))return"—";const r=Math.round((s-n)/1e3);if(r<45)return"just now";const a=Math.round(r/60);if(a<60)return`${a}m ago`;const o=Math.round(a/60);if(o<24)return`${o}h ago`;const i=Math.round(o/24);return i<7?`${i}d ago`:new Date(n).toLocaleDateString()}const Xr={Valid:"success",Ready:"success",Connected:"success",Reachable:"success",Synced:"success",Healthy:"success",Active:"success",Degraded:"warning",Syncing:"warning",Pending:"warning",Progressing:"warning",Failed:"destructive",Invalid:"destructive",Error:"destructive",Down:"destructive",Running:"primary",Deferred:"primary"},Zr={success:"border-success/40 bg-success/10 text-success",warning:"border-warning/40 bg-warning/10 text-warning",destructive:"border-destructive/40 bg-destructive/10 text-destructive",primary:"border-primary/40 bg-primary/10 text-primary",muted:"border-border bg-background text-muted-foreground"};function Es(t){return Xr[t]??"muted"}function Ds(t){return Zr[t]}function at(t){return Ds(Es(t))}function At({phase:t}){return e.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] ${at(t??"")}`,children:t||"—"})}function Br({s:t,onOpen:s}){const n=t.active?"bg-primary":t.phase==="Failed"?"bg-destructive":"bg-success",r=t.pendingToolGrants+t.pendingLeakageApprovals;return e.jsxs(me,{className:"cursor-pointer transition-colors hover:border-primary/50",onClick:s,children:[e.jsx(he,{className:"pb-2",children:e.jsxs(fe,{className:"flex items-center justify-between text-sm font-medium",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx("span",{className:Ge("inline-block h-2 w-2 rounded-full",n,t.active&&"animate-ap-pulse-dot")}),e.jsxs("span",{className:"font-mono",children:[t.class??"?"," / ",e.jsx(Z,{ns:t.namespace,name:t.name})]})]}),e.jsx(At,{phase:t.phase})]})}),e.jsxs(pe,{className:"space-y-2 text-xs text-muted-foreground",children:[t.statusText&&e.jsx("p",{className:"truncate font-mono",children:t.statusText}),e.jsxs("p",{className:"font-mono",children:[ge(t.inputTokens)," in · ",ge(t.outputTokens)," out · ",t.elapsedSeconds,"s · ",t.toolCallCount," tools"]}),e.jsxs("div",{className:"flex gap-2",children:[t.channelKind&&e.jsx(z,{variant:"outline",children:t.channelKind}),r>0&&e.jsxs(z,{variant:"destructive",children:[r," approval",r>1?"s":""," pending"]})]}),t.active&&e.jsx("div",{className:"h-0.5 rounded bg-gradient-to-r from-primary via-state to-primary bg-[length:200%_100%] animate-ap-flow"})]})]})}function qt(t){te({type:"detail",entity:"session",id:`${t.namespace}/${t.name}`})}function ea({kinds:t,value:s,onChange:n}){const r="rounded-full border px-2 py-0.5 text-[10px] transition-colors",a="border-border bg-background text-muted-foreground hover:bg-accent/50",o="border-primary/40 bg-primary/10 text-primary";return e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("button",{type:"button","aria-pressed":s==="",onClick:()=>n(""),className:`${r} ${s===""?o:a}`,children:"All"}),t.map(i=>{const l=s===i;return e.jsx("button",{type:"button","aria-pressed":l,onClick:()=>n(l?"":i),className:`${r} ${l?o:a}`,children:i},i)})]})}function ta({apiBase:t}){const[s,n]=d.useState(new Map),[r,a]=d.useState(!1),[o,i]=d.useState(null),[l,c]=d.useState(""),[u,h]=$t("sessions_view","grid");d.useEffect(()=>{let x=!1;const j=new Map;let $=0;const C=()=>{const w=++$;j.clear(),_(`${t}/sessions`).then(R=>{if(x||w!==$)return;const O=new Map(R.map(L=>[ee(L),L]));for(const[L,k]of j)k?O.set(L,k):O.delete(L);j.clear(),n(O),i(null)}).catch(R=>{x||i(R.message)})};C();let E=!1;const m=kr(t,{onSession:w=>{j.set(ee(w),w),n(R=>new Map(R).set(ee(w),w))},onRemove:w=>{j.set(ee(w),null),n(R=>{const O=new Map(R);return O.delete(ee(w)),O})},onStale:w=>{a(w),!w&&E&&C(),E=w}});return()=>{x=!0,m()}},[t]);const N=[...s.values()].sort((x,j)=>(j.startedAt??"").localeCompare(x.startedAt??"")),p=[...new Set(N.map(x=>x.channelKind).filter(x=>!!x))].sort(),b=l?N.filter(x=>x.channelKind===l):N;return e.jsxs("div",{className:"space-y-4",children:[o&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load sessions: ",o]})}),r&&e.jsx(I,{children:e.jsx(F,{children:"Live feed stalled — showing last known state."})}),e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[p.length>0?e.jsx(ea,{kinds:p,value:l,onChange:c}):e.jsx("span",{}),e.jsx($s,{value:u,onChange:h})]}),b.length===0&&!o&&e.jsx("p",{className:"text-sm text-muted-foreground",children:N.length===0?"No sessions.":"No sessions match this filter."}),b.length>0&&u==="grid"&&e.jsx("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3",children:b.map(x=>e.jsx(Br,{s:x,onOpen:()=>qt(x)},ee(x)))}),b.length>0&&u==="table"&&e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Session"}),e.jsx(y,{children:"Class"}),e.jsx(y,{children:"Channel"}),e.jsx(y,{children:"Phase"}),e.jsx(y,{children:"Tokens"}),e.jsx(y,{children:"Started"})]})}),e.jsx(X,{children:b.map(x=>e.jsxs(M,{className:"cursor-pointer",onClick:()=>qt(x),children:[e.jsx(v,{className:"font-mono text-xs text-foreground",children:e.jsx(Z,{id:ee(x)})}),e.jsx(v,{className:"font-mono text-xs",children:x.class?e.jsx(W,{entity:"agent",id:`${x.namespace}/${x.class}`,className:"text-link hover:underline",onClick:j=>j.stopPropagation(),children:x.class}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}),e.jsxs(v,{className:"text-xs",children:[x.channelName?e.jsx(W,{entity:"channel",id:`${x.namespace}/${x.channelName}`,className:"font-mono text-link hover:underline",onClick:j=>j.stopPropagation(),children:e.jsx(Z,{ns:x.namespace,name:x.channelName})}):!x.channelKind&&e.jsx("span",{className:"font-mono text-muted-foreground",children:"—"}),x.channelKind&&e.jsx(rt,{kind:x.channelKind,className:x.channelName?"ml-1.5":""})]}),e.jsx(v,{children:e.jsx(At,{phase:x.phase})}),e.jsxs(v,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:[ge(x.inputTokens)," in · ",ge(x.outputTokens)," out"]}),e.jsxs(v,{className:"whitespace-nowrap text-xs text-muted-foreground",children:[Lt(x.startedAt),x.startedBy&&e.jsxs("span",{className:"mt-0.5 flex items-center gap-1.5 text-[10px] text-muted-foreground/70",children:[e.jsx(Tt,{kind:x.startedBy}),e.jsx(W,{entity:"user",id:x.startedBy,className:"font-mono text-link hover:underline",children:ae(x.startedBy)})]})]})]},ee(x)))})]})]})}function sa(t){const s=new Date(t);return Number.isNaN(s.getTime())?t:s.toLocaleTimeString()}const na=4e3;function ra({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1);return d.useEffect(()=>{let l=!0;const c=()=>{dr(t).then(h=>{l&&(n(h),a(null),i(!0))}).catch(h=>{l&&(a(h.message),i(!0))})};c();const u=setInterval(c,na);return()=>{l=!1,clearInterval(u)}},[t]),e.jsxs("div",{className:"space-y-3",children:[r&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load tool calls: ",r]})}),o&&s.length===0&&!r&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No tool calls yet — they appear here as agents dispatch them."}),s.length>0&&e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"When"}),e.jsx(y,{children:"Session"}),e.jsx(y,{children:"Agent"}),e.jsx(y,{children:"Tool"})]})}),e.jsx(X,{children:s.map((l,c)=>e.jsxs(M,{children:[e.jsx(v,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:sa(l.t)}),e.jsx(v,{className:"font-mono text-xs",children:e.jsx(W,{entity:"session",id:ee(l),className:"text-link hover:underline",children:e.jsx(Z,{id:ee(l)})})}),e.jsx(v,{className:"text-xs",children:l.agentClass?e.jsx(W,{entity:"agent",id:`${l.namespace}/${l.agentClass}`,className:"text-link hover:underline",children:l.agentClass}):"(unknown)"}),e.jsx(v,{className:"font-mono text-xs text-foreground",children:l.tool||"(tool)"})]},`${ee(l)}-${l.t}-${c}`))})]})]})}const aa=4e3,oa={tool_call:{label:"Tool call",cls:"border-warning/40 text-warning"},leakage:{label:"Leakage",cls:"border-state/40 text-state"},content_inspection:{label:"Content",cls:"border-warning/40 text-warning"}};function la(t){const s=new Date(t).getTime();if(Number.isNaN(s)||s{let c=!0;const u=()=>{xr(t).then(N=>{c&&(r(N),o(null),l(!0))}).catch(N=>{c&&(o(N.message),l(!0))})};u();const h=setInterval(u,aa);return()=>{c=!1,clearInterval(h)}},[t]),e.jsxs("div",{className:"space-y-3",children:[a&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load approvals: ",a]})}),i&&n.length===0&&!a&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing awaiting a decision — pending approvals appear here as agents request them."}),n.length>0&&e.jsx("div",{className:"grid grid-cols-1 gap-3 lg:grid-cols-2",children:n.map((c,u)=>{const h=oa[c.kind]??{label:c.kind,cls:"border-input text-muted-foreground"},N=la(c.requestedAt);return e.jsxs(me,{className:"ring-1 ring-warning/20",children:[e.jsx(he,{className:"pb-2",children:e.jsxs(fe,{className:"flex items-center justify-between text-sm font-medium",children:[e.jsx("span",{className:"font-mono",children:c.tool||h.label}),e.jsx(z,{variant:"outline",className:Ge(h.cls),children:h.label})]})}),e.jsxs(pe,{className:"space-y-3 text-xs text-muted-foreground",children:[e.jsxs("p",{className:"font-mono",children:[c.agentClass||"(unknown)"," / ",ee(c)]}),e.jsxs("div",{className:"flex items-center justify-between",children:[c.requester?e.jsxs("span",{children:["requested by ",e.jsx("span",{className:"text-foreground",children:c.requester})]}):e.jsx("span",{}),N&&e.jsxs("span",{className:"rounded-full border border-warning/40 bg-warning/10 px-2 py-0.5 text-[10px] text-warning",children:["waiting ",N]})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(U,{variant:"outline",size:"sm",disabled:!s,onClick:()=>s==null?void 0:s(c.namespace,c.name),children:"Open session"}),e.jsx("span",{className:"text-[11px] text-muted-foreground",children:"decide in the channel thread"})]})]})]},`${ee(c)}-${c.kind}-${c.requestedAt}-${u}`)})}),n.length>0&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Read-only — decisions happen in the originating channel thread. Open the session to watch it."})]})}const ze="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50";function gt({label:t,children:s}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("div",{className:"text-sm text-foreground",children:s})]})}function Gt(t){return t==null?"":typeof t=="string"?t:Array.isArray(t)?t.join(","):String(t)}function ca({q:t,value:s,onChange:n}){const r=t.required!==!1,a=`${t.prompt||t.name}${r?" *":""}`;return e.jsxs(gt,{label:a,children:[t.type==="bool"?e.jsx("input",{type:"checkbox",checked:s==="true",onChange:o=>n(o.target.checked?"true":"false"),"aria-label":a,className:"h-4 w-4 rounded border-border"}):t.type==="enum"?e.jsxs("select",{value:s,onChange:o=>n(o.target.value),"aria-label":a,className:ze,children:[e.jsx("option",{value:"",disabled:!0,children:"Select…"}),(t.enum??[]).map((o,i)=>{var u;const l=(u=t.enumLabels)==null?void 0:u[i],c=l!==void 0&&l.trim()!==""?l:o;return e.jsx("option",{value:o,children:c},o)})]}):e.jsx("input",{type:t.type==="secret"?"password":t.type==="int"?"number":"text",value:s,onChange:o=>n(o.target.value),"aria-label":a,placeholder:t.type==="resourceList"?"comma-separated":void 0,autoComplete:t.type==="secret"?"new-password":"off",className:ze}),t.description&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:t.description})]})}function da({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(s.namespace),[l,c]=d.useState(s.suggestedName??""),[u,h]=d.useState({}),[N,p]=d.useState(null),[b,x]=d.useState([]),[j,$]=d.useState([]),[C,E]=d.useState(null),[m,w]=d.useState(!1),R=()=>{x([]),$([])},O=(g,T)=>{h(P=>({...P,[g]:T})),R()},L=()=>{E(null),p(null),R()},k=async()=>{E(null),w(!0);try{await ar(t,s.namespace,s.name,{namespace:o,name:l,values:u,adopt:j}),a(!1),L(),n()}catch(g){g instanceof tt?(p(g.questions),h(T=>{const P={...T};for(const V of g.questions)P[V.name]===void 0&&V.default!==void 0&&V.default!==null&&(P[V.name]=Gt(V.default));return P})):g instanceof st?x(g.conflicts):(g instanceof J,E(g.message))}finally{w(!1)}};return e.jsxs(Je,{open:r,onOpenChange:g=>{a(g),g||L()},children:[e.jsx(Ye,{asChild:!0,children:e.jsx(U,{size:"sm",children:"Install"})}),e.jsxs(Qe,{children:[e.jsxs(Xe,{children:[e.jsxs(Ze,{children:["Install ",s.suggestedName||s.name]}),e.jsx(Be,{children:"Installs this workshop's drafted bundle under the operator identity. No secret answer is ever pre-filled — type it below."})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[C&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:C})}),e.jsxs("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2",children:[e.jsx(gt,{label:"Namespace *",children:e.jsx("input",{type:"text",value:o,onChange:g=>{i(g.target.value),R()},"aria-label":"Namespace",className:ze})}),e.jsx(gt,{label:"Name (optional)",children:e.jsx("input",{type:"text",value:l,onChange:g=>{c(g.target.value),R()},placeholder:"defaults to the bundle's own name","aria-label":"Name",className:ze})})]}),b.length>0&&e.jsxs("div",{className:"flex flex-col gap-2 rounded-md border border-destructive/50 p-3",children:[e.jsx("p",{className:"text-xs font-medium text-foreground",children:"These objects already exist and were not created by this install. Adopting one overwrites its spec with this bundle's."}),b.map(g=>{const T=`${g.kind}/${g.name}`,P=g.namespace?`${g.kind} ${g.namespace}/${g.name}`:T;return e.jsxs("label",{className:"flex items-center gap-2 text-xs text-foreground",children:[e.jsx("input",{type:"checkbox",checked:j.includes(T),"aria-label":P,onChange:V=>$(q=>V.target.checked?[...q,T]:q.filter(ie=>ie!==T)),className:"h-4 w-4 rounded border-border"}),e.jsx("span",{children:P}),g.secret&&e.jsx("span",{className:"text-destructive",children:"overwrites this Secret's data"})]},T)})]}),N&&N.length>0&&e.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-dashed border-border p-3",children:[e.jsxs("p",{className:"text-xs text-muted-foreground",children:["This bundle needs ",N.length===1?"an answer":"answers"," before it can install:"]}),N.map(g=>e.jsx(ca,{q:g,value:u[g.name]??Gt(g.default),onChange:T=>O(g.name,T)},g.name))]})]}),e.jsxs(et,{children:[e.jsx(wt,{asChild:!0,children:e.jsx(U,{variant:"outline",children:"Cancel"})}),e.jsx(U,{onClick:k,disabled:m||o.trim()==="",children:m?"Installing…":b.length>0&&j.length>0?"Adopt and install":N?"Submit answers":"Confirm install"})]})]})]})}function ua({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(null),l=async()=>{i(null),a(!0);try{await ft(`${t}/workshops/${s.namespace}/${s.name}/decline`,{}),n()}catch(c){i(c.message)}finally{a(!1)}};return e.jsxs("span",{className:"flex flex-col items-end gap-1",children:[e.jsx(U,{variant:"outline",size:"sm",onClick:l,disabled:r,children:r?"Declining…":"Decline"}),o&&e.jsx("span",{className:"text-[11px] text-destructive",children:o})]})}function xa({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(null),[l,c]=d.useState(!1),u=async()=>{i(null),c(!0);try{await vs(`${t}/workshops/${s.namespace}/${s.name}`),a(!1),n()}catch(h){i(h.message)}finally{c(!1)}};return e.jsxs(Je,{open:r,onOpenChange:a,children:[e.jsx(Ye,{asChild:!0,children:e.jsx(U,{variant:"destructive",size:"sm",children:"Kill"})}),e.jsxs(Qe,{children:[e.jsxs(Xe,{children:[e.jsxs(Ze,{children:["Kill workshop ",s.name,"?"]}),e.jsx(Be,{children:"Deletes the Workshop — its provisioned builder namespace is torn down. This cannot be undone."})]}),o&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:o})}),e.jsxs(et,{children:[e.jsx(wt,{asChild:!0,children:e.jsx(U,{variant:"outline",children:"Cancel"})}),e.jsx(U,{variant:"destructive",onClick:u,disabled:l,children:l?"Killing…":"Kill it"})]})]})]})}function ma({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1),l=d.useCallback(()=>{pr(t).then(c=>{n(c),a(null),i(!0)}).catch(c=>{a(c.message),i(!0)})},[t]);return d.useEffect(()=>{l()},[l]),r?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load workshops: ",r]})}):e.jsxs("div",{className:"space-y-3",children:[o&&s.length===0&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No workshops yet."}),s.length>0&&e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Workshop"}),e.jsx(y,{children:"Session"}),e.jsx(y,{children:"Starter"}),e.jsx(y,{children:"Phase"}),e.jsx(y,{children:"Install"}),e.jsx(y,{className:"text-right",children:"Actions"})]})}),e.jsx(X,{children:s.map(c=>{const u=`${c.namespace}/${c.name}`;return e.jsxs(M,{children:[e.jsxs(v,{className:"font-mono text-xs",children:[u,c.exported&&e.jsx(z,{variant:"outline",className:"ml-2",children:"exported"})]}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:c.session}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:c.starter}),e.jsx(v,{children:e.jsx(z,{variant:"outline",children:c.phase||"—"})}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:c.installPhase?e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx(z,{variant:c.installPhase==="Failed"?"destructive":"outline",children:c.installPhase}),c.installedRef&&e.jsx("span",{children:c.installedRef})]}):c.pendingCapability?"capability requested":"—"}),e.jsx(v,{className:"text-right",children:e.jsxs("div",{className:"flex justify-end gap-2",children:[c.pendingInstall&&e.jsxs(e.Fragment,{children:[e.jsx(da,{apiBase:t,row:c,onDone:l}),e.jsx(ua,{apiBase:t,row:c,onDone:l})]}),e.jsx(xa,{apiBase:t,row:c,onDone:l})]})})]},u)})})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Workshop — an agent-builder session's drafted bundle, awaiting an admin's install/decline/kill decision."})]})}function qe({value:t,className:s}){const[n,r]=d.useState(!1);return e.jsxs("div",{className:Ge("rounded border border-border bg-background/40 text-xs",s),children:[e.jsx("div",{className:"flex justify-end border-b border-border/50 px-1.5 py-0.5",children:e.jsx("button",{type:"button",onClick:()=>r(a=>!a),className:"font-mono text-[10px] uppercase tracking-wide text-muted-foreground hover:text-foreground","aria-pressed":n,children:n?"tree":"raw"})}),e.jsx("div",{className:"overflow-x-auto p-2 font-mono leading-relaxed",children:n?e.jsx("pre",{className:"whitespace-pre-wrap break-words text-foreground",children:ha(t)}):e.jsx(Os,{value:t})})]})}function ha(t){const s=JSON.stringify(t,null,2);return s===void 0?String(t):s}function Rs(t){const s=t.trim();if(!(!s.startsWith("{")&&!s.startsWith("[")))try{return JSON.parse(s)}catch{return}}function Os({value:t,label:s}){return t===null||typeof t!="object"?e.jsxs("div",{className:"flex flex-wrap items-start gap-1.5",children:[s!==void 0&&e.jsxs("span",{className:"shrink-0 text-foreground/70",children:[s,":"]}),e.jsx(pa,{value:t})]}):e.jsx(ga,{value:t,label:s})}function fa(t){switch(typeof t){case"string":return"text-success";case"number":return"text-primary";case"boolean":return"text-state";default:return"text-muted-foreground"}}function pa({value:t}){const s=typeof t=="string"?JSON.stringify(t):String(t);return e.jsx("span",{className:fa(t),children:s})}function ga({value:t,label:s}){const[n,r]=d.useState(!0),a=Array.isArray(t),o=a?t.map((u,h)=>[String(h),u]):Object.entries(t),[i,l]=a?["[","]"]:["{","}"],c=s!==void 0?e.jsxs("span",{className:"text-foreground/70",children:[s,": "]}):null;return o.length===0?e.jsxs("div",{children:[c,e.jsxs("span",{className:"text-muted-foreground",children:[i,l]})]}):e.jsxs("div",{children:[e.jsxs("button",{type:"button",onClick:()=>r(u=>!u),"aria-expanded":n,className:"text-muted-foreground hover:text-foreground",children:[c,e.jsx("span",{"aria-hidden":"true",children:n?"▾":"▸"})," ",i,!n&&e.jsxs("span",{className:"text-muted-foreground",children:[" ","…",o.length," ",l]})]}),n&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ml-2 border-l border-border/40 pl-2",children:o.map(([u,h])=>e.jsx(Os,{value:h,label:a?void 0:u},u))}),e.jsx("div",{className:"text-muted-foreground",children:l})]})]})}const ja=560,De=28,be={channel:60,agent:260,tools:470};function dt({x:t,y:s,label:n,stroke:r}){return e.jsxs("g",{children:[e.jsx("circle",{cx:t,cy:s,r:De,fill:"hsl(var(--card))",stroke:r,strokeWidth:1.5}),e.jsx("text",{x:t,y:s+4,textAnchor:"middle",fontSize:10,fill:"hsl(var(--foreground))",fontFamily:"monospace",children:n.length>11?n.slice(0,10)+"…":n})]})}function Jt({x1:t,y1:s,x2:n,y2:r,active:a}){return e.jsx("path",{className:a?"ap-edge-active":void 0,d:`M ${t} ${s} C ${(t+n)/2} ${s}, ${(t+n)/2} ${r}, ${n} ${r}`,fill:"none",stroke:a?"hsl(var(--primary))":"hsl(var(--border))",strokeWidth:1.5,strokeDasharray:a?"6 6":void 0,children:a&&e.jsx("animate",{attributeName:"stroke-dashoffset",from:"24",to:"0",dur:"0.9s",repeatCount:"indefinite"})})}function ba({channelKind:t,agentClass:s,tools:n,active:r}){const a=n.slice(0,8),o=Math.max(160,a.length*64+40),i=o/2;return e.jsxs("svg",{viewBox:`0 0 ${ja} ${o}`,className:"w-full",role:"img","aria-label":"session flow graph",children:[e.jsx(Jt,{x1:be.channel+De,y1:i,x2:be.agent-De,y2:i,active:r}),a.map((l,c)=>{const u=40+c*64+12;return e.jsxs(d.Fragment,{children:[e.jsx(Jt,{x1:be.agent+De,y1:i,x2:be.tools-De,y2:u,active:r}),e.jsx(dt,{x:be.tools,y:u,label:l,stroke:"hsl(var(--state))"})]},l)}),e.jsx(dt,{x:be.channel,y:i,label:t??"channel",stroke:"hsl(var(--success))"}),e.jsx(dt,{x:be.agent,y:i,label:s??"agent",stroke:"hsl(var(--primary))"})]})}const va=[{id:"overview",label:"Overview"},{id:"activity",label:"Recent Activity"},{id:"logs",label:"Full Logs"}];function ya(t){const s=t.indexOf("/");return s<0?{ns:"",name:t}:{ns:t.slice(0,s),name:t.slice(s+1)}}function Na(t){var n;const s=new Set;for(const r of t.recentEvents)if(r.kind==="tool_activity"){const a=(n=r.payload)==null?void 0:n.tool;a&&s.add(a)}return[...s]}function wa({apiBase:t,id:s,tab:n}){const{ns:r,name:a}=ya(s),o=n??"overview",i=`${t}/sessions/${r}/${a}`,[l,c]=d.useState(null),[u,h]=d.useState(null),[N,p]=d.useState(!1),[b,x]=d.useState(null),[j,$]=d.useState(null),[C,E]=d.useState(!1),m=d.useRef(null);d.useEffect(()=>()=>{m.current!==null&&clearTimeout(m.current)},[]),d.useEffect(()=>{let O=!0;return c(null),h(null),p(!1),_(i).then(L=>{O&&c(L)}).catch(L=>{O&&(L instanceof J&&L.status===404?p(!0):h(L.message))}),()=>{O=!1}},[i]);const w=async()=>{$(null);try{await vs(i),E(!1),x("Session terminated."),m.current=setTimeout(()=>te({type:"view",view:"sessions"}),800)}catch(O){E(!1),$(O.message)}},R=O=>te({type:"detail",entity:"session",id:s,tab:O});return e.jsx(nt,{title:e.jsxs("span",{className:"font-mono",children:[l&&`${l.class??"?"} / `,e.jsx(Z,{ns:r,name:a})]}),backRoute:{type:"view",view:"sessions"},tabs:va,activeTab:o,onTab:R,headerRight:l!=null&&l.phase?e.jsx(z,{variant:"outline",children:l.phase}):void 0,children:o==="logs"?e.jsx($a,{apiBase:t,ns:r,name:a}):u?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load session: ",u]})}):N?e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Session ",s," is not tracked — it may have completed or never started."]}):l?o==="activity"?e.jsx(Ca,{detail:l}):e.jsx(ka,{detail:l,name:a,killNote:b,killError:j,killOpen:C,onKillOpenChange:E,onKill:w}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"})})}function ka({detail:t,name:s,killNote:n,killError:r,killOpen:a,onKillOpenChange:o,onKill:i}){return e.jsxs("div",{className:"flex flex-col gap-4",children:[n&&e.jsx("p",{className:"text-sm text-success",children:n}),r&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not kill session: ",r]})}),e.jsx("div",{className:"rounded-lg border border-border bg-card p-4",children:e.jsx(ba,{channelKind:t.channelKind,agentClass:t.class,tools:Na(t),active:t.active})}),t.statusText&&e.jsx("p",{className:"font-mono text-sm text-muted-foreground",children:t.statusText}),e.jsxs("p",{className:"font-mono text-xs text-muted-foreground",children:[ge(t.inputTokens)," in · ",ge(t.outputTokens)," out · ",t.toolCallCount," tool calls · turn ",t.turnCount]}),e.jsxs("dl",{className:"grid grid-cols-2 gap-x-6 gap-y-2 text-xs sm:grid-cols-3",children:[e.jsx(Ae,{label:"Phase",value:t.phase??"—"}),e.jsx(Ae,{label:"Model",children:t.model?e.jsx(kt,{model:t.model}):"—"}),e.jsx(Ae,{label:"Elapsed",value:`${t.elapsedSeconds}s`}),t.startedAt&&e.jsx(Ae,{label:"Started",value:new Date(t.startedAt).toLocaleString()}),t.activityCause&&e.jsx(Ae,{label:"Activity",value:t.activityCause})]}),t.bundles&&t.bundles.length>0&&e.jsx(Sa,{bundles:t.bundles}),e.jsxs(Je,{open:a,onOpenChange:o,children:[e.jsx(Ye,{asChild:!0,children:e.jsx(U,{variant:"destructive",className:"mt-2 self-start",children:"Kill session"})}),e.jsxs(Qe,{children:[e.jsxs(Xe,{children:[e.jsxs(Ze,{children:["Kill ",s,"?"]}),e.jsx(Be,{children:"Deletes the AgentSession — the runner pod is torn down immediately. This cannot be undone."})]}),e.jsxs(et,{children:[e.jsx(wt,{asChild:!0,children:e.jsx(U,{variant:"outline",children:"Cancel"})}),e.jsx(U,{variant:"destructive",onClick:i,children:"Kill it"})]})]})]})]})}function Ae({label:t,value:s,children:n}){return e.jsxs("div",{className:"flex flex-col",children:[e.jsx("dt",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("dd",{className:"font-mono text-foreground",children:n??s})]})}function Sa({bundles:t}){return e.jsxs("div",{className:"rounded-lg border border-border bg-card p-4",children:[e.jsx("h3",{className:"mb-2 text-[10px] uppercase tracking-wide text-muted-foreground",children:"Bundles"}),e.jsx("ul",{className:"space-y-2",children:t.map(s=>e.jsxs("li",{className:"flex flex-wrap items-center gap-2 font-mono text-xs",children:[e.jsx("span",{className:"font-semibold text-foreground",children:s.name}),e.jsx("span",{className:"text-muted-foreground",children:s.spiceboxSessionName}),s.sandboxKind?e.jsxs(e.Fragment,{children:[e.jsx(z,{variant:"outline",children:s.sandboxKind}),e.jsx("span",{className:"break-all text-muted-foreground",children:s.sandboxRef}),s.prewarmed&&e.jsx(z,{variant:"outline",children:"warm"})]}):e.jsx("span",{className:"text-muted-foreground",children:"sandbox not yet resolved"}),s.workspaceMode&&e.jsxs(z,{variant:"outline",children:[s.workspaceMode," workspace"]}),s.phase&&e.jsx(z,{variant:s.phase==="Failed"?"destructive":"outline",children:s.phase}),s.reason&&e.jsx("span",{className:"text-muted-foreground",children:s.reason})]},s.name))})]})}function Ca({detail:t}){return t.recentEvents.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No recent activity."}):e.jsx("ul",{className:"space-y-1 font-mono text-xs text-muted-foreground",children:t.recentEvents.slice().reverse().map((s,n)=>e.jsxs("li",{children:[new Date(s.at).toLocaleTimeString()," · ",s.kind]},n))})}function $a({apiBase:t,ns:s,name:n}){const[r,a]=d.useState(null),[o,i]=d.useState(null);return d.useEffect(()=>{let l=!0;return a(null),i(null),ur(t,s,n).then(c=>{l&&a(c)}).catch(c=>{l&&i(c.message)}),()=>{l=!1}},[t,s,n]),o?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load logs: ",o]})}):r?r.entries.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No transcript entries yet."}):e.jsxs("div",{className:"space-y-3",children:[r.truncated&&e.jsxs("p",{className:"text-xs text-warning",children:["Showing the latest ",r.entries.length," entries — older turns were truncated."]}),e.jsx("ul",{className:"space-y-2",children:r.entries.map(l=>e.jsx(La,{entry:l},l.id))})]}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading logs…"})}function Ta(t){switch(t){case"user":case"inbox":return{container:"border-state/40 bg-state/5",badge:"bg-state/15 text-state"};case"assistant":return{container:"border-primary/40 bg-primary/5",badge:"bg-primary/15 text-primary"};case"tool":return{container:"border-warning/40 bg-warning/5",badge:"bg-warning/15 text-warning"};default:return{container:"border-border bg-card",badge:"bg-muted text-muted-foreground"}}}function La({entry:t}){const s=t.role||t.kind,n=Ta(t.role??"");return e.jsxs("li",{className:`rounded-md border p-3 ${n.container}`,children:[e.jsxs("div",{className:"mb-2 flex items-center justify-between gap-2 font-mono text-[11px] text-muted-foreground",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx("span",{className:`rounded px-1.5 py-0.5 text-[10px] uppercase tracking-wide ${n.badge}`,children:s}),t.actor&&e.jsxs("span",{className:"flex items-center gap-1",children:[t.actorInferred?"requested by":"by"," ",e.jsx(W,{entity:"user",id:t.actor,className:"text-link hover:underline",children:ae(t.actor)}),t.actorInferred&&e.jsxs("span",{className:"text-muted-foreground/60",title:"Session initiator — this legacy turn predates per-turn authorship.",children:[" ","(session initiator)"]})]})]}),e.jsx("span",{children:new Date(t.createdAt).toLocaleString()})]}),t.blocks&&t.blocks.length>0?e.jsx("div",{className:"space-y-2",children:t.blocks.map((r,a)=>e.jsx(Aa,{block:r,role:t.role??""},a))}):e.jsx(jt,{text:t.content,className:"text-foreground"})]})}function Aa({block:t,role:s}){if(t.type==="tool_use")return e.jsxs("div",{className:"rounded border border-warning/40 bg-warning/5 p-2",children:[e.jsxs("div",{className:"mb-1 font-mono text-[11px] text-warning",children:["→ ",t.name||"tool"]}),e.jsx(qe,{value:t.input??{}})]});if(t.type==="tool_result"){const r=t.isError,a=r?"border-destructive/50 bg-destructive/10":"border-success/40 bg-success/5",o=r?"text-destructive":"text-success",i=Ea(t.content);return e.jsxs("div",{className:`rounded border p-2 ${a}`,children:[e.jsxs("div",{className:`mb-1 font-mono text-[11px] ${o}`,children:["← ",r?"error":"result"]}),i!==null?e.jsx(jt,{text:i,className:"text-foreground/90"}):e.jsx(qe,{value:t.content??""})]})}const n=s==="assistant"?"text-foreground":"text-foreground/90";return e.jsx(jt,{text:t.text??"",className:n})}function Ea(t){if(typeof t!="string")return null;let s=t;if(s.length>=2&&s.startsWith('"')&&s.endsWith('"'))try{const n=JSON.parse(s);typeof n=="string"&&(s=n)}catch{}return s.includes("]*)?>/g;function Yt({chunk:t}){const s=Rs(t);return s!==void 0?e.jsx(qe,{value:s,className:"my-1"}):e.jsx("span",{className:"rounded-sm bg-muted/40 text-muted-foreground",children:t})}function jt({text:t,className:s}){const n=`whitespace-pre-wrap break-words font-mono text-xs ${s??""}`;if(!t.includes("t.stopPropagation(),Ra=t=>t==="denied"||t==="read_denied"||t==="failed"?"destructive":t==="pending"?"secondary":"outline",Is=[{key:"kind",label:"Kind",value:t=>t.kind},{key:"outcome",label:"Outcome",value:t=>t.outcome},{key:"agent",label:"Agent",value:t=>t.agentClass??""},{key:"tool",label:"Tool",value:t=>t.tool??""},{key:"actor",label:"Actor",value:t=>t.actor?ae(t.actor):""}];function Oa(t){return{kind:t.kind,outcome:t.outcome,agent:t.agentClass??"",tool:t.tool??"",actor:t.actor?ae(t.actor):""}}function Ee({color:t}){return t?e.jsx("span",{className:"mr-1.5 inline-block h-2 w-2 shrink-0 rounded-full align-middle",style:{backgroundColor:t}}):null}function Ia({colorMaps:t}){return e.jsx("div",{role:"group","aria-label":"Color legends",className:"grid gap-2 rounded-md border border-border bg-card p-2 text-xs sm:grid-cols-2 lg:grid-cols-3",children:Is.map(s=>{const n=[...t[s.key].entries()];return e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground",children:s.label}),n.length===0?e.jsx("span",{className:"text-muted-foreground",children:"—"}):e.jsx("div",{className:"flex flex-wrap gap-1",children:n.map(([r,a])=>e.jsxs("span",{className:"inline-flex max-w-full items-center gap-1 rounded border border-border bg-background px-1.5 py-0.5 font-mono",children:[e.jsx("span",{className:"h-2 w-2 shrink-0 rounded-full",style:{backgroundColor:a}}),e.jsx("span",{className:"truncate",children:r})]},r))})]},s.key)})})}function Fa({events:t,onRow:s,colorMaps:n}){const r=(a,o)=>n==null?void 0:n[a].get(o);return e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"When"}),e.jsx(y,{children:"Kind"}),e.jsx(y,{children:"Outcome"}),e.jsx(y,{children:"Agent"}),e.jsx(y,{children:"Tool"}),e.jsx(y,{children:"Actor"}),e.jsx(y,{children:"Session"}),e.jsx(y,{children:"Event"})]})}),e.jsx(X,{children:t.map(a=>{const o=Oa(a);return e.jsxs(M,{className:`cursor-pointer${a.outcome==="denied"||a.outcome==="read_denied"?" bg-destructive/5":""}`,onClick:()=>s(a),children:[e.jsx(v,{className:"whitespace-nowrap font-mono text-xs",children:new Date(a.time).toLocaleString()}),e.jsxs(v,{className:"whitespace-nowrap",children:[e.jsx(Ee,{color:r("kind",o.kind)}),e.jsx(z,{variant:"outline",children:a.kind})]}),e.jsxs(v,{className:"whitespace-nowrap",children:[e.jsx(Ee,{color:r("outcome",o.outcome)}),e.jsx(z,{variant:Ra(a.outcome),children:a.outcome})]}),e.jsxs(v,{className:"font-mono text-xs",children:[e.jsx(Ee,{color:r("agent",o.agent)}),a.agentClass?e.jsx(W,{entity:"agent",id:`${a.sessionNamespace}/${a.agentClass}`,className:"text-link hover:underline",onClick:Qt,children:a.agentClass}):"—"]}),e.jsxs(v,{className:"font-mono text-xs",children:[e.jsx(Ee,{color:r("tool",o.tool)}),a.tool||"—"]}),e.jsxs(v,{className:"font-mono text-xs",title:a.actor||void 0,children:[e.jsx(Ee,{color:r("actor",o.actor)}),o.actor||"—"]}),e.jsx(v,{className:"font-mono text-xs",children:e.jsxs(W,{entity:"session",id:`${a.sessionNamespace}/${a.sessionName}`,className:"text-link hover:underline",onClick:Qt,children:[a.sessionNamespace,"/",a.sessionName]})}),e.jsx(v,{className:"max-w-md truncate text-xs",children:a.summary})]},a.entryId)})})]})}function Ma({event:t,onClose:s}){return e.jsx(an,{open:!0,onOpenChange:n=>!n&&s(),children:e.jsxs(on,{side:"right",className:"w-full overflow-y-auto sm:max-w-xl",children:[e.jsx(ln,{children:e.jsx(cn,{className:"text-sm",children:t.summary})}),e.jsxs("dl",{className:"mt-4 space-y-1 font-mono text-xs",children:[e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"when:"})," ",e.jsx("dd",{className:"inline",children:t.time})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"session:"})," ",e.jsxs("dd",{className:"inline",children:[t.sessionNamespace,"/",t.sessionName]})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"agent:"})," ",e.jsx("dd",{className:"inline",children:t.agentClass})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"actor:"})," ",e.jsx("dd",{className:"inline",children:t.actor})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"tool:"})," ",e.jsx("dd",{className:"inline",children:t.tool})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"entry:"})," ",e.jsx("dd",{className:"inline",children:t.entryId})]})]}),e.jsx("h3",{className:"mt-4 text-xs font-semibold uppercase text-muted-foreground",children:"Raw"}),e.jsx("pre",{className:"mt-1 overflow-x-auto rounded bg-card p-3 font-mono text-xs",children:JSON.stringify(t.raw??{},null,2)})]})})}const Xt=50,Zt=[{facet:"kind",field:"kinds",label:"Kind"},{facet:"outcome",field:"outcome",label:"Outcome"},{facet:"agentClass",field:"agentClass",label:"Agent"},{facet:"tool",field:"tool",label:"Tool"},{facet:"actor",field:"actor",label:"Actor"}];function Pa(t,s){const n=s.trim().toLowerCase();return n?[t.kind,t.agentClass,t.sessionNamespace,t.sessionName,`${t.sessionNamespace}/${t.sessionName}`,t.outcome,t.tool,t.actor,t.actor?ae(t.actor):"",t.summary].filter(Boolean).join(" ").toLowerCase().includes(n):!0}function Ka({apiBase:t,initialFilter:s}){const[n,r]=d.useState(s),[a,o]=d.useState(0),[i,l]=d.useState(null),[c,u]=d.useState(null),[h,N]=d.useState(null),[p,b]=d.useState(null),[x,j]=d.useState(""),[$,C]=d.useState(!0);d.useEffect(()=>{let k=!0;const g={...n,limit:Xt,offset:a*Xt};return Promise.all([ft(`${t}/audit/query`,g),ft(`${t}/audit/facets`,n)]).then(([T,P])=>{k&&(l(T),u(P),b(null))}).catch(T=>{k&&b(T.message)}),()=>{k=!1}},[t,n,a]);const E=(k,g)=>{o(0),r(T=>({...T,[k]:k==="kinds"?[g]:g}))},m=k=>{o(0),r(g=>{const T={...g};return delete T[k],T})},w=Zt.filter(({field:k})=>n[k]).map(({field:k,label:g})=>({field:k,label:g,value:k==="kinds"?(n.kinds??[]).join(","):String(n[k])})),R=(i==null?void 0:i.events)??[],O=d.useMemo(()=>R.filter(k=>Pa(k,x)),[R,x]),L=d.useMemo(()=>{const k={};for(const g of Is)k[g.key]=Ls(O.map(g.value));return k},[O]);return e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row lg:gap-6",children:[e.jsx("aside",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:block lg:w-48 lg:shrink-0 lg:space-y-4",children:Zt.map(({facet:k,field:g,label:T})=>e.jsxs("div",{children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:T}),e.jsx("ul",{className:"mt-1 space-y-0.5 text-xs",children:Object.entries((c==null?void 0:c.counts[k])??{}).sort(([,P],[,V])=>V-P).slice(0,8).map(([P,V])=>e.jsx("li",{children:e.jsxs("button",{className:"hover:text-primary",onClick:()=>E(g,P),children:[P," (",V,")"]})},P))})]},k))}),e.jsxs("div",{className:"min-w-0 flex-1 space-y-3",children:[p&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Audit query failed: ",p]})}),(i==null?void 0:i.truncated)&&e.jsx(I,{children:e.jsx(F,{children:"Results truncated — narrow the time range or filters for complete counts."})}),e.jsx("div",{className:"flex flex-wrap gap-2",children:w.map(k=>e.jsxs(z,{variant:"secondary",className:"cursor-pointer",onClick:()=>m(k.field),children:[k.label.toLowerCase(),": ",k.value," ✕"]},k.field))}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("div",{className:"relative min-w-0 flex-1",children:e.jsx("input",{type:"search",value:x,onChange:k=>j(k.target.value),placeholder:"Filter loaded events…","aria-label":"Filter loaded events",className:"w-full rounded-md border border-border bg-background px-2 py-1 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"})}),x&&e.jsx(U,{variant:"ghost",size:"sm",onClick:()=>j(""),children:"Clear"}),e.jsx(U,{variant:"outline",size:"sm",onClick:()=>C(k=>!k),children:$?"Hide legend":"Show legend"}),e.jsxs("span",{className:"whitespace-nowrap text-xs text-muted-foreground",children:[O.length,x?` of ${R.length}`:""," shown"]})]}),$&&e.jsx(Ia,{colorMaps:L}),e.jsx(Fa,{events:O,onRow:N,colorMaps:L}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(U,{variant:"outline",size:"sm",disabled:a===0,onClick:()=>o(k=>k-1),children:"Prev"}),e.jsx(U,{variant:"outline",size:"sm",disabled:!(i!=null&&i.hasMore),onClick:()=>o(k=>k+1),children:"Next"}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["page ",a+1]})]})]}),h&&e.jsx(Ma,{event:h,onClose:()=>N(null)})]})}const _a={agents:"agent",sessions:"session",tools:"tool",users:"user"};function Ua(t,s){switch(t){case"agents":return{agentClass:s};case"sessions":{const n=s.indexOf("/");return n<0?{sessionName:s}:{sessionNamespace:s.slice(0,n),sessionName:s.slice(n+1)}}case"tools":return{tool:s};case"users":return{actor:s}}}function Va(t,s){return t==="users"?ae(s):s}function Wa({status:t}){return t?e.jsx("span",{className:`inline-flex rounded-full border px-2 py-0.5 text-[10px] font-medium ${at(t)}`,children:t}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}function Ha({apiBase:t,axis:s,onDrill:n}){const[r,a]=d.useState([]),[o,i]=d.useState(null);d.useEffect(()=>{let u=!0;return _(`${t}/audit/entities/${s}`).then(h=>{u&&(a(h),i(null))}).catch(h=>{u&&i(h.message)}),()=>{u=!1}},[t,s]);const l=s==="sessions",c=_a[s];return e.jsxs("div",{className:"space-y-3",children:[o&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load rollups: ",o]})}),e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{className:"capitalize",children:s}),l&&e.jsx(y,{children:"Status"}),l&&e.jsx(y,{className:"text-right",children:"Est. tokens"}),l&&e.jsx(y,{children:"Started"}),l&&e.jsx(y,{children:"Started by"}),e.jsx(y,{className:"text-right",children:"Events"}),e.jsx(y,{className:"text-right",children:"Denied"}),e.jsx(y,{className:"text-right",children:"Logs"})]})}),e.jsx(X,{children:r.map(u=>e.jsxs(M,{children:[e.jsx(v,{className:"font-mono text-xs",children:e.jsx(W,{entity:c,id:u.key,className:"text-link hover:underline",children:l?e.jsx(Z,{id:u.key}):Va(s,u.key)})}),l&&e.jsx(v,{children:e.jsx(Wa,{status:u.status})}),l&&e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",title:u.estimatedCostUSD?`~$${u.estimatedCostUSD.toFixed(2)} (est.)`:void 0,children:u.inputTokens||u.outputTokens?`${ge(u.inputTokens??0)} in · ${ge(u.outputTokens??0)} out`:"—"}),l&&e.jsx(v,{className:"whitespace-nowrap text-xs text-muted-foreground",children:Lt(u.startedAt)}),l&&e.jsx(v,{className:"text-xs",children:u.startedBy?e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx(Tt,{kind:u.startedBy}),e.jsx(W,{entity:"user",id:u.startedBy,className:"font-mono text-link hover:underline",children:ae(u.startedBy)})]}):e.jsx("span",{className:"font-mono text-muted-foreground",children:"—"})}),e.jsx(v,{className:"text-right font-mono text-xs",children:u.events}),e.jsx(v,{className:"text-right font-mono text-xs text-destructive",children:u.denied||""}),e.jsx(v,{className:"text-right",children:e.jsx("button",{type:"button",className:"text-xs text-muted-foreground hover:text-link hover:underline",onClick:()=>n(Ua(s,u.key)),children:"filter logs"})})]},u.key))})]}),e.jsxs("p",{className:"text-[11px] text-muted-foreground",children:["Row → ",c," detail · “filter logs” → the audit log scoped to that ",c]})]})}const bt=14,Bt=8,ut=4;function Fs(t){return`${t.namespace}/${t.artifactId||t.name}`}function Re(t){return`${t.namespace}/${t.name}`}function za(t){const s=new Map;for(const n of t){const r=Fs(n),a=s.get(r);if(a===void 0){s.set(r,n);continue}const o=new Date(n.created).getTime(),i=new Date(a.created).getTime();(o>i||o===i&&Re(n){const x=Fs(p);a.push(x),s.has(x)||(s.set(x,b),r.set(x,As(x)??"#888")),n.set(x,b)});const o=[...s.keys()],i=[],l=new Map;for(const p of o){const b=s.get(p);let x=i.findIndex(j=>jnew Array(t.length).fill(void 0));for(const p of o){const b=l.get(p);for(let x=s.get(p);x<=n.get(p);x++)u[b][x]===void 0&&(u[b][x]=p)}const h=za(t),N=t.map((p,b)=>{const x=a[b],j=l.get(x),$=[];for(let C=0;Cs.get(E),R=E!==void 0&&b{const r=es(n.lane);return e.jsxs("g",{children:[n.up&&e.jsx("line",{x1:r,y1:0,x2:r,y2:5,stroke:n.color,strokeWidth:1.5,strokeOpacity:.7}),n.down&&e.jsx("line",{x1:r,y1:5,x2:r,y2:10,stroke:n.color,strokeWidth:1.5,strokeOpacity:.7})]},n.lane)})}),e.jsx("div",{className:"absolute inset-0 flex items-center",children:t.segments.filter(n=>n.dot).map(n=>e.jsx("span",{className:"absolute rounded-full border",style:{left:es(n.lane)-ut,width:ut*2,height:ut*2,backgroundColor:n.color,borderColor:"hsl(var(--card))",opacity:t.isLatest?1:.55},"data-lane":n.lane,"data-testid":"track-dot"},n.lane))})]})}const Ja={Ready:"border-success/40 bg-success/10 text-success",Failed:"border-destructive/40 bg-destructive/10 text-destructive",Rendering:"border-primary/40 bg-primary/10 text-primary",Pending:"border-border bg-muted text-muted-foreground"};function ot({phase:t}){const s=Ja[t]??"border-border bg-background text-muted-foreground";return e.jsx("span",{className:`inline-flex rounded-full border px-2 py-0.5 text-[10px] font-medium ${s}`,children:t||"—"})}function Et(t){if(!t)return"—";const s=["B","KB","MB","GB"];let n=0,r=t;for(;r>=1024&&n{let u=!0;return hr(t).then(h=>{u&&(r(h),o(null),l(!0))}).catch(h=>{u&&(o(h.message),l(!0))}),()=>{u=!1}},[t]);const c=d.useMemo(()=>qa(n),[n]);return a?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load artifacts: ",a]})}):e.jsxs("div",{className:"space-y-3",children:[i&&n.length===0&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No artifacts rendered yet."}),n.length>0&&e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"When"}),e.jsx(y,{children:"Artifact"}),e.jsx(y,{children:"Session"}),e.jsx(y,{children:"Kind"}),e.jsx(y,{children:"MIME"}),e.jsx(y,{className:"text-right",children:"Size"}),e.jsx(y,{className:"text-right",children:"Revisions"}),e.jsx(y,{children:"Phase"}),e.jsx(y,{className:"text-right",children:"Tracks"})]})}),e.jsx(X,{children:n.map((u,h)=>{const N=!!(u.session&&s),p=c.rows[h],b=p.isLatest;return e.jsxs(M,{children:[e.jsx(v,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:new Date(u.created).toLocaleString()}),e.jsx(v,{className:"font-mono text-xs",children:e.jsx(W,{entity:"artifact",id:`${u.namespace}/${u.name}`,className:b?"text-link hover:underline":"text-muted-foreground hover:underline",children:u.name})}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:u.session?N?e.jsx("button",{type:"button",className:"hover:text-link hover:underline",onClick:()=>s(Ya(u.session)),children:e.jsx(Z,{id:u.session})}):e.jsx(Z,{id:u.session}):"—"}),e.jsx(v,{children:e.jsx(z,{variant:"outline",children:u.kind})}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:u.mime||"—"}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:Et(u.size)}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:u.revisions||""}),e.jsx(v,{children:e.jsx(ot,{phase:u.phase})}),e.jsx(v,{className:"p-0 pl-2 align-middle",children:e.jsx(Ga,{row:p,width:c.width})})]},p.uid)})})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"ArtifactRender — versioned, sanitized agent outputs · click the name → artifact detail · the session → its Logs · Tracks = git-graph of revisions per logical artifact · oap artifact"})]})}const Xa=[{id:"overview",label:"Overview"},{id:"revisions",label:"Revisions"}];function Za(t){const s=t.indexOf("/");return s<0?{ns:"",name:t}:{ns:t.slice(0,s),name:t.slice(s+1)}}function Ba(t){try{return new URL(t,"http://placeholder.invalid").searchParams.has("artifactId")}catch{return!1}}function ke({label:t,children:s}){return e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("dt",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("dd",{className:"font-mono text-xs text-foreground",children:s})]})}function eo({apiBase:t,id:s,tab:n}){const{ns:r,name:a}=Za(s),o=n??"overview",[i,l]=d.useState(null),[c,u]=d.useState(null),[h,N]=d.useState(!1);d.useEffect(()=>{let b=!0;return l(null),u(null),N(!1),fr(t,r,a).then(x=>{b&&l(x)}).catch(x=>{b&&(x instanceof J&&x.status===404?N(!0):u(x.message))}),()=>{b=!1}},[t,r,a]);const p=b=>te({type:"detail",entity:"artifact",id:s,tab:b});return e.jsx(nt,{title:e.jsx("span",{className:"font-mono",children:e.jsx(Z,{ns:r,name:a})}),backRoute:{type:"view",view:"artifacts"},tabs:Xa,activeTab:o,onTab:p,headerRight:i?e.jsx(ot,{phase:i.phase}):void 0,children:c?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load artifact: ",c]})}):h?e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Artifact ",s," not found — it may have been garbage-collected."]}):i?o==="revisions"?e.jsx(so,{detail:i}):e.jsx(to,{detail:i}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"})})}function to({detail:t}){const s=Ba(t.viewPath);return e.jsxs("div",{className:"flex flex-col gap-5",children:[e.jsxs("dl",{className:"grid grid-cols-2 gap-x-6 gap-y-3 sm:grid-cols-3",children:[e.jsx(ke,{label:"Kind",children:t.kind||"—"}),e.jsx(ke,{label:"Phase",children:e.jsx(ot,{phase:t.phase})}),e.jsx(ke,{label:"MIME",children:t.mime||"—"}),e.jsx(ke,{label:"Size",children:Et(t.size)}),e.jsx(ke,{label:"Created",children:new Date(t.created).toLocaleString()}),e.jsx(ke,{label:"Session",children:t.session?e.jsx(W,{entity:"session",id:t.session,className:"text-link hover:underline",children:e.jsx(Z,{id:t.session})}):"—"})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-muted-foreground",children:"Viewer"}),s?e.jsx("a",{href:t.viewPath,target:"_blank",rel:"noopener noreferrer",className:"w-fit text-sm text-link hover:underline",children:"Open viewer"}):e.jsx("span",{className:"text-[11px] text-muted-foreground",children:"No viewer available for this artifact."})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-muted-foreground",children:"Download"}),t.outputRef?e.jsx("code",{className:"w-fit break-all rounded border border-border bg-background px-2 py-1 font-mono text-xs text-foreground",children:t.outputRef}):e.jsx("span",{className:"text-sm text-muted-foreground",children:"No content ref yet — the render has not reached Ready."})]})]})}function so({detail:t}){return t.revisions.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No revisions."}):e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Name"}),e.jsx(y,{children:"Phase"}),e.jsx(y,{children:"MIME"}),e.jsx(y,{className:"text-right",children:"Size"}),e.jsx(y,{children:"Created"})]})}),e.jsx(X,{children:t.revisions.map(s=>e.jsxs(M,{className:s.name===t.name?"bg-muted/40":void 0,children:[e.jsx(v,{className:"font-mono text-xs",children:e.jsx(W,{entity:"artifact",id:`${t.namespace}/${s.name}`,className:"text-link hover:underline",children:s.name})}),e.jsx(v,{children:e.jsx(ot,{phase:s.phase})}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:s.mime||"—"}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:Et(s.size)}),e.jsx(v,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:new Date(s.created).toLocaleString()})]},s.name))})]})}const Ms=t=>t==null||Number.isNaN(t)?"NaN":`$${t.toFixed(2)}`,ue=[{id:"byModel",label:"By model",keyLabel:"Model"},{id:"byAgentClass",label:"By agent",keyLabel:"Agent class",keyEntity:"agent"},{id:"bySession",label:"By session",keyLabel:"Session",keyEntity:"session"},{id:"byUser",label:"By user",keyLabel:"User",decode:!0}];function no({color:t}){return t?e.jsx("span",{"aria-hidden":"true",className:"inline-block h-2.5 w-2.5 shrink-0 rounded-sm",style:{backgroundColor:t}}):null}function ro({rows:t,keyLabel:s,highlightKey:n,keyEntity:r,decode:a,isModel:o}){if(t.length===0)return e.jsx("p",{className:"px-1 py-6 text-center text-xs text-muted-foreground",children:"No usage recorded."});const i=Ls(t.map(l=>l.key));return e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:s}),e.jsx(y,{className:"text-right",children:"Input tokens"}),e.jsx(y,{className:"text-right",children:"Output tokens"}),e.jsx(y,{className:"text-right",children:"Est. cost"})]})}),e.jsx(X,{children:t.map(l=>{const c=n!=null&&l.key===n,u=r==="session"?e.jsx(Z,{id:l.key}):a?ae(l.key):l.key;return e.jsxs(M,{className:c?"bg-accent/60":void 0,children:[e.jsx(v,{className:"font-mono text-xs",children:e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx(no,{color:i.get(l.key)}),l.key&&r?e.jsx(W,{entity:r,id:l.key,className:"text-link hover:underline",children:u}):o&&l.key?e.jsx(kt,{model:l.key}):e.jsx("span",{children:l.key?u:"(unknown)"}),c&&e.jsx(z,{variant:"secondary",className:"align-middle text-[10px]",children:"from Overview"})]})}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:xe(l.inputTokens)}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:xe(l.outputTokens)}),e.jsx(v,{className:"text-right font-mono text-xs",children:Ms(l.estimatedCostUSD)})]},l.key)})})]})}function ao({apiBase:t}){const s=ks(),n=s.type==="view"&&s.query?s.query.get("model")??void 0:void 0,[r,a]=d.useState(null),[o,i]=d.useState(null);d.useEffect(()=>{let p=!0;return cr(t).then(b=>{p&&(a(b),i(null))}).catch(b=>{p&&i(b.message)}),()=>{p=!1}},[t]);const[l,c]=d.useState("byModel"),u=d.useRef(null),h=p=>{var C,E;if(!["ArrowRight","ArrowLeft","Home","End"].includes(p.key))return;p.preventDefault();const x=ue.findIndex(m=>m.id===l),j=p.key==="ArrowRight"?(x+1)%ue.length:p.key==="ArrowLeft"?(x-1+ue.length)%ue.length:p.key==="Home"?0:ue.length-1,$=ue[j].id;c($),(E=(C=u.current)==null?void 0:C.querySelector(`#budget-tab-${$}`))==null||E.focus()};if(o)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load budget: ",o]})});if(!r)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading budget…"});const N=r.byModel.reduce((p,b)=>p+(b.estimatedCostUSD??NaN),0);return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2 rounded-xl border bg-card p-4 shadow",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:"Total estimated spend"}),e.jsx("div",{className:"mt-0.5 text-2xl font-semibold tracking-tight",children:Ms(N)})]}),e.jsx("p",{className:"max-w-md text-[11px] text-muted-foreground",children:"Estimated from list prices — not a metered bill. Same live-session scope as the Overview budget panel."})]}),e.jsx("div",{ref:u,role:"tablist","aria-label":"Budget breakdown axes",onKeyDown:h,className:"inline-flex h-9 items-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground",children:ue.map(p=>{const b=l===p.id;return e.jsx("button",{id:`budget-tab-${p.id}`,type:"button",role:"tab","aria-selected":b,"aria-controls":`budget-panel-${p.id}`,tabIndex:b?0:-1,onClick:()=>c(p.id),className:["inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",b?"bg-background text-foreground shadow":"hover:text-foreground"].join(" "),children:p.label},p.id)})}),ue.filter(p=>p.id===l).map(p=>e.jsx("div",{id:`budget-panel-${p.id}`,role:"tabpanel","aria-labelledby":`budget-tab-${p.id}`,tabIndex:0,className:"rounded-xl border bg-card p-2 shadow",children:e.jsx(ro,{rows:r[p.id],keyLabel:p.keyLabel,highlightKey:p.id==="byModel"?n:void 0,keyEntity:p.keyEntity,decode:p.decode,isModel:p.id==="byModel"})},p.id))]})}function oo({apiBase:t}){const[s,n]=d.useState(""),[r,a]=d.useState(""),[o,i]=d.useState(null),[l,c]=d.useState(null),[u,h]=d.useState(!1);d.useEffect(()=>{let j=!0;return gr(t,r?{q:r}:{}).then($=>{j&&(i($),c(null),h(!0))}).catch($=>{j&&(c($.message),h(!0))}),()=>{j=!1}},[t,r]);const N=j=>{j.preventDefault(),a(s.trim())},p=(o==null?void 0:o.rollups)??[],b=(o==null?void 0:o.entries)??[],x=r!=="";return e.jsxs("div",{className:"space-y-3",children:[e.jsx("form",{onSubmit:N,className:"flex items-center gap-2",children:e.jsxs("label",{className:"flex h-9 flex-1 items-center gap-2 rounded-md border border-input bg-background px-3 text-xs",children:[e.jsx(fs,{className:"h-3.5 w-3.5 text-muted-foreground"}),e.jsx("input",{type:"search",value:s,onChange:j=>n(j.target.value),placeholder:"search memory — text, tags, vectors…","aria-label":"Search memory",className:"flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground"})]})}),l&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load memory: ",l]})}),(o==null?void 0:o.truncated)&&e.jsx(I,{children:e.jsx(F,{children:"Results truncated — narrow the scan for complete rollup counts."})}),x?b.length===0?e.jsxs("p",{className:"text-sm text-muted-foreground",children:["No matches for “",r,"”."]}):e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Kind"}),e.jsx(y,{children:"Scope"}),e.jsx(y,{children:"Entry"}),e.jsx(y,{children:"When"}),e.jsx(y,{className:"text-right",children:"Score"})]})}),e.jsx(X,{children:b.map(j=>e.jsxs(M,{children:[e.jsx(v,{children:e.jsx(z,{variant:"outline",children:j.kind})}),e.jsx(v,{className:"font-mono text-xs text-muted-foreground",children:j.scope}),e.jsx(v,{className:"font-mono text-xs text-foreground",children:j.id}),e.jsx(v,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:j.created?new Date(j.created).toLocaleString():"—"}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:j.score!=null?j.score.toFixed(2):""})]},`${j.scope}/${j.kind}/${j.id}`))})]}):p.length===0?u&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No memory entries yet."}):e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Kind"}),e.jsx(y,{className:"text-right",children:"Entries"}),e.jsx(y,{className:"text-right",children:"Scopes"}),e.jsx(y,{children:"Last write"}),e.jsx(y,{})]})}),e.jsx(X,{children:p.map(j=>e.jsxs(M,{children:[e.jsx(v,{className:"font-mono text-xs text-foreground",children:j.kind}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:j.entries}),e.jsx(v,{className:"text-right font-mono text-xs text-muted-foreground",children:j.scopes}),e.jsx(v,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:j.lastWrite?new Date(j.lastWrite).toLocaleString():"—"}),e.jsx(v,{children:j.appendOnly&&e.jsx(z,{children:"append-only"})})]},j.kind))})]}),e.jsxs("p",{className:"text-[11px] text-muted-foreground",children:["Append-only kinds are tamper-evident (Ed25519 hash chains) — verify with"," ",e.jsx("span",{className:"font-mono",children:"oap audit verify"})," · oap memory list / search / query"]})]})}function vt({uuid:t,label:s,onOpen:n}){return t?e.jsxs("button",{type:"button",onClick:()=>n(t),className:"inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1 font-mono text-xs hover:bg-accent hover:text-accent-foreground",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-state"}),s||t]}):e.jsx("span",{className:"font-mono text-xs text-muted-foreground",children:s||"—"})}function ts({facts:t,onOpen:s}){return t.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No facts."}):e.jsx("ul",{className:"space-y-2",children:t.map(n=>e.jsxs("li",{className:"rounded-md border bg-background p-3",children:[e.jsx("div",{className:"text-sm text-foreground",children:n.fact||n.name}),e.jsxs("div",{className:"mt-1.5 flex flex-wrap items-center gap-1.5",children:[e.jsx(vt,{uuid:n.fromEntity,label:n.fromEntity,onOpen:s}),e.jsx("span",{className:"text-muted-foreground",children:"→"}),e.jsx(vt,{uuid:n.toEntity,label:n.toEntity,onOpen:s}),n.validAt&&e.jsx("span",{className:"ml-1 font-mono text-[11px] text-muted-foreground",children:new Date(n.validAt).toLocaleDateString()})]})]},n.uuid))})}function lo({apiBase:t}){var L,k;const[s,n]=d.useState(null),[r,a]=d.useState(""),[o,i]=d.useState(null),[l,c]=d.useState([]),[u,h]=d.useState(""),[N,p]=d.useState(""),[b,x]=d.useState(null),[j,$]=d.useState(null),[C,E]=d.useState(null),m=d.useRef(null);d.useEffect(()=>{let g=!0;return wr(t).then(T=>{g&&(n(T.available),a(T.note??""),c(T.communities??[]),i(T.error??null))}).catch(T=>{g&&(n(!0),i(T.message))}),()=>{g=!1}},[t]),d.useEffect(()=>{if(!N){x(null),$(null);return}let g=!0;return br(t,N).then(T=>{g&&(x(T.facts??[]),$(T.error??null))}).catch(T=>{g&&(x([]),$(T.message))}),()=>{g=!1}},[t,N]);const w=d.useCallback(g=>{g&&(m.current=g,E({uuid:g,loading:!0,entity:null,facts:[],related:[]}),Promise.all([vr(t,g),yr(t,g),Nr(t,g)]).then(([T,P,V])=>{m.current===g&&E({uuid:g,loading:!1,entity:T.entity??null,facts:P.facts??[],related:V.entities??[],error:T.error||P.error||V.error||void 0})}).catch(T=>{m.current===g&&E({uuid:g,loading:!1,entity:null,facts:[],related:[],error:T.message})}))},[t]),R=g=>{g.preventDefault(),p(u.trim())};if(s===!1)return e.jsxs("div",{className:"rounded-xl border border-dashed bg-card p-6 shadow",children:[e.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold text-foreground",children:[e.jsx(hs,{className:"h-4 w-4 text-muted-foreground"})," Knowledge graph not configured"]}),e.jsxs("p",{className:"mt-2 text-sm text-muted-foreground",children:[r||"The knowledge graph is not available."," Set"," ",e.jsx("span",{className:"font-mono",children:"--graphiti-endpoint"})," on the operator to enable graph-aware entity, fact, and community search."]}),e.jsx("p",{className:"mt-3 text-[11px] text-muted-foreground",children:"oap kg search / entity / facts / related / communities"})]});if(s===null&&!o)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading knowledge graph…"});const O=N!=="";return e.jsxs("div",{className:"space-y-4",children:[o&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load knowledge graph: ",o]})}),e.jsx("form",{onSubmit:R,className:"flex items-center gap-2",children:e.jsxs("label",{className:"flex h-9 flex-1 items-center gap-2 rounded-md border border-input bg-background px-3 text-xs",children:[e.jsx(fs,{className:"h-3.5 w-3.5 text-muted-foreground"}),e.jsx("input",{type:"search",value:u,onChange:g=>h(g.target.value),placeholder:"search facts — graph-aware…","aria-label":"Search facts",className:"flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground"})]})}),j&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Fact search degraded: ",j]})}),O&&e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsxs("div",{className:"text-sm font-semibold text-foreground",children:["Facts matching “",N,"”"]}),e.jsx("div",{className:"mt-3",children:b===null?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Searching…"}):e.jsx(ts,{facts:b,onOpen:w})})]}),C&&e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-semibold text-foreground",children:((L=C.entity)==null?void 0:L.name)||C.uuid}),((k=C.entity)==null?void 0:k.summary)&&e.jsx("div",{className:"mt-0.5 text-xs text-muted-foreground",children:C.entity.summary})]}),e.jsx("button",{type:"button","aria-label":"Close entity",onClick:()=>E(null),className:"inline-flex h-6 w-6 items-center justify-center rounded border border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:e.jsx(zn,{className:"h-3 w-3"})})]}),C.error&&e.jsx(I,{variant:"destructive",className:"mt-3",children:e.jsxs(F,{children:["Entity lookup degraded: ",C.error]})}),C.loading?e.jsx("p",{className:"mt-3 text-sm text-muted-foreground",children:"Loading entity…"}):e.jsxs("div",{className:"mt-3 space-y-4",children:[e.jsxs("div",{children:[e.jsx("div",{className:"mb-2 text-xs font-semibold text-muted-foreground",children:"Facts"}),e.jsx(ts,{facts:C.facts,onOpen:w})]}),e.jsxs("div",{children:[e.jsx("div",{className:"mb-2 text-xs font-semibold text-muted-foreground",children:"Related entities"}),C.related.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No related entities."}):e.jsx("div",{className:"flex flex-wrap gap-2",children:C.related.map(g=>e.jsx(vt,{uuid:g.uuid,label:g.name||g.uuid,onOpen:w},g.uuid))})]})]})]}),e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsx("div",{className:"text-sm font-semibold text-foreground",children:"Communities"}),l.length===0?e.jsx("p",{className:"mt-2 text-sm text-muted-foreground",children:"No communities yet."}):e.jsx("ul",{className:"mt-3 space-y-2",children:l.map(g=>e.jsxs("li",{className:"rounded-md border bg-background p-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"font-mono text-xs text-foreground",children:g.name||g.uuid}),e.jsxs(z,{variant:"outline",children:[g.members.length," members"]})]}),g.summary&&e.jsx("div",{className:"mt-1 text-xs text-muted-foreground",children:g.summary})]},g.uuid))}),e.jsx("p",{className:"mt-3 text-[11px] text-muted-foreground",children:"Graphiti knowledge graph — entities, facts, communities · oap kg search / entity / facts / related"})]})]})}const ss=t=>new URLSearchParams({model:t}),io=t=>t==null||Number.isNaN(t)?"NaN":`$${t.toFixed(2)}`,Ue=["bg-chart-1","bg-chart-2","bg-chart-3","bg-chart-4","bg-chart-5"],co={Healthy:"bg-success",Degraded:"bg-warning",Down:"bg-destructive",NotConfigured:"bg-muted-foreground"},uo={Healthy:"text-success",Degraded:"text-warning",Down:"text-destructive",NotConfigured:"text-muted-foreground"};function xo({values:t,width:s=600,height:n=150}){const r=Math.max(1,...t)*1.12,a=6,o=t.length,i=h=>a+(o<=1?0:h*(s-2*a)/(o-1)),l=h=>n-a-h/r*(n-2*a),c=t.map((h,N)=>`${N?"L":"M"} ${i(N).toFixed(1)} ${l(h).toFixed(1)}`).join(" "),u=`${c} L ${i(o-1).toFixed(1)} ${(n-a).toFixed(1)} L ${i(0).toFixed(1)} ${(n-a).toFixed(1)} Z`;return e.jsxs("svg",{viewBox:`0 0 ${s} ${n}`,className:"h-40 w-full",preserveAspectRatio:"none",role:"img","aria-label":"Tokens over time",children:[e.jsx("defs",{children:e.jsxs("linearGradient",{id:"ap-overview-area",x1:"0",x2:"0",y1:"0",y2:"1",children:[e.jsx("stop",{offset:"0%",stopColor:"hsl(var(--chart-1))",stopOpacity:"0.35"}),e.jsx("stop",{offset:"100%",stopColor:"hsl(var(--chart-1))",stopOpacity:"0"})]})}),e.jsx("path",{d:u,fill:"url(#ap-overview-area)"}),e.jsx("path",{d:c,fill:"none",stroke:"hsl(var(--chart-1))",strokeWidth:"1.5",vectorEffect:"non-scaling-stroke"})]})}function mo({label:t,value:s,sub:n}){return e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:t}),e.jsx("div",{className:"mt-1 text-2xl font-semibold tracking-tight",children:s}),n&&e.jsx("div",{className:"mt-0.5 font-mono text-[11px] text-muted-foreground",children:n})]})}function ho({apiBase:t}){const[s,n]=d.useState(null),[r,a]=d.useState(null),[o,i]=d.useState(null),[l,c]=d.useState(null);if(d.useEffect(()=>{let m=!0;return or(t).then(w=>{m&&(n(w),i(null))}).catch(w=>{m&&i(w.message)}),lr(t).then(w=>{m&&(a(w),c(null))}).catch(w=>{m&&c(w.message)}),()=>{m=!1}},[t]),o)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load overview: ",o]})});if(!s)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading overview…"});const u=s.kpis,h=s.budget,N=s.series24h.map(m=>m.inputTokens+m.outputTokens),p=N.reduce((m,w)=>m+w,0),b=h.tokenCeiling&&h.tokenCeiling>0?h.tokensSpent/h.tokenCeiling*100:null,x=b!=null?`${b.toFixed(0)}%`:xe(h.tokensSpent),j=[{label:"Active sessions",value:`${u.activeSessions}`},{label:"Tokens today",value:xe(u.tokensToday)},{label:"Tool calls (24h)",value:`${u.toolCalls24h}`},{label:"Approvals pending",value:`${u.approvalsPending}`},{label:"Denials (24h)",value:`${u.denials24h}`}],$=s.byModel.reduce((m,w)=>m+w.inputTokens+w.outputTokens,0)||1,C=Math.max(1,...s.byAgentClass.map(m=>m.inputTokens+m.outputTokens)),E=((r==null?void 0:r.components)??[]).filter(m=>m.status==="Degraded"||m.status==="Down").length;return e.jsxs("div",{className:"space-y-4",children:[e.jsx("div",{className:"grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-5",children:j.map(m=>e.jsx(mo,{label:m.label,value:m.value},m.label))}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 lg:grid-cols-3",children:[e.jsxs(me,{className:"lg:col-span-2",children:[e.jsxs(he,{className:"flex-row items-center justify-between pb-2",children:[e.jsxs("div",{children:[e.jsx(fe,{className:"text-sm",children:"Tokens over time"}),e.jsx("div",{className:"text-[11px] text-muted-foreground",children:"last 24 hours · all models"})]}),e.jsxs("div",{className:"font-mono text-xs text-muted-foreground",children:[xe(p)," total"]})]}),e.jsx(pe,{children:e.jsx(xo,{values:N})})]}),e.jsx(zt,{view:"budget",className:"block h-full rounded-xl",children:e.jsxs(me,{className:"h-full transition-colors hover:border-primary/50 hover:bg-accent/20",children:[e.jsxs(he,{className:"flex-row items-center justify-between pb-2",children:[e.jsx(fe,{className:"text-sm",children:"Budget"}),e.jsx("span",{className:"text-[11px] text-link",children:"View breakdown →"})]}),e.jsxs(pe,{className:"space-y-2",children:[e.jsxs("div",{className:"flex items-end gap-2",children:[e.jsx("div",{className:"text-3xl font-semibold tracking-tight",children:x}),e.jsx("div",{className:"mb-1 font-mono text-xs text-muted-foreground",children:b!=null?`${xe(h.tokensSpent)} tokens`:"tokens spent"})]}),e.jsxs("div",{className:"font-mono text-sm text-muted-foreground",children:["est. ",io(h.estimatedCostUSD)]}),e.jsxs("div",{className:"text-[11px] text-muted-foreground",children:[h.estimated?"estimated · list-price":"metered"," · daily budget resets 00:00 UTC"]})]})]})})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 lg:grid-cols-2",children:[e.jsxs(me,{children:[e.jsx(he,{className:"pb-2",children:e.jsx(fe,{className:"text-sm",children:"Tokens by model"})}),e.jsxs(pe,{className:"space-y-3",children:[e.jsx("div",{"aria-hidden":"true",className:"flex h-3 w-full overflow-hidden rounded",children:s.byModel.map((m,w)=>e.jsx("div",{onClick:()=>te({type:"view",view:"budget",query:ss(m.model)}),title:`${m.model||"(unknown)"} — view budget`,className:`h-full cursor-pointer ${Ue[w%Ue.length]}`,style:{width:`${((m.inputTokens+m.outputTokens)/$*100).toFixed(1)}%`}},m.model))}),e.jsxs("div",{className:"space-y-2",children:[s.byModel.length===0&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"No live model usage."}),s.byModel.map((m,w)=>e.jsxs(zt,{view:"budget",query:ss(m.model),className:"-mx-1 flex items-center justify-between rounded px-1 py-0.5 text-xs transition-colors hover:bg-accent/40",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx("span",{"aria-hidden":"true",className:`h-2.5 w-2.5 rounded-sm ${Ue[w%Ue.length]}`}),m.model?e.jsx(kt,{model:m.model}):e.jsx("span",{className:"font-mono",children:"(unknown)"})]}),e.jsxs("span",{className:"font-mono text-muted-foreground",children:[xe(m.inputTokens+m.outputTokens)," · ",m.sessions," sess"]})]},m.model))]})]})]}),e.jsxs(me,{children:[e.jsx(he,{className:"pb-2",children:e.jsx(fe,{className:"text-sm",children:"Tokens by agent class"})}),e.jsxs(pe,{className:"space-y-2.5",children:[s.byAgentClass.length===0&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"No live agent-class usage."}),s.byAgentClass.map(m=>{const w=m.inputTokens+m.outputTokens,R=e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"w-28 shrink-0 truncate font-mono",children:m.class||"(unknown)"}),e.jsx("div",{className:"h-2.5 flex-1 overflow-hidden rounded bg-muted",children:e.jsx("div",{className:"h-full rounded bg-chart-1",style:{width:`${(w/C*100).toFixed(1)}%`}})}),e.jsx("span",{className:"w-12 shrink-0 text-right font-mono text-muted-foreground",children:xe(w)})]});return m.class?e.jsx(W,{entity:"agent",id:m.class,className:"-mx-1 flex items-center gap-3 rounded px-1 py-0.5 text-xs transition-colors hover:bg-accent/40",children:R},m.class):e.jsx("div",{className:"flex items-center gap-3 px-1 py-0.5 text-xs text-muted-foreground",children:R},"__unknown__")})]})]})]}),e.jsxs(me,{children:[e.jsxs(he,{className:"flex-row items-center justify-between pb-2",children:[e.jsx(fe,{className:"text-sm",children:"Cluster health"}),E>0?e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] text-warning",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-warning"}),E," component",E>1?"s":""," degraded"]}):r&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] text-success",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-success"}),"all healthy"]})]}),e.jsxs(pe,{className:"space-y-3",children:[l&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load cluster health: ",l]})}),r&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"grid grid-cols-2 gap-2 md:grid-cols-4",children:r.components.map(m=>e.jsxs("div",{className:"flex items-center justify-between rounded-md border bg-background px-3 py-2",children:[e.jsxs("span",{className:"flex items-center gap-2 font-mono text-xs",children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${co[m.status]}`}),m.name]}),e.jsx("span",{className:`text-[10px] ${uo[m.status]}`,title:m.detail,children:m.status})]},m.name))}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 md:grid-cols-4",children:[e.jsxs("div",{className:"rounded-md border bg-background px-3 py-2",children:[e.jsx("div",{className:"text-[10px] text-muted-foreground",children:"Pods"}),e.jsxs("div",{className:"mt-0.5 font-mono text-sm",children:[r.rollup.ready," / ",r.rollup.pods," ready"]})]}),e.jsxs("div",{className:"rounded-md border bg-background px-3 py-2",children:[e.jsx("div",{className:"text-[10px] text-muted-foreground",children:"CPU"}),e.jsx("div",{className:"mt-0.5 font-mono text-sm",children:r.rollup.cpu})]}),e.jsxs("div",{className:"rounded-md border bg-background px-3 py-2",children:[e.jsx("div",{className:"text-[10px] text-muted-foreground",children:"Memory"}),e.jsx("div",{className:"mt-0.5 font-mono text-sm",children:r.rollup.memory})]})]})]})]})]})]})}function Ps({text:t}){const[s,n]=d.useState(!1),r=()=>{var o;const a=(o=navigator.clipboard)==null?void 0:o.writeText(t);a&&typeof a.then=="function"&&a.then(()=>{n(!0),setTimeout(()=>n(!1),1200)}).catch(()=>{})};return e.jsx("button",{type:"button","aria-label":"Copy command",onClick:r,className:"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:s?e.jsx(qn,{className:"h-3 w-3 text-success"}):e.jsx(Gn,{className:"h-3 w-3"})})}function lt({text:t}){return e.jsxs("div",{className:"flex items-center gap-2 rounded-md border bg-background px-3 py-2",children:[e.jsx("code",{className:"flex-1 truncate font-mono text-xs text-foreground",title:t,children:t}),e.jsx(Ps,{text:t})]})}const ns="rounded-full border px-2 py-0.5 text-[10px] transition-colors",rs="border-border bg-background text-muted-foreground hover:bg-accent/50",fo="border-primary/40 bg-primary/10 text-primary";function as({statuses:t,value:s,onChange:n}){return e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("button",{type:"button","aria-pressed":s==="",onClick:()=>n(""),className:`${ns} ${s===""?fo:rs}`,children:"All"}),t.map(r=>{const a=s===r;return e.jsx("button",{type:"button","aria-pressed":a,onClick:()=>n(a?"":r),className:`${ns} ${a?Ds(Es(r)):rs}`,children:r},r)})]})}const po=new Set(["Degraded","Failed","Down","Invalid"]);function go(t){return po.has(t)}function Ks({status:t,reason:s}){return e.jsx("span",{title:s||void 0,className:`inline-flex items-center rounded-full border px-2.5 py-0.5 text-[11px] ${at(t)}`,children:t||"Unknown"})}function jo({status:t,reason:s}){return go(t)?e.jsxs(I,{variant:"destructive",children:[e.jsx(dn,{children:t}),s&&e.jsx(F,{children:s})]}):null}function le({label:t,children:s}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("div",{className:"text-sm text-foreground",children:s})]})}function yt({children:t}){return e.jsx("span",{className:"rounded-full border bg-background px-2 py-0.5 text-[10px] font-mono text-muted-foreground",children:t})}function ye(t,s){var n,r;return(r=(n=t.badges)==null?void 0:n.find(a=>a.key===s))==null?void 0:r.value}function it(t){return t.namespace?`${t.namespace}/${t.name}`:t.name}function bo({status:t,reason:s}){const n=e.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] ${at(t)}`,children:t||"—"});return s?e.jsx(un,{delayDuration:0,children:e.jsxs(xn,{children:[e.jsx(mn,{asChild:!0,children:n}),e.jsx(hn,{className:"max-w-xs whitespace-normal break-words text-xs",children:s})]})}):n}function vo(t,s){if(s.render)return s.render(t);if(s.source==="scope")return e.jsx(yt,{children:t.scope});if(s.source==="count"){const r=t.counts??[],a=s.key?r.find(o=>o.label===s.key):r[0];return a?e.jsx("span",{className:"font-mono text-xs text-muted-foreground",children:a.value}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}const n=(t.badges??[]).filter(r=>s.key?r.key===s.key:!0);return n.length===0?e.jsx("span",{className:"text-muted-foreground",children:"—"}):e.jsx("span",{className:"flex flex-wrap gap-1",children:n.map((r,a)=>s.colorize?e.jsx(rt,{kind:r.value},`${r.key}-${r.value}-${a}`):e.jsx(yt,{children:r.value},`${r.key}-${r.value}-${a}`))})}function yo(t){const s=d.useState(""),n=$t(t??"","");return t?n:s}function _s({rows:t,columns:s,entity:n,facet:r,statusCookieKey:a,loading:o,note:i,emptyText:l}){const[c,u]=d.useState(""),[h,N]=yo(a),[p,b]=d.useState(""),x=d.useMemo(()=>{const m=new Set;for(const w of t)w.status&&m.add(w.status);return Array.from(m).sort()},[t]),j=d.useMemo(()=>{if(!r)return[];const m=new Set;for(const w of t)for(const R of w.badges??[])R.key===r.key&&m.add(R.value);return Array.from(m).sort()},[t,r]),$=t.some(m=>m.manageCmd),C=c.trim().toLowerCase(),E=t.filter(m=>(h===""||m.status===h)&&(p===""||(m.badges??[]).some(w=>r&&w.key===r.key&&w.value===p))&&(C===""||m.name.toLowerCase().includes(C)||(m.namespace??"").toLowerCase().includes(C)));return o?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"}):t.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:l??"Nothing configured yet."}):e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",value:c,onChange:m=>u(m.target.value),placeholder:"Filter by name…","aria-label":"Filter by name",className:"h-8 w-56 rounded-md border border-input bg-background px-3 text-xs outline-none focus-visible:ring-1 focus-visible:ring-ring"}),x.length>1&&e.jsx(as,{statuses:x,value:h,onChange:N})]}),r&&j.length>1&&e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:r.label}),e.jsx(as,{statuses:j,value:p,onChange:b})]}),e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Name"}),s.map(m=>e.jsx(y,{className:m.align==="right"?"text-right":"",children:m.header},m.header)),e.jsx(y,{children:"Status"}),$&&e.jsx(y,{children:"Manage"})]})}),e.jsxs(X,{children:[E.map(m=>e.jsxs(M,{children:[e.jsxs(v,{children:[n?e.jsx(W,{entity:n,id:it(m),className:"font-mono text-xs text-link hover:underline",children:m.name}):e.jsx("div",{className:"font-mono text-xs text-foreground",children:m.name}),m.namespace&&e.jsx("div",{className:"font-mono text-[10px] text-muted-foreground",children:m.namespace})]}),s.map(w=>e.jsx(v,{className:w.align==="right"?"text-right":"",children:vo(m,w)},w.header)),e.jsx(v,{children:e.jsx(bo,{status:m.status,reason:m.statusReason})}),$&&e.jsx(v,{children:m.manageCmd?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("code",{className:"truncate font-mono text-[11px] text-muted-foreground",title:m.manageCmd,children:m.manageCmd}),e.jsx(Ps,{text:m.manageCmd})]}):e.jsx("span",{className:"text-muted-foreground",children:"—"})})]},`${m.namespace??""}/${m.name}`)),E.length===0&&e.jsx(M,{children:e.jsx(v,{colSpan:s.length+($?3:2),className:"text-center text-xs text-muted-foreground",children:"No matches."})})]})]}),i&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:i})]})}const ce={agents:[{header:"Identity",source:"badge",key:"identityMode"},{header:"Tools",source:"count",key:"tools",align:"right"},{header:"Skills",source:"count",key:"skills",align:"right"}],tools:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Capabilities",source:"count",align:"right"}],skills:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Delivery",source:"badge",key:"delivery"},{header:"Pin",source:"badge",key:"pin"}],sources:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Repo",source:"badge",key:"repo"},{header:"Ref",source:"badge",key:"ref"},{header:"Skills",source:"count",key:"discoveredSkills",align:"right"}],directory:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Credential",source:"badge",key:"credential"},{header:"Scopes",source:"count",key:"scopes",align:"right"},{header:"Written",source:"count",key:"written",align:"right"},{header:"Join misses",source:"count",key:"joinMisses",align:"right"},{header:"Errors",source:"count",key:"errors",align:"right"}],channels:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Role",source:"badge",key:"role"},{header:"Agent class",source:"badge",key:"agentClass"}],users:[{header:"Subject",source:"badge",key:"subject"},{header:"Credentials",source:"count",key:"availableCredentials",align:"right"},{header:"Resolved",source:"count",key:"resolved",align:"right"}],identities:[{header:"Credential kinds",source:"badge",key:"cred",colorize:!0},{header:"Credentials",source:"count",key:"credentials",align:"right"},{header:"Resolved",source:"count",key:"resolved",align:"right"}],providers:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Issuer",source:"badge",key:"issuer"}]},Oe="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50";function os(t){return t?`Dependency ${t}`:"Root agent"}function Ve(t){var n;const s=((n=t.agentPath)==null?void 0:n.split(" > ").map(r=>r.trim()).filter(Boolean))??[];return s.length===0?t.name:`${s.map(r=>`agents.${r}`).join(".")}.${t.name}`}function re(t){return`${t.agentPath??""}\0${t.name}\0${t.setupToken??""}`}function xt(t,s){const n={};for(const r of s)t[r]!==void 0&&(n[r]=t[r]);return n}function We(t){return t==null?"":typeof t=="string"?t:Array.isArray(t)?t.join(","):String(t)}function No(t,s){var r,a;const n=(r=t.enumLabels)==null?void 0:r[s];return n!==void 0&&n.trim()!==""?n:((a=t.enum)==null?void 0:a[s])??""}function ls({q:t,value:s,onChange:n,disabled:r=!1}){const a=t.required!==!1,o=`${t.prompt||t.name}${a?" *":""}`;return e.jsxs(le,{label:o,children:[t.type==="bool"?e.jsx("input",{type:"checkbox",checked:s==="true",onChange:i=>{r||n(i.target.checked?"true":"false")},disabled:r,"aria-label":o,className:"h-4 w-4 rounded border-border"}):t.type==="enum"?e.jsxs("select",{value:s,onChange:i=>{r||n(i.target.value)},disabled:r,"aria-label":o,className:Oe,children:[e.jsx("option",{value:"",disabled:!0,children:"Select…"}),(t.enum??[]).map((i,l)=>e.jsx("option",{value:i,children:No(t,l)},i))]}):e.jsx("input",{type:t.type==="secret"?"password":t.type==="int"?"number":"text",value:s,onChange:i=>{r||n(i.target.value)},disabled:r,"aria-label":o,placeholder:t.type==="resourceList"?"comma-separated":void 0,autoComplete:t.type==="secret"?"new-password":"off",className:Oe}),t.description&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:t.description})]})}function wo({apiBase:t,onInstalled:s}){const[n,r]=d.useState("file"),[a,o]=d.useState(null),[i,l]=d.useState(""),[c,u]=d.useState("default"),[h,N]=d.useState(""),[p,b]=d.useState({}),[x,j]=d.useState(null),[$,C]=d.useState([]),[E,m]=d.useState([]),[w,R]=d.useState([]),[O,L]=d.useState([]),[k,g]=d.useState([]),[T,P]=d.useState({}),[V,q]=d.useState({}),[ie,Ce]=d.useState({}),[Rt,Ne]=d.useState(null),[Ot,oe]=d.useState(null),[zs,Me]=d.useState(!1),[It,Ft]=d.useState(!1),qs=n==="file"?a!==null:i.trim()!=="",Mt=()=>{R([]),L([])},Gs=()=>{g([]),P({}),q({}),Ce({}),Ne(null)},we=()=>{Mt(),Gs()},Js=(f,A)=>{b(D=>({...D,[f]:A})),Mt();const S=new Set(Object.entries(V).filter(([,D])=>D==="staged").map(([D])=>D));g(D=>D.filter(K=>S.has(re(K)))),P(D=>xt(D,S)),q(D=>xt(D,S)),Ce(D=>xt(D,S)),Ne(null)},Pt=(f,A,S,D,K,B=!1)=>{f.length>0?j(f):B||j(null),R(A),g(S),C(D),m(K),b(H=>{const G={...H};for(const ne of f){const se=Ve(ne);G[se]===void 0&&ne.default!==void 0&&ne.default!==null&&(G[se]=We(ne.default))}return G}),P(H=>{const G={};for(const ne of S){const se=re(ne),ct={...H[se]??{}};for(const $e of ne.questions??[])ct[$e.name]===void 0&&$e.default!==void 0&&$e.default!==null&&(ct[$e.name]=We($e.default));G[se]=ct}return G}),q(H=>{const G={};for(const ne of S){const se=re(ne);G[se]=H[se]??"pending"}return G}),Ce(H=>{const G={};for(const ne of S){const se=re(ne);H[se]&&(G[se]=H[se])}return G})},Ys=(f,A,S)=>{const D=re(f);P(K=>({...K,[D]:{...K[D]??{},[A]:S}}))},Kt=(f,A)=>{const S=re(f);g(D=>D.map(K=>re(K)===S?{...K,questions:A}:K))},Qs=async f=>{const A=re(f);if(!f.setupToken){oe(`Channel ${f.name} has no setup token.`);return}oe(null),Ne(A);try{const S=await Ut(t,f.setupToken,T[A]??{});Ce(D=>({...D,[A]:{nextSteps:S.nextSteps??[],warnings:S.warnings??[]}})),q(D=>({...D,[A]:"staged"}))}catch(S){S instanceof ht?Kt(f,S.questions):S instanceof J&&S.status===403?Me(!0):oe(S.message)}finally{Ne(null)}},Xs=f=>{const A=Se(f.url);if(!A)return oe("Channel setup returned an unsafe handoff URL."),!1;if(!f.formFields||Object.keys(f.formFields).length===0)return window.open(A,"_blank","noopener,noreferrer"),!0;const S=document.createElement("form");S.method="POST",S.action=A,S.target="_blank",S.rel="noopener noreferrer";for(const[D,K]of Object.entries(f.formFields)){const B=document.createElement("input");B.type="hidden",B.name=D,B.value=K,S.appendChild(B)}document.body.appendChild(S);try{S.submit()}finally{S.remove()}return!0},Zs=async f=>{const A=re(f);if(!f.setupToken){oe(`Channel ${f.name} has no setup token.`);return}oe(null),Ne(A);try{const S=await tr(t,f.setupToken,T[A]??{});if(S.nextAction==="setup"){const D=await Ut(t,f.setupToken,T[A]??{});Ce(K=>({...K,[A]:{nextSteps:D.nextSteps??[],warnings:D.warnings??[]}})),q(K=>({...K,[A]:"staged"}));return}if(!S.url){oe("Channel setup returned no handoff URL or next action.");return}Xs({url:S.url,formFields:S.formFields})&&q(D=>({...D,[A]:"handoff-started"}))}catch(S){S instanceof ht?Kt(f,S.questions):S instanceof J&&S.status===403?Me(!0):oe(S.message)}finally{Ne(null)}},Bs=async()=>{oe(null),Me(!1),C([]),m([]),R([]),L([]),Ft(!0);try{const f=n==="file"?await nr(t,a,{namespace:c,name:h||void 0,values:p,adopt:O}):await sr(t,{ref:i.trim(),namespace:c,name:h||void 0,values:p,adopt:O}),A=k.flatMap(D=>{const K=ie[re(D)];return K?[...K.nextSteps,...K.warnings].map(B=>`${D.name}: ${B}`):[]}),S=A.length===0?f:{...f,warnings:[...f.warnings??[],...A]};j(null),g([]),s(S,c)}catch(f){f instanceof tt?Pt(f.questions,f.conflicts,f.channels,f.skillClones,f.warnings):f instanceof st?Pt(f.questions,f.conflicts,f.channels,f.skillClones,f.warnings,!0):f instanceof J&&f.status===403?Me(!0):oe(f.message)}finally{Ft(!1)}};return e.jsxs("div",{className:"flex flex-col gap-4",children:[zs&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:"Not authorized to install agents."})}),Ot&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:Ot})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(U,{type:"button",variant:n==="file"?"default":"outline",size:"sm",onClick:()=>{r("file"),we()},children:"Upload a .oap file"}),e.jsx(U,{type:"button",variant:n==="ref"?"default":"outline",size:"sm",onClick:()=>{r("ref"),we()},children:"Registry ref"})]}),n==="file"?e.jsx(le,{label:"Bundle file",children:e.jsx("input",{type:"file",accept:".oap","aria-label":"Bundle file",onChange:f=>{var A;o(((A=f.target.files)==null?void 0:A[0])??null),we()},className:"text-sm text-foreground"},"file-input")}):e.jsx(le,{label:"Registry ref",children:e.jsx("input",{type:"text",value:i,onChange:f=>{l(f.target.value),we()},placeholder:"ghcr.io/acme/support-bot:1.0.0","aria-label":"Registry ref",className:Oe},"ref-input")}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-2",children:[e.jsx(le,{label:"Namespace *",children:e.jsx("input",{type:"text",value:c,onChange:f=>{u(f.target.value),we()},"aria-label":"Namespace",className:Oe})}),e.jsx(le,{label:"Name (optional)",children:e.jsx("input",{type:"text",value:h,onChange:f=>{N(f.target.value),we()},placeholder:"defaults to the bundled agent's name","aria-label":"Name",className:Oe})})]}),$.length>0&&e.jsxs("div",{className:"flex flex-col gap-1 rounded-md border border-dashed border-border p-3",children:[e.jsxs("p",{className:"text-xs font-medium text-foreground",children:["Installing this agent will clone ",$.length===1?"an external repository":"external repositories",":"]}),e.jsx("ul",{className:"list-disc pl-5 text-xs text-muted-foreground",children:$.map(f=>e.jsxs("li",{children:[e.jsx("span",{className:"text-foreground",children:f.repoURL})," @ ",f.ref||"default branch"," (SkillSource ",f.skillSource,")"]},f.skillSource))})]}),E.length>0&&e.jsx(I,{children:e.jsx(F,{children:e.jsx("ul",{className:"list-disc pl-5 text-xs",children:E.map(f=>e.jsx("li",{children:f},f))})})}),w.length>0&&e.jsxs("div",{className:"flex flex-col gap-2 rounded-md border border-destructive/50 p-3",children:[e.jsx("p",{className:"text-xs font-medium text-foreground",children:"These objects already exist and were not created by this install. Adopting one overwrites its spec with this bundle's, and uninstalling the agent will delete it."}),w.map(f=>{const A=`${f.kind}/${f.name}`,S=f.namespace?`${f.kind} ${f.namespace}/${f.name}`:A;return e.jsxs("label",{className:"flex items-center gap-2 text-xs text-foreground",children:[e.jsx("input",{type:"checkbox",checked:O.includes(A),"aria-label":S,onChange:D=>L(K=>D.target.checked?[...K,A]:K.filter(B=>B!==A)),className:"h-4 w-4 rounded border-border"}),e.jsx("span",{children:S}),f.secret&&e.jsx("span",{className:"text-destructive",children:"overwrites this Secret's data"})]},A)})]}),x&&x.length>0&&e.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-dashed border-border p-3",children:[e.jsxs("p",{className:"text-xs text-muted-foreground",children:["This bundle needs ",x.length===1?"an answer":"answers"," before it can install:"]}),x.map(f=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("p",{className:"text-[11px] font-medium text-muted-foreground",children:os(f.agentPath)}),e.jsx(ls,{q:f,value:p[Ve(f)]??We(f.default),onChange:A=>Js(Ve(f),A)})]},Ve(f)))]}),k.length>0&&e.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-dashed border-border p-3",children:[e.jsx("p",{className:"text-xs text-muted-foreground",children:"Complete the required channel setup before resuming the install:"}),k.map(f=>{var K,B;const A=re(f),S=V[A]??"pending",D=T[A]??{};return e.jsxs("div",{className:"flex flex-col gap-2 rounded-md border border-border p-3",children:[e.jsx("p",{className:"text-[11px] font-medium text-muted-foreground",children:os(f.agentPath)}),e.jsx("p",{className:"text-sm font-medium text-foreground",children:f.name}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:[f.kind," · ",f.role]}),f.purpose&&e.jsx("p",{className:"text-xs text-foreground",children:f.purpose}),f.reason&&e.jsx("p",{className:"text-xs text-destructive",children:f.reason}),f.remedy&&e.jsx("p",{className:"text-xs text-muted-foreground",children:f.remedy}),(f.questions??[]).map(H=>e.jsx(ls,{q:H,value:D[H.name]??We(H.default),onChange:G=>Ys(f,H.name,G),disabled:S==="staged"},H.name)),S==="staged"?e.jsxs("div",{className:"flex flex-col gap-1 text-xs",children:[e.jsx("p",{className:"text-success",children:"Setup staged"}),(((K=ie[A])==null?void 0:K.nextSteps)??[]).map(H=>e.jsx("p",{className:"text-foreground",children:H},`next-${H}`)),(((B=ie[A])==null?void 0:B.warnings)??[]).map(H=>e.jsx("p",{className:"text-warning",children:H},`warning-${H}`))]}):f.status==="handoff"&&S==="pending"?e.jsxs(U,{type:"button",size:"sm",className:"self-start",disabled:Rt===A,onClick:()=>void Zs(f),children:["Start browser setup for ",f.name]}):f.status==="ask"||S==="handoff-started"?e.jsx(U,{type:"button",size:"sm",className:"self-start",disabled:Rt===A,onClick:()=>void Qs(f),children:S==="handoff-started"?`Resume ${f.name}`:`Set up ${f.name}`}):e.jsx("p",{className:"text-xs text-destructive",children:"This channel cannot be set up from the browser."})]},A)})]}),e.jsx(U,{type:"button",onClick:Bs,disabled:!qs||It,className:"self-start",children:It?"Installing…":w.length>0&&O.length>0&&Object.values(V).includes("staged")?"Adopt and resume install":w.length>0&&O.length>0?"Adopt and install":Object.values(V).includes("staged")?"Resume install":x?"Submit answers":"Install"})]})}function Us({agents:t}){return e.jsx("ul",{className:"flex flex-col gap-2 pl-4 text-xs text-muted-foreground",children:t.map(s=>e.jsxs("li",{children:[e.jsx("p",{children:s.agentPath?`Dependency ${s.agentPath}`:"Dependency"}),e.jsx("p",{className:"font-mono text-foreground",children:s.name}),s.agents&&s.agents.length>0&&e.jsx(Us,{agents:s.agents})]},`${s.agentPath??""}/${s.name}`))})}function ko({result:t,namespace:s,onClose:n}){return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("p",{className:"text-sm text-success",children:["Installed ",e.jsx("span",{className:"font-mono",children:t.name})," — ",t.appliedKinds.join(", "),t.secretsCreated>0?`, ${t.secretsCreated} secret${t.secretsCreated===1?"":"s"} created`:"","."]}),t.adopted&&t.adopted.length>0&&e.jsxs("div",{className:"flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",children:[e.jsxs("p",{className:"text-xs font-medium text-warning",children:["This install seized ",t.adopted.length," pre-existing object",t.adopted.length===1?"":"s"," ","not created by it — their specs were overwritten with this bundle's, and"," ",e.jsx("span",{className:"font-mono",children:"oap agent uninstall"})," will delete them."]}),e.jsx("ul",{className:"list-disc pl-4 text-xs text-muted-foreground",children:t.adopted.map(r=>e.jsx("li",{className:"font-mono",children:r},r))})]}),t.warnings&&t.warnings.length>0&&e.jsx("ul",{className:"list-disc pl-4 text-xs text-muted-foreground",children:t.warnings.map(r=>e.jsx("li",{children:r},r))}),t.agents&&t.agents.length>0&&e.jsxs("div",{className:"flex flex-col gap-1 rounded-md border border-border p-3",children:[e.jsx("p",{className:"text-xs font-medium text-foreground",children:"Installed dependencies"}),e.jsx(Us,{agents:t.agents})]}),e.jsxs(et,{children:[e.jsx(U,{variant:"outline",onClick:n,children:"Close"}),e.jsx(U,{onClick:()=>{n(),te({type:"detail",entity:"agent",id:`${s}/${t.name}`})},children:"View agent"})]})]})}function So({apiBase:t,onInstalled:s}){const[n,r]=d.useState(!1),[a,o]=d.useState(null),i=l=>{r(l),l&&o(null)};return e.jsxs(Je,{open:n,onOpenChange:i,children:[e.jsx(Ye,{asChild:!0,children:e.jsx(U,{variant:"outline",size:"sm",children:"Install agent"})}),e.jsxs(Qe,{className:"max-w-xl",children:[e.jsxs(Xe,{children:[e.jsx(Ze,{children:"Install an agent"}),e.jsx(Be,{children:"Upload a packaged .oap bundle or pull one from a registry ref."})]}),a?e.jsx(ko,{result:a.result,namespace:a.namespace,onClose:()=>r(!1)}):e.jsx(wo,{apiBase:t,onInstalled:(l,c)=>{o({result:l,namespace:c}),s==null||s()}})]})]})}function Co(t){return t==="Valid"||t==="Ready"}function is(t,s){var n,r;return((r=(n=t.counts)==null?void 0:n.find(a=>a.label===s))==null?void 0:r.value)??0}function $o(t){return t==="agent"?"bg-state":t==="userPassthrough"?"bg-success":"bg-muted-foreground/40"}function Vs({row:t,linked:s=!0}){const n=ye(t,"identityMode"),r=ye(t,"identityRef");return e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx("span",{"aria-hidden":"true",title:n||void 0,className:`inline-block h-2 w-2 shrink-0 rounded-full ${$o(n)}`}),r&&s?e.jsx(W,{entity:"identity",id:t.namespace?`${t.namespace}/${r}`:r,className:"font-mono text-[11px] text-link hover:underline",onClick:a=>a.stopPropagation(),children:r}):e.jsx("span",{className:"font-mono text-[11px] text-muted-foreground",children:r||n||"—"})]})}const To=ce.agents.map(t=>t.header==="Identity"?{...t,render:s=>e.jsx(Vs,{row:s})}:t);function Lo({a:t}){const s=Co(t.status),n=is(t,"tools"),r=is(t,"skills");return e.jsxs(W,{entity:"agent",id:it(t),className:"block rounded-xl border bg-card p-4 shadow transition-colors hover:border-primary/40",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"font-mono text-sm font-medium text-foreground",children:t.name}),e.jsx("span",{title:t.statusReason||void 0,className:`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] ${s?"border-success/40 bg-success/10 text-success":"border-destructive/40 bg-destructive/10 text-destructive"}`,children:t.status||"Unknown"})]}),t.namespace&&e.jsx("p",{className:"mt-0.5 font-mono text-[10px] text-muted-foreground",children:t.namespace}),!s&&t.statusReason&&e.jsx("p",{className:"mt-2 text-xs text-destructive",children:t.statusReason}),e.jsx("div",{className:"mt-3 flex flex-wrap gap-1.5",children:e.jsx(Vs,{row:t,linked:!1})}),e.jsxs("p",{className:"mt-3 font-mono text-xs text-muted-foreground",children:[n," tool",n===1?"":"s"," · ",r," skill",r===1?"":"s"]})]})}function Ao({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1),[l,c]=$t("agents_view","grid"),u=d.useCallback(()=>{let p=!0;return Ct(t,"agents").then(b=>{p&&(n(b),a(null),i(!0))}).catch(b=>{p&&(a(b.message),i(!0))}),()=>{p=!1}},[t]);d.useEffect(()=>u(),[u]);const h="AgentClass — readiness gates sessions · managed via oap class / kubectl agentclass",N=()=>r?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load agents: ",r]})}):o?s.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No AgentClasses configured yet."}):l==="grid"?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3",children:s.map(p=>e.jsx(Lo,{a:p},it(p)))}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:h})]}):e.jsx(_s,{rows:s,columns:To,entity:"agent",statusCookieKey:"status_agents",note:h}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading agents…"});return e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx(So,{apiBase:t,onInstalled:u}),o&&!r&&s.length>0&&e.jsx($s,{value:l,onChange:c})]}),N()]})}function je({apiBase:t,resource:s,columns:n,entity:r,facet:a,mapRows:o,note:i,emptyText:l}){const[c,u]=d.useState([]),[h,N]=d.useState(null),[p,b]=d.useState(!1);d.useEffect(()=>{let j=!0;return Ct(t,s).then($=>{j&&(u($??[]),N(null),b(!0))}).catch($=>{j&&(N($.message),b(!0))}),()=>{j=!1}},[t,s]);const x=o?o(c):c;return e.jsxs("div",{className:"space-y-3",children:[h&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load ",s,": ",h]})}),!h&&e.jsx(_s,{rows:x,columns:n,entity:r,facet:a,statusCookieKey:`status_${s}`,loading:!p,note:i,emptyText:l})]})}function Eo({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"tools",entity:"tool",columns:ce.tools,note:"MCPServer · SidecarToolbox · SpiceboxToolkit · SpiceboxToolspec — managed via oap tools / kubectl",emptyText:"No tools configured yet."})}function Do({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"skills",entity:"skill",columns:ce.skills,note:"Skill · ClusterSkill — instruction & executable skills · managed via oap skill / kubectl get skills",emptyText:"No skills configured yet."})}const Ro=ce.sources.map(t=>t.key==="repo"?{...t,render:s=>{const n=ye(s,"repo");return n?Se(n)?e.jsx("a",{href:n,target:"_blank",rel:"noopener noreferrer nofollow",className:"whitespace-nowrap font-mono text-[11px] text-link hover:underline",onClick:r=>r.stopPropagation(),children:n}):e.jsx("code",{className:"whitespace-nowrap font-mono text-[11px] text-muted-foreground",children:n}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}}:t);function Oo({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"sources",entity:"source",columns:Ro,note:"SkillSource · ClusterSkillSource — synced from git · managed via oap skill source add",emptyText:"No skill sources configured yet."})}function Io({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"directory",entity:"directory",columns:ce.directory,note:"RelationshipSource — Slack · GitHub · 1Password · managed via oap directory configure",emptyText:"No directory sources configured yet."})}function Fo(t){return ye(t,"role")==="monitoring"}function Mo({row:t}){const s=ye(t,"role");if(!s)return e.jsx("span",{className:"text-muted-foreground",children:"—"});const n=s==="monitoring";return e.jsx("span",{className:`rounded-full border px-2 py-0.5 text-[10px] font-mono ${n?"border-border bg-background text-muted-foreground":"border-primary/40 bg-primary/10 text-primary"}`,children:s})}function Po({row:t}){const s=ye(t,"agentClass");if(!s)return e.jsx("span",{className:"text-muted-foreground",children:"—"});const n=t.namespace?`${t.namespace}/${s}`:s;return e.jsx(W,{entity:"agent",id:n,className:"font-mono text-[11px] text-link hover:underline",children:s})}const Ko=ce.channels.map(t=>t.key==="role"?{...t,render:s=>e.jsx(Mo,{row:s})}:t.key==="agentClass"?{...t,render:s=>e.jsx(Po,{row:s})}:t);function _o(t){return t.map(s=>Fo(s)?{...s,status:"monitoring"}:s)}function Uo({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"channels",entity:"channel",columns:Ko,mapRows:_o,note:"Channel — where agents are reachable · managed via oap channel / kubectl channel",emptyText:"No channels configured yet."})}const mt=[{id:"identities",label:"Agent identities",sub:"how agents authenticate to upstreams"},{id:"providers",label:"Login providers",sub:"human login for the admin UI & CLI"}];function Vo({apiBase:t}){const[s,n]=d.useState("identities"),r=mt.find(a=>a.id===s)??mt[0];return e.jsxs("div",{className:"space-y-3",children:[e.jsx("div",{role:"tablist",className:"inline-flex h-9 items-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground",children:mt.map(a=>{const o=s===a.id;return e.jsx("button",{type:"button",role:"tab","aria-selected":o,onClick:()=>n(a.id),className:["inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",o?"bg-background text-foreground shadow":"hover:text-foreground"].join(" "),children:a.label},a.id)})}),e.jsx("p",{className:"text-xs text-muted-foreground",children:r.sub}),s==="identities"?e.jsx(je,{apiBase:t,resource:"identities",entity:"identity",columns:ce.identities,facet:{key:"cred",label:"Type"},note:"AgentIdentity — managed via oap identity · setup flows: github_pat, oauth_mcp, federated (ID-JAG)",emptyText:"No agent identities configured yet."}):e.jsx(je,{apiBase:t,resource:"providers",entity:"provider",columns:ce.providers,note:"ClusterIdentityProvider — managed via oap idp",emptyText:"No login providers configured yet."})]})}const Wo=ce.users.map(t=>t.key==="subject"?{...t,render:s=>{const n=ye(s,"subject");return n?e.jsx(yt,{children:ae(n)}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}}:t);function Ho({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"users",entity:"user",columns:Wo,note:"UserIdentity — people linked to the platform · managed via oap user-identity",emptyText:"No users linked yet."})}function cs({label:t,value:s,sub:n}){return e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:t}),e.jsx("div",{className:"mt-1 text-2xl font-semibold",children:s}),e.jsx("div",{className:"text-[11px] text-muted-foreground",children:n})]})}function zo({apiBase:t}){const[s,n]=d.useState(null),[r,a]=d.useState(null);if(d.useEffect(()=>{let i=!0;return mr(t).then(l=>{i&&(n(l),a(null))}).catch(l=>{i&&a(l.message)}),()=>{i=!1}},[t]),r)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load access: ",r]})});if(!s)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading access…"});const o=s.stats.available;return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"mb-2 text-xs font-semibold text-muted-foreground",children:["Platform admins ",e.jsx("span",{className:"font-normal",children:"· who can reach this admin UI (platform:platform#admin)"})]}),s.admins.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No platform admins granted yet."}):e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Subject"}),e.jsx(y,{children:"Kind"})]})}),e.jsx(X,{children:s.admins.map(i=>e.jsxs(M,{children:[e.jsx(v,{className:"font-mono text-xs text-foreground",title:i.subject,children:ae(i.subject)}),e.jsx(v,{children:e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-muted-foreground",children:[i.kind==="group"?e.jsx(ms,{className:"h-3.5 w-3.5"}):e.jsx(Jn,{className:"h-3.5 w-3.5"}),i.kind]})})]},i.subject))})]})]}),e.jsxs("div",{className:"space-y-1.5",children:[e.jsx(lt,{text:s.grantCmd}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Run via the CLI — the admin UI is read-only. Also: oap platform revoke-admin / list-admins."})]}),o?e.jsxs("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2",children:[e.jsx(cs,{label:"SpiceDB schema",value:s.stats.schemaDefs!=null?`${s.stats.schemaDefs}`:"—",sub:"definitions"}),e.jsx(cs,{label:"Relationships",value:s.stats.relationships!=null?s.stats.relationships.toLocaleString():"—",sub:"stored tuples"})]}):e.jsxs("div",{className:"rounded-xl border border-dashed bg-card p-4 shadow",children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:"SpiceDB stats"}),e.jsx("div",{className:"mt-1 text-sm text-muted-foreground",children:"Stats unavailable — schema-definition & relationship counts aren't exposed through the permissions API in v1. Wiring a live source is a follow-up."})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Authorization primitive — schema composed by the operator · oap spicedb apply-schema / oap spicedb check"})]})}const ds=["Limits","Defaults"];function qo(t){const s=new Map;for(const r of t){const a=s.get(r.group)??[];a.push(r),s.set(r.group,a)}return[...s.keys()].sort((r,a)=>{const o=ds.indexOf(r),i=ds.indexOf(a);return o===-1&&i===-1?0:o===-1?1:i===-1?-1:o-i}).flatMap(r=>s.get(r).map((a,o)=>({row:a,group:o===0?r:void 0})))}function Go({apiBase:t}){const[s,n]=d.useState(null),[r,a]=d.useState(null);if(d.useEffect(()=>{let i=!0;return jr(t).then(l=>{i&&(n(l),a(null))}).catch(l=>{i&&a(l.message)}),()=>{i=!1}},[t]),r)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load settings: ",r]})});if(!s)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading settings…"});const o=qo(s.rows);return e.jsxs("div",{className:"space-y-4",children:[s.rows.length===0?e.jsx("div",{className:"rounded-xl border border-dashed bg-card p-4 shadow",children:e.jsx("p",{className:"text-sm text-muted-foreground",children:s.note||"No ClusterAgentSettings configured — cluster governance uses built-in defaults."})}):e.jsx("div",{className:"rounded-xl border bg-card shadow",children:e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{className:"w-28",children:"Group"}),e.jsx(y,{children:"Setting"}),e.jsx(y,{children:"Value"}),e.jsx(y,{children:"Note"})]})}),e.jsx(X,{children:o.map(({row:i,group:l},c)=>e.jsxs(M,{children:[e.jsx(v,{className:"align-top text-xs font-semibold text-muted-foreground",children:l??""}),e.jsx(v,{className:"align-top text-xs text-muted-foreground",children:i.label}),e.jsx(v,{className:"align-top font-mono text-xs text-foreground",children:i.value}),e.jsx(v,{className:"align-top text-[11px] text-muted-foreground",children:i.note??""})]},`${i.group}-${i.label}-${c}`))})]})}),e.jsx(lt,{text:s.manageCmd}),e.jsxs("p",{className:"text-[11px] text-muted-foreground",children:["ClusterAgentSettings (singleton ",e.jsx("span",{className:"font-mono",children:"cluster"}),") → namespace AgentSettings → AgentClass → session. Edit via the CLI — the admin UI is read-only."]})]})}const Jo=["oap agent pull "];function Yo(t){return Jo.some(s=>t.startsWith(s))}const Qo=new Set(["kind","channel kind","credential kind","credential type"]);function Xo(t){return Qo.has(t.trim().toLowerCase())}function Ws({section:t}){switch(t.kind){case"text":return e.jsx(el,{text:t.text??""});case"list":return e.jsx(tl,{items:t.items??[],notice:t.text});case"fields":default:return e.jsx(Bo,{fields:t.fields??[]})}}function Hs({entity:t,id:s,children:n}){return Ns(t)?e.jsx(W,{entity:t,id:s,className:"font-mono text-xs text-link hover:underline",children:n}):e.jsx(e.Fragment,{children:n})}function Nt({href:t,children:s}){return e.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer nofollow",className:"break-words font-mono text-xs text-link hover:underline",children:s})}function Zo({field:t}){if(t.link)return e.jsx(Hs,{entity:t.link.entity,id:t.link.id,children:t.value});if(t.href){const n=Se(t.href);if(n)return e.jsx(Nt,{href:n,children:t.value})}if(t.value&&Xo(t.label))return e.jsx(rt,{kind:t.value});if(Yo(t.value))return e.jsx(lt,{text:t.value});const s=Se(t.value);return s?e.jsx(Nt,{href:s,children:t.value}):e.jsx("span",{className:"break-words",children:t.value})}function Bo({fields:t}){return t.length===0?e.jsx(Dt,{}):e.jsx("div",{className:"flex flex-col gap-4",children:t.map((s,n)=>e.jsx(le,{label:s.label,children:e.jsx(Zo,{field:s})},`${s.label}-${n}`))})}function el({text:t}){return t===""?e.jsx(Dt,{}):e.jsx("pre",{className:"whitespace-pre-wrap break-words rounded-md border border-border bg-card p-4 font-mono text-xs text-foreground",children:t})}function tl({items:t,notice:s}){const n=s?e.jsx(I,{variant:"destructive",className:"mb-3",children:e.jsx(F,{children:s})}):null;return t.length===0?e.jsxs(e.Fragment,{children:[n,!s&&e.jsx(Dt,{})]}):e.jsxs(e.Fragment,{children:[n,e.jsx("ul",{className:"flex flex-col gap-2",children:t.map((r,a)=>e.jsxs("li",{className:"rounded-md border border-border bg-card p-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(sl,{item:r}),e.jsx(nl,{badges:r.badges})]}),r.subtitle&&e.jsx("p",{className:"mt-1 text-[11px] text-muted-foreground",children:r.subtitle})]},`${r.title}-${a}`))})]})}function sl({item:t}){if(t.link)return e.jsx(Hs,{entity:t.link.entity,id:t.link.id,children:t.title});if(t.href){const s=Se(t.href);if(s)return e.jsx(Nt,{href:s,children:t.title})}return e.jsx("span",{className:"font-mono text-xs text-foreground",children:t.title})}function nl({badges:t}){const s=t??[];return s.length===0?null:e.jsx("span",{className:"flex flex-wrap gap-1",children:s.map((n,r)=>e.jsx(rt,{kind:n.value},`${n.key}-${n.value}-${r}`))})}function Dt(){return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing here."})}const us={detail:null,loading:!0,error:null,notFound:!1};function rl(t,s,n){const[r,a]=d.useState(us);return d.useEffect(()=>{let o=!0;return a(us),(async()=>{if(!n.includes("/")){const c=(await Ct(t,s)).find(u=>u.name===n);if(c)return Vt(t,s,it(c))}return Vt(t,s,n)})().then(l=>{o&&a({detail:l,loading:!1,error:null,notFound:!1})}).catch(l=>{o&&(l instanceof J&&l.status===404?a({detail:null,loading:!1,error:null,notFound:!0}):a({detail:null,loading:!1,error:l.message,notFound:!1}))}),()=>{o=!1}},[t,s,n]),r}function xs({detail:t,section:s}){return e.jsxs("div",{className:"flex flex-col gap-5",children:[t.description&&e.jsx("p",{className:"text-base text-foreground",children:t.description}),e.jsx(jo,{status:t.status,reason:t.statusReason}),e.jsxs("div",{className:"flex flex-wrap gap-8",children:[e.jsx(le,{label:"Status",children:e.jsx(Ks,{status:t.status,reason:t.statusReason})}),e.jsx(le,{label:"Scope",children:e.jsx("span",{className:"text-sm capitalize text-foreground",children:t.scope})}),t.namespace&&e.jsx(le,{label:"Namespace",children:e.jsx("span",{className:"font-mono text-xs text-foreground",children:t.namespace})})]}),s&&e.jsx(Ws,{section:s}),t.manageCmd&&e.jsx(le,{label:"Manage",children:e.jsx(lt,{text:t.manageCmd})})]})}function de({apiBase:t,resource:s,entity:n,id:r,tab:a,backRoute:o,extraTabs:i=[],subtitle:l}){var O;const{detail:c,loading:u,error:h,notFound:N}=rl(t,s,r),p=new Set(["overview",...i.map(L=>L.id)]),b=(O=c==null?void 0:c.sections)==null?void 0:O.find(L=>L.id==="overview"),x=((c==null?void 0:c.sections)??[]).filter(L=>!p.has(L.id)),j=[{id:"overview",label:"Overview"},...i.map(L=>({id:L.id,label:L.label})),...x.map(L=>({id:L.id,label:L.label}))],$=new Set(j.map(L=>L.id)),C=a&&$.has(a)?a:"overview",E=L=>te({type:"detail",entity:n,id:r,tab:L}),m=c?e.jsx(Z,{ns:c.namespace,name:c.name}):e.jsx(Z,{id:r}),w=c&&l?l(c):void 0,R=()=>{if(u)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"});if(h)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load ",s,": ",h]})});if(N||!c)return e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Not found: ",r]});if(C==="overview")return e.jsx(xs,{detail:c,section:b});const L=i.find(g=>g.id===C);if(L)return L.render(c);const k=x.find(g=>g.id===C);return k?e.jsx(Ws,{section:k}):e.jsx(xs,{detail:c,section:b})};return e.jsx(nt,{title:m,subtitle:w,backRoute:o,tabs:j,activeTab:C,onTab:E,headerRight:c?e.jsx(Ks,{status:c.status,reason:c.statusReason}):void 0,children:R()})}function al({apiBase:t,agentClass:s}){const[n,r]=d.useState(null),[a,o]=d.useState(null);if(d.useEffect(()=>{let l=!0;return _(`${t}/sessions`).then(c=>{l&&(r(c),o(null))}).catch(c=>{l&&o(c.message)}),()=>{l=!1}},[t]),a)return e.jsxs("p",{className:"text-xs text-destructive",children:["Could not load sessions: ",a]});if(n===null)return e.jsx("p",{className:"text-xs text-muted-foreground",children:"Loading sessions…"});const i=n.filter(l=>l.class===s);return i.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No live sessions for this agent."}):e.jsxs(Y,{children:[e.jsx(Q,{children:e.jsxs(M,{children:[e.jsx(y,{children:"Session"}),e.jsx(y,{children:"Phase"}),e.jsx(y,{children:"Started"})]})}),e.jsx(X,{children:i.map(l=>e.jsxs(M,{children:[e.jsx(v,{className:"font-mono text-xs",children:e.jsx(W,{entity:"session",id:`${l.namespace}/${l.name}`,className:"text-link hover:underline",children:e.jsx(Z,{ns:l.namespace,name:l.name})})}),e.jsx(v,{children:e.jsx(At,{phase:l.phase})}),e.jsx(v,{className:"whitespace-nowrap text-xs text-muted-foreground",children:Lt(l.startedAt)})]},`${l.namespace}/${l.name}`))})]})}function ol({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"agents",entity:"agent",id:s,tab:n,backRoute:{type:"view",view:"agents"},extraTabs:[{id:"sessions",label:"Sessions",render:r=>e.jsx(al,{apiBase:t,agentClass:r.name})}]})}function ll({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"tools",entity:"tool",id:s,tab:n,backRoute:{type:"view",view:"tools"}})}function il({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"skills",entity:"skill",id:s,tab:n,backRoute:{type:"view",view:"skills"}})}function cl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"sources",entity:"source",id:s,tab:n,backRoute:{type:"view",view:"sources"}})}function dl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"directory",entity:"directory",id:s,tab:n,backRoute:{type:"view",view:"directory"}})}function ul({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"channels",entity:"channel",id:s,tab:n,backRoute:{type:"view",view:"channels"}})}function xl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"identities",entity:"identity",id:s,tab:n,backRoute:{type:"view",view:"identity"}})}function ml({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"providers",entity:"provider",id:s,tab:n,backRoute:{type:"view",view:"identity"}})}function hl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"users",entity:"user",id:s,tab:n,backRoute:{type:"view",view:"users"},subtitle:r=>ae(r.description??"")})}fn();function fl({apiBase:t,currentUser:s}){const n=ks(),[r,a]=d.useState({});function o(u){a(u),te({type:"view",view:"logs"})}const i=Kr(n),l=n.type==="detail"?pl(n,t):c(n.view);function c(u){switch(u){case"overview":return e.jsx(ho,{apiBase:t});case"sessions":return e.jsx(ta,{apiBase:t});case"toolcalls":return e.jsx(ra,{apiBase:t});case"approvals":return e.jsx(ia,{apiBase:t,onOpenSession:(h,N)=>te({type:"detail",entity:"session",id:`${h}/${N}`})});case"workshops":return e.jsx(ma,{apiBase:t});case"logs":return e.jsx(Ka,{apiBase:t,initialFilter:r},JSON.stringify(r));case"sessionsAudit":return e.jsx(Ha,{apiBase:t,axis:"sessions",onDrill:o});case"budget":return e.jsx(ao,{apiBase:t});case"artifacts":return e.jsx(Qa,{apiBase:t,onDrill:o});case"memory":return e.jsx(oo,{apiBase:t});case"knowledge":return e.jsx(lo,{apiBase:t});case"agents":return e.jsx(Ao,{apiBase:t});case"tools":return e.jsx(Eo,{apiBase:t});case"skills":return e.jsx(Do,{apiBase:t});case"sources":return e.jsx(Oo,{apiBase:t});case"directory":return e.jsx(Io,{apiBase:t});case"channels":return e.jsx(Uo,{apiBase:t});case"identity":return e.jsx(Vo,{apiBase:t});case"users":return e.jsx(Ho,{apiBase:t});case"access":return e.jsx(zo,{apiBase:t});case"settings":return e.jsx(Go,{apiBase:t});default:return e.jsx(Or,{view:u})}}return e.jsx(Rr,{view:i,onNavigate:u=>te({type:"view",view:u}),currentUser:s,apiBase:t,children:l})}function pl(t,s){const{entity:n,id:r,tab:a}=t;switch(n){case"session":return e.jsx(wa,{apiBase:s,id:r,tab:a});case"agent":return e.jsx(ol,{apiBase:s,id:r,tab:a});case"tool":return e.jsx(ll,{apiBase:s,id:r,tab:a});case"skill":return e.jsx(il,{apiBase:s,id:r,tab:a});case"source":return e.jsx(cl,{apiBase:s,id:r,tab:a});case"directory":return e.jsx(dl,{apiBase:s,id:r,tab:a});case"channel":return e.jsx(ul,{apiBase:s,id:r,tab:a});case"identity":return e.jsx(xl,{apiBase:s,id:r,tab:a});case"provider":return e.jsx(ml,{apiBase:s,id:r,tab:a});case"user":return e.jsx(hl,{apiBase:s,id:r,tab:a});case"artifact":return e.jsx(eo,{apiBase:s,id:r,tab:a});default:return e.jsx(gl,{route:t})}}function gl({route:t}){const s=t.tab??"overview";return e.jsx(nt,{title:`${t.entity} ${t.id}`,subtitle:"Detail page lands in a later batch",tabs:[{id:"overview",label:"Overview"}],activeTab:s,onTab:n=>te({type:"detail",entity:t.entity,id:t.id,tab:n}),children:e.jsx("p",{className:"text-sm text-muted-foreground",children:"Detail page lands in a later batch."})})}pn(fl); diff --git a/pkg/web/webui/webassets/dist/admin.Cq1sS6SU.js b/pkg/web/webui/webassets/dist/admin.Cq1sS6SU.js new file mode 100644 index 00000000..9055635f --- /dev/null +++ b/pkg/web/webui/webassets/dist/admin.Cq1sS6SU.js @@ -0,0 +1 @@ +import{c as Ye,r as en,f as Ke,s as _t,C as me,b as he,d as fe,e as pe,T as tn,g as sn,h as nn,i as rn,A as I,a as F,j as G,k as J,l as M,n as v,o as Y,p as b,B as U,D as Qe,q as Xe,t as Ze,u as Be,v as et,w as tt,x as st,y as wt,S as an,z as on,E as ln,F as cn,G as dn,H as un,I as xn,J as mn,K as hn,L as fn,m as pn}from"./mount.Bxdfy1H2.js";import{r as d,j as e,L as gn,A as jn,T as bn,S as vn,H as yn,B as Nn,W as kn,a as wn,G as Sn,F as Cn,b as $n,K as Tn,U as ms,c as An,d as Ln,e as En,f as Dn,g as Rn,h as On,i as In,D as Fn,N as hs,k as Mn,l as Pn,m as Kn,M as _n,C as Un,n as Vn,o as Wn,p as zn,q as Hn,s as fs,X as qn,t as Gn,u as Jn,v as Yn}from"./vendor.BHG48rjj.js";import{B as W}from"./badge.D9YA1CKv.js";import{M as St}from"./model-name.B3Csp-bS.js";const _e="M159.186 7.59677C164.993 -2.53226 179.604 -2.53226 185.411 7.59677L342.572 281.702C348.349 291.779 341.075 304.335 329.459 304.335H15.138C3.52262 304.335 -3.75273 291.779 2.02468 281.702L159.186 7.59677Z",Qn="M178.855 133.226C175.951 128.161 168.646 128.161 165.742 133.226L117.254 217.794C114.366 222.832 118.003 229.11 123.811 229.11H220.786C226.594 229.11 230.232 222.832 227.343 217.794L178.855 133.226Z",Ae=[1,2,3,4,5].map(t=>`hsl(var(--chart-${t}))`),Ue=12,Xn=3.2,Zn=.55,Bn=.35;function er(){return typeof window<"u"&&typeof window.matchMedia=="function"?window.matchMedia("(prefers-reduced-motion: reduce)").matches:!1}function Le(t,s,n,r){if(t){if(n<=.05){t.style.opacity="0";return}t.style.opacity="1",t.style.stroke=r,t.style.strokeDasharray=`${n} ${100-n}`,t.style.strokeDashoffset=`${-s}`}}function tr({className:t,title:s,active:n=!1,lapMs:r=2400,band:a=12,bandWidth:o=12,handoverEvery:i=2,...l}){const c=d.useRef(null),u=d.useRef(null),h=d.useRef([]),N=n&&!er();d.useEffect(()=>{if(!N)return;const y=performance.now();let x=0;const j=$=>{const C=($-y)/r,E=C*100%100,m=(E-a+100)%100,k=Math.floor(C*3),R=Math.floor(k/i),O=Ae[R%Ae.length],A=Ae[(R-1+Ae.length)%Ae.length],w=k/3*100%100,g=(E-w+100)%100,T=k%i===0&&gcancelAnimationFrame(x)},[N,r,a,i]);const p={opacity:0};return e.jsxs("svg",{viewBox:"0 0 345 305",fill:"currentColor","aria-hidden":s?void 0:!0,role:s?"img":void 0,className:Ye("shrink-0 overflow-visible",t),"data-relay":N?"on":"off",...l,children:[s?e.jsx("title",{children:s}):null,e.jsx("path",{d:_e+Qn,style:{opacity:N?Bn:1,transition:"opacity 240ms ease-out"},"data-ink":!0}),e.jsxs("g",{fill:"none",strokeWidth:o,strokeLinecap:"round",strokeLinejoin:"round",style:{opacity:N?1:0,transition:"opacity 240ms ease-out"},children:[Array.from({length:Ue},(y,x)=>e.jsx("path",{ref:j=>{h.current[x]=j},d:_e,pathLength:100,style:p,strokeLinecap:"butt","data-seg":"tail"},x)),e.jsx("path",{ref:c,d:_e,pathLength:100,style:p,"data-seg":"a"}),e.jsx("path",{ref:u,d:_e,pathLength:100,style:p,"data-seg":"b"})]})]})}const ps=[{group:"",items:[{id:"overview",label:"Overview",icon:gn}]},{group:"Live",items:[{id:"sessions",label:"Agent sessions",icon:jn},{id:"toolcalls",label:"Live tool calls",icon:bn},{id:"approvals",label:"Approvals",icon:vn},{id:"workshops",label:"Workshops",icon:yn}]},{group:"Config",items:[{id:"agents",label:"Agents",icon:Nn},{id:"tools",label:"Tools",icon:kn},{id:"skills",label:"Skills",icon:wn},{id:"sources",label:"Sources",icon:Sn},{id:"directory",label:"Directory",icon:Cn},{id:"channels",label:"Channels",icon:$n},{id:"identity",label:"Identity",icon:Tn},{id:"users",label:"Users",icon:ms},{id:"access",label:"Access",icon:An},{id:"tokens",label:"Tokens",icon:Ln},{id:"settings",label:"Settings",icon:En}]},{group:"Audit",items:[{id:"logs",label:"Logs",icon:Dn},{id:"sessionsAudit",label:"Sessions",icon:Rn},{id:"budget",label:"Budget",icon:On},{id:"artifacts",label:"Artifacts",icon:In},{id:"memory",label:"Memory",icon:Fn},{id:"knowledge",label:"Knowledge",icon:hs}]}],gs={overview:{title:"Overview",sub:"Platform at a glance — usage, spend, and health"},sessions:{title:"Agent sessions",sub:"Live agent sessions across the platform"},toolcalls:{title:"Live tool calls",sub:"Tool invocations as they happen, across every session"},approvals:{title:"Approvals",sub:"Tool calls & leakage gates awaiting a decision"},workshops:{title:"Workshops",sub:"Agent-builder sessions' drafted bundles — install, decline, or kill"},agents:{title:"Agents",sub:"Configured AgentClasses and their readiness · oap class / kubectl agentclass"},tools:{title:"Tools",sub:"MCP servers, sandboxed CLIs, sidecars, toolkits · oap tools"},skills:{title:"Skills",sub:"Instruction & executable skills available to agents · oap skill"},sources:{title:"Sources",sub:"Skill sources synced from git · oap skill source"},directory:{title:"Directory",sub:"Directory syncs feeding SpiceDB — Slack, GitHub, 1Password · oap directory"},channels:{title:"Channels",sub:"Where agents are reachable · oap channel / kubectl channel"},identity:{title:"Identity",sub:"Agent identities, credentials, and login providers · oap identity / oap idp"},users:{title:"Users",sub:"People linked to the platform · oap user-identity"},access:{title:"Access",sub:"Platform admins & the SpiceDB authorization schema · oap platform / oap spicedb"},tokens:{title:"Tokens",sub:"Delegated OAuth access tokens minted via MCP consent — view and revoke"},settings:{title:"Settings",sub:"Cluster configuration — defaults, ceilings, security · oap settings"},logs:{title:"Logs",sub:"Cross-session audit — approvals, authz, tool calls, scope changes · oap audit"},sessionsAudit:{title:"Sessions",sub:"Every agent session, current and historical"},budget:{title:"Budget",sub:"Estimated spend by model, agent, session, user"},artifacts:{title:"Artifacts",sub:"Rendered outputs agents produced · oap artifact"},memory:{title:"Memory",sub:"The searchable, authorized memory store · oap memory"},knowledge:{title:"Knowledge",sub:"The knowledge graph — entities, facts, communities · oap kg"}};class X extends Error{constructor(s,n){super(s),this.status=n}}class nt extends X{constructor(s,n,r=[],a=[],o=[],i=[]){super(s,400),this.questions=n,this.skillClones=r,this.warnings=a,this.channels=o,this.conflicts=i}}class rt extends X{constructor(s,n,r=[],a=[],o=[],i=[]){super(s,409),this.conflicts=n,this.skillClones=r,this.channels=a,this.warnings=o,this.questions=i}}async function js(t,s){var a,o,i;const n=await fetch(`${t}/agents/oap-install`,s);if(n.ok)return await n.json();let r={};try{r=await n.json()}catch{}throw n.status===400&&((((a=r.questions)==null?void 0:a.length)??0)>0||(((o=r.channels)==null?void 0:o.length)??0)>0||(((i=r.conflicts)==null?void 0:i.length)??0)>0)?new nt(r.error||"missing required question(s)",r.questions??[],r.skillClones??[],r.warnings??[],r.channels??[],r.conflicts??[]):n.status===409&&r.conflicts&&r.conflicts.length>0?new rt(r.error||"install would overwrite pre-existing object(s)",r.conflicts,r.skillClones??[],r.channels??[],r.warnings??[],r.questions??[]):new X(r.error||`request failed (${n.status})`,n.status)}class pt extends X{constructor(s,n){super(s,400),this.questions=n}}async function bs(t,s,n){var o;const r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({setupToken:s,answers:n})});if(r.ok)return await r.json();let a={};try{a=await r.json()}catch{}throw r.status===400&&(((o=a.questions)==null?void 0:o.length)??0)>0?new pt(a.error||"missing required channel answer(s)",a.questions??[]):new X(a.error||`request failed (${r.status})`,r.status)}const Ut=(t,s,n)=>bs(`${t}/agents/channel-setup`,s,n),sr=(t,s,n)=>bs(`${t}/agents/channel-handoff`,s,n),nr=(t,s)=>js(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),rr=(t,s,n)=>{var a;const r=new FormData;return r.append("file",s),r.append("namespace",n.namespace),n.name&&r.append("name",n.name),n.values&&r.append("values",JSON.stringify(n.values)),(a=n.adopt)!=null&&a.length&&r.append("adopt",JSON.stringify(n.adopt)),js(t,{method:"POST",body:r})};async function ar(t,s){const n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(n.ok)return await n.json();let r={};try{r=await n.json()}catch{}throw n.status===400&&r.questions&&r.questions.length>0?new nt(r.error||"missing required question(s)",r.questions,r.skillClones??[],r.warnings??[]):n.status===409&&r.conflicts&&r.conflicts.length>0?new rt(r.error||"install would overwrite pre-existing object(s)",r.conflicts,r.skillClones??[]):new X(r.error||`request failed (${n.status})`,n.status)}const or=(t,s,n,r)=>ar(`${t}/workshops/${s}/${n}/install`,r);async function Ct(t){if(!t.ok){let s=`request failed (${t.status})`;try{const n=await t.json();n.error&&(s=n.error)}catch{}throw new X(s,t.status)}return await t.json()}const _=async t=>Ct(await fetch(t)),lr=t=>_(`${t}/overview`),ir=t=>_(`${t}/health`),cr=t=>_(`${t}/cluster`),dr=t=>_(`${t}/budget`),ur=t=>_(`${t}/toolcalls`),xr=(t,s,n)=>_(`${t}/sessions/${s}/${n}/logs`),mr=t=>_(`${t}/approvals`),$t=async(t,s)=>await _(`${t}/config/${s}`)??[],Vt=(t,s,n)=>_(`${t}/config/${s}/${n}`),hr=t=>_(`${t}/access`),fr=t=>_(`${t}/artifacts`),pr=(t,s,n)=>_(`${t}/artifacts/${s}/${n}`),gr=t=>_(`${t}/workshops`),jr=t=>_(`${t}/tokens`).then(s=>s.tokens??[]),br=(t,s)=>He(`${t}/tokens/revoke`,{name:s}),vr=(t,s={})=>{const n=new URLSearchParams;s.q&&n.set("q",s.q),s.kind&&n.set("kind",s.kind);const r=n.toString();return _(`${t}/memory${r?`?${r}`:""}`)},yr=t=>_(`${t}/config/settings`),Me=(t,s,n={})=>{const r=new URLSearchParams(n).toString();return`${t}/kg/${s}${r?`?${r}`:""}`},Nr=(t,s)=>_(Me(t,"search",s?{q:s}:{})),kr=(t,s)=>_(Me(t,"entity",{uuid:s})),wr=(t,s)=>_(Me(t,"facts",{uuid:s})),Sr=(t,s)=>_(Me(t,"related",{uuid:s})),Cr=t=>_(Me(t,"communities")),He=async(t,s)=>Ct(await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})),vs=async t=>{const s=await fetch(t,{method:"DELETE"});!s.ok&&s.status!==404&&await Ct(s)};function $r(t,s){const n=new EventSource(`${t}/sessions/stream`);let r;const a=()=>{r&&clearTimeout(r),s.onStale(!1),r=setTimeout(()=>s.onStale(!0),15e3)};return n.addEventListener("session",o=>{a(),s.onSession(JSON.parse(o.data))}),n.addEventListener("remove",o=>{a(),s.onRemove(JSON.parse(o.data))}),n.addEventListener("heartbeat",a),n.onerror=()=>s.onStale(!0),a(),()=>{r&&clearTimeout(r),n.close()}}const ee=t=>`${t.namespace}/${t.name}`;function Ce(t){try{const s=new URL(t);return s.protocol==="http:"||s.protocol==="https:"?t:null}catch{return null}}const Tr={gke:"GKE",eks:"EKS",aks:"AKS",local:"Local",unknown:"Cluster"},Fe="h-3.5 w-3.5 shrink-0";function Ar(){return e.jsxs("svg",{viewBox:"0 0 24 24",className:Fe,"aria-hidden":"true",fill:"none",children:[e.jsx("path",{d:"M12 2.5 L20.16 7.25 V16.75 L12 21.5 L3.84 16.75 V7.25 Z",fill:"#4285F4",fillOpacity:"0.16",stroke:"#4285F4",strokeWidth:"1.5",strokeLinejoin:"round"}),e.jsx("circle",{cx:"12",cy:"12",r:"3",stroke:"#4285F4",strokeWidth:"1.5"})]})}function Lr(){return e.jsxs("svg",{viewBox:"0 0 24 24",className:Fe,"aria-hidden":"true",fill:"none",children:[e.jsx("path",{d:"M4 12.5 C 8 16.2, 16 16.2, 20 12.5",stroke:"#FF9900",strokeWidth:"2",strokeLinecap:"round"}),e.jsx("path",{d:"M16.6 12.2 L20.4 12.4 L18.9 15.6 Z",fill:"#FF9900"})]})}function Er(){return e.jsxs("svg",{viewBox:"0 0 24 24",className:Fe,"aria-hidden":"true",children:[e.jsx("path",{d:"M12.8 3 L21 20 H14.8 L12.4 13.6 Z",fill:"#0078D4"}),e.jsx("path",{d:"M10.8 8 L3.2 20 H15 L12.8 15.4 H9 Z",fill:"#0078D4",fillOpacity:"0.7"})]})}function Dr({type:t}){switch(t){case"gke":return e.jsx(Ar,{});case"eks":return e.jsx(Lr,{});case"aks":return e.jsx(Er,{});case"local":return e.jsx(Pn,{"aria-hidden":"true",className:`${Fe} text-muted-foreground`});default:return e.jsx(Mn,{"aria-hidden":"true",className:`${Fe} text-muted-foreground`})}}function Rr({apiBase:t}){const[s,n]=d.useState(null);if(d.useEffect(()=>{let u=!0;return cr(t).then(h=>{u&&n(h)}).catch(h=>{console.warn("cluster info fetch failed",h)}),()=>{u=!1}},[t]),!s)return null;const r=Tr[s.type]??"Cluster",a=s.name||r||"cluster",o=!!s.name,i=s.consoleURL?Ce(s.consoleURL):null,l=e.jsxs(e.Fragment,{children:[e.jsx(Dr,{type:s.type}),e.jsx("span",{className:"max-w-[16rem] truncate font-medium text-foreground",children:a}),o&&e.jsx("span",{className:"font-mono text-[10px] uppercase tracking-wide text-muted-foreground",children:r})]}),c="flex items-center gap-1.5 rounded-full border bg-card/60 px-2 py-0.5 text-xs";return i?e.jsx("a",{href:i,target:"_blank",rel:"noopener noreferrer",title:`Open ${a} in the cloud console`,className:`${c} transition-colors hover:border-primary/50 hover:bg-accent/40`,children:l}):e.jsx("span",{className:c,title:a,children:l})}const Or={email:"admin@example.com"};function Ir(){typeof window>"u"||window.location.assign("/admin/logout")}function Fr({view:t,onNavigate:s,currentUser:n=Or,apiBase:r="/admin/api",onSignOut:a=Ir,children:o}){const i=gs[t],[l,c]=d.useState(!1),[u,h]=d.useState(!1),[N,p]=d.useState(()=>en()),y=d.useRef(null);return d.useEffect(()=>{if(!l)return;function x($){y.current&&!y.current.contains($.target)&&c(!1)}function j($){$.key==="Escape"&&c(!1)}return document.addEventListener("mousedown",x),document.addEventListener("keydown",j),()=>{document.removeEventListener("mousedown",x),document.removeEventListener("keydown",j)}},[l]),e.jsxs("div",{className:"flex h-screen flex-col bg-background text-foreground",children:[e.jsxs("header",{className:"flex shrink-0 items-center border-b bg-card/30 px-5 py-2.5",children:[e.jsxs("div",{className:"mr-auto flex items-center gap-2.5",onPointerEnter:()=>h(!0),onPointerLeave:()=>h(!1),children:[e.jsx(tr,{active:u,bandWidth:32,band:14,className:"h-5 w-5 text-foreground"}),e.jsx("span",{className:"text-sm font-semibold",children:"Open Agent Primitives"}),e.jsx(Rr,{apiBase:r})]}),e.jsx("button",{type:"button","aria-label":Ke(N)==="dark"?"Switch to light theme":"Switch to dark theme",title:Ke(N)==="dark"?"Light theme":"Dark theme",onClick:()=>{const x=Ke(N)==="dark"?"light":"dark";_t(x),p(x)},className:"mr-2 grid h-7 w-7 place-items-center rounded-md border text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground",children:Ke(N)==="dark"?e.jsx(Kn,{className:"h-3.5 w-3.5","aria-hidden":"true"}):e.jsx(_n,{className:"h-3.5 w-3.5","aria-hidden":"true"})}),e.jsxs("div",{ref:y,className:"relative",children:[e.jsxs("button",{type:"button","aria-haspopup":"menu","aria-expanded":l,"aria-label":"User menu",onClick:()=>c(x=>!x),className:"flex items-center gap-2 rounded-md border bg-card px-2 py-1 transition-colors hover:bg-accent/50",children:[e.jsx("div",{"aria-hidden":"true",className:"grid h-6 w-6 shrink-0 place-items-center rounded-full border border-border bg-surface-3 font-mono text-[11px] font-medium uppercase leading-none text-foreground",children:n.email.trim()[0]||"?"}),e.jsx("div",{className:"text-left leading-tight",children:e.jsx("div",{className:"font-mono text-xs",children:n.email})}),e.jsx(Un,{className:`h-3.5 w-3.5 text-muted-foreground transition-transform ${l?"rotate-180":""}`})]}),l&&e.jsxs("div",{role:"menu",className:"absolute right-0 z-50 mt-1 w-44 overflow-hidden rounded-md border bg-card shadow-lg",children:[e.jsx("div",{className:"px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",children:"Appearance"}),e.jsx("div",{role:"group","aria-label":"Appearance",className:"flex gap-1 px-2 pb-2",children:["dark","light","system"].map(x=>e.jsx("button",{type:"button",role:"menuitemradio","aria-checked":N===x,onClick:()=>{_t(x),p(x)},className:["flex-1 rounded px-2 py-1 text-xs capitalize transition-colors",N===x?"bg-surface-active text-foreground":"text-muted-foreground hover:bg-surface-2 hover:text-foreground"].join(" "),children:x},x))}),e.jsx("div",{className:"border-t"}),e.jsxs("button",{type:"button",role:"menuitem",onClick:()=>{c(!1),a()},className:"flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-foreground transition-colors hover:bg-accent",children:[e.jsx(Vn,{"aria-hidden":"true",className:"h-4 w-4 text-muted-foreground"}),"Sign Out"]})]})]})]}),e.jsxs("div",{className:"flex min-h-0 flex-1",children:[e.jsx("aside",{className:"flex w-48 shrink-0 flex-col border-r bg-card/20 lg:w-60",children:e.jsx("nav",{className:"flex-1 overflow-y-auto px-2 py-1",children:ps.map(x=>e.jsxs("div",{children:[x.group&&e.jsx("p",{className:"mt-3 mb-0.5 px-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",children:x.group}),x.items.map(j=>{const $=j.icon,C=t===j.id;return e.jsxs("button",{type:"button","aria-current":C?"page":void 0,onClick:()=>s(j.id),className:["flex w-full items-center gap-2 py-1.5 text-left text-sm transition-colors",C?"-ml-2 rounded-r-md bg-surface-active pl-4 pr-2 font-medium text-foreground shadow-[inset_3px_0_0_hsl(var(--state))]":"rounded-md px-2 text-foreground/80 hover:bg-surface-2 hover:text-foreground"].join(" "),children:[e.jsx($,{className:"h-4 w-4 shrink-0"}),j.label]},j.id)})]},x.group||"__root__"))})}),e.jsxs("div",{className:"flex min-w-0 flex-1 flex-col",children:[e.jsx("header",{className:"flex shrink-0 items-center justify-between border-b bg-card/10 px-6 py-3.5",children:e.jsxs("div",{children:[e.jsx("h1",{className:"text-base font-semibold",children:i.title}),e.jsx("p",{className:"text-xs text-muted-foreground",children:i.sub})]})}),e.jsx("main",{className:"flex-1 overflow-y-auto px-4 py-4 lg:px-6 lg:py-5",children:o})]})]})]})}function Mr({view:t}){const s=gs[t];return e.jsxs(me,{className:"mt-2 border-dashed",children:[e.jsx(he,{children:e.jsx(fe,{className:"text-sm font-medium text-muted-foreground",children:s.title})}),e.jsx(pe,{children:e.jsx("p",{className:"text-xs text-muted-foreground",children:"This surface lands in a later Part 7 task."})})]})}const ve="/admin",Pr=new Set(ps.flatMap(t=>t.items.map(s=>s.id))),ys={session:"sessions",agent:"agents",tool:"tools",skill:"skills",source:"sources",directory:"directory",channel:"channels",identity:"identity",provider:"identity",artifact:"artifacts",user:"users"},Kr=Object.keys(ys),_r=new Set(Kr);function Ns(t){return _r.has(t)}const Wt={type:"view",view:"overview"};function Ur(t,s=""){const r=(t.startsWith(ve)?t.slice(ve.length):t).split("/").filter(Boolean);if(r.length===0)return Wt;const[a,...o]=r;if(r.length===1&&Pr.has(a)){const i=a,l=new URLSearchParams(s);return[...l].length>0?{type:"view",view:i,query:l}:{type:"view",view:i}}if(Ns(a)&&o.length>0){const i=o.join("/"),l=new URLSearchParams(s).get("tab")??void 0;return l?{type:"detail",entity:a,id:i,tab:l}:{type:"detail",entity:a,id:i}}return Wt}function ks(t){if(t.type==="detail"){const r=`${ve}/${t.entity}/${t.id}`;return t.tab?`${r}?tab=${encodeURIComponent(t.tab)}`:r}const s=t.query?t.query.toString():"";if(t.view==="overview"&&s==="")return ve;const n=`${ve}/${t.view}`;return s?`${n}?${s}`:n}function Vr(t){return t.type==="view"?t.view:ys[t.entity]}const gt=new Set;function Wr(){for(const t of gt)t()}function zr(t){return gt.add(t),typeof window<"u"&&window.addEventListener("popstate",t),()=>{gt.delete(t),typeof window<"u"&&window.removeEventListener("popstate",t)}}function Hr(){return typeof window>"u"?ve:window.location.pathname+window.location.search}function qr(){return ve}function te(t){typeof window>"u"||(window.history.pushState({},"",ks(t)),Wr())}function ws(){const t=d.useSyncExternalStore(zr,Hr,qr),s=t.indexOf("?"),n=s>=0?t.slice(0,s):t,r=s>=0?t.slice(s):"";return Ur(n,r)}function at({title:t,subtitle:s,backLabel:n="Back",backRoute:r,tabs:a,activeTab:o,onTab:i,children:l,headerRight:c}){return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{children:[e.jsxs("button",{type:"button",onClick:()=>{r?te(r):typeof window<"u"&&window.history.back()},className:"mb-2 inline-flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground",children:[e.jsx(Wn,{className:"h-3.5 w-3.5"}),n]}),e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("h2",{className:"truncate text-lg font-semibold",children:t}),s&&e.jsx("p",{className:"text-sm text-muted-foreground",children:s})]}),c&&e.jsx("div",{className:"shrink-0",children:c})]})]}),e.jsxs(tn,{value:o,onValueChange:i,children:[e.jsx(sn,{children:a.map(u=>e.jsx(nn,{value:u.id,children:u.label},u.id))}),a.map(u=>e.jsx(rn,{value:u.id,className:"mt-4",forceMount:!0,hidden:u.id!==o,children:u.id===o?l:null},u.id))]})]})}const Ss="ap_admin_";function Cs(){return typeof document<"u"}function Gr(t){if(!Cs())return null;const s=Ss+t+"=";for(const n of document.cookie.split(";")){const r=n.trimStart();if(r.startsWith(s))return decodeURIComponent(r.slice(s.length))}return null}function Jr(t,s,n){if(!Cs())return;let r=`${Ss+t}=${encodeURIComponent(s)}; path=/; SameSite=Lax`;const a=365;r+=`; Expires=${new Date(Date.now()+a*864e5).toUTCString()}`,document.cookie=r}function Yr(t){if(t.length>0&&t[0]==='"')try{const s=JSON.parse(t);if(typeof s=="string")return s}catch{}return t}function Tt(t,s){const[n,r]=d.useState(()=>{const o=Gr(t);return o===null?s:Yr(o)}),a=d.useCallback(o=>{r(o),Jr(t,o)},[t]);return[n,a]}function $s({value:t,onChange:s}){return e.jsxs("div",{className:"inline-flex items-center rounded-md border bg-background p-0.5",children:[e.jsx(zt,{label:"Grid view",active:t==="grid",onClick:()=>s("grid"),children:e.jsx(zn,{className:"h-3.5 w-3.5"})}),e.jsx(zt,{label:"Table view",active:t==="table",onClick:()=>s("table"),children:e.jsx(Hn,{className:"h-3.5 w-3.5"})})]})}function zt({label:t,active:s,onClick:n,children:r}){return e.jsx("button",{type:"button","aria-label":t,"aria-pressed":s,onClick:n,className:`inline-flex h-6 w-6 items-center justify-center rounded ${s?"bg-accent text-primary":"text-muted-foreground hover:text-foreground"}`,children:r})}function Qr(t){const s=t.indexOf("/");return s<0?{ns:"",name:t}:{ns:t.slice(0,s),name:t.slice(s+1)}}function Xr(t){return t===""||t==="default"}function Z({id:t,ns:s,name:n,className:r}){const a=t!=null?Qr(t):{ns:s??"",name:n??""};return e.jsxs("span",{className:r,children:[!Xr(a.ns)&&e.jsx("span",{className:"text-muted-foreground",children:`${a.ns}/`}),a.name]})}function Zr(t){return t.button===0&&!t.metaKey&&!t.ctrlKey&&!t.shiftKey&&!t.altKey}function Ts({route:t,className:s,onClick:n,children:r}){const a=ks(t);return e.jsx("a",{href:a,className:s,onClick:o=>{n==null||n(o),Zr(o)&&(o.preventDefault(),te(t))},children:r})}function z({entity:t,id:s,tab:n,className:r,onClick:a,children:o}){return e.jsx(Ts,{route:{type:"detail",entity:t,id:s,...n?{tab:n}:{}},className:r,onClick:a,children:o})}function Ht({view:t,query:s,className:n,children:r}){return e.jsx(Ts,{route:{type:"view",view:t,...s?{query:s}:{}},className:n,children:r})}const qe=["hsl(var(--chart-1))","hsl(var(--chart-2))","hsl(var(--chart-3))","hsl(var(--chart-4))","hsl(var(--chart-5))"];function As(t){const s=Array.from(new Set(t.filter(r=>r!==""))).sort(),n=new Map;return s.forEach((r,a)=>n.set(r,qe[a%qe.length])),n}function Ls(t){if(t==="")return;let s=0;for(let n=0;n>>0)%qe.length]}function ot({kind:t,className:s}){return t?e.jsxs("span",{className:`inline-flex items-center gap-1.5 rounded-full border bg-background px-2 py-0.5 font-mono text-[10px] text-muted-foreground${s?` ${s}`:""}`,children:[e.jsx(At,{kind:t}),t]}):null}function At({kind:t}){const s=Ls(t);return s?e.jsx("span",{"aria-hidden":"true",className:"inline-block h-2 w-2 shrink-0 rounded-full",style:{backgroundColor:s}}):null}function Br(t){const s=t.replace(/-/g,"+").replace(/_/g,"/"),n=s.length%4===0?"":"=".repeat(4-s.length%4),r=atob(s+n),a=new Uint8Array(r.length);for(let o=0;o=1e3?`${(t/1e3).toFixed(1)}K`:`${t}`}function xe(t){return t>=1e6?`${(t/1e6).toFixed(2)}M`:t>=1e3?`${Math.round(t/1e3)}K`:`${t}`}function Se(t,s=Date.now()){if(!t)return"—";const n=new Date(t).getTime();if(Number.isNaN(n))return"—";const r=Math.round((s-n)/1e3);if(r<45)return"just now";const a=Math.round(r/60);if(a<60)return`${a}m ago`;const o=Math.round(a/60);if(o<24)return`${o}h ago`;const i=Math.round(o/24);return i<7?`${i}d ago`:new Date(n).toLocaleDateString()}const ea={Valid:"success",Ready:"success",Connected:"success",Reachable:"success",Synced:"success",Healthy:"success",Active:"success",Degraded:"warning",Syncing:"warning",Pending:"warning",Progressing:"warning",Failed:"destructive",Invalid:"destructive",Error:"destructive",Down:"destructive",Running:"primary",Deferred:"primary"},ta={success:"border-success/40 bg-success/10 text-success",warning:"border-warning/40 bg-warning/10 text-warning",destructive:"border-destructive/40 bg-destructive/10 text-destructive",primary:"border-primary/40 bg-primary/10 text-primary",muted:"border-border bg-background text-muted-foreground"};function Es(t){return ea[t]??"muted"}function Ds(t){return ta[t]}function lt(t){return Ds(Es(t))}function Lt({phase:t}){return e.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] ${lt(t??"")}`,children:t||"—"})}function sa({s:t,onOpen:s}){const n=t.active?"bg-primary":t.phase==="Failed"?"bg-destructive":"bg-success",r=t.pendingToolGrants+t.pendingLeakageApprovals;return e.jsxs(me,{className:"cursor-pointer transition-colors hover:border-primary/50",onClick:s,children:[e.jsx(he,{className:"pb-2",children:e.jsxs(fe,{className:"flex items-center justify-between text-sm font-medium",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx("span",{className:Ye("inline-block h-2 w-2 rounded-full",n,t.active&&"animate-ap-pulse-dot")}),e.jsxs("span",{className:"font-mono",children:[t.class??"?"," / ",e.jsx(Z,{ns:t.namespace,name:t.name})]})]}),e.jsx(Lt,{phase:t.phase})]})}),e.jsxs(pe,{className:"space-y-2 text-xs text-muted-foreground",children:[t.statusText&&e.jsx("p",{className:"truncate font-mono",children:t.statusText}),e.jsxs("p",{className:"font-mono",children:[ge(t.inputTokens)," in · ",ge(t.outputTokens)," out · ",t.elapsedSeconds,"s · ",t.toolCallCount," tools"]}),e.jsxs("div",{className:"flex gap-2",children:[t.channelKind&&e.jsx(W,{variant:"outline",children:t.channelKind}),r>0&&e.jsxs(W,{variant:"destructive",children:[r," approval",r>1?"s":""," pending"]})]}),t.active&&e.jsx("div",{className:"h-0.5 rounded bg-gradient-to-r from-primary via-state to-primary bg-[length:200%_100%] animate-ap-flow"})]})]})}function qt(t){te({type:"detail",entity:"session",id:`${t.namespace}/${t.name}`})}function na({kinds:t,value:s,onChange:n}){const r="rounded-full border px-2 py-0.5 text-[10px] transition-colors",a="border-border bg-background text-muted-foreground hover:bg-accent/50",o="border-primary/40 bg-primary/10 text-primary";return e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("button",{type:"button","aria-pressed":s==="",onClick:()=>n(""),className:`${r} ${s===""?o:a}`,children:"All"}),t.map(i=>{const l=s===i;return e.jsx("button",{type:"button","aria-pressed":l,onClick:()=>n(l?"":i),className:`${r} ${l?o:a}`,children:i},i)})]})}function ra({apiBase:t}){const[s,n]=d.useState(new Map),[r,a]=d.useState(!1),[o,i]=d.useState(null),[l,c]=d.useState(""),[u,h]=Tt("sessions_view","grid");d.useEffect(()=>{let x=!1;const j=new Map;let $=0;const C=()=>{const k=++$;j.clear(),_(`${t}/sessions`).then(R=>{if(x||k!==$)return;const O=new Map(R.map(A=>[ee(A),A]));for(const[A,w]of j)w?O.set(A,w):O.delete(A);j.clear(),n(O),i(null)}).catch(R=>{x||i(R.message)})};C();let E=!1;const m=$r(t,{onSession:k=>{j.set(ee(k),k),n(R=>new Map(R).set(ee(k),k))},onRemove:k=>{j.set(ee(k),null),n(R=>{const O=new Map(R);return O.delete(ee(k)),O})},onStale:k=>{a(k),!k&&E&&C(),E=k}});return()=>{x=!0,m()}},[t]);const N=[...s.values()].sort((x,j)=>(j.startedAt??"").localeCompare(x.startedAt??"")),p=[...new Set(N.map(x=>x.channelKind).filter(x=>!!x))].sort(),y=l?N.filter(x=>x.channelKind===l):N;return e.jsxs("div",{className:"space-y-4",children:[o&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load sessions: ",o]})}),r&&e.jsx(I,{children:e.jsx(F,{children:"Live feed stalled — showing last known state."})}),e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[p.length>0?e.jsx(na,{kinds:p,value:l,onChange:c}):e.jsx("span",{}),e.jsx($s,{value:u,onChange:h})]}),y.length===0&&!o&&e.jsx("p",{className:"text-sm text-muted-foreground",children:N.length===0?"No sessions.":"No sessions match this filter."}),y.length>0&&u==="grid"&&e.jsx("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3",children:y.map(x=>e.jsx(sa,{s:x,onOpen:()=>qt(x)},ee(x)))}),y.length>0&&u==="table"&&e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Session"}),e.jsx(v,{children:"Class"}),e.jsx(v,{children:"Channel"}),e.jsx(v,{children:"Phase"}),e.jsx(v,{children:"Tokens"}),e.jsx(v,{children:"Started"})]})}),e.jsx(Y,{children:y.map(x=>e.jsxs(M,{className:"cursor-pointer",onClick:()=>qt(x),children:[e.jsx(b,{className:"font-mono text-xs text-foreground",children:e.jsx(Z,{id:ee(x)})}),e.jsx(b,{className:"font-mono text-xs",children:x.class?e.jsx(z,{entity:"agent",id:`${x.namespace}/${x.class}`,className:"text-link hover:underline",onClick:j=>j.stopPropagation(),children:x.class}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}),e.jsxs(b,{className:"text-xs",children:[x.channelName?e.jsx(z,{entity:"channel",id:`${x.namespace}/${x.channelName}`,className:"font-mono text-link hover:underline",onClick:j=>j.stopPropagation(),children:e.jsx(Z,{ns:x.namespace,name:x.channelName})}):!x.channelKind&&e.jsx("span",{className:"font-mono text-muted-foreground",children:"—"}),x.channelKind&&e.jsx(ot,{kind:x.channelKind,className:x.channelName?"ml-1.5":""})]}),e.jsx(b,{children:e.jsx(Lt,{phase:x.phase})}),e.jsxs(b,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:[ge(x.inputTokens)," in · ",ge(x.outputTokens)," out"]}),e.jsxs(b,{className:"whitespace-nowrap text-xs text-muted-foreground",children:[Se(x.startedAt),x.startedBy&&e.jsxs("span",{className:"mt-0.5 flex items-center gap-1.5 text-[10px] text-muted-foreground/70",children:[e.jsx(At,{kind:x.startedBy}),e.jsx(z,{entity:"user",id:x.startedBy,className:"font-mono text-link hover:underline",children:ne(x.startedBy)})]})]})]},ee(x)))})]})]})}function aa(t){const s=new Date(t);return Number.isNaN(s.getTime())?t:s.toLocaleTimeString()}const oa=4e3;function la({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1);return d.useEffect(()=>{let l=!0;const c=()=>{ur(t).then(h=>{l&&(n(h),a(null),i(!0))}).catch(h=>{l&&(a(h.message),i(!0))})};c();const u=setInterval(c,oa);return()=>{l=!1,clearInterval(u)}},[t]),e.jsxs("div",{className:"space-y-3",children:[r&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load tool calls: ",r]})}),o&&s.length===0&&!r&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No tool calls yet — they appear here as agents dispatch them."}),s.length>0&&e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"When"}),e.jsx(v,{children:"Session"}),e.jsx(v,{children:"Agent"}),e.jsx(v,{children:"Tool"})]})}),e.jsx(Y,{children:s.map((l,c)=>e.jsxs(M,{children:[e.jsx(b,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:aa(l.t)}),e.jsx(b,{className:"font-mono text-xs",children:e.jsx(z,{entity:"session",id:ee(l),className:"text-link hover:underline",children:e.jsx(Z,{id:ee(l)})})}),e.jsx(b,{className:"text-xs",children:l.agentClass?e.jsx(z,{entity:"agent",id:`${l.namespace}/${l.agentClass}`,className:"text-link hover:underline",children:l.agentClass}):"(unknown)"}),e.jsx(b,{className:"font-mono text-xs text-foreground",children:l.tool||"(tool)"})]},`${ee(l)}-${l.t}-${c}`))})]})]})}const ia=4e3,ca={tool_call:{label:"Tool call",cls:"border-warning/40 text-warning"},leakage:{label:"Leakage",cls:"border-state/40 text-state"},content_inspection:{label:"Content",cls:"border-warning/40 text-warning"}};function da(t){const s=new Date(t).getTime();if(Number.isNaN(s)||s{let c=!0;const u=()=>{mr(t).then(N=>{c&&(r(N),o(null),l(!0))}).catch(N=>{c&&(o(N.message),l(!0))})};u();const h=setInterval(u,ia);return()=>{c=!1,clearInterval(h)}},[t]),e.jsxs("div",{className:"space-y-3",children:[a&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load approvals: ",a]})}),i&&n.length===0&&!a&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing awaiting a decision — pending approvals appear here as agents request them."}),n.length>0&&e.jsx("div",{className:"grid grid-cols-1 gap-3 lg:grid-cols-2",children:n.map((c,u)=>{const h=ca[c.kind]??{label:c.kind,cls:"border-input text-muted-foreground"},N=da(c.requestedAt);return e.jsxs(me,{className:"ring-1 ring-warning/20",children:[e.jsx(he,{className:"pb-2",children:e.jsxs(fe,{className:"flex items-center justify-between text-sm font-medium",children:[e.jsx("span",{className:"font-mono",children:c.tool||h.label}),e.jsx(W,{variant:"outline",className:Ye(h.cls),children:h.label})]})}),e.jsxs(pe,{className:"space-y-3 text-xs text-muted-foreground",children:[e.jsxs("p",{className:"font-mono",children:[c.agentClass||"(unknown)"," / ",ee(c)]}),e.jsxs("div",{className:"flex items-center justify-between",children:[c.requester?e.jsxs("span",{children:["requested by ",e.jsx("span",{className:"text-foreground",children:c.requester})]}):e.jsx("span",{}),N&&e.jsxs("span",{className:"rounded-full border border-warning/40 bg-warning/10 px-2 py-0.5 text-[10px] text-warning",children:["waiting ",N]})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(U,{variant:"outline",size:"sm",disabled:!s,onClick:()=>s==null?void 0:s(c.namespace,c.name),children:"Open session"}),e.jsx("span",{className:"text-[11px] text-muted-foreground",children:"decide in the channel thread"})]})]})]},`${ee(c)}-${c.kind}-${c.requestedAt}-${u}`)})}),n.length>0&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Read-only — decisions happen in the originating channel thread. Open the session to watch it."})]})}const Ge="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50";function jt({label:t,children:s}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("div",{className:"text-sm text-foreground",children:s})]})}function Gt(t){return t==null?"":typeof t=="string"?t:Array.isArray(t)?t.join(","):String(t)}function xa({q:t,value:s,onChange:n}){const r=t.required!==!1,a=`${t.prompt||t.name}${r?" *":""}`;return e.jsxs(jt,{label:a,children:[t.type==="bool"?e.jsx("input",{type:"checkbox",checked:s==="true",onChange:o=>n(o.target.checked?"true":"false"),"aria-label":a,className:"h-4 w-4 rounded border-border"}):t.type==="enum"?e.jsxs("select",{value:s,onChange:o=>n(o.target.value),"aria-label":a,className:Ge,children:[e.jsx("option",{value:"",disabled:!0,children:"Select…"}),(t.enum??[]).map((o,i)=>{var u;const l=(u=t.enumLabels)==null?void 0:u[i],c=l!==void 0&&l.trim()!==""?l:o;return e.jsx("option",{value:o,children:c},o)})]}):e.jsx("input",{type:t.type==="secret"?"password":t.type==="int"?"number":"text",value:s,onChange:o=>n(o.target.value),"aria-label":a,placeholder:t.type==="resourceList"?"comma-separated":void 0,autoComplete:t.type==="secret"?"new-password":"off",className:Ge}),t.description&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:t.description})]})}function ma({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(s.namespace),[l,c]=d.useState(s.suggestedName??""),[u,h]=d.useState({}),[N,p]=d.useState(null),[y,x]=d.useState([]),[j,$]=d.useState([]),[C,E]=d.useState(null),[m,k]=d.useState(!1),R=()=>{x([]),$([])},O=(g,T)=>{h(P=>({...P,[g]:T})),R()},A=()=>{E(null),p(null),R()},w=async()=>{E(null),k(!0);try{await or(t,s.namespace,s.name,{namespace:o,name:l,values:u,adopt:j}),a(!1),A(),n()}catch(g){g instanceof nt?(p(g.questions),h(T=>{const P={...T};for(const V of g.questions)P[V.name]===void 0&&V.default!==void 0&&V.default!==null&&(P[V.name]=Gt(V.default));return P})):g instanceof rt?x(g.conflicts):(g instanceof X,E(g.message))}finally{k(!1)}};return e.jsxs(Qe,{open:r,onOpenChange:g=>{a(g),g||A()},children:[e.jsx(Xe,{asChild:!0,children:e.jsx(U,{size:"sm",children:"Install"})}),e.jsxs(Ze,{children:[e.jsxs(Be,{children:[e.jsxs(et,{children:["Install ",s.suggestedName||s.name]}),e.jsx(tt,{children:"Installs this workshop's drafted bundle under the operator identity. No secret answer is ever pre-filled — type it below."})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[C&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:C})}),e.jsxs("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2",children:[e.jsx(jt,{label:"Namespace *",children:e.jsx("input",{type:"text",value:o,onChange:g=>{i(g.target.value),R()},"aria-label":"Namespace",className:Ge})}),e.jsx(jt,{label:"Name (optional)",children:e.jsx("input",{type:"text",value:l,onChange:g=>{c(g.target.value),R()},placeholder:"defaults to the bundle's own name","aria-label":"Name",className:Ge})})]}),y.length>0&&e.jsxs("div",{className:"flex flex-col gap-2 rounded-md border border-destructive/50 p-3",children:[e.jsx("p",{className:"text-xs font-medium text-foreground",children:"These objects already exist and were not created by this install. Adopting one overwrites its spec with this bundle's."}),y.map(g=>{const T=`${g.kind}/${g.name}`,P=g.namespace?`${g.kind} ${g.namespace}/${g.name}`:T;return e.jsxs("label",{className:"flex items-center gap-2 text-xs text-foreground",children:[e.jsx("input",{type:"checkbox",checked:j.includes(T),"aria-label":P,onChange:V=>$(q=>V.target.checked?[...q,T]:q.filter(ie=>ie!==T)),className:"h-4 w-4 rounded border-border"}),e.jsx("span",{children:P}),g.secret&&e.jsx("span",{className:"text-destructive",children:"overwrites this Secret's data"})]},T)})]}),N&&N.length>0&&e.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-dashed border-border p-3",children:[e.jsxs("p",{className:"text-xs text-muted-foreground",children:["This bundle needs ",N.length===1?"an answer":"answers"," before it can install:"]}),N.map(g=>e.jsx(xa,{q:g,value:u[g.name]??Gt(g.default),onChange:T=>O(g.name,T)},g.name))]})]}),e.jsxs(st,{children:[e.jsx(wt,{asChild:!0,children:e.jsx(U,{variant:"outline",children:"Cancel"})}),e.jsx(U,{onClick:w,disabled:m||o.trim()==="",children:m?"Installing…":y.length>0&&j.length>0?"Adopt and install":N?"Submit answers":"Confirm install"})]})]})]})}function ha({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(null),l=async()=>{i(null),a(!0);try{await He(`${t}/workshops/${s.namespace}/${s.name}/decline`,{}),n()}catch(c){i(c.message)}finally{a(!1)}};return e.jsxs("span",{className:"flex flex-col items-end gap-1",children:[e.jsx(U,{variant:"outline",size:"sm",onClick:l,disabled:r,children:r?"Declining…":"Decline"}),o&&e.jsx("span",{className:"text-[11px] text-destructive",children:o})]})}function fa({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(null),[l,c]=d.useState(!1),u=async()=>{i(null),c(!0);try{await vs(`${t}/workshops/${s.namespace}/${s.name}`),a(!1),n()}catch(h){i(h.message)}finally{c(!1)}};return e.jsxs(Qe,{open:r,onOpenChange:a,children:[e.jsx(Xe,{asChild:!0,children:e.jsx(U,{variant:"destructive",size:"sm",children:"Kill"})}),e.jsxs(Ze,{children:[e.jsxs(Be,{children:[e.jsxs(et,{children:["Kill workshop ",s.name,"?"]}),e.jsx(tt,{children:"Deletes the Workshop — its provisioned builder namespace is torn down. This cannot be undone."})]}),o&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:o})}),e.jsxs(st,{children:[e.jsx(wt,{asChild:!0,children:e.jsx(U,{variant:"outline",children:"Cancel"})}),e.jsx(U,{variant:"destructive",onClick:u,disabled:l,children:l?"Killing…":"Kill it"})]})]})]})}function pa({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1),l=d.useCallback(()=>{gr(t).then(c=>{n(c),a(null),i(!0)}).catch(c=>{a(c.message),i(!0)})},[t]);return d.useEffect(()=>{l()},[l]),r?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load workshops: ",r]})}):e.jsxs("div",{className:"space-y-3",children:[o&&s.length===0&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No workshops yet."}),s.length>0&&e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Workshop"}),e.jsx(v,{children:"Session"}),e.jsx(v,{children:"Starter"}),e.jsx(v,{children:"Phase"}),e.jsx(v,{children:"Install"}),e.jsx(v,{className:"text-right",children:"Actions"})]})}),e.jsx(Y,{children:s.map(c=>{const u=`${c.namespace}/${c.name}`;return e.jsxs(M,{children:[e.jsxs(b,{className:"font-mono text-xs",children:[u,c.exported&&e.jsx(W,{variant:"outline",className:"ml-2",children:"exported"})]}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:c.session}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:c.starter}),e.jsx(b,{children:e.jsx(W,{variant:"outline",children:c.phase||"—"})}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:c.installPhase?e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx(W,{variant:c.installPhase==="Failed"?"destructive":"outline",children:c.installPhase}),c.installedRef&&e.jsx("span",{children:c.installedRef})]}):c.pendingCapability?"capability requested":"—"}),e.jsx(b,{className:"text-right",children:e.jsxs("div",{className:"flex justify-end gap-2",children:[c.pendingInstall&&e.jsxs(e.Fragment,{children:[e.jsx(ma,{apiBase:t,row:c,onDone:l}),e.jsx(ha,{apiBase:t,row:c,onDone:l})]}),e.jsx(fa,{apiBase:t,row:c,onDone:l})]})})]},u)})})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Workshop — an agent-builder session's drafted bundle, awaiting an admin's install/decline/kill decision."})]})}function Je({value:t,className:s}){const[n,r]=d.useState(!1);return e.jsxs("div",{className:Ye("rounded border border-border bg-background/40 text-xs",s),children:[e.jsx("div",{className:"flex justify-end border-b border-border/50 px-1.5 py-0.5",children:e.jsx("button",{type:"button",onClick:()=>r(a=>!a),className:"font-mono text-[10px] uppercase tracking-wide text-muted-foreground hover:text-foreground","aria-pressed":n,children:n?"tree":"raw"})}),e.jsx("div",{className:"overflow-x-auto p-2 font-mono leading-relaxed",children:n?e.jsx("pre",{className:"whitespace-pre-wrap break-words text-foreground",children:ga(t)}):e.jsx(Os,{value:t})})]})}function ga(t){const s=JSON.stringify(t,null,2);return s===void 0?String(t):s}function Rs(t){const s=t.trim();if(!(!s.startsWith("{")&&!s.startsWith("[")))try{return JSON.parse(s)}catch{return}}function Os({value:t,label:s}){return t===null||typeof t!="object"?e.jsxs("div",{className:"flex flex-wrap items-start gap-1.5",children:[s!==void 0&&e.jsxs("span",{className:"shrink-0 text-foreground/70",children:[s,":"]}),e.jsx(ba,{value:t})]}):e.jsx(va,{value:t,label:s})}function ja(t){switch(typeof t){case"string":return"text-success";case"number":return"text-primary";case"boolean":return"text-state";default:return"text-muted-foreground"}}function ba({value:t}){const s=typeof t=="string"?JSON.stringify(t):String(t);return e.jsx("span",{className:ja(t),children:s})}function va({value:t,label:s}){const[n,r]=d.useState(!0),a=Array.isArray(t),o=a?t.map((u,h)=>[String(h),u]):Object.entries(t),[i,l]=a?["[","]"]:["{","}"],c=s!==void 0?e.jsxs("span",{className:"text-foreground/70",children:[s,": "]}):null;return o.length===0?e.jsxs("div",{children:[c,e.jsxs("span",{className:"text-muted-foreground",children:[i,l]})]}):e.jsxs("div",{children:[e.jsxs("button",{type:"button",onClick:()=>r(u=>!u),"aria-expanded":n,className:"text-muted-foreground hover:text-foreground",children:[c,e.jsx("span",{"aria-hidden":"true",children:n?"▾":"▸"})," ",i,!n&&e.jsxs("span",{className:"text-muted-foreground",children:[" ","…",o.length," ",l]})]}),n&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ml-2 border-l border-border/40 pl-2",children:o.map(([u,h])=>e.jsx(Os,{value:h,label:a?void 0:u},u))}),e.jsx("div",{className:"text-muted-foreground",children:l})]})]})}const ya=560,Re=28,be={channel:60,agent:260,tools:470};function xt({x:t,y:s,label:n,stroke:r}){return e.jsxs("g",{children:[e.jsx("circle",{cx:t,cy:s,r:Re,fill:"hsl(var(--card))",stroke:r,strokeWidth:1.5}),e.jsx("text",{x:t,y:s+4,textAnchor:"middle",fontSize:10,fill:"hsl(var(--foreground))",fontFamily:"monospace",children:n.length>11?n.slice(0,10)+"…":n})]})}function Jt({x1:t,y1:s,x2:n,y2:r,active:a}){return e.jsx("path",{className:a?"ap-edge-active":void 0,d:`M ${t} ${s} C ${(t+n)/2} ${s}, ${(t+n)/2} ${r}, ${n} ${r}`,fill:"none",stroke:a?"hsl(var(--primary))":"hsl(var(--border))",strokeWidth:1.5,strokeDasharray:a?"6 6":void 0,children:a&&e.jsx("animate",{attributeName:"stroke-dashoffset",from:"24",to:"0",dur:"0.9s",repeatCount:"indefinite"})})}function Na({channelKind:t,agentClass:s,tools:n,active:r}){const a=n.slice(0,8),o=Math.max(160,a.length*64+40),i=o/2;return e.jsxs("svg",{viewBox:`0 0 ${ya} ${o}`,className:"w-full",role:"img","aria-label":"session flow graph",children:[e.jsx(Jt,{x1:be.channel+Re,y1:i,x2:be.agent-Re,y2:i,active:r}),a.map((l,c)=>{const u=40+c*64+12;return e.jsxs(d.Fragment,{children:[e.jsx(Jt,{x1:be.agent+Re,y1:i,x2:be.tools-Re,y2:u,active:r}),e.jsx(xt,{x:be.tools,y:u,label:l,stroke:"hsl(var(--state))"})]},l)}),e.jsx(xt,{x:be.channel,y:i,label:t??"channel",stroke:"hsl(var(--success))"}),e.jsx(xt,{x:be.agent,y:i,label:s??"agent",stroke:"hsl(var(--primary))"})]})}const ka=[{id:"overview",label:"Overview"},{id:"activity",label:"Recent Activity"},{id:"logs",label:"Full Logs"}];function wa(t){const s=t.indexOf("/");return s<0?{ns:"",name:t}:{ns:t.slice(0,s),name:t.slice(s+1)}}function Sa(t){var n;const s=new Set;for(const r of t.recentEvents)if(r.kind==="tool_activity"){const a=(n=r.payload)==null?void 0:n.tool;a&&s.add(a)}return[...s]}function Ca({apiBase:t,id:s,tab:n}){const{ns:r,name:a}=wa(s),o=n??"overview",i=`${t}/sessions/${r}/${a}`,[l,c]=d.useState(null),[u,h]=d.useState(null),[N,p]=d.useState(!1),[y,x]=d.useState(null),[j,$]=d.useState(null),[C,E]=d.useState(!1),m=d.useRef(null);d.useEffect(()=>()=>{m.current!==null&&clearTimeout(m.current)},[]),d.useEffect(()=>{let O=!0;return c(null),h(null),p(!1),_(i).then(A=>{O&&c(A)}).catch(A=>{O&&(A instanceof X&&A.status===404?p(!0):h(A.message))}),()=>{O=!1}},[i]);const k=async()=>{$(null);try{await vs(i),E(!1),x("Session terminated."),m.current=setTimeout(()=>te({type:"view",view:"sessions"}),800)}catch(O){E(!1),$(O.message)}},R=O=>te({type:"detail",entity:"session",id:s,tab:O});return e.jsx(at,{title:e.jsxs("span",{className:"font-mono",children:[l&&`${l.class??"?"} / `,e.jsx(Z,{ns:r,name:a})]}),backRoute:{type:"view",view:"sessions"},tabs:ka,activeTab:o,onTab:R,headerRight:l!=null&&l.phase?e.jsx(W,{variant:"outline",children:l.phase}):void 0,children:o==="logs"?e.jsx(La,{apiBase:t,ns:r,name:a}):u?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load session: ",u]})}):N?e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Session ",s," is not tracked — it may have completed or never started."]}):l?o==="activity"?e.jsx(Aa,{detail:l}):e.jsx($a,{detail:l,name:a,killNote:y,killError:j,killOpen:C,onKillOpenChange:E,onKill:k}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"})})}function $a({detail:t,name:s,killNote:n,killError:r,killOpen:a,onKillOpenChange:o,onKill:i}){return e.jsxs("div",{className:"flex flex-col gap-4",children:[n&&e.jsx("p",{className:"text-sm text-success",children:n}),r&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not kill session: ",r]})}),e.jsx("div",{className:"rounded-lg border border-border bg-card p-4",children:e.jsx(Na,{channelKind:t.channelKind,agentClass:t.class,tools:Sa(t),active:t.active})}),t.statusText&&e.jsx("p",{className:"font-mono text-sm text-muted-foreground",children:t.statusText}),e.jsxs("p",{className:"font-mono text-xs text-muted-foreground",children:[ge(t.inputTokens)," in · ",ge(t.outputTokens)," out · ",t.toolCallCount," tool calls · turn ",t.turnCount]}),e.jsxs("dl",{className:"grid grid-cols-2 gap-x-6 gap-y-2 text-xs sm:grid-cols-3",children:[e.jsx(Ee,{label:"Phase",value:t.phase??"—"}),e.jsx(Ee,{label:"Model",children:t.model?e.jsx(St,{model:t.model}):"—"}),e.jsx(Ee,{label:"Elapsed",value:`${t.elapsedSeconds}s`}),t.startedAt&&e.jsx(Ee,{label:"Started",value:new Date(t.startedAt).toLocaleString()}),t.activityCause&&e.jsx(Ee,{label:"Activity",value:t.activityCause})]}),t.bundles&&t.bundles.length>0&&e.jsx(Ta,{bundles:t.bundles}),e.jsxs(Qe,{open:a,onOpenChange:o,children:[e.jsx(Xe,{asChild:!0,children:e.jsx(U,{variant:"destructive",className:"mt-2 self-start",children:"Kill session"})}),e.jsxs(Ze,{children:[e.jsxs(Be,{children:[e.jsxs(et,{children:["Kill ",s,"?"]}),e.jsx(tt,{children:"Deletes the AgentSession — the runner pod is torn down immediately. This cannot be undone."})]}),e.jsxs(st,{children:[e.jsx(wt,{asChild:!0,children:e.jsx(U,{variant:"outline",children:"Cancel"})}),e.jsx(U,{variant:"destructive",onClick:i,children:"Kill it"})]})]})]})]})}function Ee({label:t,value:s,children:n}){return e.jsxs("div",{className:"flex flex-col",children:[e.jsx("dt",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("dd",{className:"font-mono text-foreground",children:n??s})]})}function Ta({bundles:t}){return e.jsxs("div",{className:"rounded-lg border border-border bg-card p-4",children:[e.jsx("h3",{className:"mb-2 text-[10px] uppercase tracking-wide text-muted-foreground",children:"Bundles"}),e.jsx("ul",{className:"space-y-2",children:t.map(s=>e.jsxs("li",{className:"flex flex-wrap items-center gap-2 font-mono text-xs",children:[e.jsx("span",{className:"font-semibold text-foreground",children:s.name}),e.jsx("span",{className:"text-muted-foreground",children:s.spiceboxSessionName}),s.sandboxKind?e.jsxs(e.Fragment,{children:[e.jsx(W,{variant:"outline",children:s.sandboxKind}),e.jsx("span",{className:"break-all text-muted-foreground",children:s.sandboxRef}),s.prewarmed&&e.jsx(W,{variant:"outline",children:"warm"})]}):e.jsx("span",{className:"text-muted-foreground",children:"sandbox not yet resolved"}),s.workspaceMode&&e.jsxs(W,{variant:"outline",children:[s.workspaceMode," workspace"]}),s.phase&&e.jsx(W,{variant:s.phase==="Failed"?"destructive":"outline",children:s.phase}),s.reason&&e.jsx("span",{className:"text-muted-foreground",children:s.reason})]},s.name))})]})}function Aa({detail:t}){return t.recentEvents.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No recent activity."}):e.jsx("ul",{className:"space-y-1 font-mono text-xs text-muted-foreground",children:t.recentEvents.slice().reverse().map((s,n)=>e.jsxs("li",{children:[new Date(s.at).toLocaleTimeString()," · ",s.kind]},n))})}function La({apiBase:t,ns:s,name:n}){const[r,a]=d.useState(null),[o,i]=d.useState(null);return d.useEffect(()=>{let l=!0;return a(null),i(null),xr(t,s,n).then(c=>{l&&a(c)}).catch(c=>{l&&i(c.message)}),()=>{l=!1}},[t,s,n]),o?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load logs: ",o]})}):r?r.entries.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No transcript entries yet."}):e.jsxs("div",{className:"space-y-3",children:[r.truncated&&e.jsxs("p",{className:"text-xs text-warning",children:["Showing the latest ",r.entries.length," entries — older turns were truncated."]}),e.jsx("ul",{className:"space-y-2",children:r.entries.map(l=>e.jsx(Da,{entry:l},l.id))})]}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading logs…"})}function Ea(t){switch(t){case"user":case"inbox":return{container:"border-state/40 bg-state/5",badge:"bg-state/15 text-state"};case"assistant":return{container:"border-primary/40 bg-primary/5",badge:"bg-primary/15 text-primary"};case"tool":return{container:"border-warning/40 bg-warning/5",badge:"bg-warning/15 text-warning"};default:return{container:"border-border bg-card",badge:"bg-muted text-muted-foreground"}}}function Da({entry:t}){const s=t.role||t.kind,n=Ea(t.role??"");return e.jsxs("li",{className:`rounded-md border p-3 ${n.container}`,children:[e.jsxs("div",{className:"mb-2 flex items-center justify-between gap-2 font-mono text-[11px] text-muted-foreground",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx("span",{className:`rounded px-1.5 py-0.5 text-[10px] uppercase tracking-wide ${n.badge}`,children:s}),t.actor&&e.jsxs("span",{className:"flex items-center gap-1",children:[t.actorInferred?"requested by":"by"," ",e.jsx(z,{entity:"user",id:t.actor,className:"text-link hover:underline",children:ne(t.actor)}),t.actorInferred&&e.jsxs("span",{className:"text-muted-foreground/60",title:"Session initiator — this legacy turn predates per-turn authorship.",children:[" ","(session initiator)"]})]})]}),e.jsx("span",{children:new Date(t.createdAt).toLocaleString()})]}),t.blocks&&t.blocks.length>0?e.jsx("div",{className:"space-y-2",children:t.blocks.map((r,a)=>e.jsx(Ra,{block:r,role:t.role??""},a))}):e.jsx(bt,{text:t.content,className:"text-foreground"})]})}function Ra({block:t,role:s}){if(t.type==="tool_use")return e.jsxs("div",{className:"rounded border border-warning/40 bg-warning/5 p-2",children:[e.jsxs("div",{className:"mb-1 font-mono text-[11px] text-warning",children:["→ ",t.name||"tool"]}),e.jsx(Je,{value:t.input??{}})]});if(t.type==="tool_result"){const r=t.isError,a=r?"border-destructive/50 bg-destructive/10":"border-success/40 bg-success/5",o=r?"text-destructive":"text-success",i=Oa(t.content);return e.jsxs("div",{className:`rounded border p-2 ${a}`,children:[e.jsxs("div",{className:`mb-1 font-mono text-[11px] ${o}`,children:["← ",r?"error":"result"]}),i!==null?e.jsx(bt,{text:i,className:"text-foreground/90"}):e.jsx(Je,{value:t.content??""})]})}const n=s==="assistant"?"text-foreground":"text-foreground/90";return e.jsx(bt,{text:t.text??"",className:n})}function Oa(t){if(typeof t!="string")return null;let s=t;if(s.length>=2&&s.startsWith('"')&&s.endsWith('"'))try{const n=JSON.parse(s);typeof n=="string"&&(s=n)}catch{}return s.includes("]*)?>/g;function Yt({chunk:t}){const s=Rs(t);return s!==void 0?e.jsx(Je,{value:s,className:"my-1"}):e.jsx("span",{className:"rounded-sm bg-muted/40 text-muted-foreground",children:t})}function bt({text:t,className:s}){const n=`whitespace-pre-wrap break-words font-mono text-xs ${s??""}`;if(!t.includes("t.stopPropagation(),Fa=t=>t==="denied"||t==="read_denied"||t==="failed"?"destructive":t==="pending"?"secondary":"outline",Is=[{key:"kind",label:"Kind",value:t=>t.kind},{key:"outcome",label:"Outcome",value:t=>t.outcome},{key:"agent",label:"Agent",value:t=>t.agentClass??""},{key:"tool",label:"Tool",value:t=>t.tool??""},{key:"actor",label:"Actor",value:t=>t.actor?ne(t.actor):""}];function Ma(t){return{kind:t.kind,outcome:t.outcome,agent:t.agentClass??"",tool:t.tool??"",actor:t.actor?ne(t.actor):""}}function De({color:t}){return t?e.jsx("span",{className:"mr-1.5 inline-block h-2 w-2 shrink-0 rounded-full align-middle",style:{backgroundColor:t}}):null}function Pa({colorMaps:t}){return e.jsx("div",{role:"group","aria-label":"Color legends",className:"grid gap-2 rounded-md border border-border bg-card p-2 text-xs sm:grid-cols-2 lg:grid-cols-3",children:Is.map(s=>{const n=[...t[s.key].entries()];return e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"mb-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground",children:s.label}),n.length===0?e.jsx("span",{className:"text-muted-foreground",children:"—"}):e.jsx("div",{className:"flex flex-wrap gap-1",children:n.map(([r,a])=>e.jsxs("span",{className:"inline-flex max-w-full items-center gap-1 rounded border border-border bg-background px-1.5 py-0.5 font-mono",children:[e.jsx("span",{className:"h-2 w-2 shrink-0 rounded-full",style:{backgroundColor:a}}),e.jsx("span",{className:"truncate",children:r})]},r))})]},s.key)})})}function Ka({events:t,onRow:s,colorMaps:n}){const r=(a,o)=>n==null?void 0:n[a].get(o);return e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"When"}),e.jsx(v,{children:"Kind"}),e.jsx(v,{children:"Outcome"}),e.jsx(v,{children:"Agent"}),e.jsx(v,{children:"Tool"}),e.jsx(v,{children:"Actor"}),e.jsx(v,{children:"Session"}),e.jsx(v,{children:"Event"})]})}),e.jsx(Y,{children:t.map(a=>{const o=Ma(a);return e.jsxs(M,{className:`cursor-pointer${a.outcome==="denied"||a.outcome==="read_denied"?" bg-destructive/5":""}`,onClick:()=>s(a),children:[e.jsx(b,{className:"whitespace-nowrap font-mono text-xs",children:new Date(a.time).toLocaleString()}),e.jsxs(b,{className:"whitespace-nowrap",children:[e.jsx(De,{color:r("kind",o.kind)}),e.jsx(W,{variant:"outline",children:a.kind})]}),e.jsxs(b,{className:"whitespace-nowrap",children:[e.jsx(De,{color:r("outcome",o.outcome)}),e.jsx(W,{variant:Fa(a.outcome),children:a.outcome})]}),e.jsxs(b,{className:"font-mono text-xs",children:[e.jsx(De,{color:r("agent",o.agent)}),a.agentClass?e.jsx(z,{entity:"agent",id:`${a.sessionNamespace}/${a.agentClass}`,className:"text-link hover:underline",onClick:Qt,children:a.agentClass}):"—"]}),e.jsxs(b,{className:"font-mono text-xs",children:[e.jsx(De,{color:r("tool",o.tool)}),a.tool||"—"]}),e.jsxs(b,{className:"font-mono text-xs",title:a.actor||void 0,children:[e.jsx(De,{color:r("actor",o.actor)}),o.actor||"—"]}),e.jsx(b,{className:"font-mono text-xs",children:e.jsxs(z,{entity:"session",id:`${a.sessionNamespace}/${a.sessionName}`,className:"text-link hover:underline",onClick:Qt,children:[a.sessionNamespace,"/",a.sessionName]})}),e.jsx(b,{className:"max-w-md truncate text-xs",children:a.summary})]},a.entryId)})})]})}function _a({event:t,onClose:s}){return e.jsx(an,{open:!0,onOpenChange:n=>!n&&s(),children:e.jsxs(on,{side:"right",className:"w-full overflow-y-auto sm:max-w-xl",children:[e.jsx(ln,{children:e.jsx(cn,{className:"text-sm",children:t.summary})}),e.jsxs("dl",{className:"mt-4 space-y-1 font-mono text-xs",children:[e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"when:"})," ",e.jsx("dd",{className:"inline",children:t.time})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"session:"})," ",e.jsxs("dd",{className:"inline",children:[t.sessionNamespace,"/",t.sessionName]})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"agent:"})," ",e.jsx("dd",{className:"inline",children:t.agentClass})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"actor:"})," ",e.jsx("dd",{className:"inline",children:t.actor})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"tool:"})," ",e.jsx("dd",{className:"inline",children:t.tool})]}),e.jsxs("div",{children:[e.jsx("dt",{className:"inline text-muted-foreground",children:"entry:"})," ",e.jsx("dd",{className:"inline",children:t.entryId})]})]}),e.jsx("h3",{className:"mt-4 text-xs font-semibold uppercase text-muted-foreground",children:"Raw"}),e.jsx("pre",{className:"mt-1 overflow-x-auto rounded bg-card p-3 font-mono text-xs",children:JSON.stringify(t.raw??{},null,2)})]})})}const Xt=50,Zt=[{facet:"kind",field:"kinds",label:"Kind"},{facet:"outcome",field:"outcome",label:"Outcome"},{facet:"agentClass",field:"agentClass",label:"Agent"},{facet:"tool",field:"tool",label:"Tool"},{facet:"actor",field:"actor",label:"Actor"}];function Ua(t,s){const n=s.trim().toLowerCase();return n?[t.kind,t.agentClass,t.sessionNamespace,t.sessionName,`${t.sessionNamespace}/${t.sessionName}`,t.outcome,t.tool,t.actor,t.actor?ne(t.actor):"",t.summary].filter(Boolean).join(" ").toLowerCase().includes(n):!0}function Va({apiBase:t,initialFilter:s}){const[n,r]=d.useState(s),[a,o]=d.useState(0),[i,l]=d.useState(null),[c,u]=d.useState(null),[h,N]=d.useState(null),[p,y]=d.useState(null),[x,j]=d.useState(""),[$,C]=d.useState(!0);d.useEffect(()=>{let w=!0;const g={...n,limit:Xt,offset:a*Xt};return Promise.all([He(`${t}/audit/query`,g),He(`${t}/audit/facets`,n)]).then(([T,P])=>{w&&(l(T),u(P),y(null))}).catch(T=>{w&&y(T.message)}),()=>{w=!1}},[t,n,a]);const E=(w,g)=>{o(0),r(T=>({...T,[w]:w==="kinds"?[g]:g}))},m=w=>{o(0),r(g=>{const T={...g};return delete T[w],T})},k=Zt.filter(({field:w})=>n[w]).map(({field:w,label:g})=>({field:w,label:g,value:w==="kinds"?(n.kinds??[]).join(","):String(n[w])})),R=(i==null?void 0:i.events)??[],O=d.useMemo(()=>R.filter(w=>Ua(w,x)),[R,x]),A=d.useMemo(()=>{const w={};for(const g of Is)w[g.key]=As(O.map(g.value));return w},[O]);return e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row lg:gap-6",children:[e.jsx("aside",{className:"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:block lg:w-48 lg:shrink-0 lg:space-y-4",children:Zt.map(({facet:w,field:g,label:T})=>e.jsxs("div",{children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:T}),e.jsx("ul",{className:"mt-1 space-y-0.5 text-xs",children:Object.entries((c==null?void 0:c.counts[w])??{}).sort(([,P],[,V])=>V-P).slice(0,8).map(([P,V])=>e.jsx("li",{children:e.jsxs("button",{className:"hover:text-primary",onClick:()=>E(g,P),children:[P," (",V,")"]})},P))})]},w))}),e.jsxs("div",{className:"min-w-0 flex-1 space-y-3",children:[p&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Audit query failed: ",p]})}),(i==null?void 0:i.truncated)&&e.jsx(I,{children:e.jsx(F,{children:"Results truncated — narrow the time range or filters for complete counts."})}),e.jsx("div",{className:"flex flex-wrap gap-2",children:k.map(w=>e.jsxs(W,{variant:"secondary",className:"cursor-pointer",onClick:()=>m(w.field),children:[w.label.toLowerCase(),": ",w.value," ✕"]},w.field))}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("div",{className:"relative min-w-0 flex-1",children:e.jsx("input",{type:"search",value:x,onChange:w=>j(w.target.value),placeholder:"Filter loaded events…","aria-label":"Filter loaded events",className:"w-full rounded-md border border-border bg-background px-2 py-1 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"})}),x&&e.jsx(U,{variant:"ghost",size:"sm",onClick:()=>j(""),children:"Clear"}),e.jsx(U,{variant:"outline",size:"sm",onClick:()=>C(w=>!w),children:$?"Hide legend":"Show legend"}),e.jsxs("span",{className:"whitespace-nowrap text-xs text-muted-foreground",children:[O.length,x?` of ${R.length}`:""," shown"]})]}),$&&e.jsx(Pa,{colorMaps:A}),e.jsx(Ka,{events:O,onRow:N,colorMaps:A}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(U,{variant:"outline",size:"sm",disabled:a===0,onClick:()=>o(w=>w-1),children:"Prev"}),e.jsx(U,{variant:"outline",size:"sm",disabled:!(i!=null&&i.hasMore),onClick:()=>o(w=>w+1),children:"Next"}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["page ",a+1]})]})]}),h&&e.jsx(_a,{event:h,onClose:()=>N(null)})]})}const Wa={agents:"agent",sessions:"session",tools:"tool",users:"user"};function za(t,s){switch(t){case"agents":return{agentClass:s};case"sessions":{const n=s.indexOf("/");return n<0?{sessionName:s}:{sessionNamespace:s.slice(0,n),sessionName:s.slice(n+1)}}case"tools":return{tool:s};case"users":return{actor:s}}}function Ha(t,s){return t==="users"?ne(s):s}function qa({status:t}){return t?e.jsx("span",{className:`inline-flex rounded-full border px-2 py-0.5 text-[10px] font-medium ${lt(t)}`,children:t}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}function Ga({apiBase:t,axis:s,onDrill:n}){const[r,a]=d.useState([]),[o,i]=d.useState(null);d.useEffect(()=>{let u=!0;return _(`${t}/audit/entities/${s}`).then(h=>{u&&(a(h),i(null))}).catch(h=>{u&&i(h.message)}),()=>{u=!1}},[t,s]);const l=s==="sessions",c=Wa[s];return e.jsxs("div",{className:"space-y-3",children:[o&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load rollups: ",o]})}),e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{className:"capitalize",children:s}),l&&e.jsx(v,{children:"Status"}),l&&e.jsx(v,{className:"text-right",children:"Est. tokens"}),l&&e.jsx(v,{children:"Started"}),l&&e.jsx(v,{children:"Started by"}),e.jsx(v,{className:"text-right",children:"Events"}),e.jsx(v,{className:"text-right",children:"Denied"}),e.jsx(v,{className:"text-right",children:"Logs"})]})}),e.jsx(Y,{children:r.map(u=>e.jsxs(M,{children:[e.jsx(b,{className:"font-mono text-xs",children:e.jsx(z,{entity:c,id:u.key,className:"text-link hover:underline",children:l?e.jsx(Z,{id:u.key}):Ha(s,u.key)})}),l&&e.jsx(b,{children:e.jsx(qa,{status:u.status})}),l&&e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",title:u.estimatedCostUSD?`~$${u.estimatedCostUSD.toFixed(2)} (est.)`:void 0,children:u.inputTokens||u.outputTokens?`${ge(u.inputTokens??0)} in · ${ge(u.outputTokens??0)} out`:"—"}),l&&e.jsx(b,{className:"whitespace-nowrap text-xs text-muted-foreground",children:Se(u.startedAt)}),l&&e.jsx(b,{className:"text-xs",children:u.startedBy?e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx(At,{kind:u.startedBy}),e.jsx(z,{entity:"user",id:u.startedBy,className:"font-mono text-link hover:underline",children:ne(u.startedBy)})]}):e.jsx("span",{className:"font-mono text-muted-foreground",children:"—"})}),e.jsx(b,{className:"text-right font-mono text-xs",children:u.events}),e.jsx(b,{className:"text-right font-mono text-xs text-destructive",children:u.denied||""}),e.jsx(b,{className:"text-right",children:e.jsx("button",{type:"button",className:"text-xs text-muted-foreground hover:text-link hover:underline",onClick:()=>n(za(s,u.key)),children:"filter logs"})})]},u.key))})]}),e.jsxs("p",{className:"text-[11px] text-muted-foreground",children:["Row → ",c," detail · “filter logs” → the audit log scoped to that ",c]})]})}const vt=14,Bt=8,mt=4;function Fs(t){return`${t.namespace}/${t.artifactId||t.name}`}function Oe(t){return`${t.namespace}/${t.name}`}function Ja(t){const s=new Map;for(const n of t){const r=Fs(n),a=s.get(r);if(a===void 0){s.set(r,n);continue}const o=new Date(n.created).getTime(),i=new Date(a.created).getTime();(o>i||o===i&&Oe(n){const x=Fs(p);a.push(x),s.has(x)||(s.set(x,y),r.set(x,Ls(x)??"#888")),n.set(x,y)});const o=[...s.keys()],i=[],l=new Map;for(const p of o){const y=s.get(p);let x=i.findIndex(j=>jnew Array(t.length).fill(void 0));for(const p of o){const y=l.get(p);for(let x=s.get(p);x<=n.get(p);x++)u[y][x]===void 0&&(u[y][x]=p)}const h=Ja(t),N=t.map((p,y)=>{const x=a[y],j=l.get(x),$=[];for(let C=0;Cs.get(E),R=E!==void 0&&y{const r=es(n.lane);return e.jsxs("g",{children:[n.up&&e.jsx("line",{x1:r,y1:0,x2:r,y2:5,stroke:n.color,strokeWidth:1.5,strokeOpacity:.7}),n.down&&e.jsx("line",{x1:r,y1:5,x2:r,y2:10,stroke:n.color,strokeWidth:1.5,strokeOpacity:.7})]},n.lane)})}),e.jsx("div",{className:"absolute inset-0 flex items-center",children:t.segments.filter(n=>n.dot).map(n=>e.jsx("span",{className:"absolute rounded-full border",style:{left:es(n.lane)-mt,width:mt*2,height:mt*2,backgroundColor:n.color,borderColor:"hsl(var(--card))",opacity:t.isLatest?1:.55},"data-lane":n.lane,"data-testid":"track-dot"},n.lane))})]})}const Xa={Ready:"border-success/40 bg-success/10 text-success",Failed:"border-destructive/40 bg-destructive/10 text-destructive",Rendering:"border-primary/40 bg-primary/10 text-primary",Pending:"border-border bg-muted text-muted-foreground"};function it({phase:t}){const s=Xa[t]??"border-border bg-background text-muted-foreground";return e.jsx("span",{className:`inline-flex rounded-full border px-2 py-0.5 text-[10px] font-medium ${s}`,children:t||"—"})}function Et(t){if(!t)return"—";const s=["B","KB","MB","GB"];let n=0,r=t;for(;r>=1024&&n{let u=!0;return fr(t).then(h=>{u&&(r(h),o(null),l(!0))}).catch(h=>{u&&(o(h.message),l(!0))}),()=>{u=!1}},[t]);const c=d.useMemo(()=>Ya(n),[n]);return a?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load artifacts: ",a]})}):e.jsxs("div",{className:"space-y-3",children:[i&&n.length===0&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No artifacts rendered yet."}),n.length>0&&e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"When"}),e.jsx(v,{children:"Artifact"}),e.jsx(v,{children:"Session"}),e.jsx(v,{children:"Kind"}),e.jsx(v,{children:"MIME"}),e.jsx(v,{className:"text-right",children:"Size"}),e.jsx(v,{className:"text-right",children:"Revisions"}),e.jsx(v,{children:"Phase"}),e.jsx(v,{className:"text-right",children:"Tracks"})]})}),e.jsx(Y,{children:n.map((u,h)=>{const N=!!(u.session&&s),p=c.rows[h],y=p.isLatest;return e.jsxs(M,{children:[e.jsx(b,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:new Date(u.created).toLocaleString()}),e.jsx(b,{className:"font-mono text-xs",children:e.jsx(z,{entity:"artifact",id:`${u.namespace}/${u.name}`,className:y?"text-link hover:underline":"text-muted-foreground hover:underline",children:u.name})}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:u.session?N?e.jsx("button",{type:"button",className:"hover:text-link hover:underline",onClick:()=>s(Za(u.session)),children:e.jsx(Z,{id:u.session})}):e.jsx(Z,{id:u.session}):"—"}),e.jsx(b,{children:e.jsx(W,{variant:"outline",children:u.kind})}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:u.mime||"—"}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:Et(u.size)}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:u.revisions||""}),e.jsx(b,{children:e.jsx(it,{phase:u.phase})}),e.jsx(b,{className:"p-0 pl-2 align-middle",children:e.jsx(Qa,{row:p,width:c.width})})]},p.uid)})})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"ArtifactRender — versioned, sanitized agent outputs · click the name → artifact detail · the session → its Logs · Tracks = git-graph of revisions per logical artifact · oap artifact"})]})}const eo=[{id:"overview",label:"Overview"},{id:"revisions",label:"Revisions"}];function to(t){const s=t.indexOf("/");return s<0?{ns:"",name:t}:{ns:t.slice(0,s),name:t.slice(s+1)}}function so(t){try{return new URL(t,"http://placeholder.invalid").searchParams.has("artifactId")}catch{return!1}}function we({label:t,children:s}){return e.jsxs("div",{className:"flex flex-col gap-0.5",children:[e.jsx("dt",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("dd",{className:"font-mono text-xs text-foreground",children:s})]})}function no({apiBase:t,id:s,tab:n}){const{ns:r,name:a}=to(s),o=n??"overview",[i,l]=d.useState(null),[c,u]=d.useState(null),[h,N]=d.useState(!1);d.useEffect(()=>{let y=!0;return l(null),u(null),N(!1),pr(t,r,a).then(x=>{y&&l(x)}).catch(x=>{y&&(x instanceof X&&x.status===404?N(!0):u(x.message))}),()=>{y=!1}},[t,r,a]);const p=y=>te({type:"detail",entity:"artifact",id:s,tab:y});return e.jsx(at,{title:e.jsx("span",{className:"font-mono",children:e.jsx(Z,{ns:r,name:a})}),backRoute:{type:"view",view:"artifacts"},tabs:eo,activeTab:o,onTab:p,headerRight:i?e.jsx(it,{phase:i.phase}):void 0,children:c?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load artifact: ",c]})}):h?e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Artifact ",s," not found — it may have been garbage-collected."]}):i?o==="revisions"?e.jsx(ao,{detail:i}):e.jsx(ro,{detail:i}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"})})}function ro({detail:t}){const s=so(t.viewPath);return e.jsxs("div",{className:"flex flex-col gap-5",children:[e.jsxs("dl",{className:"grid grid-cols-2 gap-x-6 gap-y-3 sm:grid-cols-3",children:[e.jsx(we,{label:"Kind",children:t.kind||"—"}),e.jsx(we,{label:"Phase",children:e.jsx(it,{phase:t.phase})}),e.jsx(we,{label:"MIME",children:t.mime||"—"}),e.jsx(we,{label:"Size",children:Et(t.size)}),e.jsx(we,{label:"Created",children:new Date(t.created).toLocaleString()}),e.jsx(we,{label:"Session",children:t.session?e.jsx(z,{entity:"session",id:t.session,className:"text-link hover:underline",children:e.jsx(Z,{id:t.session})}):"—"})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-muted-foreground",children:"Viewer"}),s?e.jsx("a",{href:t.viewPath,target:"_blank",rel:"noopener noreferrer",className:"w-fit text-sm text-link hover:underline",children:"Open viewer"}):e.jsx("span",{className:"text-[11px] text-muted-foreground",children:"No viewer available for this artifact."})]}),e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-wide text-muted-foreground",children:"Download"}),t.outputRef?e.jsx("code",{className:"w-fit break-all rounded border border-border bg-background px-2 py-1 font-mono text-xs text-foreground",children:t.outputRef}):e.jsx("span",{className:"text-sm text-muted-foreground",children:"No content ref yet — the render has not reached Ready."})]})]})}function ao({detail:t}){return t.revisions.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No revisions."}):e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Name"}),e.jsx(v,{children:"Phase"}),e.jsx(v,{children:"MIME"}),e.jsx(v,{className:"text-right",children:"Size"}),e.jsx(v,{children:"Created"})]})}),e.jsx(Y,{children:t.revisions.map(s=>e.jsxs(M,{className:s.name===t.name?"bg-muted/40":void 0,children:[e.jsx(b,{className:"font-mono text-xs",children:e.jsx(z,{entity:"artifact",id:`${t.namespace}/${s.name}`,className:"text-link hover:underline",children:s.name})}),e.jsx(b,{children:e.jsx(it,{phase:s.phase})}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:s.mime||"—"}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:Et(s.size)}),e.jsx(b,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:new Date(s.created).toLocaleString()})]},s.name))})]})}const Ms=t=>t==null||Number.isNaN(t)?"NaN":`$${t.toFixed(2)}`,ue=[{id:"byModel",label:"By model",keyLabel:"Model"},{id:"byAgentClass",label:"By agent",keyLabel:"Agent class",keyEntity:"agent"},{id:"bySession",label:"By session",keyLabel:"Session",keyEntity:"session"},{id:"byUser",label:"By user",keyLabel:"User",decode:!0}];function oo({color:t}){return t?e.jsx("span",{"aria-hidden":"true",className:"inline-block h-2.5 w-2.5 shrink-0 rounded-sm",style:{backgroundColor:t}}):null}function lo({rows:t,keyLabel:s,highlightKey:n,keyEntity:r,decode:a,isModel:o}){if(t.length===0)return e.jsx("p",{className:"px-1 py-6 text-center text-xs text-muted-foreground",children:"No usage recorded."});const i=As(t.map(l=>l.key));return e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:s}),e.jsx(v,{className:"text-right",children:"Input tokens"}),e.jsx(v,{className:"text-right",children:"Output tokens"}),e.jsx(v,{className:"text-right",children:"Est. cost"})]})}),e.jsx(Y,{children:t.map(l=>{const c=n!=null&&l.key===n,u=r==="session"?e.jsx(Z,{id:l.key}):a?ne(l.key):l.key;return e.jsxs(M,{className:c?"bg-accent/60":void 0,children:[e.jsx(b,{className:"font-mono text-xs",children:e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx(oo,{color:i.get(l.key)}),l.key&&r?e.jsx(z,{entity:r,id:l.key,className:"text-link hover:underline",children:u}):o&&l.key?e.jsx(St,{model:l.key}):e.jsx("span",{children:l.key?u:"(unknown)"}),c&&e.jsx(W,{variant:"secondary",className:"align-middle text-[10px]",children:"from Overview"})]})}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:xe(l.inputTokens)}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:xe(l.outputTokens)}),e.jsx(b,{className:"text-right font-mono text-xs",children:Ms(l.estimatedCostUSD)})]},l.key)})})]})}function io({apiBase:t}){const s=ws(),n=s.type==="view"&&s.query?s.query.get("model")??void 0:void 0,[r,a]=d.useState(null),[o,i]=d.useState(null);d.useEffect(()=>{let p=!0;return dr(t).then(y=>{p&&(a(y),i(null))}).catch(y=>{p&&i(y.message)}),()=>{p=!1}},[t]);const[l,c]=d.useState("byModel"),u=d.useRef(null),h=p=>{var C,E;if(!["ArrowRight","ArrowLeft","Home","End"].includes(p.key))return;p.preventDefault();const x=ue.findIndex(m=>m.id===l),j=p.key==="ArrowRight"?(x+1)%ue.length:p.key==="ArrowLeft"?(x-1+ue.length)%ue.length:p.key==="Home"?0:ue.length-1,$=ue[j].id;c($),(E=(C=u.current)==null?void 0:C.querySelector(`#budget-tab-${$}`))==null||E.focus()};if(o)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load budget: ",o]})});if(!r)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading budget…"});const N=r.byModel.reduce((p,y)=>p+(y.estimatedCostUSD??NaN),0);return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2 rounded-xl border bg-card p-4 shadow",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:"Total estimated spend"}),e.jsx("div",{className:"mt-0.5 text-2xl font-semibold tracking-tight",children:Ms(N)})]}),e.jsx("p",{className:"max-w-md text-[11px] text-muted-foreground",children:"Estimated from list prices — not a metered bill. Same live-session scope as the Overview budget panel."})]}),e.jsx("div",{ref:u,role:"tablist","aria-label":"Budget breakdown axes",onKeyDown:h,className:"inline-flex h-9 items-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground",children:ue.map(p=>{const y=l===p.id;return e.jsx("button",{id:`budget-tab-${p.id}`,type:"button",role:"tab","aria-selected":y,"aria-controls":`budget-panel-${p.id}`,tabIndex:y?0:-1,onClick:()=>c(p.id),className:["inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",y?"bg-background text-foreground shadow":"hover:text-foreground"].join(" "),children:p.label},p.id)})}),ue.filter(p=>p.id===l).map(p=>e.jsx("div",{id:`budget-panel-${p.id}`,role:"tabpanel","aria-labelledby":`budget-tab-${p.id}`,tabIndex:0,className:"rounded-xl border bg-card p-2 shadow",children:e.jsx(lo,{rows:r[p.id],keyLabel:p.keyLabel,highlightKey:p.id==="byModel"?n:void 0,keyEntity:p.keyEntity,decode:p.decode,isModel:p.id==="byModel"})},p.id))]})}function co({apiBase:t}){const[s,n]=d.useState(""),[r,a]=d.useState(""),[o,i]=d.useState(null),[l,c]=d.useState(null),[u,h]=d.useState(!1);d.useEffect(()=>{let j=!0;return vr(t,r?{q:r}:{}).then($=>{j&&(i($),c(null),h(!0))}).catch($=>{j&&(c($.message),h(!0))}),()=>{j=!1}},[t,r]);const N=j=>{j.preventDefault(),a(s.trim())},p=(o==null?void 0:o.rollups)??[],y=(o==null?void 0:o.entries)??[],x=r!=="";return e.jsxs("div",{className:"space-y-3",children:[e.jsx("form",{onSubmit:N,className:"flex items-center gap-2",children:e.jsxs("label",{className:"flex h-9 flex-1 items-center gap-2 rounded-md border border-input bg-background px-3 text-xs",children:[e.jsx(fs,{className:"h-3.5 w-3.5 text-muted-foreground"}),e.jsx("input",{type:"search",value:s,onChange:j=>n(j.target.value),placeholder:"search memory — text, tags, vectors…","aria-label":"Search memory",className:"flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground"})]})}),l&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load memory: ",l]})}),(o==null?void 0:o.truncated)&&e.jsx(I,{children:e.jsx(F,{children:"Results truncated — narrow the scan for complete rollup counts."})}),x?y.length===0?e.jsxs("p",{className:"text-sm text-muted-foreground",children:["No matches for “",r,"”."]}):e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Kind"}),e.jsx(v,{children:"Scope"}),e.jsx(v,{children:"Entry"}),e.jsx(v,{children:"When"}),e.jsx(v,{className:"text-right",children:"Score"})]})}),e.jsx(Y,{children:y.map(j=>e.jsxs(M,{children:[e.jsx(b,{children:e.jsx(W,{variant:"outline",children:j.kind})}),e.jsx(b,{className:"font-mono text-xs text-muted-foreground",children:j.scope}),e.jsx(b,{className:"font-mono text-xs text-foreground",children:j.id}),e.jsx(b,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:j.created?new Date(j.created).toLocaleString():"—"}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:j.score!=null?j.score.toFixed(2):""})]},`${j.scope}/${j.kind}/${j.id}`))})]}):p.length===0?u&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No memory entries yet."}):e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Kind"}),e.jsx(v,{className:"text-right",children:"Entries"}),e.jsx(v,{className:"text-right",children:"Scopes"}),e.jsx(v,{children:"Last write"}),e.jsx(v,{})]})}),e.jsx(Y,{children:p.map(j=>e.jsxs(M,{children:[e.jsx(b,{className:"font-mono text-xs text-foreground",children:j.kind}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:j.entries}),e.jsx(b,{className:"text-right font-mono text-xs text-muted-foreground",children:j.scopes}),e.jsx(b,{className:"whitespace-nowrap font-mono text-xs text-muted-foreground",children:j.lastWrite?new Date(j.lastWrite).toLocaleString():"—"}),e.jsx(b,{children:j.appendOnly&&e.jsx(W,{children:"append-only"})})]},j.kind))})]}),e.jsxs("p",{className:"text-[11px] text-muted-foreground",children:["Append-only kinds are tamper-evident (Ed25519 hash chains) — verify with"," ",e.jsx("span",{className:"font-mono",children:"oap audit verify"})," · oap memory list / search / query"]})]})}function yt({uuid:t,label:s,onOpen:n}){return t?e.jsxs("button",{type:"button",onClick:()=>n(t),className:"inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1 font-mono text-xs hover:bg-accent hover:text-accent-foreground",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-state"}),s||t]}):e.jsx("span",{className:"font-mono text-xs text-muted-foreground",children:s||"—"})}function ts({facts:t,onOpen:s}){return t.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No facts."}):e.jsx("ul",{className:"space-y-2",children:t.map(n=>e.jsxs("li",{className:"rounded-md border bg-background p-3",children:[e.jsx("div",{className:"text-sm text-foreground",children:n.fact||n.name}),e.jsxs("div",{className:"mt-1.5 flex flex-wrap items-center gap-1.5",children:[e.jsx(yt,{uuid:n.fromEntity,label:n.fromEntity,onOpen:s}),e.jsx("span",{className:"text-muted-foreground",children:"→"}),e.jsx(yt,{uuid:n.toEntity,label:n.toEntity,onOpen:s}),n.validAt&&e.jsx("span",{className:"ml-1 font-mono text-[11px] text-muted-foreground",children:new Date(n.validAt).toLocaleDateString()})]})]},n.uuid))})}function uo({apiBase:t}){var A,w;const[s,n]=d.useState(null),[r,a]=d.useState(""),[o,i]=d.useState(null),[l,c]=d.useState([]),[u,h]=d.useState(""),[N,p]=d.useState(""),[y,x]=d.useState(null),[j,$]=d.useState(null),[C,E]=d.useState(null),m=d.useRef(null);d.useEffect(()=>{let g=!0;return Cr(t).then(T=>{g&&(n(T.available),a(T.note??""),c(T.communities??[]),i(T.error??null))}).catch(T=>{g&&(n(!0),i(T.message))}),()=>{g=!1}},[t]),d.useEffect(()=>{if(!N){x(null),$(null);return}let g=!0;return Nr(t,N).then(T=>{g&&(x(T.facts??[]),$(T.error??null))}).catch(T=>{g&&(x([]),$(T.message))}),()=>{g=!1}},[t,N]);const k=d.useCallback(g=>{g&&(m.current=g,E({uuid:g,loading:!0,entity:null,facts:[],related:[]}),Promise.all([kr(t,g),wr(t,g),Sr(t,g)]).then(([T,P,V])=>{m.current===g&&E({uuid:g,loading:!1,entity:T.entity??null,facts:P.facts??[],related:V.entities??[],error:T.error||P.error||V.error||void 0})}).catch(T=>{m.current===g&&E({uuid:g,loading:!1,entity:null,facts:[],related:[],error:T.message})}))},[t]),R=g=>{g.preventDefault(),p(u.trim())};if(s===!1)return e.jsxs("div",{className:"rounded-xl border border-dashed bg-card p-6 shadow",children:[e.jsxs("div",{className:"flex items-center gap-2 text-sm font-semibold text-foreground",children:[e.jsx(hs,{className:"h-4 w-4 text-muted-foreground"})," Knowledge graph not configured"]}),e.jsxs("p",{className:"mt-2 text-sm text-muted-foreground",children:[r||"The knowledge graph is not available."," Set"," ",e.jsx("span",{className:"font-mono",children:"--graphiti-endpoint"})," on the operator to enable graph-aware entity, fact, and community search."]}),e.jsx("p",{className:"mt-3 text-[11px] text-muted-foreground",children:"oap kg search / entity / facts / related / communities"})]});if(s===null&&!o)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading knowledge graph…"});const O=N!=="";return e.jsxs("div",{className:"space-y-4",children:[o&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load knowledge graph: ",o]})}),e.jsx("form",{onSubmit:R,className:"flex items-center gap-2",children:e.jsxs("label",{className:"flex h-9 flex-1 items-center gap-2 rounded-md border border-input bg-background px-3 text-xs",children:[e.jsx(fs,{className:"h-3.5 w-3.5 text-muted-foreground"}),e.jsx("input",{type:"search",value:u,onChange:g=>h(g.target.value),placeholder:"search facts — graph-aware…","aria-label":"Search facts",className:"flex-1 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground"})]})}),j&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Fact search degraded: ",j]})}),O&&e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsxs("div",{className:"text-sm font-semibold text-foreground",children:["Facts matching “",N,"”"]}),e.jsx("div",{className:"mt-3",children:y===null?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Searching…"}):e.jsx(ts,{facts:y,onOpen:k})})]}),C&&e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-semibold text-foreground",children:((A=C.entity)==null?void 0:A.name)||C.uuid}),((w=C.entity)==null?void 0:w.summary)&&e.jsx("div",{className:"mt-0.5 text-xs text-muted-foreground",children:C.entity.summary})]}),e.jsx("button",{type:"button","aria-label":"Close entity",onClick:()=>E(null),className:"inline-flex h-6 w-6 items-center justify-center rounded border border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:e.jsx(qn,{className:"h-3 w-3"})})]}),C.error&&e.jsx(I,{variant:"destructive",className:"mt-3",children:e.jsxs(F,{children:["Entity lookup degraded: ",C.error]})}),C.loading?e.jsx("p",{className:"mt-3 text-sm text-muted-foreground",children:"Loading entity…"}):e.jsxs("div",{className:"mt-3 space-y-4",children:[e.jsxs("div",{children:[e.jsx("div",{className:"mb-2 text-xs font-semibold text-muted-foreground",children:"Facts"}),e.jsx(ts,{facts:C.facts,onOpen:k})]}),e.jsxs("div",{children:[e.jsx("div",{className:"mb-2 text-xs font-semibold text-muted-foreground",children:"Related entities"}),C.related.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No related entities."}):e.jsx("div",{className:"flex flex-wrap gap-2",children:C.related.map(g=>e.jsx(yt,{uuid:g.uuid,label:g.name||g.uuid,onOpen:k},g.uuid))})]})]})]}),e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsx("div",{className:"text-sm font-semibold text-foreground",children:"Communities"}),l.length===0?e.jsx("p",{className:"mt-2 text-sm text-muted-foreground",children:"No communities yet."}):e.jsx("ul",{className:"mt-3 space-y-2",children:l.map(g=>e.jsxs("li",{className:"rounded-md border bg-background p-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"font-mono text-xs text-foreground",children:g.name||g.uuid}),e.jsxs(W,{variant:"outline",children:[g.members.length," members"]})]}),g.summary&&e.jsx("div",{className:"mt-1 text-xs text-muted-foreground",children:g.summary})]},g.uuid))}),e.jsx("p",{className:"mt-3 text-[11px] text-muted-foreground",children:"Graphiti knowledge graph — entities, facts, communities · oap kg search / entity / facts / related"})]})]})}const ss=t=>new URLSearchParams({model:t}),xo=t=>t==null||Number.isNaN(t)?"NaN":`$${t.toFixed(2)}`,Ve=["bg-chart-1","bg-chart-2","bg-chart-3","bg-chart-4","bg-chart-5"],mo={Healthy:"bg-success",Degraded:"bg-warning",Down:"bg-destructive",NotConfigured:"bg-muted-foreground"},ho={Healthy:"text-success",Degraded:"text-warning",Down:"text-destructive",NotConfigured:"text-muted-foreground"};function fo({values:t,width:s=600,height:n=150}){const r=Math.max(1,...t)*1.12,a=6,o=t.length,i=h=>a+(o<=1?0:h*(s-2*a)/(o-1)),l=h=>n-a-h/r*(n-2*a),c=t.map((h,N)=>`${N?"L":"M"} ${i(N).toFixed(1)} ${l(h).toFixed(1)}`).join(" "),u=`${c} L ${i(o-1).toFixed(1)} ${(n-a).toFixed(1)} L ${i(0).toFixed(1)} ${(n-a).toFixed(1)} Z`;return e.jsxs("svg",{viewBox:`0 0 ${s} ${n}`,className:"h-40 w-full",preserveAspectRatio:"none",role:"img","aria-label":"Tokens over time",children:[e.jsx("defs",{children:e.jsxs("linearGradient",{id:"ap-overview-area",x1:"0",x2:"0",y1:"0",y2:"1",children:[e.jsx("stop",{offset:"0%",stopColor:"hsl(var(--chart-1))",stopOpacity:"0.35"}),e.jsx("stop",{offset:"100%",stopColor:"hsl(var(--chart-1))",stopOpacity:"0"})]})}),e.jsx("path",{d:u,fill:"url(#ap-overview-area)"}),e.jsx("path",{d:c,fill:"none",stroke:"hsl(var(--chart-1))",strokeWidth:"1.5",vectorEffect:"non-scaling-stroke"})]})}function po({label:t,value:s,sub:n}){return e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:t}),e.jsx("div",{className:"mt-1 text-2xl font-semibold tracking-tight",children:s}),n&&e.jsx("div",{className:"mt-0.5 font-mono text-[11px] text-muted-foreground",children:n})]})}function go({apiBase:t}){const[s,n]=d.useState(null),[r,a]=d.useState(null),[o,i]=d.useState(null),[l,c]=d.useState(null);if(d.useEffect(()=>{let m=!0;return lr(t).then(k=>{m&&(n(k),i(null))}).catch(k=>{m&&i(k.message)}),ir(t).then(k=>{m&&(a(k),c(null))}).catch(k=>{m&&c(k.message)}),()=>{m=!1}},[t]),o)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load overview: ",o]})});if(!s)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading overview…"});const u=s.kpis,h=s.budget,N=s.series24h.map(m=>m.inputTokens+m.outputTokens),p=N.reduce((m,k)=>m+k,0),y=h.tokenCeiling&&h.tokenCeiling>0?h.tokensSpent/h.tokenCeiling*100:null,x=y!=null?`${y.toFixed(0)}%`:xe(h.tokensSpent),j=[{label:"Active sessions",value:`${u.activeSessions}`},{label:"Tokens today",value:xe(u.tokensToday)},{label:"Tool calls (24h)",value:`${u.toolCalls24h}`},{label:"Approvals pending",value:`${u.approvalsPending}`},{label:"Denials (24h)",value:`${u.denials24h}`}],$=s.byModel.reduce((m,k)=>m+k.inputTokens+k.outputTokens,0)||1,C=Math.max(1,...s.byAgentClass.map(m=>m.inputTokens+m.outputTokens)),E=((r==null?void 0:r.components)??[]).filter(m=>m.status==="Degraded"||m.status==="Down").length;return e.jsxs("div",{className:"space-y-4",children:[e.jsx("div",{className:"grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-5",children:j.map(m=>e.jsx(po,{label:m.label,value:m.value},m.label))}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 lg:grid-cols-3",children:[e.jsxs(me,{className:"lg:col-span-2",children:[e.jsxs(he,{className:"flex-row items-center justify-between pb-2",children:[e.jsxs("div",{children:[e.jsx(fe,{className:"text-sm",children:"Tokens over time"}),e.jsx("div",{className:"text-[11px] text-muted-foreground",children:"last 24 hours · all models"})]}),e.jsxs("div",{className:"font-mono text-xs text-muted-foreground",children:[xe(p)," total"]})]}),e.jsx(pe,{children:e.jsx(fo,{values:N})})]}),e.jsx(Ht,{view:"budget",className:"block h-full rounded-xl",children:e.jsxs(me,{className:"h-full transition-colors hover:border-primary/50 hover:bg-accent/20",children:[e.jsxs(he,{className:"flex-row items-center justify-between pb-2",children:[e.jsx(fe,{className:"text-sm",children:"Budget"}),e.jsx("span",{className:"text-[11px] text-link",children:"View breakdown →"})]}),e.jsxs(pe,{className:"space-y-2",children:[e.jsxs("div",{className:"flex items-end gap-2",children:[e.jsx("div",{className:"text-3xl font-semibold tracking-tight",children:x}),e.jsx("div",{className:"mb-1 font-mono text-xs text-muted-foreground",children:y!=null?`${xe(h.tokensSpent)} tokens`:"tokens spent"})]}),e.jsxs("div",{className:"font-mono text-sm text-muted-foreground",children:["est. ",xo(h.estimatedCostUSD)]}),e.jsxs("div",{className:"text-[11px] text-muted-foreground",children:[h.estimated?"estimated · list-price":"metered"," · daily budget resets 00:00 UTC"]})]})]})})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 lg:grid-cols-2",children:[e.jsxs(me,{children:[e.jsx(he,{className:"pb-2",children:e.jsx(fe,{className:"text-sm",children:"Tokens by model"})}),e.jsxs(pe,{className:"space-y-3",children:[e.jsx("div",{"aria-hidden":"true",className:"flex h-3 w-full overflow-hidden rounded",children:s.byModel.map((m,k)=>e.jsx("div",{onClick:()=>te({type:"view",view:"budget",query:ss(m.model)}),title:`${m.model||"(unknown)"} — view budget`,className:`h-full cursor-pointer ${Ve[k%Ve.length]}`,style:{width:`${((m.inputTokens+m.outputTokens)/$*100).toFixed(1)}%`}},m.model))}),e.jsxs("div",{className:"space-y-2",children:[s.byModel.length===0&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"No live model usage."}),s.byModel.map((m,k)=>e.jsxs(Ht,{view:"budget",query:ss(m.model),className:"-mx-1 flex items-center justify-between rounded px-1 py-0.5 text-xs transition-colors hover:bg-accent/40",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx("span",{"aria-hidden":"true",className:`h-2.5 w-2.5 rounded-sm ${Ve[k%Ve.length]}`}),m.model?e.jsx(St,{model:m.model}):e.jsx("span",{className:"font-mono",children:"(unknown)"})]}),e.jsxs("span",{className:"font-mono text-muted-foreground",children:[xe(m.inputTokens+m.outputTokens)," · ",m.sessions," sess"]})]},m.model))]})]})]}),e.jsxs(me,{children:[e.jsx(he,{className:"pb-2",children:e.jsx(fe,{className:"text-sm",children:"Tokens by agent class"})}),e.jsxs(pe,{className:"space-y-2.5",children:[s.byAgentClass.length===0&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"No live agent-class usage."}),s.byAgentClass.map(m=>{const k=m.inputTokens+m.outputTokens,R=e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"w-28 shrink-0 truncate font-mono",children:m.class||"(unknown)"}),e.jsx("div",{className:"h-2.5 flex-1 overflow-hidden rounded bg-muted",children:e.jsx("div",{className:"h-full rounded bg-chart-1",style:{width:`${(k/C*100).toFixed(1)}%`}})}),e.jsx("span",{className:"w-12 shrink-0 text-right font-mono text-muted-foreground",children:xe(k)})]});return m.class?e.jsx(z,{entity:"agent",id:m.class,className:"-mx-1 flex items-center gap-3 rounded px-1 py-0.5 text-xs transition-colors hover:bg-accent/40",children:R},m.class):e.jsx("div",{className:"flex items-center gap-3 px-1 py-0.5 text-xs text-muted-foreground",children:R},"__unknown__")})]})]})]}),e.jsxs(me,{children:[e.jsxs(he,{className:"flex-row items-center justify-between pb-2",children:[e.jsx(fe,{className:"text-sm",children:"Cluster health"}),E>0?e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] text-warning",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-warning"}),E," component",E>1?"s":""," degraded"]}):r&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] text-success",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-success"}),"all healthy"]})]}),e.jsxs(pe,{className:"space-y-3",children:[l&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load cluster health: ",l]})}),r&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"grid grid-cols-2 gap-2 md:grid-cols-4",children:r.components.map(m=>e.jsxs("div",{className:"flex items-center justify-between rounded-md border bg-background px-3 py-2",children:[e.jsxs("span",{className:"flex items-center gap-2 font-mono text-xs",children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${mo[m.status]}`}),m.name]}),e.jsx("span",{className:`text-[10px] ${ho[m.status]}`,title:m.detail,children:m.status})]},m.name))}),e.jsxs("div",{className:"grid grid-cols-2 gap-2 md:grid-cols-4",children:[e.jsxs("div",{className:"rounded-md border bg-background px-3 py-2",children:[e.jsx("div",{className:"text-[10px] text-muted-foreground",children:"Pods"}),e.jsxs("div",{className:"mt-0.5 font-mono text-sm",children:[r.rollup.ready," / ",r.rollup.pods," ready"]})]}),e.jsxs("div",{className:"rounded-md border bg-background px-3 py-2",children:[e.jsx("div",{className:"text-[10px] text-muted-foreground",children:"CPU"}),e.jsx("div",{className:"mt-0.5 font-mono text-sm",children:r.rollup.cpu})]}),e.jsxs("div",{className:"rounded-md border bg-background px-3 py-2",children:[e.jsx("div",{className:"text-[10px] text-muted-foreground",children:"Memory"}),e.jsx("div",{className:"mt-0.5 font-mono text-sm",children:r.rollup.memory})]})]})]})]})]})]})}function Ps({text:t}){const[s,n]=d.useState(!1),r=()=>{var o;const a=(o=navigator.clipboard)==null?void 0:o.writeText(t);a&&typeof a.then=="function"&&a.then(()=>{n(!0),setTimeout(()=>n(!1),1200)}).catch(()=>{})};return e.jsx("button",{type:"button","aria-label":"Copy command",onClick:r,className:"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded border border-input bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:s?e.jsx(Gn,{className:"h-3 w-3 text-success"}):e.jsx(Jn,{className:"h-3 w-3"})})}function ct({text:t}){return e.jsxs("div",{className:"flex items-center gap-2 rounded-md border bg-background px-3 py-2",children:[e.jsx("code",{className:"flex-1 truncate font-mono text-xs text-foreground",title:t,children:t}),e.jsx(Ps,{text:t})]})}const ns="rounded-full border px-2 py-0.5 text-[10px] transition-colors",rs="border-border bg-background text-muted-foreground hover:bg-accent/50",jo="border-primary/40 bg-primary/10 text-primary";function as({statuses:t,value:s,onChange:n}){return e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("button",{type:"button","aria-pressed":s==="",onClick:()=>n(""),className:`${ns} ${s===""?jo:rs}`,children:"All"}),t.map(r=>{const a=s===r;return e.jsx("button",{type:"button","aria-pressed":a,onClick:()=>n(a?"":r),className:`${ns} ${a?Ds(Es(r)):rs}`,children:r},r)})]})}const bo=new Set(["Degraded","Failed","Down","Invalid"]);function vo(t){return bo.has(t)}function Ks({status:t,reason:s}){return e.jsx("span",{title:s||void 0,className:`inline-flex items-center rounded-full border px-2.5 py-0.5 text-[11px] ${lt(t)}`,children:t||"Unknown"})}function yo({status:t,reason:s}){return vo(t)?e.jsxs(I,{variant:"destructive",children:[e.jsx(dn,{children:t}),s&&e.jsx(F,{children:s})]}):null}function le({label:t,children:s}){return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("span",{className:"text-[10px] font-medium uppercase tracking-wide text-muted-foreground",children:t}),e.jsx("div",{className:"text-sm text-foreground",children:s})]})}function Nt({children:t}){return e.jsx("span",{className:"rounded-full border bg-background px-2 py-0.5 text-[10px] font-mono text-muted-foreground",children:t})}function ye(t,s){var n,r;return(r=(n=t.badges)==null?void 0:n.find(a=>a.key===s))==null?void 0:r.value}function dt(t){return t.namespace?`${t.namespace}/${t.name}`:t.name}function No({status:t,reason:s}){const n=e.jsx("span",{className:`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] ${lt(t)}`,children:t||"—"});return s?e.jsx(un,{delayDuration:0,children:e.jsxs(xn,{children:[e.jsx(mn,{asChild:!0,children:n}),e.jsx(hn,{className:"max-w-xs whitespace-normal break-words text-xs",children:s})]})}):n}function ko(t,s){if(s.render)return s.render(t);if(s.source==="scope")return e.jsx(Nt,{children:t.scope});if(s.source==="count"){const r=t.counts??[],a=s.key?r.find(o=>o.label===s.key):r[0];return a?e.jsx("span",{className:"font-mono text-xs text-muted-foreground",children:a.value}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}const n=(t.badges??[]).filter(r=>s.key?r.key===s.key:!0);return n.length===0?e.jsx("span",{className:"text-muted-foreground",children:"—"}):e.jsx("span",{className:"flex flex-wrap gap-1",children:n.map((r,a)=>s.colorize?e.jsx(ot,{kind:r.value},`${r.key}-${r.value}-${a}`):e.jsx(Nt,{children:r.value},`${r.key}-${r.value}-${a}`))})}function wo(t){const s=d.useState(""),n=Tt(t??"","");return t?n:s}function _s({rows:t,columns:s,entity:n,facet:r,statusCookieKey:a,loading:o,note:i,emptyText:l}){const[c,u]=d.useState(""),[h,N]=wo(a),[p,y]=d.useState(""),x=d.useMemo(()=>{const m=new Set;for(const k of t)k.status&&m.add(k.status);return Array.from(m).sort()},[t]),j=d.useMemo(()=>{if(!r)return[];const m=new Set;for(const k of t)for(const R of k.badges??[])R.key===r.key&&m.add(R.value);return Array.from(m).sort()},[t,r]),$=t.some(m=>m.manageCmd),C=c.trim().toLowerCase(),E=t.filter(m=>(h===""||m.status===h)&&(p===""||(m.badges??[]).some(k=>r&&k.key===r.key&&k.value===p))&&(C===""||m.name.toLowerCase().includes(C)||(m.namespace??"").toLowerCase().includes(C)));return o?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"}):t.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:l??"Nothing configured yet."}):e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",value:c,onChange:m=>u(m.target.value),placeholder:"Filter by name…","aria-label":"Filter by name",className:"h-8 w-56 rounded-md border border-input bg-background px-3 text-xs outline-none focus-visible:ring-1 focus-visible:ring-ring"}),x.length>1&&e.jsx(as,{statuses:x,value:h,onChange:N})]}),r&&j.length>1&&e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"text-[10px] uppercase tracking-wide text-muted-foreground",children:r.label}),e.jsx(as,{statuses:j,value:p,onChange:y})]}),e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Name"}),s.map(m=>e.jsx(v,{className:m.align==="right"?"text-right":"",children:m.header},m.header)),e.jsx(v,{children:"Status"}),$&&e.jsx(v,{children:"Manage"})]})}),e.jsxs(Y,{children:[E.map(m=>e.jsxs(M,{children:[e.jsxs(b,{children:[n?e.jsx(z,{entity:n,id:dt(m),className:"font-mono text-xs text-link hover:underline",children:m.name}):e.jsx("div",{className:"font-mono text-xs text-foreground",children:m.name}),m.namespace&&e.jsx("div",{className:"font-mono text-[10px] text-muted-foreground",children:m.namespace})]}),s.map(k=>e.jsx(b,{className:k.align==="right"?"text-right":"",children:ko(m,k)},k.header)),e.jsx(b,{children:e.jsx(No,{status:m.status,reason:m.statusReason})}),$&&e.jsx(b,{children:m.manageCmd?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("code",{className:"truncate font-mono text-[11px] text-muted-foreground",title:m.manageCmd,children:m.manageCmd}),e.jsx(Ps,{text:m.manageCmd})]}):e.jsx("span",{className:"text-muted-foreground",children:"—"})})]},`${m.namespace??""}/${m.name}`)),E.length===0&&e.jsx(M,{children:e.jsx(b,{colSpan:s.length+($?3:2),className:"text-center text-xs text-muted-foreground",children:"No matches."})})]})]}),i&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:i})]})}const ce={agents:[{header:"Identity",source:"badge",key:"identityMode"},{header:"Tools",source:"count",key:"tools",align:"right"},{header:"Skills",source:"count",key:"skills",align:"right"}],tools:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Capabilities",source:"count",align:"right"}],skills:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Delivery",source:"badge",key:"delivery"},{header:"Pin",source:"badge",key:"pin"}],sources:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Repo",source:"badge",key:"repo"},{header:"Ref",source:"badge",key:"ref"},{header:"Skills",source:"count",key:"discoveredSkills",align:"right"}],directory:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Credential",source:"badge",key:"credential"},{header:"Scopes",source:"count",key:"scopes",align:"right"},{header:"Written",source:"count",key:"written",align:"right"},{header:"Join misses",source:"count",key:"joinMisses",align:"right"},{header:"Errors",source:"count",key:"errors",align:"right"}],channels:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Role",source:"badge",key:"role"},{header:"Agent class",source:"badge",key:"agentClass"}],users:[{header:"Subject",source:"badge",key:"subject"},{header:"Credentials",source:"count",key:"availableCredentials",align:"right"},{header:"Resolved",source:"count",key:"resolved",align:"right"}],identities:[{header:"Credential kinds",source:"badge",key:"cred",colorize:!0},{header:"Credentials",source:"count",key:"credentials",align:"right"},{header:"Resolved",source:"count",key:"resolved",align:"right"}],providers:[{header:"Kind",source:"badge",key:"kind",colorize:!0},{header:"Issuer",source:"badge",key:"issuer"}]},Ie="w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50";function os(t){return t?`Dependency ${t}`:"Root agent"}function We(t){var n;const s=((n=t.agentPath)==null?void 0:n.split(" > ").map(r=>r.trim()).filter(Boolean))??[];return s.length===0?t.name:`${s.map(r=>`agents.${r}`).join(".")}.${t.name}`}function ae(t){return`${t.agentPath??""}\0${t.name}\0${t.setupToken??""}`}function ht(t,s){const n={};for(const r of s)t[r]!==void 0&&(n[r]=t[r]);return n}function ze(t){return t==null?"":typeof t=="string"?t:Array.isArray(t)?t.join(","):String(t)}function So(t,s){var r,a;const n=(r=t.enumLabels)==null?void 0:r[s];return n!==void 0&&n.trim()!==""?n:((a=t.enum)==null?void 0:a[s])??""}function ls({q:t,value:s,onChange:n,disabled:r=!1}){const a=t.required!==!1,o=`${t.prompt||t.name}${a?" *":""}`;return e.jsxs(le,{label:o,children:[t.type==="bool"?e.jsx("input",{type:"checkbox",checked:s==="true",onChange:i=>{r||n(i.target.checked?"true":"false")},disabled:r,"aria-label":o,className:"h-4 w-4 rounded border-border"}):t.type==="enum"?e.jsxs("select",{value:s,onChange:i=>{r||n(i.target.value)},disabled:r,"aria-label":o,className:Ie,children:[e.jsx("option",{value:"",disabled:!0,children:"Select…"}),(t.enum??[]).map((i,l)=>e.jsx("option",{value:i,children:So(t,l)},i))]}):e.jsx("input",{type:t.type==="secret"?"password":t.type==="int"?"number":"text",value:s,onChange:i=>{r||n(i.target.value)},disabled:r,"aria-label":o,placeholder:t.type==="resourceList"?"comma-separated":void 0,autoComplete:t.type==="secret"?"new-password":"off",className:Ie}),t.description&&e.jsx("p",{className:"text-[11px] text-muted-foreground",children:t.description})]})}function Co({apiBase:t,onInstalled:s}){const[n,r]=d.useState("file"),[a,o]=d.useState(null),[i,l]=d.useState(""),[c,u]=d.useState("default"),[h,N]=d.useState(""),[p,y]=d.useState({}),[x,j]=d.useState(null),[$,C]=d.useState([]),[E,m]=d.useState([]),[k,R]=d.useState([]),[O,A]=d.useState([]),[w,g]=d.useState([]),[T,P]=d.useState({}),[V,q]=d.useState({}),[ie,$e]=d.useState({}),[Rt,Ne]=d.useState(null),[Ot,oe]=d.useState(null),[Hs,Pe]=d.useState(!1),[It,Ft]=d.useState(!1),qs=n==="file"?a!==null:i.trim()!=="",Mt=()=>{R([]),A([])},Gs=()=>{g([]),P({}),q({}),$e({}),Ne(null)},ke=()=>{Mt(),Gs()},Js=(f,L)=>{y(D=>({...D,[f]:L})),Mt();const S=new Set(Object.entries(V).filter(([,D])=>D==="staged").map(([D])=>D));g(D=>D.filter(K=>S.has(ae(K)))),P(D=>ht(D,S)),q(D=>ht(D,S)),$e(D=>ht(D,S)),Ne(null)},Pt=(f,L,S,D,K,B=!1)=>{f.length>0?j(f):B||j(null),R(L),g(S),C(D),m(K),y(H=>{const Q={...H};for(const re of f){const se=We(re);Q[se]===void 0&&re.default!==void 0&&re.default!==null&&(Q[se]=ze(re.default))}return Q}),P(H=>{const Q={};for(const re of S){const se=ae(re),ut={...H[se]??{}};for(const Te of re.questions??[])ut[Te.name]===void 0&&Te.default!==void 0&&Te.default!==null&&(ut[Te.name]=ze(Te.default));Q[se]=ut}return Q}),q(H=>{const Q={};for(const re of S){const se=ae(re);Q[se]=H[se]??"pending"}return Q}),$e(H=>{const Q={};for(const re of S){const se=ae(re);H[se]&&(Q[se]=H[se])}return Q})},Ys=(f,L,S)=>{const D=ae(f);P(K=>({...K,[D]:{...K[D]??{},[L]:S}}))},Kt=(f,L)=>{const S=ae(f);g(D=>D.map(K=>ae(K)===S?{...K,questions:L}:K))},Qs=async f=>{const L=ae(f);if(!f.setupToken){oe(`Channel ${f.name} has no setup token.`);return}oe(null),Ne(L);try{const S=await Ut(t,f.setupToken,T[L]??{});$e(D=>({...D,[L]:{nextSteps:S.nextSteps??[],warnings:S.warnings??[]}})),q(D=>({...D,[L]:"staged"}))}catch(S){S instanceof pt?Kt(f,S.questions):S instanceof X&&S.status===403?Pe(!0):oe(S.message)}finally{Ne(null)}},Xs=f=>{const L=Ce(f.url);if(!L)return oe("Channel setup returned an unsafe handoff URL."),!1;if(!f.formFields||Object.keys(f.formFields).length===0)return window.open(L,"_blank","noopener,noreferrer"),!0;const S=document.createElement("form");S.method="POST",S.action=L,S.target="_blank",S.rel="noopener noreferrer";for(const[D,K]of Object.entries(f.formFields)){const B=document.createElement("input");B.type="hidden",B.name=D,B.value=K,S.appendChild(B)}document.body.appendChild(S);try{S.submit()}finally{S.remove()}return!0},Zs=async f=>{const L=ae(f);if(!f.setupToken){oe(`Channel ${f.name} has no setup token.`);return}oe(null),Ne(L);try{const S=await sr(t,f.setupToken,T[L]??{});if(S.nextAction==="setup"){const D=await Ut(t,f.setupToken,T[L]??{});$e(K=>({...K,[L]:{nextSteps:D.nextSteps??[],warnings:D.warnings??[]}})),q(K=>({...K,[L]:"staged"}));return}if(!S.url){oe("Channel setup returned no handoff URL or next action.");return}Xs({url:S.url,formFields:S.formFields})&&q(D=>({...D,[L]:"handoff-started"}))}catch(S){S instanceof pt?Kt(f,S.questions):S instanceof X&&S.status===403?Pe(!0):oe(S.message)}finally{Ne(null)}},Bs=async()=>{oe(null),Pe(!1),C([]),m([]),R([]),A([]),Ft(!0);try{const f=n==="file"?await rr(t,a,{namespace:c,name:h||void 0,values:p,adopt:O}):await nr(t,{ref:i.trim(),namespace:c,name:h||void 0,values:p,adopt:O}),L=w.flatMap(D=>{const K=ie[ae(D)];return K?[...K.nextSteps,...K.warnings].map(B=>`${D.name}: ${B}`):[]}),S=L.length===0?f:{...f,warnings:[...f.warnings??[],...L]};j(null),g([]),s(S,c)}catch(f){f instanceof nt?Pt(f.questions,f.conflicts,f.channels,f.skillClones,f.warnings):f instanceof rt?Pt(f.questions,f.conflicts,f.channels,f.skillClones,f.warnings,!0):f instanceof X&&f.status===403?Pe(!0):oe(f.message)}finally{Ft(!1)}};return e.jsxs("div",{className:"flex flex-col gap-4",children:[Hs&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:"Not authorized to install agents."})}),Ot&&e.jsx(I,{variant:"destructive",children:e.jsx(F,{children:Ot})}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(U,{type:"button",variant:n==="file"?"default":"outline",size:"sm",onClick:()=>{r("file"),ke()},children:"Upload a .oap file"}),e.jsx(U,{type:"button",variant:n==="ref"?"default":"outline",size:"sm",onClick:()=>{r("ref"),ke()},children:"Registry ref"})]}),n==="file"?e.jsx(le,{label:"Bundle file",children:e.jsx("input",{type:"file",accept:".oap","aria-label":"Bundle file",onChange:f=>{var L;o(((L=f.target.files)==null?void 0:L[0])??null),ke()},className:"text-sm text-foreground"},"file-input")}):e.jsx(le,{label:"Registry ref",children:e.jsx("input",{type:"text",value:i,onChange:f=>{l(f.target.value),ke()},placeholder:"ghcr.io/acme/support-bot:1.0.0","aria-label":"Registry ref",className:Ie},"ref-input")}),e.jsxs("div",{className:"grid grid-cols-1 gap-4 sm:grid-cols-2",children:[e.jsx(le,{label:"Namespace *",children:e.jsx("input",{type:"text",value:c,onChange:f=>{u(f.target.value),ke()},"aria-label":"Namespace",className:Ie})}),e.jsx(le,{label:"Name (optional)",children:e.jsx("input",{type:"text",value:h,onChange:f=>{N(f.target.value),ke()},placeholder:"defaults to the bundled agent's name","aria-label":"Name",className:Ie})})]}),$.length>0&&e.jsxs("div",{className:"flex flex-col gap-1 rounded-md border border-dashed border-border p-3",children:[e.jsxs("p",{className:"text-xs font-medium text-foreground",children:["Installing this agent will clone ",$.length===1?"an external repository":"external repositories",":"]}),e.jsx("ul",{className:"list-disc pl-5 text-xs text-muted-foreground",children:$.map(f=>e.jsxs("li",{children:[e.jsx("span",{className:"text-foreground",children:f.repoURL})," @ ",f.ref||"default branch"," (SkillSource ",f.skillSource,")"]},f.skillSource))})]}),E.length>0&&e.jsx(I,{children:e.jsx(F,{children:e.jsx("ul",{className:"list-disc pl-5 text-xs",children:E.map(f=>e.jsx("li",{children:f},f))})})}),k.length>0&&e.jsxs("div",{className:"flex flex-col gap-2 rounded-md border border-destructive/50 p-3",children:[e.jsx("p",{className:"text-xs font-medium text-foreground",children:"These objects already exist and were not created by this install. Adopting one overwrites its spec with this bundle's, and uninstalling the agent will delete it."}),k.map(f=>{const L=`${f.kind}/${f.name}`,S=f.namespace?`${f.kind} ${f.namespace}/${f.name}`:L;return e.jsxs("label",{className:"flex items-center gap-2 text-xs text-foreground",children:[e.jsx("input",{type:"checkbox",checked:O.includes(L),"aria-label":S,onChange:D=>A(K=>D.target.checked?[...K,L]:K.filter(B=>B!==L)),className:"h-4 w-4 rounded border-border"}),e.jsx("span",{children:S}),f.secret&&e.jsx("span",{className:"text-destructive",children:"overwrites this Secret's data"})]},L)})]}),x&&x.length>0&&e.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-dashed border-border p-3",children:[e.jsxs("p",{className:"text-xs text-muted-foreground",children:["This bundle needs ",x.length===1?"an answer":"answers"," before it can install:"]}),x.map(f=>e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("p",{className:"text-[11px] font-medium text-muted-foreground",children:os(f.agentPath)}),e.jsx(ls,{q:f,value:p[We(f)]??ze(f.default),onChange:L=>Js(We(f),L)})]},We(f)))]}),w.length>0&&e.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-dashed border-border p-3",children:[e.jsx("p",{className:"text-xs text-muted-foreground",children:"Complete the required channel setup before resuming the install:"}),w.map(f=>{var K,B;const L=ae(f),S=V[L]??"pending",D=T[L]??{};return e.jsxs("div",{className:"flex flex-col gap-2 rounded-md border border-border p-3",children:[e.jsx("p",{className:"text-[11px] font-medium text-muted-foreground",children:os(f.agentPath)}),e.jsx("p",{className:"text-sm font-medium text-foreground",children:f.name}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:[f.kind," · ",f.role]}),f.purpose&&e.jsx("p",{className:"text-xs text-foreground",children:f.purpose}),f.reason&&e.jsx("p",{className:"text-xs text-destructive",children:f.reason}),f.remedy&&e.jsx("p",{className:"text-xs text-muted-foreground",children:f.remedy}),(f.questions??[]).map(H=>e.jsx(ls,{q:H,value:D[H.name]??ze(H.default),onChange:Q=>Ys(f,H.name,Q),disabled:S==="staged"},H.name)),S==="staged"?e.jsxs("div",{className:"flex flex-col gap-1 text-xs",children:[e.jsx("p",{className:"text-success",children:"Setup staged"}),(((K=ie[L])==null?void 0:K.nextSteps)??[]).map(H=>e.jsx("p",{className:"text-foreground",children:H},`next-${H}`)),(((B=ie[L])==null?void 0:B.warnings)??[]).map(H=>e.jsx("p",{className:"text-warning",children:H},`warning-${H}`))]}):f.status==="handoff"&&S==="pending"?e.jsxs(U,{type:"button",size:"sm",className:"self-start",disabled:Rt===L,onClick:()=>void Zs(f),children:["Start browser setup for ",f.name]}):f.status==="ask"||S==="handoff-started"?e.jsx(U,{type:"button",size:"sm",className:"self-start",disabled:Rt===L,onClick:()=>void Qs(f),children:S==="handoff-started"?`Resume ${f.name}`:`Set up ${f.name}`}):e.jsx("p",{className:"text-xs text-destructive",children:"This channel cannot be set up from the browser."})]},L)})]}),e.jsx(U,{type:"button",onClick:Bs,disabled:!qs||It,className:"self-start",children:It?"Installing…":k.length>0&&O.length>0&&Object.values(V).includes("staged")?"Adopt and resume install":k.length>0&&O.length>0?"Adopt and install":Object.values(V).includes("staged")?"Resume install":x?"Submit answers":"Install"})]})}function Us({agents:t}){return e.jsx("ul",{className:"flex flex-col gap-2 pl-4 text-xs text-muted-foreground",children:t.map(s=>e.jsxs("li",{children:[e.jsx("p",{children:s.agentPath?`Dependency ${s.agentPath}`:"Dependency"}),e.jsx("p",{className:"font-mono text-foreground",children:s.name}),s.agents&&s.agents.length>0&&e.jsx(Us,{agents:s.agents})]},`${s.agentPath??""}/${s.name}`))})}function $o({result:t,namespace:s,onClose:n}){return e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("p",{className:"text-sm text-success",children:["Installed ",e.jsx("span",{className:"font-mono",children:t.name})," — ",t.appliedKinds.join(", "),t.secretsCreated>0?`, ${t.secretsCreated} secret${t.secretsCreated===1?"":"s"} created`:"","."]}),t.adopted&&t.adopted.length>0&&e.jsxs("div",{className:"flex flex-col gap-1 rounded-md border border-warning/40 bg-warning/5 p-3",children:[e.jsxs("p",{className:"text-xs font-medium text-warning",children:["This install seized ",t.adopted.length," pre-existing object",t.adopted.length===1?"":"s"," ","not created by it — their specs were overwritten with this bundle's, and"," ",e.jsx("span",{className:"font-mono",children:"oap agent uninstall"})," will delete them."]}),e.jsx("ul",{className:"list-disc pl-4 text-xs text-muted-foreground",children:t.adopted.map(r=>e.jsx("li",{className:"font-mono",children:r},r))})]}),t.warnings&&t.warnings.length>0&&e.jsx("ul",{className:"list-disc pl-4 text-xs text-muted-foreground",children:t.warnings.map(r=>e.jsx("li",{children:r},r))}),t.agents&&t.agents.length>0&&e.jsxs("div",{className:"flex flex-col gap-1 rounded-md border border-border p-3",children:[e.jsx("p",{className:"text-xs font-medium text-foreground",children:"Installed dependencies"}),e.jsx(Us,{agents:t.agents})]}),e.jsxs(st,{children:[e.jsx(U,{variant:"outline",onClick:n,children:"Close"}),e.jsx(U,{onClick:()=>{n(),te({type:"detail",entity:"agent",id:`${s}/${t.name}`})},children:"View agent"})]})]})}function To({apiBase:t,onInstalled:s}){const[n,r]=d.useState(!1),[a,o]=d.useState(null),i=l=>{r(l),l&&o(null)};return e.jsxs(Qe,{open:n,onOpenChange:i,children:[e.jsx(Xe,{asChild:!0,children:e.jsx(U,{variant:"outline",size:"sm",children:"Install agent"})}),e.jsxs(Ze,{className:"max-w-xl",children:[e.jsxs(Be,{children:[e.jsx(et,{children:"Install an agent"}),e.jsx(tt,{children:"Upload a packaged .oap bundle or pull one from a registry ref."})]}),a?e.jsx($o,{result:a.result,namespace:a.namespace,onClose:()=>r(!1)}):e.jsx(Co,{apiBase:t,onInstalled:(l,c)=>{o({result:l,namespace:c}),s==null||s()}})]})]})}function Ao(t){return t==="Valid"||t==="Ready"}function is(t,s){var n,r;return((r=(n=t.counts)==null?void 0:n.find(a=>a.label===s))==null?void 0:r.value)??0}function Lo(t){return t==="agent"?"bg-state":t==="userPassthrough"?"bg-success":"bg-muted-foreground/40"}function Vs({row:t,linked:s=!0}){const n=ye(t,"identityMode"),r=ye(t,"identityRef");return e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[e.jsx("span",{"aria-hidden":"true",title:n||void 0,className:`inline-block h-2 w-2 shrink-0 rounded-full ${Lo(n)}`}),r&&s?e.jsx(z,{entity:"identity",id:t.namespace?`${t.namespace}/${r}`:r,className:"font-mono text-[11px] text-link hover:underline",onClick:a=>a.stopPropagation(),children:r}):e.jsx("span",{className:"font-mono text-[11px] text-muted-foreground",children:r||n||"—"})]})}const Eo=ce.agents.map(t=>t.header==="Identity"?{...t,render:s=>e.jsx(Vs,{row:s})}:t);function Do({a:t}){const s=Ao(t.status),n=is(t,"tools"),r=is(t,"skills");return e.jsxs(z,{entity:"agent",id:dt(t),className:"block rounded-xl border bg-card p-4 shadow transition-colors hover:border-primary/40",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"font-mono text-sm font-medium text-foreground",children:t.name}),e.jsx("span",{title:t.statusReason||void 0,className:`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] ${s?"border-success/40 bg-success/10 text-success":"border-destructive/40 bg-destructive/10 text-destructive"}`,children:t.status||"Unknown"})]}),t.namespace&&e.jsx("p",{className:"mt-0.5 font-mono text-[10px] text-muted-foreground",children:t.namespace}),!s&&t.statusReason&&e.jsx("p",{className:"mt-2 text-xs text-destructive",children:t.statusReason}),e.jsx("div",{className:"mt-3 flex flex-wrap gap-1.5",children:e.jsx(Vs,{row:t,linked:!1})}),e.jsxs("p",{className:"mt-3 font-mono text-xs text-muted-foreground",children:[n," tool",n===1?"":"s"," · ",r," skill",r===1?"":"s"]})]})}function Ro({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1),[l,c]=Tt("agents_view","grid"),u=d.useCallback(()=>{let p=!0;return $t(t,"agents").then(y=>{p&&(n(y),a(null),i(!0))}).catch(y=>{p&&(a(y.message),i(!0))}),()=>{p=!1}},[t]);d.useEffect(()=>u(),[u]);const h="AgentClass — readiness gates sessions · managed via oap class / kubectl agentclass",N=()=>r?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load agents: ",r]})}):o?s.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No AgentClasses configured yet."}):l==="grid"?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3",children:s.map(p=>e.jsx(Do,{a:p},dt(p)))}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:h})]}):e.jsx(_s,{rows:s,columns:Eo,entity:"agent",statusCookieKey:"status_agents",note:h}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading agents…"});return e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx(To,{apiBase:t,onInstalled:u}),o&&!r&&s.length>0&&e.jsx($s,{value:l,onChange:c})]}),N()]})}function je({apiBase:t,resource:s,columns:n,entity:r,facet:a,mapRows:o,note:i,emptyText:l}){const[c,u]=d.useState([]),[h,N]=d.useState(null),[p,y]=d.useState(!1);d.useEffect(()=>{let j=!0;return $t(t,s).then($=>{j&&(u($??[]),N(null),y(!0))}).catch($=>{j&&(N($.message),y(!0))}),()=>{j=!1}},[t,s]);const x=o?o(c):c;return e.jsxs("div",{className:"space-y-3",children:[h&&e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load ",s,": ",h]})}),!h&&e.jsx(_s,{rows:x,columns:n,entity:r,facet:a,statusCookieKey:`status_${s}`,loading:!p,note:i,emptyText:l})]})}function Oo({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"tools",entity:"tool",columns:ce.tools,note:"MCPServer · SidecarToolbox · SpiceboxToolkit · SpiceboxToolspec — managed via oap tools / kubectl",emptyText:"No tools configured yet."})}function Io({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"skills",entity:"skill",columns:ce.skills,note:"Skill · ClusterSkill — instruction & executable skills · managed via oap skill / kubectl get skills",emptyText:"No skills configured yet."})}const Fo=ce.sources.map(t=>t.key==="repo"?{...t,render:s=>{const n=ye(s,"repo");return n?Ce(n)?e.jsx("a",{href:n,target:"_blank",rel:"noopener noreferrer nofollow",className:"whitespace-nowrap font-mono text-[11px] text-link hover:underline",onClick:r=>r.stopPropagation(),children:n}):e.jsx("code",{className:"whitespace-nowrap font-mono text-[11px] text-muted-foreground",children:n}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}}:t);function Mo({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"sources",entity:"source",columns:Fo,note:"SkillSource · ClusterSkillSource — synced from git · managed via oap skill source add",emptyText:"No skill sources configured yet."})}function Po({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"directory",entity:"directory",columns:ce.directory,note:"RelationshipSource — Slack · GitHub · 1Password · managed via oap directory configure",emptyText:"No directory sources configured yet."})}function Ko(t){return ye(t,"role")==="monitoring"}function _o({row:t}){const s=ye(t,"role");if(!s)return e.jsx("span",{className:"text-muted-foreground",children:"—"});const n=s==="monitoring";return e.jsx("span",{className:`rounded-full border px-2 py-0.5 text-[10px] font-mono ${n?"border-border bg-background text-muted-foreground":"border-primary/40 bg-primary/10 text-primary"}`,children:s})}function Uo({row:t}){const s=ye(t,"agentClass");if(!s)return e.jsx("span",{className:"text-muted-foreground",children:"—"});const n=t.namespace?`${t.namespace}/${s}`:s;return e.jsx(z,{entity:"agent",id:n,className:"font-mono text-[11px] text-link hover:underline",children:s})}const Vo=ce.channels.map(t=>t.key==="role"?{...t,render:s=>e.jsx(_o,{row:s})}:t.key==="agentClass"?{...t,render:s=>e.jsx(Uo,{row:s})}:t);function Wo(t){return t.map(s=>Ko(s)?{...s,status:"monitoring"}:s)}function zo({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"channels",entity:"channel",columns:Vo,mapRows:Wo,note:"Channel — where agents are reachable · managed via oap channel / kubectl channel",emptyText:"No channels configured yet."})}const ft=[{id:"identities",label:"Agent identities",sub:"how agents authenticate to upstreams"},{id:"providers",label:"Login providers",sub:"human login for the admin UI & CLI"}];function Ho({apiBase:t}){const[s,n]=d.useState("identities"),r=ft.find(a=>a.id===s)??ft[0];return e.jsxs("div",{className:"space-y-3",children:[e.jsx("div",{role:"tablist",className:"inline-flex h-9 items-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground",children:ft.map(a=>{const o=s===a.id;return e.jsx("button",{type:"button",role:"tab","aria-selected":o,onClick:()=>n(a.id),className:["inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",o?"bg-background text-foreground shadow":"hover:text-foreground"].join(" "),children:a.label},a.id)})}),e.jsx("p",{className:"text-xs text-muted-foreground",children:r.sub}),s==="identities"?e.jsx(je,{apiBase:t,resource:"identities",entity:"identity",columns:ce.identities,facet:{key:"cred",label:"Type"},note:"AgentIdentity — managed via oap identity · setup flows: github_pat, oauth_mcp, federated (ID-JAG)",emptyText:"No agent identities configured yet."}):e.jsx(je,{apiBase:t,resource:"providers",entity:"provider",columns:ce.providers,note:"ClusterIdentityProvider — managed via oap idp",emptyText:"No login providers configured yet."})]})}const qo=ce.users.map(t=>t.key==="subject"?{...t,render:s=>{const n=ye(s,"subject");return n?e.jsx(Nt,{children:ne(n)}):e.jsx("span",{className:"text-muted-foreground",children:"—"})}}:t);function Go({apiBase:t}){return e.jsx(je,{apiBase:t,resource:"users",entity:"user",columns:qo,note:"UserIdentity — people linked to the platform · managed via oap user-identity",emptyText:"No users linked yet."})}function cs({label:t,value:s,sub:n}){return e.jsxs("div",{className:"rounded-xl border bg-card p-4 shadow",children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:t}),e.jsx("div",{className:"mt-1 text-2xl font-semibold",children:s}),e.jsx("div",{className:"text-[11px] text-muted-foreground",children:n})]})}function Jo({apiBase:t}){const[s,n]=d.useState(null),[r,a]=d.useState(null);if(d.useEffect(()=>{let i=!0;return hr(t).then(l=>{i&&(n(l),a(null))}).catch(l=>{i&&a(l.message)}),()=>{i=!1}},[t]),r)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load access: ",r]})});if(!s)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading access…"});const o=s.stats.available;return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"mb-2 text-xs font-semibold text-muted-foreground",children:["Platform admins ",e.jsx("span",{className:"font-normal",children:"· who can reach this admin UI (platform:platform#admin)"})]}),s.admins.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No platform admins granted yet."}):e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Subject"}),e.jsx(v,{children:"Kind"})]})}),e.jsx(Y,{children:s.admins.map(i=>e.jsxs(M,{children:[e.jsx(b,{className:"font-mono text-xs text-foreground",title:i.subject,children:ne(i.subject)}),e.jsx(b,{children:e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-muted-foreground",children:[i.kind==="group"?e.jsx(ms,{className:"h-3.5 w-3.5"}):e.jsx(Yn,{className:"h-3.5 w-3.5"}),i.kind]})})]},i.subject))})]})]}),e.jsxs("div",{className:"space-y-1.5",children:[e.jsx(ct,{text:s.grantCmd}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Run via the CLI — the admin UI is read-only. Also: oap platform revoke-admin / list-admins."})]}),o?e.jsxs("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2",children:[e.jsx(cs,{label:"SpiceDB schema",value:s.stats.schemaDefs!=null?`${s.stats.schemaDefs}`:"—",sub:"definitions"}),e.jsx(cs,{label:"Relationships",value:s.stats.relationships!=null?s.stats.relationships.toLocaleString():"—",sub:"stored tuples"})]}):e.jsxs("div",{className:"rounded-xl border border-dashed bg-card p-4 shadow",children:[e.jsx("div",{className:"text-[11px] text-muted-foreground",children:"SpiceDB stats"}),e.jsx("div",{className:"mt-1 text-sm text-muted-foreground",children:"Stats unavailable — schema-definition & relationship counts aren't exposed through the permissions API in v1. Wiring a live source is a follow-up."})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Authorization primitive — schema composed by the operator · oap spicedb apply-schema / oap spicedb check"})]})}function Yo(t){return t==="unknown"?"outline":"secondary"}function Qo({apiBase:t,row:s,onDone:n}){const[r,a]=d.useState(!1),[o,i]=d.useState(null),l=async()=>{const c=s.clientName||s.name;if(window.confirm(`Revoke access token "${c}"? This cannot be undone.`)){i(null),a(!0);try{await br(t,s.name),n()}catch(u){i(u.message)}finally{a(!1)}}};return e.jsxs("span",{className:"flex flex-col items-end gap-1",children:[e.jsx(U,{variant:"destructive",size:"sm",onClick:l,disabled:r,children:r?"Revoking…":"Revoke"}),o&&e.jsx("span",{className:"text-[11px] text-destructive",children:o})]})}function Xo({apiBase:t}){const[s,n]=d.useState([]),[r,a]=d.useState(null),[o,i]=d.useState(!1),l=d.useCallback(()=>{jr(t).then(c=>{n(c),a(null),i(!0)}).catch(c=>{a(c.message),i(!0)})},[t]);return d.useEffect(()=>{l()},[l]),r?e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load access tokens: ",r]})}):e.jsxs("div",{className:"space-y-3",children:[o&&s.length===0&&e.jsx("p",{className:"text-sm text-muted-foreground",children:"No access tokens yet."}),s.length>0&&e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Owner"}),e.jsx(v,{children:"Client"}),e.jsx(v,{children:"Role"}),e.jsx(v,{children:"Scope"}),e.jsx(v,{children:"Created"}),e.jsx(v,{children:"Expires"}),e.jsx(v,{children:"Last used"}),e.jsx(v,{className:"text-right",children:"Revoke"})]})}),e.jsx(Y,{children:s.map(c=>{var u;return e.jsxs(M,{children:[e.jsx(b,{className:"font-mono text-xs text-foreground",title:c.owner,children:ne(`user:${c.owner}`)}),e.jsx(b,{className:"text-sm text-foreground",children:c.clientName||"—"}),e.jsx(b,{children:e.jsx(W,{variant:Yo(c.role),children:c.role})}),e.jsx(b,{className:"text-xs text-muted-foreground",children:c.unfiltered?"all classes":(u=c.scopeClasses)!=null&&u.length?c.scopeClasses.join(", "):"—"}),e.jsx(b,{className:"text-xs text-muted-foreground",children:Se(c.createdAt)}),e.jsx(b,{className:"text-xs text-muted-foreground",children:Se(c.expiresAt)}),e.jsx(b,{className:"text-xs text-muted-foreground",children:c.lastUsedAt?Se(c.lastUsedAt):"never"}),e.jsx(b,{className:"text-right",children:c.revoked?e.jsx(W,{variant:"destructive",children:"Revoked"}):e.jsx(Qo,{apiBase:t,row:c,onDone:l})})]},c.name)})})]}),e.jsx("p",{className:"text-[11px] text-muted-foreground",children:"Delegated OAuth access tokens minted via MCP consent · revoking deletes the AccessToken CR — its finalizer removes the underlying SpiceDB grant."})]})}const ds=["Limits","Defaults"];function Zo(t){const s=new Map;for(const r of t){const a=s.get(r.group)??[];a.push(r),s.set(r.group,a)}return[...s.keys()].sort((r,a)=>{const o=ds.indexOf(r),i=ds.indexOf(a);return o===-1&&i===-1?0:o===-1?1:i===-1?-1:o-i}).flatMap(r=>s.get(r).map((a,o)=>({row:a,group:o===0?r:void 0})))}function Bo({apiBase:t}){const[s,n]=d.useState(null),[r,a]=d.useState(null);if(d.useEffect(()=>{let i=!0;return yr(t).then(l=>{i&&(n(l),a(null))}).catch(l=>{i&&a(l.message)}),()=>{i=!1}},[t]),r)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load settings: ",r]})});if(!s)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading settings…"});const o=Zo(s.rows);return e.jsxs("div",{className:"space-y-4",children:[s.rows.length===0?e.jsx("div",{className:"rounded-xl border border-dashed bg-card p-4 shadow",children:e.jsx("p",{className:"text-sm text-muted-foreground",children:s.note||"No ClusterAgentSettings configured — cluster governance uses built-in defaults."})}):e.jsx("div",{className:"rounded-xl border bg-card shadow",children:e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{className:"w-28",children:"Group"}),e.jsx(v,{children:"Setting"}),e.jsx(v,{children:"Value"}),e.jsx(v,{children:"Note"})]})}),e.jsx(Y,{children:o.map(({row:i,group:l},c)=>e.jsxs(M,{children:[e.jsx(b,{className:"align-top text-xs font-semibold text-muted-foreground",children:l??""}),e.jsx(b,{className:"align-top text-xs text-muted-foreground",children:i.label}),e.jsx(b,{className:"align-top font-mono text-xs text-foreground",children:i.value}),e.jsx(b,{className:"align-top text-[11px] text-muted-foreground",children:i.note??""})]},`${i.group}-${i.label}-${c}`))})]})}),e.jsx(ct,{text:s.manageCmd}),e.jsxs("p",{className:"text-[11px] text-muted-foreground",children:["ClusterAgentSettings (singleton ",e.jsx("span",{className:"font-mono",children:"cluster"}),") → namespace AgentSettings → AgentClass → session. Edit via the CLI — the admin UI is read-only."]})]})}const el=["oap agent pull "];function tl(t){return el.some(s=>t.startsWith(s))}const sl=new Set(["kind","channel kind","credential kind","credential type"]);function nl(t){return sl.has(t.trim().toLowerCase())}function Ws({section:t}){switch(t.kind){case"text":return e.jsx(ol,{text:t.text??""});case"list":return e.jsx(ll,{items:t.items??[],notice:t.text});case"fields":default:return e.jsx(al,{fields:t.fields??[]})}}function zs({entity:t,id:s,children:n}){return Ns(t)?e.jsx(z,{entity:t,id:s,className:"font-mono text-xs text-link hover:underline",children:n}):e.jsx(e.Fragment,{children:n})}function kt({href:t,children:s}){return e.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer nofollow",className:"break-words font-mono text-xs text-link hover:underline",children:s})}function rl({field:t}){if(t.link)return e.jsx(zs,{entity:t.link.entity,id:t.link.id,children:t.value});if(t.href){const n=Ce(t.href);if(n)return e.jsx(kt,{href:n,children:t.value})}if(t.value&&nl(t.label))return e.jsx(ot,{kind:t.value});if(tl(t.value))return e.jsx(ct,{text:t.value});const s=Ce(t.value);return s?e.jsx(kt,{href:s,children:t.value}):e.jsx("span",{className:"break-words",children:t.value})}function al({fields:t}){return t.length===0?e.jsx(Dt,{}):e.jsx("div",{className:"flex flex-col gap-4",children:t.map((s,n)=>e.jsx(le,{label:s.label,children:e.jsx(rl,{field:s})},`${s.label}-${n}`))})}function ol({text:t}){return t===""?e.jsx(Dt,{}):e.jsx("pre",{className:"whitespace-pre-wrap break-words rounded-md border border-border bg-card p-4 font-mono text-xs text-foreground",children:t})}function ll({items:t,notice:s}){const n=s?e.jsx(I,{variant:"destructive",className:"mb-3",children:e.jsx(F,{children:s})}):null;return t.length===0?e.jsxs(e.Fragment,{children:[n,!s&&e.jsx(Dt,{})]}):e.jsxs(e.Fragment,{children:[n,e.jsx("ul",{className:"flex flex-col gap-2",children:t.map((r,a)=>e.jsxs("li",{className:"rounded-md border border-border bg-card p-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(il,{item:r}),e.jsx(cl,{badges:r.badges})]}),r.subtitle&&e.jsx("p",{className:"mt-1 text-[11px] text-muted-foreground",children:r.subtitle})]},`${r.title}-${a}`))})]})}function il({item:t}){if(t.link)return e.jsx(zs,{entity:t.link.entity,id:t.link.id,children:t.title});if(t.href){const s=Ce(t.href);if(s)return e.jsx(kt,{href:s,children:t.title})}return e.jsx("span",{className:"font-mono text-xs text-foreground",children:t.title})}function cl({badges:t}){const s=t??[];return s.length===0?null:e.jsx("span",{className:"flex flex-wrap gap-1",children:s.map((n,r)=>e.jsx(ot,{kind:n.value},`${n.key}-${n.value}-${r}`))})}function Dt(){return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing here."})}const us={detail:null,loading:!0,error:null,notFound:!1};function dl(t,s,n){const[r,a]=d.useState(us);return d.useEffect(()=>{let o=!0;return a(us),(async()=>{if(!n.includes("/")){const c=(await $t(t,s)).find(u=>u.name===n);if(c)return Vt(t,s,dt(c))}return Vt(t,s,n)})().then(l=>{o&&a({detail:l,loading:!1,error:null,notFound:!1})}).catch(l=>{o&&(l instanceof X&&l.status===404?a({detail:null,loading:!1,error:null,notFound:!0}):a({detail:null,loading:!1,error:l.message,notFound:!1}))}),()=>{o=!1}},[t,s,n]),r}function xs({detail:t,section:s}){return e.jsxs("div",{className:"flex flex-col gap-5",children:[t.description&&e.jsx("p",{className:"text-base text-foreground",children:t.description}),e.jsx(yo,{status:t.status,reason:t.statusReason}),e.jsxs("div",{className:"flex flex-wrap gap-8",children:[e.jsx(le,{label:"Status",children:e.jsx(Ks,{status:t.status,reason:t.statusReason})}),e.jsx(le,{label:"Scope",children:e.jsx("span",{className:"text-sm capitalize text-foreground",children:t.scope})}),t.namespace&&e.jsx(le,{label:"Namespace",children:e.jsx("span",{className:"font-mono text-xs text-foreground",children:t.namespace})})]}),s&&e.jsx(Ws,{section:s}),t.manageCmd&&e.jsx(le,{label:"Manage",children:e.jsx(ct,{text:t.manageCmd})})]})}function de({apiBase:t,resource:s,entity:n,id:r,tab:a,backRoute:o,extraTabs:i=[],subtitle:l}){var O;const{detail:c,loading:u,error:h,notFound:N}=dl(t,s,r),p=new Set(["overview",...i.map(A=>A.id)]),y=(O=c==null?void 0:c.sections)==null?void 0:O.find(A=>A.id==="overview"),x=((c==null?void 0:c.sections)??[]).filter(A=>!p.has(A.id)),j=[{id:"overview",label:"Overview"},...i.map(A=>({id:A.id,label:A.label})),...x.map(A=>({id:A.id,label:A.label}))],$=new Set(j.map(A=>A.id)),C=a&&$.has(a)?a:"overview",E=A=>te({type:"detail",entity:n,id:r,tab:A}),m=c?e.jsx(Z,{ns:c.namespace,name:c.name}):e.jsx(Z,{id:r}),k=c&&l?l(c):void 0,R=()=>{if(u)return e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"});if(h)return e.jsx(I,{variant:"destructive",children:e.jsxs(F,{children:["Could not load ",s,": ",h]})});if(N||!c)return e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Not found: ",r]});if(C==="overview")return e.jsx(xs,{detail:c,section:y});const A=i.find(g=>g.id===C);if(A)return A.render(c);const w=x.find(g=>g.id===C);return w?e.jsx(Ws,{section:w}):e.jsx(xs,{detail:c,section:y})};return e.jsx(at,{title:m,subtitle:k,backRoute:o,tabs:j,activeTab:C,onTab:E,headerRight:c?e.jsx(Ks,{status:c.status,reason:c.statusReason}):void 0,children:R()})}function ul({apiBase:t,agentClass:s}){const[n,r]=d.useState(null),[a,o]=d.useState(null);if(d.useEffect(()=>{let l=!0;return _(`${t}/sessions`).then(c=>{l&&(r(c),o(null))}).catch(c=>{l&&o(c.message)}),()=>{l=!1}},[t]),a)return e.jsxs("p",{className:"text-xs text-destructive",children:["Could not load sessions: ",a]});if(n===null)return e.jsx("p",{className:"text-xs text-muted-foreground",children:"Loading sessions…"});const i=n.filter(l=>l.class===s);return i.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"No live sessions for this agent."}):e.jsxs(G,{children:[e.jsx(J,{children:e.jsxs(M,{children:[e.jsx(v,{children:"Session"}),e.jsx(v,{children:"Phase"}),e.jsx(v,{children:"Started"})]})}),e.jsx(Y,{children:i.map(l=>e.jsxs(M,{children:[e.jsx(b,{className:"font-mono text-xs",children:e.jsx(z,{entity:"session",id:`${l.namespace}/${l.name}`,className:"text-link hover:underline",children:e.jsx(Z,{ns:l.namespace,name:l.name})})}),e.jsx(b,{children:e.jsx(Lt,{phase:l.phase})}),e.jsx(b,{className:"whitespace-nowrap text-xs text-muted-foreground",children:Se(l.startedAt)})]},`${l.namespace}/${l.name}`))})]})}function xl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"agents",entity:"agent",id:s,tab:n,backRoute:{type:"view",view:"agents"},extraTabs:[{id:"sessions",label:"Sessions",render:r=>e.jsx(ul,{apiBase:t,agentClass:r.name})}]})}function ml({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"tools",entity:"tool",id:s,tab:n,backRoute:{type:"view",view:"tools"}})}function hl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"skills",entity:"skill",id:s,tab:n,backRoute:{type:"view",view:"skills"}})}function fl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"sources",entity:"source",id:s,tab:n,backRoute:{type:"view",view:"sources"}})}function pl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"directory",entity:"directory",id:s,tab:n,backRoute:{type:"view",view:"directory"}})}function gl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"channels",entity:"channel",id:s,tab:n,backRoute:{type:"view",view:"channels"}})}function jl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"identities",entity:"identity",id:s,tab:n,backRoute:{type:"view",view:"identity"}})}function bl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"providers",entity:"provider",id:s,tab:n,backRoute:{type:"view",view:"identity"}})}function vl({apiBase:t,id:s,tab:n}){return e.jsx(de,{apiBase:t,resource:"users",entity:"user",id:s,tab:n,backRoute:{type:"view",view:"users"},subtitle:r=>ne(r.description??"")})}fn();function yl({apiBase:t,currentUser:s}){const n=ws(),[r,a]=d.useState({});function o(u){a(u),te({type:"view",view:"logs"})}const i=Vr(n),l=n.type==="detail"?Nl(n,t):c(n.view);function c(u){switch(u){case"overview":return e.jsx(go,{apiBase:t});case"sessions":return e.jsx(ra,{apiBase:t});case"toolcalls":return e.jsx(la,{apiBase:t});case"approvals":return e.jsx(ua,{apiBase:t,onOpenSession:(h,N)=>te({type:"detail",entity:"session",id:`${h}/${N}`})});case"workshops":return e.jsx(pa,{apiBase:t});case"logs":return e.jsx(Va,{apiBase:t,initialFilter:r},JSON.stringify(r));case"sessionsAudit":return e.jsx(Ga,{apiBase:t,axis:"sessions",onDrill:o});case"budget":return e.jsx(io,{apiBase:t});case"artifacts":return e.jsx(Ba,{apiBase:t,onDrill:o});case"memory":return e.jsx(co,{apiBase:t});case"knowledge":return e.jsx(uo,{apiBase:t});case"agents":return e.jsx(Ro,{apiBase:t});case"tools":return e.jsx(Oo,{apiBase:t});case"skills":return e.jsx(Io,{apiBase:t});case"sources":return e.jsx(Mo,{apiBase:t});case"directory":return e.jsx(Po,{apiBase:t});case"channels":return e.jsx(zo,{apiBase:t});case"identity":return e.jsx(Ho,{apiBase:t});case"users":return e.jsx(Go,{apiBase:t});case"access":return e.jsx(Jo,{apiBase:t});case"tokens":return e.jsx(Xo,{apiBase:t});case"settings":return e.jsx(Bo,{apiBase:t});default:return e.jsx(Mr,{view:u})}}return e.jsx(Fr,{view:i,onNavigate:u=>te({type:"view",view:u}),currentUser:s,apiBase:t,children:l})}function Nl(t,s){const{entity:n,id:r,tab:a}=t;switch(n){case"session":return e.jsx(Ca,{apiBase:s,id:r,tab:a});case"agent":return e.jsx(xl,{apiBase:s,id:r,tab:a});case"tool":return e.jsx(ml,{apiBase:s,id:r,tab:a});case"skill":return e.jsx(hl,{apiBase:s,id:r,tab:a});case"source":return e.jsx(fl,{apiBase:s,id:r,tab:a});case"directory":return e.jsx(pl,{apiBase:s,id:r,tab:a});case"channel":return e.jsx(gl,{apiBase:s,id:r,tab:a});case"identity":return e.jsx(jl,{apiBase:s,id:r,tab:a});case"provider":return e.jsx(bl,{apiBase:s,id:r,tab:a});case"user":return e.jsx(vl,{apiBase:s,id:r,tab:a});case"artifact":return e.jsx(no,{apiBase:s,id:r,tab:a});default:return e.jsx(kl,{route:t})}}function kl({route:t}){const s=t.tab??"overview";return e.jsx(at,{title:`${t.entity} ${t.id}`,subtitle:"Detail page lands in a later batch",tabs:[{id:"overview",label:"Overview"}],activeTab:s,onTab:n=>te({type:"detail",entity:t.entity,id:t.id,tab:n}),children:e.jsx("p",{className:"text-sm text-muted-foreground",children:"Detail page lands in a later batch."})})}pn(yl); diff --git a/pkg/web/webui/webassets/dist/artifact-view.D4Y_FHws.js b/pkg/web/webui/webassets/dist/artifact-view.CW3Zih2i.js similarity index 97% rename from pkg/web/webui/webassets/dist/artifact-view.D4Y_FHws.js rename to pkg/web/webui/webassets/dist/artifact-view.CW3Zih2i.js index 425a6b61..e4c02e05 100644 --- a/pkg/web/webui/webassets/dist/artifact-view.D4Y_FHws.js +++ b/pkg/web/webui/webassets/dist/artifact-view.CW3Zih2i.js @@ -1 +1 @@ -import{c as k,I as z,J as B,K as F,H as Z,B as T,A as ee,G as te,a as ne,m as se}from"./mount.BOEH1oNx.js";import{r as o,j as t,v as re,w as ae,x as ie,y as E,z as oe,E as P,I as ce,X as le}from"./vendor.C5MRNvUt.js";import{M as de}from"./markdown.FOTo-3Mz.js";function ue(e,n){return{kind:"annotation_batch",artifactId:n,payload:{annotations:e.annotations}}}function fe(e){try{const n=JSON.parse(e);return!n||typeof n.type!="string"?null:n}catch{return null}}function he(e){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/artifact-view/ws${e.search}`}const N=3;function xe(e,n){const[i,l]=o.useState("connecting"),f=o.useRef(e);f.current=e;const r=o.useRef(n);return r.current=n,o.useEffect(()=>{let c=null,x,g=!1,m=0;const h=()=>{var d;g||(m+=1,l(m>=N?"offline":"reconnecting"),m===N&&((d=r.current)==null||d.call(r)),x=window.setTimeout(b,2e3))},b=()=>{if(!g){l(m===0?"connecting":m>=N?"offline":"reconnecting");try{c=new WebSocket(he(window.location))}catch{h();return}c.onopen=()=>{m=0,l("connected")},c.onmessage=d=>{const v=fe(d.data);v&&f.current(v)},c.onerror=()=>{g||l(m>=N?"offline":"reconnecting")},c.onclose=h}};return b(),()=>{g=!0,x&&window.clearTimeout(x),c&&c.close()}},[]),{state:i}}function me(e,n){function i(l){var r;if(!!!e.getAttribute("src")){l.hostUrl&&e.setAttribute("src",l.hostUrl);return}if(l.contentUrl)try{(r=e.contentWindow)==null||r.postMessage({ap:"host",cmd:"swap",url:l.contentUrl},n)}catch{}}return{showRevision:i}}function ge(e,n){const i=o.useRef(null);return o.useEffect(()=>{e.current&&(i.current=me(e.current,n))},[e,n]),o.useCallback(l=>{var f;return(f=i.current)==null?void 0:f.showRevision(l)},[])}function we(e,n){if(!e)return"Working";switch(n){case"awaiting_approval":case"awaiting_leakage_approval":return"Awaiting approval";case"awaiting_reply":case"idle":return"Awaiting your reply";case"awaiting_retry":return"Retrying…";case"failed":return"Failed";case"complete":return"Done";default:return"Paused"}}function pe(e){return e==="done"?"✓":e==="error"?"✕":e==="in_progress"?"▸":"○"}function be(e){return e.find(n=>n.status==="in_progress")??e.find(n=>n.status!=="done"&&n.status!=="error")??null}function ve({status:e}){if(!e)return null;const n=!!e.paused,i=e.pauseCause||"",l=Array.isArray(e.plan)?e.plan:[],f=be(l),r=i==="failed",c=i==="complete",x=r?"bg-destructive":c?"bg-success":n?"bg-warning":"bg-success",g=r?"text-destructive":c?"text-success":n?"text-warning":"text-success",m=!n;return t.jsxs("div",{className:"flex flex-1 min-w-0 items-center gap-[9px] text-xs",children:[t.jsx("span",{className:k("h-[9px] w-[9px] rounded-full shrink-0",x,m&&"animate-ap-pulse-dot")}),t.jsx("span",{className:k("text-xs font-semibold shrink-0",g),children:we(n,i)}),e.statusMessage&&t.jsx("span",{className:"text-[13px] text-foreground truncate min-w-0",children:e.statusMessage}),l.length>0&&f&&t.jsxs("span",{className:"relative group min-w-0 truncate shrink-0",tabIndex:0,children:[t.jsx("span",{className:"text-[11px] text-muted-foreground before:content-['·_']",children:f.label||""}),l.length>1&&t.jsx("div",{"aria-hidden":"true",className:"hidden group-hover:block group-focus-within:block absolute top-[calc(100%+4px)] left-0 z-20 bg-card border border-border rounded-md p-2 min-w-[240px] max-w-[420px] shadow-xl whitespace-normal",children:l.map((h,b)=>t.jsxs("div",{className:"flex gap-2 px-1 py-0.5 leading-snug items-baseline",children:[t.jsx("span",{className:k("w-3 text-center shrink-0",h.status==="in_progress"&&"text-primary",h.status==="done"&&"text-success",h.status==="error"&&"text-destructive",(h.status==="pending"||!h.status)&&"text-muted-foreground"),children:pe(h.status)}),t.jsx("span",{children:h.label||""})]},b))})]})]})}function je({className:e}){return t.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 24 24",className:e,"aria-hidden":"true",children:[t.jsx("path",{fill:"#E01E5A",d:"M5.6 14.5a2.3 2.3 0 1 1-2.3-2.3h2.3z"}),t.jsx("path",{fill:"#E01E5A",d:"M6.8 14.5a2.3 2.3 0 0 1 4.6 0v5.7a2.3 2.3 0 1 1-4.6 0z"}),t.jsx("path",{fill:"#36C5F0",d:"M9.5 5.6a2.3 2.3 0 1 1 2.3-2.3v2.3z"}),t.jsx("path",{fill:"#36C5F0",d:"M9.5 6.8a2.3 2.3 0 0 1 0 4.6H3.8a2.3 2.3 0 1 1 0-4.6z"}),t.jsx("path",{fill:"#2EB67D",d:"M18.4 9.5a2.3 2.3 0 1 1 2.3 2.3h-2.3z"}),t.jsx("path",{fill:"#2EB67D",d:"M17.2 9.5a2.3 2.3 0 0 1-4.6 0V3.8a2.3 2.3 0 1 1 4.6 0z"}),t.jsx("path",{fill:"#ECB22E",d:"M14.5 18.4a2.3 2.3 0 1 1-2.3 2.3v-2.3z"}),t.jsx("path",{fill:"#ECB22E",d:"M14.5 17.2a2.3 2.3 0 0 1 0-4.6h5.7a2.3 2.3 0 1 1 0 4.6z"})]})}function ye({kind:e,className:n}){return e==="slack"?t.jsx(je,{className:n}):t.jsx(re,{className:n,"aria-hidden":"true"})}function Ne({href:e,channelKind:n}){if(!e)return null;const i=n==="slack"?"Open the Slack thread":"Open the thread";return t.jsxs(z,{children:[t.jsx(B,{asChild:!0,children:t.jsxs("a",{href:e,target:"_blank",rel:"noreferrer",className:"group flex items-center gap-[3px] rounded-md px-[7px] py-[5px] text-inherit no-underline hover:bg-muted",children:[t.jsx(ye,{kind:n,className:"h-4 w-4"}),t.jsx(ae,{className:"h-[11px] w-[11px] text-muted-foreground/70 group-hover:text-muted-foreground","aria-hidden":"true"})]})}),t.jsx(F,{children:i})]})}const ke={connected:{Icon:oe,color:"text-success",spin:!1,tip:"Live — connected"},connecting:{Icon:E,color:"text-warning",spin:!0,tip:"Connecting…"},reconnecting:{Icon:E,color:"text-warning",spin:!0,tip:"Reconnecting…"},offline:{Icon:ie,color:"text-destructive",spin:!1,tip:"Offline — retrying"}};function Se({state:e}){const n=ke[e],{Icon:i}=n;return t.jsxs(z,{children:[t.jsx(B,{asChild:!0,children:t.jsx("div",{className:`flex h-7 w-7 items-center justify-center rounded-md cursor-default ${n.color}`,children:t.jsx(i,{className:`h-[18px] w-[18px] ${n.spin?"animate-spin":""}`,"aria-hidden":"true"})})}),t.jsx(F,{children:n.tip})]})}function Ce({revisions:e,currentRevId:n,onSelect:i,downloadHref:l}){const f=[...e].reverse();return t.jsx("ul",{className:"list-none m-0 p-1.5",children:f.map(r=>{const c=r.revisionId&&r.revisionId===n;return t.jsxs("li",{onClick:()=>r.revisionId&&i(r.revisionId),className:k("relative px-2.5 py-2 rounded-md cursor-pointer border border-transparent text-xs leading-snug hover:bg-muted",c&&"bg-muted border-border"),children:[l&&r.revisionId&&t.jsx("a",{href:l(r.revisionId),download:!0,onClick:x=>x.stopPropagation(),"aria-label":`Download revision ${r.seq??""}`.trim(),title:"Download this revision",className:"absolute bottom-1.5 right-1.5 flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:t.jsx(P,{className:"h-3 w-3","aria-hidden":"true"})}),t.jsxs("span",{className:"font-semibold text-primary",children:["#",r.seq??"?",Array.isArray(r.tags)&&r.tags.includes("latest")&&t.jsx("span",{className:"ml-1.5 text-[10px] px-1.5 rounded-lg bg-success/20 text-success align-middle",children:"latest"})]}),t.jsx("span",{className:"block text-foreground my-0.5",children:r.changeDescription||"(no description)"}),r.createdAt&&t.jsx("span",{className:"block text-muted-foreground text-[11px]",children:r.createdAt})]},r.revisionId||r.seq)})})}function Ie(e){const[n,i]=o.useState(""),[l,f]=o.useState(!1),r=async()=>{const c=n.trim();if(!(!c||l)){f(!0);try{await e.onSend(c),i("")}finally{f(!1)}}};return t.jsxs("div",{className:"flex flex-col h-full border-l border-border bg-card",children:[t.jsx("div",{className:"flex-1 overflow-y-auto p-3 space-y-2",children:e.messages.map((c,x)=>t.jsx("div",{className:`flex ${c.role==="user"?"justify-end":"justify-start"}`,children:t.jsx("div",{className:`inline-block max-w-[85%] rounded-lg px-3 py-1.5 text-sm text-left ${c.role==="user"?"bg-primary text-primary-foreground":"bg-muted text-foreground"}`,children:t.jsx(de,{children:c.text})})},x))}),e.canCompose&&t.jsxs("div",{className:"flex gap-2 p-2 border-t border-border",children:[t.jsx("input",{className:"flex-1 rounded border border-border bg-background px-2 py-1 text-sm",value:n,onChange:c=>i(c.target.value),onKeyDown:c=>{c.key==="Enter"&&r()},placeholder:"Message the agent…",disabled:l}),t.jsx("button",{className:"rounded bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",onClick:()=>void r(),disabled:l,children:"Send"})]})]})}const L=200;function Re(e,n){const i=e.concat(n);return i.length>L?i.slice(i.length-L):i}function Ee(e){return{role:e.role==="user"?"user":"agent",text:e.text,author:e.author,at:e.at}}const A="ap_artifact_reauth_at",U="ap_artifact_pending_rev";function Ue(){const e=Number(window.sessionStorage.getItem(A)||"0");return Number.isFinite(e)&&Date.now()-e<15e3}function O(e){return Ue()?!1:(window.sessionStorage.setItem(A,String(Date.now())),e&&window.sessionStorage.setItem(U,e),window.location.reload(),!0)}function Ae(e){const n=o.useRef(null),i=ge(n,e.sandboxOrigin),[l,f]=o.useState(!1),[r,c]=o.useState(!1),x=o.useRef(!1);x.current=r;const[g,m]=o.useState([]),[h,b]=o.useState(null),[d,v]=o.useState(null),[S,W]=o.useState(null),[q,C]=o.useState(!!e.hostUrl),[j,_]=o.useState(null),[H,K]=o.useState([]),[M,y]=o.useState(null),$=o.useRef(null);$.current=d;const J=o.useCallback(s=>{var a,u;if(s.type==="status"){s.status&&W(s.status);return}if(s.type==="message"){s.message&&K(w=>Re(w,Ee(s.message)));return}v(s),Array.isArray(s.revisions)&&m(s.revisions),!x.current&&((a=s.current)!=null&&a.hostUrl||(u=s.current)!=null&&u.contentUrl)&&(i({hostUrl:s.current.hostUrl,contentUrl:s.current.contentUrl}),b(s.current.revisionId||null),C(!0))},[i]),G=o.useCallback(()=>{var a,u;const s=(u=(a=$.current)==null?void 0:a.current)==null?void 0:u.revisionId;s&&fetch(`/artifact-view/revision${window.location.search}&rev=${encodeURIComponent(s)}`).then(w=>{w.status===401&&O()}).catch(()=>{})},[]),{state:V}=xe(J,G),X=!!S&&!S.paused,I=o.useCallback(s=>{c(!0),fetch(`/artifact-view/revision${window.location.search}&rev=${encodeURIComponent(s)}`).then(a=>{if(a.status===401)return O(s),null;if(!a.ok)throw new Error(`revision fetch failed: ${a.status}`);return a.json()}).then(a=>{a&&(window.sessionStorage.removeItem(A),(a.hostUrl||a.contentUrl)&&(i({hostUrl:a.hostUrl,contentUrl:a.contentUrl}),b(s),C(!0)))}).catch(a=>{window.console&&console.warn("view-older failed",a)})},[i]),D=o.useCallback(s=>{const a=new URLSearchParams({artifactId:e.artifactId,sessionRef:`${e.ns}/${e.name}`});return s&&a.set("rev",s),`/artifact-download?${a.toString()}`},[e.artifactId,e.ns,e.name]),Y=o.useCallback(()=>{var s,a,u,w,p;c(!1),((s=d==null?void 0:d.current)!=null&&s.hostUrl||(a=d==null?void 0:d.current)!=null&&a.contentUrl)&&(i({hostUrl:(u=d==null?void 0:d.current)==null?void 0:u.hostUrl,contentUrl:(w=d==null?void 0:d.current)==null?void 0:w.contentUrl}),b(((p=d==null?void 0:d.current)==null?void 0:p.revisionId)||null),C(!0))},[d,i]);o.useEffect(()=>{const s=window.sessionStorage.getItem(U);s&&(window.sessionStorage.removeItem(U),_(s))},[]),o.useEffect(()=>{if(j&&g.some(s=>s.revisionId===j)){const s=j;_(null),I(s)}},[j,g,I]),o.useEffect(()=>{const s=a=>{var w;if(a.origin!==e.sandboxOrigin||a.source!==((w=n.current)==null?void 0:w.contentWindow))return;const u=a.data;(u==null?void 0:u.ap)!=="annot"||u.cmd!=="send"||!u.bundle||fetch(`/session/${e.ns}/${e.name}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(ue(u.bundle,e.artifactId))}).then(async p=>{if(!p.ok){const R=await p.json().catch(()=>({}));console.error("annotation send failed",p.status,R),y(R.error?R.error:`The server returned ${p.status}.`);return}y(null)}).catch(p=>{console.error("annotation send failed (network)",p),y("The server was unreachable — check your connection and try again.")})};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[e.sandboxOrigin,e.ns,e.name,e.artifactId]);const Q=o.useCallback(async s=>{const a=await fetch(`/session/${e.ns}/${e.name}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:"user_message",artifactId:e.artifactId,payload:{text:s}})});if(!a.ok){const u=await a.json().catch(()=>({}));throw console.error("send failed",a.status,u),new Error((u==null?void 0:u.error)||"send failed")}},[e.ns,e.name,e.artifactId]);return t.jsx(Z,{delayDuration:200,children:t.jsxs("div",{className:"h-screen flex flex-col bg-background text-foreground",children:[t.jsxs("div",{className:"relative flex items-center gap-3 px-3 py-2 min-h-[52px] bg-card border-b border-border",children:[t.jsx(T,{variant:"ghost",size:"icon",className:"h-7 w-7 text-muted-foreground",title:"Toggle revisions",onClick:()=>f(s=>!s),children:t.jsx(ce,{className:"h-4 w-4"})}),t.jsxs("div",{className:"flex flex-col min-w-0 max-w-[46%] shrink",children:[t.jsx("span",{className:"text-[13px] font-semibold leading-[1.25] truncate",title:e.artifactName,children:e.artifactName}),e.artifactDescription&&t.jsx("span",{className:"text-[11px] text-muted-foreground leading-[1.3] truncate",title:e.artifactDescription,children:e.artifactDescription})]}),t.jsx("div",{className:"w-px h-[26px] bg-border shrink-0"}),t.jsx(ve,{status:S}),t.jsxs("div",{className:"flex items-center gap-2 ml-auto shrink-0",children:[t.jsx("a",{href:D(r&&h?h:void 0),download:!0,"aria-label":"Download",title:"Download",className:"flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:t.jsx(P,{className:"h-4 w-4","aria-hidden":"true"})}),t.jsx(Ne,{href:e.backLink,channelKind:e.channelKind}),r&&t.jsxs(T,{variant:"secondary",size:"sm",className:"h-7 gap-1.5 px-2.5",title:"Follow latest",onClick:Y,children:[t.jsx("span",{className:"inline-block h-[7px] w-[7px] rounded-full bg-success"}),"Live"]}),t.jsx(Se,{state:V})]}),X&&t.jsx("div",{"aria-hidden":"true",className:"absolute left-0 right-0 bottom-0 h-[2px] bg-[linear-gradient(90deg,transparent,hsl(var(--success)),transparent)] bg-[length:50%_100%] animate-ap-flow"})]}),t.jsxs("div",{className:"flex flex-1 min-h-0",children:[t.jsx("aside",{className:`bg-card border-r border-border overflow-hidden transition-[width] ${l?"w-[300px] overflow-y-auto":"w-0"}`,children:t.jsx(Ce,{revisions:g,currentRevId:h,onSelect:I,downloadHref:D})}),t.jsxs("div",{className:"relative flex-1 h-full",children:[t.jsx("iframe",{ref:n,className:"border-0 w-full h-full block bg-white",sandbox:"allow-scripts allow-same-origin",src:e.hostUrl||void 0,referrerPolicy:"no-referrer",title:"artifact content"}),!q&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-background text-muted-foreground",children:[t.jsx(E,{className:"h-5 w-5 animate-spin","aria-hidden":"true"}),t.jsx("span",{className:"text-sm",children:"Generating preview…"})]}),M&&t.jsx("div",{className:"absolute inset-x-3 top-3 z-10",children:t.jsxs(ee,{variant:"destructive",className:"relative bg-card pr-9 shadow-lg",children:[t.jsx(te,{children:"Annotation not sent"}),t.jsx(ne,{children:M}),t.jsx("button",{type:"button","aria-label":"Dismiss",onClick:()=>y(null),className:"absolute right-2 top-2 rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:t.jsx(le,{className:"h-4 w-4","aria-hidden":"true"})})]})})]}),e.interactions.length>0&&t.jsx("div",{className:"w-[320px] shrink-0 h-full",children:t.jsx(Ie,{messages:H,canCompose:e.interactions.includes("user_message"),onSend:Q})})]})]})})}se(Ae); +import{c as k,I as z,J as B,K as F,H as Z,B as T,A as ee,G as te,a as ne,m as se}from"./mount.Bxdfy1H2.js";import{r as o,j as t,w as re,x as ae,y as ie,z as E,E as oe,I as P,J as ce,X as le}from"./vendor.BHG48rjj.js";import{M as de}from"./markdown.CZgLwgiZ.js";function ue(e,n){return{kind:"annotation_batch",artifactId:n,payload:{annotations:e.annotations}}}function fe(e){try{const n=JSON.parse(e);return!n||typeof n.type!="string"?null:n}catch{return null}}function he(e){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/artifact-view/ws${e.search}`}const N=3;function xe(e,n){const[i,l]=o.useState("connecting"),f=o.useRef(e);f.current=e;const r=o.useRef(n);return r.current=n,o.useEffect(()=>{let c=null,x,g=!1,m=0;const h=()=>{var d;g||(m+=1,l(m>=N?"offline":"reconnecting"),m===N&&((d=r.current)==null||d.call(r)),x=window.setTimeout(b,2e3))},b=()=>{if(!g){l(m===0?"connecting":m>=N?"offline":"reconnecting");try{c=new WebSocket(he(window.location))}catch{h();return}c.onopen=()=>{m=0,l("connected")},c.onmessage=d=>{const v=fe(d.data);v&&f.current(v)},c.onerror=()=>{g||l(m>=N?"offline":"reconnecting")},c.onclose=h}};return b(),()=>{g=!0,x&&window.clearTimeout(x),c&&c.close()}},[]),{state:i}}function me(e,n){function i(l){var r;if(!!!e.getAttribute("src")){l.hostUrl&&e.setAttribute("src",l.hostUrl);return}if(l.contentUrl)try{(r=e.contentWindow)==null||r.postMessage({ap:"host",cmd:"swap",url:l.contentUrl},n)}catch{}}return{showRevision:i}}function ge(e,n){const i=o.useRef(null);return o.useEffect(()=>{e.current&&(i.current=me(e.current,n))},[e,n]),o.useCallback(l=>{var f;return(f=i.current)==null?void 0:f.showRevision(l)},[])}function we(e,n){if(!e)return"Working";switch(n){case"awaiting_approval":case"awaiting_leakage_approval":return"Awaiting approval";case"awaiting_reply":case"idle":return"Awaiting your reply";case"awaiting_retry":return"Retrying…";case"failed":return"Failed";case"complete":return"Done";default:return"Paused"}}function pe(e){return e==="done"?"✓":e==="error"?"✕":e==="in_progress"?"▸":"○"}function be(e){return e.find(n=>n.status==="in_progress")??e.find(n=>n.status!=="done"&&n.status!=="error")??null}function ve({status:e}){if(!e)return null;const n=!!e.paused,i=e.pauseCause||"",l=Array.isArray(e.plan)?e.plan:[],f=be(l),r=i==="failed",c=i==="complete",x=r?"bg-destructive":c?"bg-success":n?"bg-warning":"bg-success",g=r?"text-destructive":c?"text-success":n?"text-warning":"text-success",m=!n;return t.jsxs("div",{className:"flex flex-1 min-w-0 items-center gap-[9px] text-xs",children:[t.jsx("span",{className:k("h-[9px] w-[9px] rounded-full shrink-0",x,m&&"animate-ap-pulse-dot")}),t.jsx("span",{className:k("text-xs font-semibold shrink-0",g),children:we(n,i)}),e.statusMessage&&t.jsx("span",{className:"text-[13px] text-foreground truncate min-w-0",children:e.statusMessage}),l.length>0&&f&&t.jsxs("span",{className:"relative group min-w-0 truncate shrink-0",tabIndex:0,children:[t.jsx("span",{className:"text-[11px] text-muted-foreground before:content-['·_']",children:f.label||""}),l.length>1&&t.jsx("div",{"aria-hidden":"true",className:"hidden group-hover:block group-focus-within:block absolute top-[calc(100%+4px)] left-0 z-20 bg-card border border-border rounded-md p-2 min-w-[240px] max-w-[420px] shadow-xl whitespace-normal",children:l.map((h,b)=>t.jsxs("div",{className:"flex gap-2 px-1 py-0.5 leading-snug items-baseline",children:[t.jsx("span",{className:k("w-3 text-center shrink-0",h.status==="in_progress"&&"text-primary",h.status==="done"&&"text-success",h.status==="error"&&"text-destructive",(h.status==="pending"||!h.status)&&"text-muted-foreground"),children:pe(h.status)}),t.jsx("span",{children:h.label||""})]},b))})]})]})}function je({className:e}){return t.jsxs("svg",{width:"16",height:"16",viewBox:"0 0 24 24",className:e,"aria-hidden":"true",children:[t.jsx("path",{fill:"#E01E5A",d:"M5.6 14.5a2.3 2.3 0 1 1-2.3-2.3h2.3z"}),t.jsx("path",{fill:"#E01E5A",d:"M6.8 14.5a2.3 2.3 0 0 1 4.6 0v5.7a2.3 2.3 0 1 1-4.6 0z"}),t.jsx("path",{fill:"#36C5F0",d:"M9.5 5.6a2.3 2.3 0 1 1 2.3-2.3v2.3z"}),t.jsx("path",{fill:"#36C5F0",d:"M9.5 6.8a2.3 2.3 0 0 1 0 4.6H3.8a2.3 2.3 0 1 1 0-4.6z"}),t.jsx("path",{fill:"#2EB67D",d:"M18.4 9.5a2.3 2.3 0 1 1 2.3 2.3h-2.3z"}),t.jsx("path",{fill:"#2EB67D",d:"M17.2 9.5a2.3 2.3 0 0 1-4.6 0V3.8a2.3 2.3 0 1 1 4.6 0z"}),t.jsx("path",{fill:"#ECB22E",d:"M14.5 18.4a2.3 2.3 0 1 1-2.3 2.3v-2.3z"}),t.jsx("path",{fill:"#ECB22E",d:"M14.5 17.2a2.3 2.3 0 0 1 0-4.6h5.7a2.3 2.3 0 1 1 0 4.6z"})]})}function ye({kind:e,className:n}){return e==="slack"?t.jsx(je,{className:n}):t.jsx(re,{className:n,"aria-hidden":"true"})}function Ne({href:e,channelKind:n}){if(!e)return null;const i=n==="slack"?"Open the Slack thread":"Open the thread";return t.jsxs(z,{children:[t.jsx(B,{asChild:!0,children:t.jsxs("a",{href:e,target:"_blank",rel:"noreferrer",className:"group flex items-center gap-[3px] rounded-md px-[7px] py-[5px] text-inherit no-underline hover:bg-muted",children:[t.jsx(ye,{kind:n,className:"h-4 w-4"}),t.jsx(ae,{className:"h-[11px] w-[11px] text-muted-foreground/70 group-hover:text-muted-foreground","aria-hidden":"true"})]})}),t.jsx(F,{children:i})]})}const ke={connected:{Icon:oe,color:"text-success",spin:!1,tip:"Live — connected"},connecting:{Icon:E,color:"text-warning",spin:!0,tip:"Connecting…"},reconnecting:{Icon:E,color:"text-warning",spin:!0,tip:"Reconnecting…"},offline:{Icon:ie,color:"text-destructive",spin:!1,tip:"Offline — retrying"}};function Se({state:e}){const n=ke[e],{Icon:i}=n;return t.jsxs(z,{children:[t.jsx(B,{asChild:!0,children:t.jsx("div",{className:`flex h-7 w-7 items-center justify-center rounded-md cursor-default ${n.color}`,children:t.jsx(i,{className:`h-[18px] w-[18px] ${n.spin?"animate-spin":""}`,"aria-hidden":"true"})})}),t.jsx(F,{children:n.tip})]})}function Ce({revisions:e,currentRevId:n,onSelect:i,downloadHref:l}){const f=[...e].reverse();return t.jsx("ul",{className:"list-none m-0 p-1.5",children:f.map(r=>{const c=r.revisionId&&r.revisionId===n;return t.jsxs("li",{onClick:()=>r.revisionId&&i(r.revisionId),className:k("relative px-2.5 py-2 rounded-md cursor-pointer border border-transparent text-xs leading-snug hover:bg-muted",c&&"bg-muted border-border"),children:[l&&r.revisionId&&t.jsx("a",{href:l(r.revisionId),download:!0,onClick:x=>x.stopPropagation(),"aria-label":`Download revision ${r.seq??""}`.trim(),title:"Download this revision",className:"absolute bottom-1.5 right-1.5 flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:t.jsx(P,{className:"h-3 w-3","aria-hidden":"true"})}),t.jsxs("span",{className:"font-semibold text-primary",children:["#",r.seq??"?",Array.isArray(r.tags)&&r.tags.includes("latest")&&t.jsx("span",{className:"ml-1.5 text-[10px] px-1.5 rounded-lg bg-success/20 text-success align-middle",children:"latest"})]}),t.jsx("span",{className:"block text-foreground my-0.5",children:r.changeDescription||"(no description)"}),r.createdAt&&t.jsx("span",{className:"block text-muted-foreground text-[11px]",children:r.createdAt})]},r.revisionId||r.seq)})})}function Ie(e){const[n,i]=o.useState(""),[l,f]=o.useState(!1),r=async()=>{const c=n.trim();if(!(!c||l)){f(!0);try{await e.onSend(c),i("")}finally{f(!1)}}};return t.jsxs("div",{className:"flex flex-col h-full border-l border-border bg-card",children:[t.jsx("div",{className:"flex-1 overflow-y-auto p-3 space-y-2",children:e.messages.map((c,x)=>t.jsx("div",{className:`flex ${c.role==="user"?"justify-end":"justify-start"}`,children:t.jsx("div",{className:`inline-block max-w-[85%] rounded-lg px-3 py-1.5 text-sm text-left ${c.role==="user"?"bg-primary text-primary-foreground":"bg-muted text-foreground"}`,children:t.jsx(de,{children:c.text})})},x))}),e.canCompose&&t.jsxs("div",{className:"flex gap-2 p-2 border-t border-border",children:[t.jsx("input",{className:"flex-1 rounded border border-border bg-background px-2 py-1 text-sm",value:n,onChange:c=>i(c.target.value),onKeyDown:c=>{c.key==="Enter"&&r()},placeholder:"Message the agent…",disabled:l}),t.jsx("button",{className:"rounded bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",onClick:()=>void r(),disabled:l,children:"Send"})]})]})}const L=200;function Re(e,n){const i=e.concat(n);return i.length>L?i.slice(i.length-L):i}function Ee(e){return{role:e.role==="user"?"user":"agent",text:e.text,author:e.author,at:e.at}}const A="ap_artifact_reauth_at",U="ap_artifact_pending_rev";function Ue(){const e=Number(window.sessionStorage.getItem(A)||"0");return Number.isFinite(e)&&Date.now()-e<15e3}function O(e){return Ue()?!1:(window.sessionStorage.setItem(A,String(Date.now())),e&&window.sessionStorage.setItem(U,e),window.location.reload(),!0)}function Ae(e){const n=o.useRef(null),i=ge(n,e.sandboxOrigin),[l,f]=o.useState(!1),[r,c]=o.useState(!1),x=o.useRef(!1);x.current=r;const[g,m]=o.useState([]),[h,b]=o.useState(null),[d,v]=o.useState(null),[S,W]=o.useState(null),[q,C]=o.useState(!!e.hostUrl),[j,_]=o.useState(null),[H,J]=o.useState([]),[M,y]=o.useState(null),$=o.useRef(null);$.current=d;const K=o.useCallback(s=>{var a,u;if(s.type==="status"){s.status&&W(s.status);return}if(s.type==="message"){s.message&&J(w=>Re(w,Ee(s.message)));return}v(s),Array.isArray(s.revisions)&&m(s.revisions),!x.current&&((a=s.current)!=null&&a.hostUrl||(u=s.current)!=null&&u.contentUrl)&&(i({hostUrl:s.current.hostUrl,contentUrl:s.current.contentUrl}),b(s.current.revisionId||null),C(!0))},[i]),G=o.useCallback(()=>{var a,u;const s=(u=(a=$.current)==null?void 0:a.current)==null?void 0:u.revisionId;s&&fetch(`/artifact-view/revision${window.location.search}&rev=${encodeURIComponent(s)}`).then(w=>{w.status===401&&O()}).catch(()=>{})},[]),{state:V}=xe(K,G),X=!!S&&!S.paused,I=o.useCallback(s=>{c(!0),fetch(`/artifact-view/revision${window.location.search}&rev=${encodeURIComponent(s)}`).then(a=>{if(a.status===401)return O(s),null;if(!a.ok)throw new Error(`revision fetch failed: ${a.status}`);return a.json()}).then(a=>{a&&(window.sessionStorage.removeItem(A),(a.hostUrl||a.contentUrl)&&(i({hostUrl:a.hostUrl,contentUrl:a.contentUrl}),b(s),C(!0)))}).catch(a=>{window.console&&console.warn("view-older failed",a)})},[i]),D=o.useCallback(s=>{const a=new URLSearchParams({artifactId:e.artifactId,sessionRef:`${e.ns}/${e.name}`});return s&&a.set("rev",s),`/artifact-download?${a.toString()}`},[e.artifactId,e.ns,e.name]),Y=o.useCallback(()=>{var s,a,u,w,p;c(!1),((s=d==null?void 0:d.current)!=null&&s.hostUrl||(a=d==null?void 0:d.current)!=null&&a.contentUrl)&&(i({hostUrl:(u=d==null?void 0:d.current)==null?void 0:u.hostUrl,contentUrl:(w=d==null?void 0:d.current)==null?void 0:w.contentUrl}),b(((p=d==null?void 0:d.current)==null?void 0:p.revisionId)||null),C(!0))},[d,i]);o.useEffect(()=>{const s=window.sessionStorage.getItem(U);s&&(window.sessionStorage.removeItem(U),_(s))},[]),o.useEffect(()=>{if(j&&g.some(s=>s.revisionId===j)){const s=j;_(null),I(s)}},[j,g,I]),o.useEffect(()=>{const s=a=>{var w;if(a.origin!==e.sandboxOrigin||a.source!==((w=n.current)==null?void 0:w.contentWindow))return;const u=a.data;(u==null?void 0:u.ap)!=="annot"||u.cmd!=="send"||!u.bundle||fetch(`/session/${e.ns}/${e.name}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(ue(u.bundle,e.artifactId))}).then(async p=>{if(!p.ok){const R=await p.json().catch(()=>({}));console.error("annotation send failed",p.status,R),y(R.error?R.error:`The server returned ${p.status}.`);return}y(null)}).catch(p=>{console.error("annotation send failed (network)",p),y("The server was unreachable — check your connection and try again.")})};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[e.sandboxOrigin,e.ns,e.name,e.artifactId]);const Q=o.useCallback(async s=>{const a=await fetch(`/session/${e.ns}/${e.name}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:"user_message",artifactId:e.artifactId,payload:{text:s}})});if(!a.ok){const u=await a.json().catch(()=>({}));throw console.error("send failed",a.status,u),new Error((u==null?void 0:u.error)||"send failed")}},[e.ns,e.name,e.artifactId]);return t.jsx(Z,{delayDuration:200,children:t.jsxs("div",{className:"h-screen flex flex-col bg-background text-foreground",children:[t.jsxs("div",{className:"relative flex items-center gap-3 px-3 py-2 min-h-[52px] bg-card border-b border-border",children:[t.jsx(T,{variant:"ghost",size:"icon",className:"h-7 w-7 text-muted-foreground",title:"Toggle revisions",onClick:()=>f(s=>!s),children:t.jsx(ce,{className:"h-4 w-4"})}),t.jsxs("div",{className:"flex flex-col min-w-0 max-w-[46%] shrink",children:[t.jsx("span",{className:"text-[13px] font-semibold leading-[1.25] truncate",title:e.artifactName,children:e.artifactName}),e.artifactDescription&&t.jsx("span",{className:"text-[11px] text-muted-foreground leading-[1.3] truncate",title:e.artifactDescription,children:e.artifactDescription})]}),t.jsx("div",{className:"w-px h-[26px] bg-border shrink-0"}),t.jsx(ve,{status:S}),t.jsxs("div",{className:"flex items-center gap-2 ml-auto shrink-0",children:[t.jsx("a",{href:D(r&&h?h:void 0),download:!0,"aria-label":"Download",title:"Download",className:"flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:t.jsx(P,{className:"h-4 w-4","aria-hidden":"true"})}),t.jsx(Ne,{href:e.backLink,channelKind:e.channelKind}),r&&t.jsxs(T,{variant:"secondary",size:"sm",className:"h-7 gap-1.5 px-2.5",title:"Follow latest",onClick:Y,children:[t.jsx("span",{className:"inline-block h-[7px] w-[7px] rounded-full bg-success"}),"Live"]}),t.jsx(Se,{state:V})]}),X&&t.jsx("div",{"aria-hidden":"true",className:"absolute left-0 right-0 bottom-0 h-[2px] bg-[linear-gradient(90deg,transparent,hsl(var(--success)),transparent)] bg-[length:50%_100%] animate-ap-flow"})]}),t.jsxs("div",{className:"flex flex-1 min-h-0",children:[t.jsx("aside",{className:`bg-card border-r border-border overflow-hidden transition-[width] ${l?"w-[300px] overflow-y-auto":"w-0"}`,children:t.jsx(Ce,{revisions:g,currentRevId:h,onSelect:I,downloadHref:D})}),t.jsxs("div",{className:"relative flex-1 h-full",children:[t.jsx("iframe",{ref:n,className:"border-0 w-full h-full block bg-white",sandbox:"allow-scripts allow-same-origin",src:e.hostUrl||void 0,referrerPolicy:"no-referrer",title:"artifact content"}),!q&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-background text-muted-foreground",children:[t.jsx(E,{className:"h-5 w-5 animate-spin","aria-hidden":"true"}),t.jsx("span",{className:"text-sm",children:"Generating preview…"})]}),M&&t.jsx("div",{className:"absolute inset-x-3 top-3 z-10",children:t.jsxs(ee,{variant:"destructive",className:"relative bg-card pr-9 shadow-lg",children:[t.jsx(te,{children:"Annotation not sent"}),t.jsx(ne,{children:M}),t.jsx("button",{type:"button","aria-label":"Dismiss",onClick:()=>y(null),className:"absolute right-2 top-2 rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground",children:t.jsx(le,{className:"h-4 w-4","aria-hidden":"true"})})]})})]}),e.interactions.length>0&&t.jsx("div",{className:"w-[320px] shrink-0 h-full",children:t.jsx(Ie,{messages:H,canCompose:e.interactions.includes("user_message"),onSend:Q})})]})]})})}se(Ae); diff --git a/pkg/web/webui/webassets/dist/badge.CIlGQKPh.js b/pkg/web/webui/webassets/dist/badge.D9YA1CKv.js similarity index 67% rename from pkg/web/webui/webassets/dist/badge.CIlGQKPh.js rename to pkg/web/webui/webassets/dist/badge.D9YA1CKv.js index f3826925..b4bfb24b 100644 --- a/pkg/web/webui/webassets/dist/badge.CIlGQKPh.js +++ b/pkg/web/webui/webassets/dist/badge.D9YA1CKv.js @@ -1 +1 @@ -import{j as o,ac as n}from"./vendor.C5MRNvUt.js";import{c as a}from"./mount.BOEH1oNx.js";const s=n("inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",outline:"text-foreground"}},defaultVariants:{variant:"default"}});function c({className:r,variant:e,...t}){return o.jsx("div",{className:a(s({variant:e}),r),...t})}export{c as B}; +import{j as o,ad as n}from"./vendor.BHG48rjj.js";import{c as a}from"./mount.Bxdfy1H2.js";const s=n("inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",outline:"text-foreground"}},defaultVariants:{variant:"default"}});function u({className:r,variant:e,...t}){return o.jsx("div",{className:a(s({variant:e}),r),...t})}export{u as B}; diff --git a/pkg/web/webui/webassets/dist/chat-embed.B187PZKG.js b/pkg/web/webui/webassets/dist/chat-embed.zleXAGn8.js similarity index 50% rename from pkg/web/webui/webassets/dist/chat-embed.B187PZKG.js rename to pkg/web/webui/webassets/dist/chat-embed.zleXAGn8.js index ac71ce58..ca00a8e3 100644 --- a/pkg/web/webui/webassets/dist/chat-embed.B187PZKG.js +++ b/pkg/web/webui/webassets/dist/chat-embed.zleXAGn8.js @@ -1 +1 @@ -import{m as s}from"./mount.BOEH1oNx.js";import{j as e}from"./vendor.C5MRNvUt.js";import{S as o,a,C as i}from"./StartupLine.D55RHpak.js";import"./markdown.FOTo-3Mz.js";function m({ns:t,name:r}){return e.jsx(o,{ns:t,name:r,children:e.jsxs("div",{"data-testid":"chat-embed-root",className:"flex h-full min-h-screen flex-col",children:[e.jsx(a,{}),e.jsx(i,{ns:t,name:r,readOnly:!1})]})})}s(m); +import{m as s}from"./mount.Bxdfy1H2.js";import{j as e}from"./vendor.BHG48rjj.js";import{S as o,a,C as i}from"./StartupLine.DKqmGMaA.js";import"./markdown.CZgLwgiZ.js";function m({ns:t,name:r}){return e.jsx(o,{ns:t,name:r,children:e.jsxs("div",{"data-testid":"chat-embed-root",className:"flex h-full min-h-screen flex-col",children:[e.jsx(a,{}),e.jsx(i,{ns:t,name:r,readOnly:!1})]})})}s(m); diff --git a/pkg/web/webui/webassets/dist/health.DtWM4D-G.js b/pkg/web/webui/webassets/dist/health.CH6KPwHp.js similarity index 72% rename from pkg/web/webui/webassets/dist/health.DtWM4D-G.js rename to pkg/web/webui/webassets/dist/health.CH6KPwHp.js index eea297d5..5e292f4a 100644 --- a/pkg/web/webui/webassets/dist/health.DtWM4D-G.js +++ b/pkg/web/webui/webassets/dist/health.CH6KPwHp.js @@ -1 +1 @@ -import{j as e}from"./vendor.C5MRNvUt.js";import{m as r,C as t,b as a,d as n,e as d}from"./mount.BOEH1oNx.js";function m({status:s="ok"}){return e.jsx("div",{className:"min-h-screen flex items-center justify-center bg-background text-foreground",children:e.jsxs(t,{className:"max-w-sm w-full",children:[e.jsx(a,{children:e.jsx(n,{children:"webd"})}),e.jsx(d,{children:e.jsxs("p",{className:"text-sm text-muted-foreground",children:["status: ",s]})})]})})}r(m); +import{j as e}from"./vendor.BHG48rjj.js";import{m as r,C as t,b as a,d as n,e as d}from"./mount.Bxdfy1H2.js";function m({status:s="ok"}){return e.jsx("div",{className:"min-h-screen flex items-center justify-center bg-background text-foreground",children:e.jsxs(t,{className:"max-w-sm w-full",children:[e.jsx(a,{children:e.jsx(n,{children:"webd"})}),e.jsx(d,{children:e.jsxs("p",{className:"text-sm text-muted-foreground",children:["status: ",s]})})]})})}r(m); diff --git a/pkg/web/webui/webassets/dist/identity-link-form.vFiHWBBM.js b/pkg/web/webui/webassets/dist/identity-link-form.Byy38Uh1.js similarity index 65% rename from pkg/web/webui/webassets/dist/identity-link-form.vFiHWBBM.js rename to pkg/web/webui/webassets/dist/identity-link-form.Byy38Uh1.js index 24102f40..f6490aba 100644 --- a/pkg/web/webui/webassets/dist/identity-link-form.vFiHWBBM.js +++ b/pkg/web/webui/webassets/dist/identity-link-form.Byy38Uh1.js @@ -1 +1 @@ -import{B as n,m as i}from"./mount.BOEH1oNx.js";import{j as e}from"./vendor.C5MRNvUt.js";import{I as a}from"./IdentityShell.D-zSSgc9.js";import{N as o}from"./NoticeBanner.lKrMgKCy.js";import{P as s}from"./PatForm.BuKUBhkS.js";function m(t){return e.jsxs(a,{title:`Link ${t.credentialLabel||t.credentialName}`,children:[e.jsx(o,{search:window.location.search}),e.jsx("p",{className:"text-xs text-muted-foreground mb-3 font-mono",children:t.credentialName}),e.jsx(s,{action:`/my/accounts/${encodeURIComponent(t.credentialName)}/link/submit`,submitLabel:"Connect",instructions:t.instructions,docsUrl:t.docsUrl}),e.jsx(n,{asChild:!0,variant:"ghost",size:"sm",className:"mt-3",children:e.jsx("a",{href:"/my/accounts",children:"Cancel"})})]})}i(m); +import{B as n,m as i}from"./mount.Bxdfy1H2.js";import{j as e}from"./vendor.BHG48rjj.js";import{I as a}from"./IdentityShell.BTN6Qj3t.js";import{N as o}from"./NoticeBanner.D2SnK6rv.js";import{P as s}from"./PatForm.3E7vJM76.js";function m(t){return e.jsxs(a,{title:`Link ${t.credentialLabel||t.credentialName}`,children:[e.jsx(o,{search:window.location.search}),e.jsx("p",{className:"text-xs text-muted-foreground mb-3 font-mono",children:t.credentialName}),e.jsx(s,{action:`/my/accounts/${encodeURIComponent(t.credentialName)}/link/submit`,submitLabel:"Connect",instructions:t.instructions,docsUrl:t.docsUrl}),e.jsx(n,{asChild:!0,variant:"ghost",size:"sm",className:"mt-3",children:e.jsx("a",{href:"/my/accounts",children:"Cancel"})})]})}i(m); diff --git a/pkg/web/webui/webassets/dist/identity-link.BpTVpI-G.js b/pkg/web/webui/webassets/dist/identity-link.aV0oIOOl.js similarity index 86% rename from pkg/web/webui/webassets/dist/identity-link.BpTVpI-G.js rename to pkg/web/webui/webassets/dist/identity-link.aV0oIOOl.js index c272c45a..0559df26 100644 --- a/pkg/web/webui/webassets/dist/identity-link.BpTVpI-G.js +++ b/pkg/web/webui/webassets/dist/identity-link.aV0oIOOl.js @@ -1 +1 @@ -import{c as a,B as i,m as c}from"./mount.BOEH1oNx.js";import{j as t,r as l}from"./vendor.C5MRNvUt.js";import{I as r}from"./IdentityShell.D-zSSgc9.js";import{N as d}from"./NoticeBanner.lKrMgKCy.js";import{P as m}from"./PatForm.BuKUBhkS.js";function o(e){l.useEffect(()=>{if(!e)return;const s=()=>fetch(`/heartbeat?session=${encodeURIComponent(e)}`,{method:"POST",credentials:"same-origin"}).catch(()=>{}),n=window.setInterval(s,3e4);return()=>window.clearInterval(n)},[e])}function u({row:e,signedLink:s}){return t.jsxs("li",{className:"flex flex-col gap-1.5 py-3 border-b border-border last:border-0",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[e.iconUrl&&t.jsx("img",{src:e.iconUrl,alt:"",className:"h-4 w-4 rounded-sm"}),t.jsx("span",{className:"font-medium text-sm",children:e.label||e.credentialName}),e.status==="linked"&&t.jsx("span",{className:"ml-auto text-[10px] px-2 py-0.5 rounded-lg bg-success/20 text-success",children:"linked"})]}),e.why&&t.jsx("p",{className:"text-xs text-muted-foreground",children:e.why}),e.status==="missing"&&e.kind==="pat"&&t.jsx(m,{action:"/link/submit",submitLabel:"Save",hidden:{link:s,credential:e.credentialName},instructions:e.instructions,docsUrl:e.docsUrl}),e.status==="missing"&&e.kind==="oauth"&&t.jsx(i,{asChild:!0,variant:"secondary",size:"sm",className:"self-start",children:t.jsxs("a",{href:e.oauthUrl,children:["Connect ",e.label||e.credentialName]})})]})}function x(e){o(e.sessionRef);const s=e.agentName?`for ${e.agentName}`:void 0;return t.jsxs(r,{title:"Connect your credentials",subtitle:s,children:[t.jsx(d,{search:window.location.search}),e.what.length>0&&t.jsx("p",{className:"text-sm text-muted-foreground mb-3",children:e.what.join(" ")}),t.jsx("ul",{className:a("list-none m-0 p-0"),children:e.rows.map(n=>t.jsx(u,{row:n,signedLink:e.signedLink},n.credentialName))}),t.jsxs("p",{className:"text-[11px] text-muted-foreground/60 mt-4",children:["Session: ",e.sessionRef]})]})}c(x); +import{c as a,B as i,m as c}from"./mount.Bxdfy1H2.js";import{j as t,r as l}from"./vendor.BHG48rjj.js";import{I as r}from"./IdentityShell.BTN6Qj3t.js";import{N as d}from"./NoticeBanner.D2SnK6rv.js";import{P as m}from"./PatForm.3E7vJM76.js";function o(e){l.useEffect(()=>{if(!e)return;const s=()=>fetch(`/heartbeat?session=${encodeURIComponent(e)}`,{method:"POST",credentials:"same-origin"}).catch(()=>{}),n=window.setInterval(s,3e4);return()=>window.clearInterval(n)},[e])}function u({row:e,signedLink:s}){return t.jsxs("li",{className:"flex flex-col gap-1.5 py-3 border-b border-border last:border-0",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[e.iconUrl&&t.jsx("img",{src:e.iconUrl,alt:"",className:"h-4 w-4 rounded-sm"}),t.jsx("span",{className:"font-medium text-sm",children:e.label||e.credentialName}),e.status==="linked"&&t.jsx("span",{className:"ml-auto text-[10px] px-2 py-0.5 rounded-lg bg-success/20 text-success",children:"linked"})]}),e.why&&t.jsx("p",{className:"text-xs text-muted-foreground",children:e.why}),e.status==="missing"&&e.kind==="pat"&&t.jsx(m,{action:"/link/submit",submitLabel:"Save",hidden:{link:s,credential:e.credentialName},instructions:e.instructions,docsUrl:e.docsUrl}),e.status==="missing"&&e.kind==="oauth"&&t.jsx(i,{asChild:!0,variant:"secondary",size:"sm",className:"self-start",children:t.jsxs("a",{href:e.oauthUrl,children:["Connect ",e.label||e.credentialName]})})]})}function x(e){o(e.sessionRef);const s=e.agentName?`for ${e.agentName}`:void 0;return t.jsxs(r,{title:"Connect your credentials",subtitle:s,children:[t.jsx(d,{search:window.location.search}),e.what.length>0&&t.jsx("p",{className:"text-sm text-muted-foreground mb-3",children:e.what.join(" ")}),t.jsx("ul",{className:a("list-none m-0 p-0"),children:e.rows.map(n=>t.jsx(u,{row:n,signedLink:e.signedLink},n.credentialName))}),t.jsxs("p",{className:"text-[11px] text-muted-foreground/60 mt-4",children:["Session: ",e.sessionRef]})]})}c(x); diff --git a/pkg/web/webui/webassets/dist/identity-portal.BFvlFsRb.js b/pkg/web/webui/webassets/dist/identity-portal.BWOj-jA-.js similarity index 90% rename from pkg/web/webui/webassets/dist/identity-portal.BFvlFsRb.js rename to pkg/web/webui/webassets/dist/identity-portal.BWOj-jA-.js index 908d6c5a..10466db6 100644 --- a/pkg/web/webui/webassets/dist/identity-portal.BFvlFsRb.js +++ b/pkg/web/webui/webassets/dist/identity-portal.BWOj-jA-.js @@ -1 +1 @@ -import{B as t,m as i}from"./mount.BOEH1oNx.js";import{j as e}from"./vendor.C5MRNvUt.js";import{I as m}from"./IdentityShell.D-zSSgc9.js";import{N as r}from"./NoticeBanner.lKrMgKCy.js";function a({c:s,action:n}){return e.jsxs("li",{className:"flex items-center gap-2 py-2.5 border-b border-border last:border-0",children:[s.iconUrl&&e.jsx("img",{src:s.iconUrl,alt:"",className:"h-4 w-4 rounded-sm"}),e.jsx("span",{className:"text-sm",children:s.label||s.name}),e.jsx("span",{className:"ml-auto",children:n})]})}function o(s){return e.jsxs(m,{title:"Your connected accounts",subtitle:s.displayName?`Signed in as ${s.displayName}`:void 0,children:[e.jsx(r,{search:window.location.search}),e.jsx("h3",{className:"text-xs uppercase tracking-wide text-muted-foreground mt-1 mb-1",children:"Linked"}),s.linked.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing linked yet."}):e.jsx("ul",{className:"list-none m-0 p-0",children:s.linked.map(n=>e.jsx(a,{c:n,action:e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(t,{asChild:!0,variant:"ghost",size:"sm",children:e.jsx("a",{href:n.isOAuth?`/link/oauth/${encodeURIComponent(n.name)}`:`/my/accounts/${encodeURIComponent(n.name)}/link`,children:"Replace"})}),e.jsx("form",{method:"POST",action:`/my/accounts/${encodeURIComponent(n.name)}/revoke`,className:"m-0",children:e.jsx(t,{type:"submit",variant:"ghost",size:"sm",className:"text-destructive",children:"Remove"})})]})},n.name))}),s.suggested.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("h3",{className:"text-xs uppercase tracking-wide text-muted-foreground mt-5 mb-1",children:"Suggested"}),e.jsx("ul",{className:"list-none m-0 p-0",children:s.suggested.map(n=>e.jsx(a,{c:n,action:e.jsx(t,{asChild:!0,variant:"secondary",size:"sm",children:e.jsx("a",{href:n.isOAuth?`/link/oauth/${encodeURIComponent(n.name)}`:`/my/accounts/${encodeURIComponent(n.name)}/link`,children:n.isOAuth?"Connect":"Link"})})},n.name))})]})]})}i(o); +import{B as t,m as i}from"./mount.Bxdfy1H2.js";import{j as e}from"./vendor.BHG48rjj.js";import{I as m}from"./IdentityShell.BTN6Qj3t.js";import{N as r}from"./NoticeBanner.D2SnK6rv.js";function a({c:s,action:n}){return e.jsxs("li",{className:"flex items-center gap-2 py-2.5 border-b border-border last:border-0",children:[s.iconUrl&&e.jsx("img",{src:s.iconUrl,alt:"",className:"h-4 w-4 rounded-sm"}),e.jsx("span",{className:"text-sm",children:s.label||s.name}),e.jsx("span",{className:"ml-auto",children:n})]})}function o(s){return e.jsxs(m,{title:"Your connected accounts",subtitle:s.displayName?`Signed in as ${s.displayName}`:void 0,children:[e.jsx(r,{search:window.location.search}),e.jsx("h3",{className:"text-xs uppercase tracking-wide text-muted-foreground mt-1 mb-1",children:"Linked"}),s.linked.length===0?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing linked yet."}):e.jsx("ul",{className:"list-none m-0 p-0",children:s.linked.map(n=>e.jsx(a,{c:n,action:e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx(t,{asChild:!0,variant:"ghost",size:"sm",children:e.jsx("a",{href:n.isOAuth?`/link/oauth/${encodeURIComponent(n.name)}`:`/my/accounts/${encodeURIComponent(n.name)}/link`,children:"Replace"})}),e.jsx("form",{method:"POST",action:`/my/accounts/${encodeURIComponent(n.name)}/revoke`,className:"m-0",children:e.jsx(t,{type:"submit",variant:"ghost",size:"sm",className:"text-destructive",children:"Remove"})})]})},n.name))}),s.suggested.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("h3",{className:"text-xs uppercase tracking-wide text-muted-foreground mt-5 mb-1",children:"Suggested"}),e.jsx("ul",{className:"list-none m-0 p-0",children:s.suggested.map(n=>e.jsx(a,{c:n,action:e.jsx(t,{asChild:!0,variant:"secondary",size:"sm",children:e.jsx("a",{href:n.isOAuth?`/link/oauth/${encodeURIComponent(n.name)}`:`/my/accounts/${encodeURIComponent(n.name)}/link`,children:n.isOAuth?"Connect":"Link"})})},n.name))})]})]})}i(o); diff --git a/pkg/web/webui/webassets/dist/identity-verify-warn.CN_Li5hi.js b/pkg/web/webui/webassets/dist/identity-verify-warn.rYyUdZk2.js similarity index 79% rename from pkg/web/webui/webassets/dist/identity-verify-warn.CN_Li5hi.js rename to pkg/web/webui/webassets/dist/identity-verify-warn.rYyUdZk2.js index 09f25b6f..9a405fe6 100644 --- a/pkg/web/webui/webassets/dist/identity-verify-warn.CN_Li5hi.js +++ b/pkg/web/webui/webassets/dist/identity-verify-warn.rYyUdZk2.js @@ -1 +1 @@ -import{A as n,a,B as i,m as o}from"./mount.BOEH1oNx.js";import{j as e}from"./vendor.C5MRNvUt.js";import{I as c}from"./IdentityShell.D-zSSgc9.js";function d(t){return e.jsxs(c,{title:"Token could not be verified",children:[e.jsx(n,{className:"mb-4 border-destructive text-destructive",children:e.jsx(a,{children:t.warning})}),e.jsx("p",{className:"text-sm text-muted-foreground mb-4",children:"The provider rejected this token, so it probably won't work. You can store it anyway if you're sure it's correct — for example, a token restricted to specific source IPs."}),e.jsxs("form",{method:"POST",action:t.action,className:"flex gap-2 items-center",children:[Object.entries(t.hidden).map(([r,s])=>e.jsx("input",{type:"hidden",name:r,value:s},r)),e.jsx(i,{type:"submit",variant:"destructive",size:"sm",children:"Store anyway"}),e.jsx(i,{asChild:!0,variant:"ghost",size:"sm",children:e.jsx("a",{href:t.cancelUrl,children:"Go back"})})]})]})}o(d); +import{A as n,a,B as i,m as o}from"./mount.Bxdfy1H2.js";import{j as e}from"./vendor.BHG48rjj.js";import{I as c}from"./IdentityShell.BTN6Qj3t.js";function d(t){return e.jsxs(c,{title:"Token could not be verified",children:[e.jsx(n,{className:"mb-4 border-destructive text-destructive",children:e.jsx(a,{children:t.warning})}),e.jsx("p",{className:"text-sm text-muted-foreground mb-4",children:"The provider rejected this token, so it probably won't work. You can store it anyway if you're sure it's correct — for example, a token restricted to specific source IPs."}),e.jsxs("form",{method:"POST",action:t.action,className:"flex gap-2 items-center",children:[Object.entries(t.hidden).map(([r,s])=>e.jsx("input",{type:"hidden",name:r,value:s},r)),e.jsx(i,{type:"submit",variant:"destructive",size:"sm",children:"Store anyway"}),e.jsx(i,{asChild:!0,variant:"ghost",size:"sm",children:e.jsx("a",{href:t.cancelUrl,children:"Go back"})})]})]})}o(d); diff --git a/pkg/web/webui/webassets/dist/manifest.json b/pkg/web/webui/webassets/dist/manifest.json index dc4be0a9..8eca3afa 100644 --- a/pkg/web/webui/webassets/dist/manifest.json +++ b/pkg/web/webui/webassets/dist/manifest.json @@ -1,7 +1,7 @@ { "identity-link": { "scripts": [ - "/assets/identity-link.BpTVpI-G.js" + "/assets/identity-link.aV0oIOOl.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -9,7 +9,7 @@ }, "identity-link-form": { "scripts": [ - "/assets/identity-link-form.vFiHWBBM.js" + "/assets/identity-link-form.Byy38Uh1.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -17,7 +17,7 @@ }, "identity-portal": { "scripts": [ - "/assets/identity-portal.BFvlFsRb.js" + "/assets/identity-portal.BWOj-jA-.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -25,7 +25,7 @@ }, "identity-verify-warn": { "scripts": [ - "/assets/identity-verify-warn.CN_Li5hi.js" + "/assets/identity-verify-warn.rYyUdZk2.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -33,7 +33,7 @@ }, "admin": { "scripts": [ - "/assets/admin.C9jbAoMt.js" + "/assets/admin.Cq1sS6SU.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -41,7 +41,7 @@ }, "settings": { "scripts": [ - "/assets/settings.Bs9xT9hr.js" + "/assets/settings.DdEnJSsa.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -55,7 +55,7 @@ }, "artifact-view": { "scripts": [ - "/assets/artifact-view.D4Y_FHws.js" + "/assets/artifact-view.CW3Zih2i.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -63,7 +63,7 @@ }, "chat-embed": { "scripts": [ - "/assets/chat-embed.B187PZKG.js" + "/assets/chat-embed.zleXAGn8.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -71,7 +71,7 @@ }, "health": { "scripts": [ - "/assets/health.DtWM4D-G.js" + "/assets/health.CH6KPwHp.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -79,7 +79,7 @@ }, "sessions": { "scripts": [ - "/assets/sessions.DrV0Blkf.js" + "/assets/sessions.D94Qg8pw.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -87,7 +87,7 @@ }, "session-view": { "scripts": [ - "/assets/session-view.dtf-izky.js" + "/assets/session-view.LpTuoXzX.js" ], "css": [ "/assets/mount.BnXLt6oF.css" @@ -95,13 +95,13 @@ }, "mcpui-host": { "scripts": [ - "/assets/mcpui-host.DUWNbgMq.js" + "/assets/mcpui-host.BU3T8o-a.js" ], "css": [] }, "system": { "scripts": [ - "/assets/system.DT_ax8I5.js" + "/assets/system.BiW1Avlo.js" ], "css": [ "/assets/mount.BnXLt6oF.css" diff --git a/pkg/web/webui/webassets/dist/markdown.FOTo-3Mz.js b/pkg/web/webui/webassets/dist/markdown.CZgLwgiZ.js similarity index 92% rename from pkg/web/webui/webassets/dist/markdown.FOTo-3Mz.js rename to pkg/web/webui/webassets/dist/markdown.CZgLwgiZ.js index 663700d1..6a8b290f 100644 --- a/pkg/web/webui/webassets/dist/markdown.FOTo-3Mz.js +++ b/pkg/web/webui/webassets/dist/markdown.CZgLwgiZ.js @@ -1 +1 @@ -import{j as e,au as d,av as l}from"./vendor.C5MRNvUt.js";import{c as n}from"./mount.BOEH1oNx.js";function m({children:r,className:t}){return e.jsx("div",{className:n("leading-relaxed break-words","[&>*:first-child]:mt-0 [&>*:last-child]:mb-0","[&_p]:my-1.5","[&_ul]:my-1.5 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-1.5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:my-0.5","[&_h1]:mb-1.5 [&_h1]:mt-2 [&_h1]:text-base [&_h1]:font-semibold","[&_h2]:mb-1.5 [&_h2]:mt-2 [&_h2]:text-[0.95rem] [&_h2]:font-semibold","[&_h3]:mb-1 [&_h3]:mt-2 [&_h3]:font-semibold","[&_a]:underline [&_a]:underline-offset-2 hover:[&_a]:no-underline","[&_blockquote]:my-1.5 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-2.5 [&_blockquote]:text-muted-foreground","[&_hr]:my-2 [&_hr]:border-border","[&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-black/10 [&_:not(pre)>code]:px-1 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-[0.85em] dark:[&_:not(pre)>code]:bg-white/15","[&_pre]:my-1.5 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-black/10 [&_pre]:p-2.5 [&_pre]:text-[0.8rem] dark:[&_pre]:bg-white/10","[&_pre_code]:font-mono [&_pre_code]:whitespace-pre","[&_table]:w-full [&_table]:border-collapse [&_table]:text-xs","[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold","[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_td]:align-top",t),children:e.jsx(d,{remarkPlugins:[l],components:{a:({node:_,...o})=>e.jsx("a",{...o,target:"_blank",rel:"noopener noreferrer"}),table:({node:_,...o})=>e.jsx("div",{className:"my-1.5 overflow-x-auto",children:e.jsx("table",{...o})})},children:r})})}export{m as M}; +import{j as e,av as d,aw as l}from"./vendor.BHG48rjj.js";import{c as n}from"./mount.Bxdfy1H2.js";function m({children:r,className:t}){return e.jsx("div",{className:n("leading-relaxed break-words","[&>*:first-child]:mt-0 [&>*:last-child]:mb-0","[&_p]:my-1.5","[&_ul]:my-1.5 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-1.5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:my-0.5","[&_h1]:mb-1.5 [&_h1]:mt-2 [&_h1]:text-base [&_h1]:font-semibold","[&_h2]:mb-1.5 [&_h2]:mt-2 [&_h2]:text-[0.95rem] [&_h2]:font-semibold","[&_h3]:mb-1 [&_h3]:mt-2 [&_h3]:font-semibold","[&_a]:underline [&_a]:underline-offset-2 hover:[&_a]:no-underline","[&_blockquote]:my-1.5 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-2.5 [&_blockquote]:text-muted-foreground","[&_hr]:my-2 [&_hr]:border-border","[&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-black/10 [&_:not(pre)>code]:px-1 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-[0.85em] dark:[&_:not(pre)>code]:bg-white/15","[&_pre]:my-1.5 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:bg-black/10 [&_pre]:p-2.5 [&_pre]:text-[0.8rem] dark:[&_pre]:bg-white/10","[&_pre_code]:font-mono [&_pre_code]:whitespace-pre","[&_table]:w-full [&_table]:border-collapse [&_table]:text-xs","[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold","[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1 [&_td]:align-top",t),children:e.jsx(d,{remarkPlugins:[l],components:{a:({node:_,...o})=>e.jsx("a",{...o,target:"_blank",rel:"noopener noreferrer"}),table:({node:_,...o})=>e.jsx("div",{className:"my-1.5 overflow-x-auto",children:e.jsx("table",{...o})})},children:r})})}export{m as M}; diff --git a/pkg/web/webui/webassets/dist/mcpui-host.DUWNbgMq.js b/pkg/web/webui/webassets/dist/mcpui-host.BU3T8o-a.js similarity index 97% rename from pkg/web/webui/webassets/dist/mcpui-host.DUWNbgMq.js rename to pkg/web/webui/webassets/dist/mcpui-host.BU3T8o-a.js index acffc871..c80ae37e 100644 --- a/pkg/web/webui/webassets/dist/mcpui-host.DUWNbgMq.js +++ b/pkg/web/webui/webassets/dist/mcpui-host.BU3T8o-a.js @@ -1 +1 @@ -import{r as c,j as u,aw as T,ax as C}from"./vendor.C5MRNvUt.js";async function I(e,t){console.log("[mcpui-host] UI action received, relaying to the trusted shell:",e);try{window.parent.postMessage({ap:"mcpui",cmd:"action",result:e},t)}catch(n){console.error("[mcpui-host] postMessage to the trusted shell failed",n)}}const x=35e3;let p=0;function R(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():(p+=1,`mcpui-corr-${p}`)}function l(e){return{content:[{type:"text",text:e}],isError:!0}}function M(e){if(!e||typeof e!="object")return!1;const t=e;return typeof t.isError!="boolean"||!Array.isArray(t.content)?!1:t.content.every(n=>!!n&&typeof n=="object"&&n.type==="text"&&typeof n.text=="string")}const d=new Map;let m=null,f=!1;function E(e){if(e.origin!==m||e.source!==window.parent)return;const t=e.data;if(!t||t.ap!=="mcpui"||t.cmd!=="appToolCallReply"||typeof t.corrId!="string")return;const n=d.get(t.corrId);n&&n(M(t.response)?t.response:l("The action returned a malformed response."))}function b(){f||(f=!0,window.addEventListener("message",E))}function v(e,t){m=t,b();const n=R();return new Promise(o=>{let r=!1;const s=a=>{r||(r=!0,clearTimeout(i),d.delete(n),o(a))},i=setTimeout(()=>s(l("The action timed out.")),x);d.set(n,s);try{window.parent.postMessage({ap:"mcpui",cmd:"appToolCall",corrId:n,result:e},t)}catch(a){console.error("[mcpui-host] postMessage to the trusted shell failed",a),s(l("Failed to send the action to the trusted shell."))}})}function A(e,t){return e.type==="tool"?v(e,t):I(e,t)}function L(){const e="(prefers-color-scheme: dark)",[t,n]=c.useState(()=>typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia(e).matches?"dark":"light");return c.useEffect(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;const o=window.matchMedia(e),r=s=>n(s.matches?"dark":"light");return o.addEventListener("change",r),()=>o.removeEventListener("change",r)},[]),t}function U(e){const[t,n]=c.useState({width:0,height:0});return c.useEffect(()=>{const o=e.current;if(!o||typeof ResizeObserver>"u")return;const r=new ResizeObserver(s=>{const i=s[0];i&&n({width:i.contentRect.width,height:i.contentRect.height})});return r.observe(o),()=>r.disconnect()},[e]),t}function j({uri:e,html:t,title:n,trustedOrigin:o}){const r=c.useRef(null),s=L(),{width:i,height:a}=U(r),y=c.useMemo(()=>({uri:e,mimeType:"text/html",text:t}),[e,t]),w=c.useCallback(g=>A(g,o),[o]);return u.jsx("div",{ref:r,style:{width:"100%",height:"100%"},children:u.jsx(T,{resource:y,onUIAction:w,supportedContentTypes:["rawHtml"],htmlProps:{style:{width:"100%",height:"100%",border:"0"},iframeRenderData:{theme:s,containerWidth:i,containerHeight:a},iframeProps:{title:n||"interactive widget"}}})})}function k(){const e=document.getElementById("ap-bootstrap");if(!e||!e.textContent)return{};try{return JSON.parse(e.textContent)}catch{return{}}}const h=document.getElementById("root");if(h){const{uri:e,html:t,trustedOrigin:n}=k();C(h).render(u.jsx(j,{uri:e??"",html:t??"",trustedOrigin:n??""}))} +import{r as c,j as u,ax as T,ay as C}from"./vendor.BHG48rjj.js";async function I(e,t){console.log("[mcpui-host] UI action received, relaying to the trusted shell:",e);try{window.parent.postMessage({ap:"mcpui",cmd:"action",result:e},t)}catch(n){console.error("[mcpui-host] postMessage to the trusted shell failed",n)}}const x=35e3;let p=0;function R(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():(p+=1,`mcpui-corr-${p}`)}function l(e){return{content:[{type:"text",text:e}],isError:!0}}function M(e){if(!e||typeof e!="object")return!1;const t=e;return typeof t.isError!="boolean"||!Array.isArray(t.content)?!1:t.content.every(n=>!!n&&typeof n=="object"&&n.type==="text"&&typeof n.text=="string")}const d=new Map;let m=null,f=!1;function E(e){if(e.origin!==m||e.source!==window.parent)return;const t=e.data;if(!t||t.ap!=="mcpui"||t.cmd!=="appToolCallReply"||typeof t.corrId!="string")return;const n=d.get(t.corrId);n&&n(M(t.response)?t.response:l("The action returned a malformed response."))}function b(){f||(f=!0,window.addEventListener("message",E))}function v(e,t){m=t,b();const n=R();return new Promise(o=>{let r=!1;const s=a=>{r||(r=!0,clearTimeout(i),d.delete(n),o(a))},i=setTimeout(()=>s(l("The action timed out.")),x);d.set(n,s);try{window.parent.postMessage({ap:"mcpui",cmd:"appToolCall",corrId:n,result:e},t)}catch(a){console.error("[mcpui-host] postMessage to the trusted shell failed",a),s(l("Failed to send the action to the trusted shell."))}})}function A(e,t){return e.type==="tool"?v(e,t):I(e,t)}function L(){const e="(prefers-color-scheme: dark)",[t,n]=c.useState(()=>typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia(e).matches?"dark":"light");return c.useEffect(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;const o=window.matchMedia(e),r=s=>n(s.matches?"dark":"light");return o.addEventListener("change",r),()=>o.removeEventListener("change",r)},[]),t}function U(e){const[t,n]=c.useState({width:0,height:0});return c.useEffect(()=>{const o=e.current;if(!o||typeof ResizeObserver>"u")return;const r=new ResizeObserver(s=>{const i=s[0];i&&n({width:i.contentRect.width,height:i.contentRect.height})});return r.observe(o),()=>r.disconnect()},[e]),t}function j({uri:e,html:t,title:n,trustedOrigin:o}){const r=c.useRef(null),s=L(),{width:i,height:a}=U(r),y=c.useMemo(()=>({uri:e,mimeType:"text/html",text:t}),[e,t]),w=c.useCallback(g=>A(g,o),[o]);return u.jsx("div",{ref:r,style:{width:"100%",height:"100%"},children:u.jsx(T,{resource:y,onUIAction:w,supportedContentTypes:["rawHtml"],htmlProps:{style:{width:"100%",height:"100%",border:"0"},iframeRenderData:{theme:s,containerWidth:i,containerHeight:a},iframeProps:{title:n||"interactive widget"}}})})}function k(){const e=document.getElementById("ap-bootstrap");if(!e||!e.textContent)return{};try{return JSON.parse(e.textContent)}catch{return{}}}const h=document.getElementById("root");if(h){const{uri:e,html:t,trustedOrigin:n}=k();C(h).render(u.jsx(j,{uri:e??"",html:t??"",trustedOrigin:n??""}))} diff --git a/pkg/web/webui/webassets/dist/model-name.DtAiu0oG.js b/pkg/web/webui/webassets/dist/model-name.B3Csp-bS.js similarity index 85% rename from pkg/web/webui/webassets/dist/model-name.DtAiu0oG.js rename to pkg/web/webui/webassets/dist/model-name.B3Csp-bS.js index c49fac45..06b5e163 100644 --- a/pkg/web/webui/webassets/dist/model-name.DtAiu0oG.js +++ b/pkg/web/webui/webassets/dist/model-name.B3Csp-bS.js @@ -1 +1 @@ -import{j as i}from"./vendor.C5MRNvUt.js";import{c as o}from"./mount.BOEH1oNx.js";import{B as a}from"./badge.CIlGQKPh.js";function c(e){if(!e)return{name:""};const r=e.indexOf("/");if(r<0)return{name:e};const t=e.slice(0,r),n=e.slice(r+1);if(t!=="openrouter")return{provider:t,name:n};const s=n.indexOf("/");return s<0?{via:"openrouter",name:n}:{via:"openrouter",provider:n.slice(0,s),name:n.slice(s+1)}}function f({model:e,className:r}){if(!e)return null;const{via:t,provider:n,name:s}=c(e);return i.jsxs("span",{className:o("inline-flex items-center gap-1.5",r),children:[t&&i.jsx(a,{variant:"secondary",className:"rounded-full px-2 py-0 text-[10px] font-normal",children:t}),n&&i.jsx("span",{className:"inline-flex items-center rounded-full border bg-background px-2 py-0.5 font-mono text-[10px] text-muted-foreground",children:n}),i.jsx("span",{className:"font-mono text-xs",children:s})]})}export{f as M}; +import{j as i}from"./vendor.BHG48rjj.js";import{c as o}from"./mount.Bxdfy1H2.js";import{B as a}from"./badge.D9YA1CKv.js";function c(e){if(!e)return{name:""};const r=e.indexOf("/");if(r<0)return{name:e};const t=e.slice(0,r),n=e.slice(r+1);if(t!=="openrouter")return{provider:t,name:n};const s=n.indexOf("/");return s<0?{via:"openrouter",name:n}:{via:"openrouter",provider:n.slice(0,s),name:n.slice(s+1)}}function f({model:e,className:r}){if(!e)return null;const{via:t,provider:n,name:s}=c(e);return i.jsxs("span",{className:o("inline-flex items-center gap-1.5",r),children:[t&&i.jsx(a,{variant:"secondary",className:"rounded-full px-2 py-0 text-[10px] font-normal",children:t}),n&&i.jsx("span",{className:"inline-flex items-center rounded-full border bg-background px-2 py-0.5 font-mono text-[10px] text-muted-foreground",children:n}),i.jsx("span",{className:"font-mono text-xs",children:s})]})}export{f as M}; diff --git a/pkg/web/webui/webassets/dist/mount.BOEH1oNx.js b/pkg/web/webui/webassets/dist/mount.Bxdfy1H2.js similarity index 94% rename from pkg/web/webui/webassets/dist/mount.BOEH1oNx.js rename to pkg/web/webui/webassets/dist/mount.Bxdfy1H2.js index 778c84b0..32f5f101 100644 --- a/pkg/web/webui/webassets/dist/mount.BOEH1oNx.js +++ b/pkg/web/webui/webassets/dist/mount.Bxdfy1H2.js @@ -1 +1 @@ -import{aA as F,aB as L,r,j as s,aC as M,ac as i,aD as O,aE as g,aF as U,aG as G,aH as J,aI as x,aJ as b,aK as h,aL as K,aM as l,aN as y,aO as c,aP as u,X as N,aQ as m,aR as f,aS as w,aT as q,aU as v,aV as Q,C as j,aW as R,aX as W,aY as T,aZ as X,a_ as C,a$ as Y,b0 as S,b1 as D,b2 as Z,s as ee,b3 as ae,b4 as k,b5 as te,b6 as se,b7 as oe,b8 as $,b9 as z,ax as re}from"./vendor.C5MRNvUt.js";function o(...e){return F(L(e))}const ne=i("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground shadow hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",outline:"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2",sm:"h-8 rounded-md px-3 text-xs",lg:"h-10 rounded-md px-8",icon:"h-9 w-9"}},defaultVariants:{variant:"default",size:"default"}}),de=r.forwardRef(({className:e,variant:a,size:t,asChild:n=!1,...d},_)=>{const A=n?M:"button";return s.jsx(A,{className:o(ne({variant:a,size:t,className:e})),ref:_,...d})});de.displayName="Button";const ie=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("rounded-xl border bg-card text-card-foreground shadow",e),...a}));ie.displayName="Card";const le=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("flex flex-col space-y-1.5 p-6",e),...a}));le.displayName="CardHeader";const ce=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("font-semibold leading-none tracking-tight",e),...a}));ce.displayName="CardTitle";const me=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("text-sm text-muted-foreground",e),...a}));me.displayName="CardDescription";const fe=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("p-6 pt-0",e),...a}));fe.displayName="CardContent";const pe=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("flex items-center p-6 pt-0",e),...a}));pe.displayName="CardFooter";const ue=i("relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",{variants:{variant:{default:"bg-background text-foreground",destructive:"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive"}},defaultVariants:{variant:"default"}}),ge=r.forwardRef(({className:e,variant:a,...t},n)=>s.jsx("div",{ref:n,role:"alert",className:o(ue({variant:a}),e),...t}));ge.displayName="Alert";const xe=r.forwardRef(({className:e,...a},t)=>s.jsx("h5",{ref:t,className:o("mb-1 font-medium leading-none tracking-tight",e),...a}));xe.displayName="AlertTitle";const be=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("text-sm [&_p]:leading-relaxed",e),...a}));be.displayName="AlertDescription";const ta=U,sa=G,oa=J,he=r.forwardRef(({className:e,sideOffset:a=4,...t},n)=>s.jsx(O,{children:s.jsx(g,{ref:n,sideOffset:a,className:o("z-50 overflow-hidden rounded-md border border-border bg-background px-2 py-1 text-[11px] font-medium text-foreground shadow-[0_6px_20px_rgba(0,0,0,0.55)]",e),...t})}));he.displayName=g.displayName;const ye=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{className:"relative w-full overflow-auto",children:s.jsx("table",{ref:t,className:o("w-full caption-bottom text-sm",e),...a})}));ye.displayName="Table";const Ne=r.forwardRef(({className:e,...a},t)=>s.jsx("thead",{ref:t,className:o("[&_tr]:border-b",e),...a}));Ne.displayName="TableHeader";const we=r.forwardRef(({className:e,...a},t)=>s.jsx("tbody",{ref:t,className:o("[&_tr:last-child]:border-0",e),...a}));we.displayName="TableBody";const ve=r.forwardRef(({className:e,...a},t)=>s.jsx("tfoot",{ref:t,className:o("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",e),...a}));ve.displayName="TableFooter";const je=r.forwardRef(({className:e,...a},t)=>s.jsx("tr",{ref:t,className:o("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",e),...a}));je.displayName="TableRow";const Re=r.forwardRef(({className:e,...a},t)=>s.jsx("th",{ref:t,className:o("h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",e),...a}));Re.displayName="TableHead";const Te=r.forwardRef(({className:e,...a},t)=>s.jsx("td",{ref:t,className:o("p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",e),...a}));Te.displayName="TableCell";const Ce=r.forwardRef(({className:e,...a},t)=>s.jsx("caption",{ref:t,className:o("mt-4 text-sm text-muted-foreground",e),...a}));Ce.displayName="TableCaption";const ra=K,Se=r.forwardRef(({className:e,...a},t)=>s.jsx(x,{ref:t,className:o("inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",e),...a}));Se.displayName=x.displayName;const De=r.forwardRef(({className:e,...a},t)=>s.jsx(b,{ref:t,className:o("inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",e),...a}));De.displayName=b.displayName;const ke=r.forwardRef(({className:e,...a},t)=>s.jsx(h,{ref:t,className:o("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",e),...a}));ke.displayName=h.displayName;const na=w,da=q,$e=y,ia=u,E=r.forwardRef(({className:e,...a},t)=>s.jsx(l,{ref:t,className:o("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...a}));E.displayName=l.displayName;const ze=r.forwardRef(({className:e,children:a,...t},n)=>s.jsxs($e,{children:[s.jsx(E,{}),s.jsxs(c,{ref:n,className:o("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",e),...t,children:[a,s.jsxs(u,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[s.jsx(N,{className:"h-4 w-4"}),s.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));ze.displayName=c.displayName;const Ee=({className:e,...a})=>s.jsx("div",{className:o("flex flex-col space-y-1.5 text-center sm:text-left",e),...a});Ee.displayName="DialogHeader";const Ie=({className:e,...a})=>s.jsx("div",{className:o("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...a});Ie.displayName="DialogFooter";const Be=r.forwardRef(({className:e,...a},t)=>s.jsx(m,{ref:t,className:o("text-lg font-semibold leading-none tracking-tight",e),...a}));Be.displayName=m.displayName;const He=r.forwardRef(({className:e,...a},t)=>s.jsx(f,{ref:t,className:o("text-sm text-muted-foreground",e),...a}));He.displayName=f.displayName;const la=te,ca=se,Pe=r.forwardRef(({className:e,children:a,...t},n)=>s.jsxs(v,{ref:n,className:o("flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",e),...t,children:[a,s.jsx(Q,{asChild:!0,children:s.jsx(j,{className:"h-4 w-4 opacity-50"})})]}));Pe.displayName=v.displayName;const I=r.forwardRef(({className:e,...a},t)=>s.jsx(R,{ref:t,className:o("flex cursor-default items-center justify-center py-1",e),...a,children:s.jsx(W,{className:"h-4 w-4"})}));I.displayName=R.displayName;const B=r.forwardRef(({className:e,...a},t)=>s.jsx(T,{ref:t,className:o("flex cursor-default items-center justify-center py-1",e),...a,children:s.jsx(j,{className:"h-4 w-4"})}));B.displayName=T.displayName;const Ve=r.forwardRef(({className:e,children:a,position:t="popper",...n},d)=>s.jsx(X,{children:s.jsxs(C,{ref:d,className:o("relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",t==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",e),position:t,...n,children:[s.jsx(I,{}),s.jsx(Y,{className:o("p-1",t==="popper"&&"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),children:a}),s.jsx(B,{})]})}));Ve.displayName=C.displayName;const _e=r.forwardRef(({className:e,...a},t)=>s.jsx(S,{ref:t,className:o("px-2 py-1.5 text-sm font-semibold",e),...a}));_e.displayName=S.displayName;const Ae=r.forwardRef(({className:e,children:a,...t},n)=>s.jsxs(D,{ref:n,className:o("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...t,children:[s.jsx("span",{className:"absolute right-2 flex h-3.5 w-3.5 items-center justify-center",children:s.jsx(Z,{children:s.jsx(ee,{className:"h-4 w-4"})})}),s.jsx(ae,{children:a})]}));Ae.displayName=D.displayName;const Fe=r.forwardRef(({className:e,...a},t)=>s.jsx(k,{ref:t,className:o("-mx-1 my-1 h-px bg-muted",e),...a}));Fe.displayName=k.displayName;const ma=w,Le=y,H=r.forwardRef(({className:e,...a},t)=>s.jsx(l,{className:o("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...a,ref:t}));H.displayName=l.displayName;const Me=i("fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",{variants:{side:{top:"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",bottom:"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",left:"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",right:"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm"}},defaultVariants:{side:"right"}}),Oe=r.forwardRef(({side:e="right",className:a,children:t,...n},d)=>s.jsxs(Le,{children:[s.jsx(H,{}),s.jsxs(c,{ref:d,className:o(Me({side:e}),a),...n,children:[s.jsxs(u,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary",children:[s.jsx(N,{className:"h-4 w-4"}),s.jsx("span",{className:"sr-only",children:"Close"})]}),t]})]}));Oe.displayName=c.displayName;const Ue=({className:e,...a})=>s.jsx("div",{className:o("flex flex-col space-y-2 text-center sm:text-left",e),...a});Ue.displayName="SheetHeader";const Ge=r.forwardRef(({className:e,...a},t)=>s.jsx(m,{ref:t,className:o("text-lg font-semibold text-foreground",e),...a}));Ge.displayName=m.displayName;const Je=r.forwardRef(({className:e,...a},t)=>s.jsx(f,{ref:t,className:o("text-sm text-muted-foreground",e),...a}));Je.displayName=f.displayName;const Ke=r.forwardRef(({className:e,align:a="center",sideOffset:t=4,...n},d)=>s.jsx(oe,{children:s.jsx($,{ref:d,align:a,sideOffset:t,className:o("z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",e),...n})}));Ke.displayName=$.displayName;const qe=r.forwardRef(({className:e,type:a,...t},n)=>s.jsx("input",{type:a,className:o("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",e),ref:n,...t}));qe.displayName="Input";const Qe=i("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"),We=r.forwardRef(({className:e,...a},t)=>s.jsx(z,{ref:t,className:o(Qe(),e),...a}));We.displayName=z.displayName;const P="oap.theme";function Xe(){try{const e=window.localStorage.getItem(P);return e==="light"||e==="system"||e==="dark"?e:"system"}catch{return"system"}}function V(e){if(e!=="system")return e;try{return window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}catch{return"dark"}}function p(e){document.documentElement.dataset.theme=e}function fa(e){try{window.localStorage.setItem(P,e)}catch{}const a=V(e);return p(a),a}function Ye(){const e=Xe();if(p(V(e)),e!=="system")return()=>{};try{const a=window.matchMedia("(prefers-color-scheme: light)"),t=()=>p(a.matches?"light":"dark");return a.addEventListener("change",t),()=>a.removeEventListener("change",t)}catch{return()=>{}}}function Ze({children:e}){return r.useEffect(()=>Ye(),[]),s.jsx(s.Fragment,{children:e})}function ea(){const e=document.getElementById("ap-bootstrap");if(!e||!e.textContent)return{};try{return JSON.parse(e.textContent)}catch{return{}}}function pa(e){const a=document.getElementById("root");if(!a)return;const t=ea();re(a).render(s.jsx(r.StrictMode,{children:s.jsx(Ze,{children:s.jsx(e,{...t})})}))}export{ge as A,de as B,ie as C,na as D,Ue as E,Ge as F,xe as G,ta as H,sa as I,oa as J,he as K,Ye as L,We as M,qe as N,la as O,Pe as P,ca as Q,Ve as R,ma as S,ra as T,Ae as U,ne as V,me as W,be as a,le as b,o as c,ce as d,fe as e,V as f,Se as g,De as h,ke as i,ye as j,Ne as k,je as l,pa as m,Re as n,we as o,Te as p,da as q,Xe as r,fa as s,ze as t,Ee as u,Be as v,He as w,Ie as x,ia as y,Oe as z}; +import{aB as L,aC as A,r,j as s,aD as M,ad as i,aE as O,aF as g,aG as U,aH as G,aI as J,aJ as x,aK as b,aL as h,aM as K,aN as l,aO as y,aP as c,aQ as u,X as N,aR as m,aS as f,aT as w,aU as q,aV as v,aW as Q,C as j,aX as R,aY as W,aZ as T,a_ as X,a$ as C,b0 as Y,b1 as S,b2 as D,b3 as Z,t as ee,b4 as ae,b5 as k,b6 as te,b7 as se,b8 as oe,b9 as $,ba as z,ay as re}from"./vendor.BHG48rjj.js";function o(...e){return L(A(e))}const ne=i("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground shadow hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",outline:"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2",sm:"h-8 rounded-md px-3 text-xs",lg:"h-10 rounded-md px-8",icon:"h-9 w-9"}},defaultVariants:{variant:"default",size:"default"}}),de=r.forwardRef(({className:e,variant:a,size:t,asChild:n=!1,...d},_)=>{const F=n?M:"button";return s.jsx(F,{className:o(ne({variant:a,size:t,className:e})),ref:_,...d})});de.displayName="Button";const ie=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("rounded-xl border bg-card text-card-foreground shadow",e),...a}));ie.displayName="Card";const le=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("flex flex-col space-y-1.5 p-6",e),...a}));le.displayName="CardHeader";const ce=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("font-semibold leading-none tracking-tight",e),...a}));ce.displayName="CardTitle";const me=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("text-sm text-muted-foreground",e),...a}));me.displayName="CardDescription";const fe=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("p-6 pt-0",e),...a}));fe.displayName="CardContent";const pe=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("flex items-center p-6 pt-0",e),...a}));pe.displayName="CardFooter";const ue=i("relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",{variants:{variant:{default:"bg-background text-foreground",destructive:"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive"}},defaultVariants:{variant:"default"}}),ge=r.forwardRef(({className:e,variant:a,...t},n)=>s.jsx("div",{ref:n,role:"alert",className:o(ue({variant:a}),e),...t}));ge.displayName="Alert";const xe=r.forwardRef(({className:e,...a},t)=>s.jsx("h5",{ref:t,className:o("mb-1 font-medium leading-none tracking-tight",e),...a}));xe.displayName="AlertTitle";const be=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{ref:t,className:o("text-sm [&_p]:leading-relaxed",e),...a}));be.displayName="AlertDescription";const ta=U,sa=G,oa=J,he=r.forwardRef(({className:e,sideOffset:a=4,...t},n)=>s.jsx(O,{children:s.jsx(g,{ref:n,sideOffset:a,className:o("z-50 overflow-hidden rounded-md border border-border bg-background px-2 py-1 text-[11px] font-medium text-foreground shadow-[0_6px_20px_rgba(0,0,0,0.55)]",e),...t})}));he.displayName=g.displayName;const ye=r.forwardRef(({className:e,...a},t)=>s.jsx("div",{className:"relative w-full overflow-auto",children:s.jsx("table",{ref:t,className:o("w-full caption-bottom text-sm",e),...a})}));ye.displayName="Table";const Ne=r.forwardRef(({className:e,...a},t)=>s.jsx("thead",{ref:t,className:o("[&_tr]:border-b",e),...a}));Ne.displayName="TableHeader";const we=r.forwardRef(({className:e,...a},t)=>s.jsx("tbody",{ref:t,className:o("[&_tr:last-child]:border-0",e),...a}));we.displayName="TableBody";const ve=r.forwardRef(({className:e,...a},t)=>s.jsx("tfoot",{ref:t,className:o("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",e),...a}));ve.displayName="TableFooter";const je=r.forwardRef(({className:e,...a},t)=>s.jsx("tr",{ref:t,className:o("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",e),...a}));je.displayName="TableRow";const Re=r.forwardRef(({className:e,...a},t)=>s.jsx("th",{ref:t,className:o("h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",e),...a}));Re.displayName="TableHead";const Te=r.forwardRef(({className:e,...a},t)=>s.jsx("td",{ref:t,className:o("p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",e),...a}));Te.displayName="TableCell";const Ce=r.forwardRef(({className:e,...a},t)=>s.jsx("caption",{ref:t,className:o("mt-4 text-sm text-muted-foreground",e),...a}));Ce.displayName="TableCaption";const ra=K,Se=r.forwardRef(({className:e,...a},t)=>s.jsx(x,{ref:t,className:o("inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",e),...a}));Se.displayName=x.displayName;const De=r.forwardRef(({className:e,...a},t)=>s.jsx(b,{ref:t,className:o("inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",e),...a}));De.displayName=b.displayName;const ke=r.forwardRef(({className:e,...a},t)=>s.jsx(h,{ref:t,className:o("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",e),...a}));ke.displayName=h.displayName;const na=w,da=q,$e=y,ia=u,E=r.forwardRef(({className:e,...a},t)=>s.jsx(l,{ref:t,className:o("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...a}));E.displayName=l.displayName;const ze=r.forwardRef(({className:e,children:a,...t},n)=>s.jsxs($e,{children:[s.jsx(E,{}),s.jsxs(c,{ref:n,className:o("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",e),...t,children:[a,s.jsxs(u,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",children:[s.jsx(N,{className:"h-4 w-4"}),s.jsx("span",{className:"sr-only",children:"Close"})]})]})]}));ze.displayName=c.displayName;const Ee=({className:e,...a})=>s.jsx("div",{className:o("flex flex-col space-y-1.5 text-center sm:text-left",e),...a});Ee.displayName="DialogHeader";const Ie=({className:e,...a})=>s.jsx("div",{className:o("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",e),...a});Ie.displayName="DialogFooter";const Be=r.forwardRef(({className:e,...a},t)=>s.jsx(m,{ref:t,className:o("text-lg font-semibold leading-none tracking-tight",e),...a}));Be.displayName=m.displayName;const He=r.forwardRef(({className:e,...a},t)=>s.jsx(f,{ref:t,className:o("text-sm text-muted-foreground",e),...a}));He.displayName=f.displayName;const la=te,ca=se,Pe=r.forwardRef(({className:e,children:a,...t},n)=>s.jsxs(v,{ref:n,className:o("flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",e),...t,children:[a,s.jsx(Q,{asChild:!0,children:s.jsx(j,{className:"h-4 w-4 opacity-50"})})]}));Pe.displayName=v.displayName;const I=r.forwardRef(({className:e,...a},t)=>s.jsx(R,{ref:t,className:o("flex cursor-default items-center justify-center py-1",e),...a,children:s.jsx(W,{className:"h-4 w-4"})}));I.displayName=R.displayName;const B=r.forwardRef(({className:e,...a},t)=>s.jsx(T,{ref:t,className:o("flex cursor-default items-center justify-center py-1",e),...a,children:s.jsx(j,{className:"h-4 w-4"})}));B.displayName=T.displayName;const Ve=r.forwardRef(({className:e,children:a,position:t="popper",...n},d)=>s.jsx(X,{children:s.jsxs(C,{ref:d,className:o("relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",t==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",e),position:t,...n,children:[s.jsx(I,{}),s.jsx(Y,{className:o("p-1",t==="popper"&&"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),children:a}),s.jsx(B,{})]})}));Ve.displayName=C.displayName;const _e=r.forwardRef(({className:e,...a},t)=>s.jsx(S,{ref:t,className:o("px-2 py-1.5 text-sm font-semibold",e),...a}));_e.displayName=S.displayName;const Fe=r.forwardRef(({className:e,children:a,...t},n)=>s.jsxs(D,{ref:n,className:o("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...t,children:[s.jsx("span",{className:"absolute right-2 flex h-3.5 w-3.5 items-center justify-center",children:s.jsx(Z,{children:s.jsx(ee,{className:"h-4 w-4"})})}),s.jsx(ae,{children:a})]}));Fe.displayName=D.displayName;const Le=r.forwardRef(({className:e,...a},t)=>s.jsx(k,{ref:t,className:o("-mx-1 my-1 h-px bg-muted",e),...a}));Le.displayName=k.displayName;const ma=w,Ae=y,H=r.forwardRef(({className:e,...a},t)=>s.jsx(l,{className:o("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",e),...a,ref:t}));H.displayName=l.displayName;const Me=i("fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",{variants:{side:{top:"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",bottom:"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",left:"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",right:"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm"}},defaultVariants:{side:"right"}}),Oe=r.forwardRef(({side:e="right",className:a,children:t,...n},d)=>s.jsxs(Ae,{children:[s.jsx(H,{}),s.jsxs(c,{ref:d,className:o(Me({side:e}),a),...n,children:[s.jsxs(u,{className:"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary",children:[s.jsx(N,{className:"h-4 w-4"}),s.jsx("span",{className:"sr-only",children:"Close"})]}),t]})]}));Oe.displayName=c.displayName;const Ue=({className:e,...a})=>s.jsx("div",{className:o("flex flex-col space-y-2 text-center sm:text-left",e),...a});Ue.displayName="SheetHeader";const Ge=r.forwardRef(({className:e,...a},t)=>s.jsx(m,{ref:t,className:o("text-lg font-semibold text-foreground",e),...a}));Ge.displayName=m.displayName;const Je=r.forwardRef(({className:e,...a},t)=>s.jsx(f,{ref:t,className:o("text-sm text-muted-foreground",e),...a}));Je.displayName=f.displayName;const Ke=r.forwardRef(({className:e,align:a="center",sideOffset:t=4,...n},d)=>s.jsx(oe,{children:s.jsx($,{ref:d,align:a,sideOffset:t,className:o("z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",e),...n})}));Ke.displayName=$.displayName;const qe=r.forwardRef(({className:e,type:a,...t},n)=>s.jsx("input",{type:a,className:o("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",e),ref:n,...t}));qe.displayName="Input";const Qe=i("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"),We=r.forwardRef(({className:e,...a},t)=>s.jsx(z,{ref:t,className:o(Qe(),e),...a}));We.displayName=z.displayName;const P="oap.theme";function Xe(){try{const e=window.localStorage.getItem(P);return e==="light"||e==="system"||e==="dark"?e:"system"}catch{return"system"}}function V(e){if(e!=="system")return e;try{return window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}catch{return"dark"}}function p(e){document.documentElement.dataset.theme=e}function fa(e){try{window.localStorage.setItem(P,e)}catch{}const a=V(e);return p(a),a}function Ye(){const e=Xe();if(p(V(e)),e!=="system")return()=>{};try{const a=window.matchMedia("(prefers-color-scheme: light)"),t=()=>p(a.matches?"light":"dark");return a.addEventListener("change",t),()=>a.removeEventListener("change",t)}catch{return()=>{}}}function Ze({children:e}){return r.useEffect(()=>Ye(),[]),s.jsx(s.Fragment,{children:e})}function ea(){const e=document.getElementById("ap-bootstrap");if(!e||!e.textContent)return{};try{return JSON.parse(e.textContent)}catch{return{}}}function pa(e){const a=document.getElementById("root");if(!a)return;const t=ea();re(a).render(s.jsx(r.StrictMode,{children:s.jsx(Ze,{children:s.jsx(e,{...t})})}))}export{ge as A,de as B,ie as C,na as D,Ue as E,Ge as F,xe as G,ta as H,sa as I,oa as J,he as K,Ye as L,We as M,qe as N,la as O,Pe as P,ca as Q,Ve as R,ma as S,ra as T,Fe as U,ne as V,me as W,be as a,le as b,o as c,ce as d,fe as e,V as f,Se as g,De as h,ke as i,ye as j,Ne as k,je as l,pa as m,Re as n,we as o,Te as p,da as q,Xe as r,fa as s,ze as t,Ee as u,Be as v,He as w,Ie as x,ia as y,Oe as z}; diff --git a/pkg/web/webui/webassets/dist/session-view.dtf-izky.js b/pkg/web/webui/webassets/dist/session-view.LpTuoXzX.js similarity index 93% rename from pkg/web/webui/webassets/dist/session-view.dtf-izky.js rename to pkg/web/webui/webassets/dist/session-view.LpTuoXzX.js index 4d034f8b..ea073fdf 100644 --- a/pkg/web/webui/webassets/dist/session-view.dtf-izky.js +++ b/pkg/web/webui/webassets/dist/session-view.LpTuoXzX.js @@ -1,2 +1,2 @@ -import{H as ee,I as te,J as re,K as ne,c as N,B as se,m as ae}from"./mount.BOEH1oNx.js";import{r as c,j as t,a6 as z,ao as q,x as oe,y as P,z as ie,af as K,an as W,ag as V,c as ce,ae as le,ap as de,at as ue,ar as fe,E as xe,as as me,ah as pe}from"./vendor.C5MRNvUt.js";import{M as he}from"./markdown.FOTo-3Mz.js";function H(e){const r=e.payload??{};return{kind:"mcp_ui_action",payload:{type:e.type,...r}}}function ge(e){if(e==null)return"";if(typeof e=="string"){try{const r=JSON.parse(e);if(typeof r=="string")return r}catch{}return e}try{return JSON.stringify(e)}catch{return String(e)}}function be(e){switch(e.status){case"ok":return{content:[{type:"text",text:ge(e.result)}],isError:e.isError===!0};case"requires_approval":return{content:[{type:"text",text:e.message||"Approval requested — pending; ask in chat for the result."}],isError:!0};case"denied":return{content:[{type:"text",text:e.message||"Not authorized to run this action."}],isError:!0};case"rate_limited":return{content:[{type:"text",text:e.message||"Rate limit reached; try again shortly."}],isError:!0};case"error":default:return{content:[{type:"text",text:e.message||"The action could not be completed."}],isError:!0}}}function we(e){try{const r=JSON.parse(e);return!r||typeof r.type!="string"?null:r}catch{return null}}function ye(e,r,n){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/session-view/${encodeURIComponent(r)}/${encodeURIComponent(n)}/live`}const A=3;function je(e,r,n){const[l,u]=c.useState("connecting"),x=c.useRef(n);return x.current=n,c.useEffect(()=>{let p=null,m,y=!1,j=0;const v=()=>{y||(j+=1,u(j>=A?"offline":"reconnecting"),m=window.setTimeout(S,2e3))},S=()=>{if(!y){u(j===0?"connecting":j>=A?"offline":"reconnecting");try{p=new WebSocket(ye(window.location,e,r))}catch{v();return}p.onopen=()=>{j=0,u("connected")},p.onmessage=I=>{const T=we(I.data);T&&x.current(T)},p.onerror=()=>{y||u(j>=A?"offline":"reconnecting")},p.onclose=v}};return S(),()=>{y=!0,m&&window.clearTimeout(m),p&&p.close()}},[e,r]),{state:l}}const Ne={connected:{Icon:ie,color:"text-success",spin:!1,tip:"Live — connected"},connecting:{Icon:P,color:"text-warning",spin:!0,tip:"Connecting…"},reconnecting:{Icon:P,color:"text-warning",spin:!0,tip:"Reconnecting…"},offline:{Icon:oe,color:"text-destructive",spin:!1,tip:"Offline — retrying"}};function ke({state:e}){const r=Ne[e],{Icon:n}=r;return t.jsxs(te,{children:[t.jsx(re,{asChild:!0,children:t.jsx("div",{className:`flex h-7 w-7 items-center justify-center rounded-md cursor-default ${r.color}`,children:t.jsx(n,{className:`h-[18px] w-[18px] ${r.spin?"animate-spin":""}`,"aria-hidden":"true"})})}),t.jsx(ne,{children:r.tip})]})}function D(e){return`widget:${e}`}function ve(e){return e.startsWith("widget:")?e.slice(7):""}function Se({id:e,hostUrl:r,registerFrame:n}){return t.jsx("div",{className:"mx-auto w-full max-w-[85%] rounded-lg border border-border overflow-hidden bg-card/60",style:{height:480},children:t.jsx("iframe",{ref:l=>n(e,l),sandbox:"allow-scripts allow-same-origin",src:r,referrerPolicy:"no-referrer",title:"interactive widget",className:"w-full h-full border-0 block bg-white"})})}function Y(e){return e==="done"||e==="complete"||e==="completed"||e==="skipped"}function Ie(e,r){if(Y(e))return t.jsx(K,{className:"h-3.5 w-3.5 shrink-0 text-success","aria-hidden":"true"});switch(e){case"in_progress":case"active":case"running":return t.jsx(P,{className:N("h-3.5 w-3.5 shrink-0 text-primary",r&&"animate-spin"),"aria-hidden":"true"});case"blocked":case"failed":case"error":return t.jsx(pe,{className:"h-3.5 w-3.5 shrink-0 text-destructive","aria-hidden":"true"});default:return t.jsx(me,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground","aria-hidden":"true"})}}function Te({plan:e,live:r}){const n=e.items??[];return t.jsxs("div",{className:"mx-auto w-full max-w-[85%] rounded-lg border border-border bg-card/60 px-3 py-2",children:[t.jsxs("div",{className:"flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1.5",children:[t.jsx(fe,{className:"h-3.5 w-3.5","aria-hidden":"true"}),t.jsxs("span",{children:["Plan",n.length>0?` · ${n.length} step${n.length===1?"":"s"}`:""]})]}),n.length===0?t.jsx("div",{className:"text-xs text-muted-foreground",children:"Plan updated."}):t.jsx("ul",{className:"flex flex-col gap-1",children:n.map(l=>{const u=Y(l.status);return t.jsxs("li",{className:"flex items-start gap-1.5 text-xs leading-snug",children:[t.jsx("span",{className:"mt-0.5",children:Ie(l.status,r)}),t.jsx("span",{className:N("break-words",u?"text-muted-foreground":"text-card-foreground"),children:l.label})]},l.id)})})]})}function Ce({isUser:e,children:r}){return t.jsx("div",{className:N("flex",e?"justify-end":"justify-start"),children:t.jsx("div",{className:N("max-w-[75%] rounded-2xl px-3.5 py-2 text-sm leading-relaxed",e?"bg-primary text-primary-foreground rounded-br-sm":"bg-card border border-border text-card-foreground rounded-bl-sm"),children:r})})}function $e({ns:e,name:r,attachments:n}){return t.jsx("div",{className:"flex flex-wrap gap-1.5",children:n.map((l,u)=>{const x=new URLSearchParams({artifactId:l.artifactId??"",sessionRef:`${e}/${r}`,fn:l.filename});return t.jsxs("a",{href:`/artifact-download?${x.toString()}`,download:l.filename,className:"inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-2.5 py-1 text-xs text-card-foreground hover:bg-accent hover:text-accent-foreground",children:[t.jsx(xe,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"break-all",children:l.filename})]},u)})})}function Ee({line:e,ns:r,name:n,live:l,registerWidgetFrame:u}){if(e.role==="widget")return e.hostUrl?t.jsx(Se,{id:e.id,hostUrl:e.hostUrl,registerFrame:u}):null;if(e.role==="plan"&&e.plan)return t.jsx(Te,{plan:e.plan,live:l});if(e.role==="system"||e.role==="error")return t.jsx("div",{className:N("mx-auto max-w-[85%] text-center text-xs py-1",e.role==="error"?"text-destructive":"text-muted-foreground"),children:t.jsx("span",{className:"whitespace-pre-wrap break-words",children:e.text})});const x=e.attachments??[];return t.jsxs("div",{className:N("flex flex-col gap-1",e.role==="user"?"items-end":"items-start"),children:[t.jsx(Ce,{isUser:e.role==="user",children:t.jsx(he,{children:e.text})}),x.length>0&&t.jsx($e,{ns:r,name:n,attachments:x})]})}function Re(){return t.jsx("div",{className:"flex justify-start",role:"status","aria-label":"Agent is working",children:t.jsx("div",{className:"flex items-center gap-2 rounded-2xl rounded-bl-sm border border-border bg-card px-3.5 py-2.5",children:t.jsx("div",{className:"flex items-center gap-1",children:[0,150,300].map(e=>t.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:`${e}ms`}},e))})})})}function Oe({text:e}){return t.jsx("div",{className:"flex items-center justify-center text-[11px] text-muted-foreground py-0.5",children:t.jsx("span",{className:"animate-pulse",children:e})})}const Le={critical:{Icon:le,color:"text-destructive"},privacy:{Icon:ce,color:"text-warning"},degraded:{Icon:q,color:"text-warning"},waiting:{Icon:V,color:"text-muted-foreground"},routine:{Icon:W,color:"text-muted-foreground"},housekeeping:{Icon:W,color:"text-muted-foreground"},resolved:{Icon:K,color:"text-success"}},_e={Icon:W,color:"text-muted-foreground"},Fe={money:de,clock:V};function Ue({notice:e}){var l;const r=Le[e.tone??""]??_e,n=Fe[e.glyph??""]??r.Icon;return t.jsxs("div",{className:"flex flex-col gap-1.5 px-4 py-2 text-xs border-t border-border bg-card","data-testid":"notice-banner","data-tone":e.tone||"",children:[t.jsxs("div",{className:N("flex items-center gap-2 font-medium",r.color),children:[t.jsx(n,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"break-words text-card-foreground",children:e.lead})]}),e.body&&t.jsx("div",{className:"text-muted-foreground",children:e.body}),(l=e.fields)==null?void 0:l.map((u,x)=>t.jsxs("div",{className:"leading-snug",children:[t.jsxs("span",{className:"font-medium text-card-foreground",children:[u.label,":"]})," ",t.jsx("span",{className:"text-muted-foreground",children:u.value})]},`${u.label}:${x}`)),e.excerpt&&t.jsx("pre",{className:"max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-background px-2 py-1.5 text-[11px] leading-snug text-card-foreground",children:t.jsxs("code",{children:[e.excerpt.label?`${e.excerpt.label}: -`:"",e.excerpt.content]})}),e.nextStep&&t.jsx("div",{className:"font-medium text-card-foreground",children:e.nextStep})]})}function Ae({disabled:e,placeholder:r,onSend:n}){const[l,u]=c.useState(""),x=()=>{const m=l.trim();!m||e||(n(m),u(""))},p=m=>{m.key==="Enter"&&!m.shiftKey&&(m.preventDefault(),x())};return t.jsxs("div",{className:"flex items-end gap-2 px-4 py-3 border-t border-border bg-card",children:[t.jsx("textarea",{rows:1,value:l,disabled:e,placeholder:r,onChange:m=>u(m.target.value),onKeyDown:p,className:N("flex-1 resize-none max-h-40 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm","placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring","disabled:cursor-not-allowed disabled:opacity-50")}),t.jsx(se,{size:"icon",className:"h-9 w-9 shrink-0",disabled:e||!l.trim(),onClick:x,title:"Send",children:t.jsx(ue,{className:"h-4 w-4"})})]})}function De(e,r){return e.map(n=>n.kind==="plan"&&n.plan?{id:`plan:${n.plan.planName}`,role:"plan",text:"",plan:n.plan}:{id:r(),role:n.role==="user"?"user":"agent",text:n.text??""})}function Pe(e){const{ns:r,name:n}=e,[l,u]=c.useState([]),[x,p]=c.useState(!1),[m,y]=c.useState(""),[j,v]=c.useState(!1),[S,I]=c.useState(null),[T,_]=c.useState(null),g=c.useCallback(s=>{_(null),I(s)},[]),k=c.useCallback(s=>{I(null),_(s!=null&&s.lead?s:null)},[]),C=c.useRef(!1),G=c.useRef(0),E=c.useCallback(()=>`l${G.current++}`,[]),$=c.useCallback((s,i,a)=>{u(o=>[...o,{id:E(),role:s,text:i,attachments:a}])},[E]),X=c.useCallback(s=>{if(s.type==="snapshot"){u(a=>[...De(s.history??[],E),...a.filter(o=>o.role==="widget")]),C.current=!1,p(!1),y("");return}const i=s.event;if(i)switch(i.kind){case"user_message":{const a=i.payload,o=(a.attachments??[]).filter(d=>d.artifactId);$("agent",a.text,o.length?o:void 0);break}case"user_echo":{const a=i.payload;$("user",a.text);break}case"plan_update":{const a=i.payload;C.current&&p(!0);const o=`plan:${a.planName}`;u(d=>{const f=d.findIndex(b=>b.role==="plan"&&b.id===o);if(f>=0){const b=d.slice();return b[f]={...d[f],plan:a},b}return[...d,{id:o,role:"plan",text:"",plan:a}]});break}case"notification":{if(!C.current)break;const a=i.payload;p(!0),y(a.text);break}case"turn_activity":{i.payload.active?(C.current=!0,p(!0)):(C.current=!1,p(!1),y(""));break}case"widget_offer":{const a=i.payload;if(!a.hostUrl)break;const o=D(a.artifactId);u(d=>d.some(f=>f.id===o)?d:[...d,{id:o,role:"widget",text:"",hostUrl:a.hostUrl}]);break}}},[$,E]);c.useEffect(()=>{const s=e.activeWidgets??[];s.length!==0&&u(i=>{const a=new Set(i.map(d=>d.id)),o=s.filter(d=>!a.has(D(d.artifactId))).map(d=>({id:D(d.artifactId),role:"widget",text:"",hostUrl:d.hostUrl}));return o.length?[...i,...o]:i})},[]);const F=c.useRef(new Map),Q=c.useCallback((s,i)=>{i?F.current.set(s,i):F.current.delete(s)},[]),{state:R}=je(r,n,X),U=c.useRef(!1);c.useEffect(()=>{if(R==="connected"){U.current=!1;return}R==="offline"&&!U.current&&(U.current=!0,$("system","Connection lost — retrying…"))},[R,$]);const Z=c.useCallback(async s=>{I(null),_(null),v(!0);try{const i=await fetch(`/session/${r}/${n}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:"user_message",payload:{text:s}})}),a=await i.json().catch(()=>({}));if(!i.ok){const o=a.error||`Failed to send (${i.status}).`;g(o);return}k(a.notice)}catch{g("Network error while sending your message.")}finally{v(!1)}},[r,n,g,k]),M=c.useCallback(async(s,i,a,o)=>{var f,b;const d=h=>{if(e.sandboxOrigin)try{a.postMessage({ap:"mcpui",cmd:"appToolCallReply",corrId:i,response:h},e.sandboxOrigin)}catch(O){console.error("app-tool-call reply postMessage failed",O)}};try{const h=await fetch(`/session/${r}/${n}/app-tool-call`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({toolName:(f=s.payload)==null?void 0:f.toolName,args:((b=s.payload)==null?void 0:b.params)??{},artifactId:o})});if(!h.ok){const w=await h.json().catch(()=>({}));console.error("app-tool-call send failed",h.status,w),g(w.error||`The widget action could not be sent (${h.status}).`),d({content:[{type:"text",text:"The action could not be sent."}],isError:!0});return}const O=await h.json();if(O.status==="not_found"){try{const w=await fetch(`/session/${r}/${n}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(H(s))});if(w.ok){const L=await w.json().catch(()=>({}));k(L.notice)}else{const L=await w.json().catch(()=>({}));console.error("widget action send failed (not_found fallback)",w.status,L),g(L.error||`The widget action could not be sent (${w.status}).`)}}catch(w){console.error("widget action send failed (not_found fallback, network)",w),g("The server was unreachable while sending the widget action.")}d({content:[{type:"text",text:"Sent to the agent."}],isError:!1});return}d(be(O))}catch(h){console.error("app-tool-call send failed (network)",h),g("The server was unreachable while sending the widget action."),d({content:[{type:"text",text:"The server was unreachable."}],isError:!0})}},[r,n,e.sandboxOrigin,g,k]);c.useEffect(()=>{const s=i=>{var d;if(!e.sandboxOrigin||i.origin!==e.sandboxOrigin)return;const a=Array.from(F.current.entries()).find(([,f])=>f.contentWindow===i.source);if(!a)return;const o=i.data;if((o==null?void 0:o.ap)==="mcpui"){if(o.cmd==="appToolCall"&&typeof o.corrId=="string"&&((d=o.result)==null?void 0:d.type)==="tool"&&i.source){M(o.result,o.corrId,i.source,ve(a[0]));return}o.cmd!=="action"||!o.result||fetch(`/session/${r}/${n}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(H(o.result))}).then(async f=>{if(!f.ok){const h=await f.json().catch(()=>({}));console.error("widget action send failed",f.status,h),g(h.error||`The widget action could not be sent (${f.status}).`);return}const b=await f.json().catch(()=>({}));k(b.notice)}).catch(f=>{console.error("widget action send failed (network)",f),g("The server was unreachable while sending the widget action.")})}};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[e.sandboxOrigin,r,n,M,g,k]);const J=c.useRef(null),B=c.useRef(null);return c.useEffect(()=>{var a,o;const s=B.current;!s||!(s.scrollHeight-s.scrollTop-s.clientHeight<120)||(o=(a=J.current)==null?void 0:a.scrollIntoView)==null||o.call(a,{block:"end"})},[l.length,x,m]),t.jsx(ee,{delayDuration:200,children:t.jsxs("div",{className:"h-screen flex flex-col bg-background text-foreground",children:[t.jsxs("div",{className:"flex items-center gap-3 px-3 py-2 min-h-[52px] bg-card border-b border-border",children:[t.jsx(z,{className:"h-4 w-4 text-muted-foreground shrink-0","aria-hidden":"true"}),t.jsxs("span",{className:"text-[13px] font-semibold truncate",title:`${r}/${n}`,children:[r,"/",n]}),t.jsx("div",{className:"ml-auto shrink-0",children:t.jsx(ke,{state:R})})]}),t.jsx("div",{ref:B,className:"flex-1 min-h-0 overflow-y-auto px-4 py-4",children:t.jsxs("div",{className:"flex flex-col gap-2.5 max-w-3xl mx-auto",children:[l.length===0&&t.jsxs("div",{className:"flex flex-1 flex-col items-center justify-center gap-2 py-16 text-muted-foreground",children:[t.jsx(z,{className:"h-8 w-8","aria-hidden":"true"}),t.jsx("span",{className:"text-sm",children:"No messages yet."})]}),l.map(s=>t.jsx("div",{"data-testid":"session-view-line","data-role":s.role,children:t.jsx(Ee,{line:s,ns:r,name:n,live:x,registerWidgetFrame:Q})},s.id)),x&&!m&&t.jsx(Re,{}),x&&m&&t.jsx(Oe,{text:m}),t.jsx("div",{ref:J})]})}),S&&t.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 text-xs text-destructive border-t border-border bg-card",children:[t.jsx(q,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"break-words",children:S})]}),T&&t.jsx(Ue,{notice:T}),t.jsx(Ae,{disabled:j,placeholder:"Message the agent…",onSend:Z})]})})}ae(Pe); +import{H as ee,I as te,J as re,K as ne,c as N,B as se,m as ae}from"./mount.Bxdfy1H2.js";import{r as c,j as t,a7 as z,ap as q,y as oe,z as P,E as ie,ag as K,ao as W,ah as V,c as ce,af as le,aq as de,au as ue,as as fe,I as xe,at as me,ai as pe}from"./vendor.BHG48rjj.js";import{M as he}from"./markdown.CZgLwgiZ.js";function H(e){const r=e.payload??{};return{kind:"mcp_ui_action",payload:{type:e.type,...r}}}function ge(e){if(e==null)return"";if(typeof e=="string"){try{const r=JSON.parse(e);if(typeof r=="string")return r}catch{}return e}try{return JSON.stringify(e)}catch{return String(e)}}function be(e){switch(e.status){case"ok":return{content:[{type:"text",text:ge(e.result)}],isError:e.isError===!0};case"requires_approval":return{content:[{type:"text",text:e.message||"Approval requested — pending; ask in chat for the result."}],isError:!0};case"denied":return{content:[{type:"text",text:e.message||"Not authorized to run this action."}],isError:!0};case"rate_limited":return{content:[{type:"text",text:e.message||"Rate limit reached; try again shortly."}],isError:!0};case"error":default:return{content:[{type:"text",text:e.message||"The action could not be completed."}],isError:!0}}}function we(e){try{const r=JSON.parse(e);return!r||typeof r.type!="string"?null:r}catch{return null}}function ye(e,r,n){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/session-view/${encodeURIComponent(r)}/${encodeURIComponent(n)}/live`}const A=3;function je(e,r,n){const[l,u]=c.useState("connecting"),x=c.useRef(n);return x.current=n,c.useEffect(()=>{let p=null,m,y=!1,j=0;const v=()=>{y||(j+=1,u(j>=A?"offline":"reconnecting"),m=window.setTimeout(I,2e3))},I=()=>{if(!y){u(j===0?"connecting":j>=A?"offline":"reconnecting");try{p=new WebSocket(ye(window.location,e,r))}catch{v();return}p.onopen=()=>{j=0,u("connected")},p.onmessage=S=>{const T=we(S.data);T&&x.current(T)},p.onerror=()=>{y||u(j>=A?"offline":"reconnecting")},p.onclose=v}};return I(),()=>{y=!0,m&&window.clearTimeout(m),p&&p.close()}},[e,r]),{state:l}}const Ne={connected:{Icon:ie,color:"text-success",spin:!1,tip:"Live — connected"},connecting:{Icon:P,color:"text-warning",spin:!0,tip:"Connecting…"},reconnecting:{Icon:P,color:"text-warning",spin:!0,tip:"Reconnecting…"},offline:{Icon:oe,color:"text-destructive",spin:!1,tip:"Offline — retrying"}};function ke({state:e}){const r=Ne[e],{Icon:n}=r;return t.jsxs(te,{children:[t.jsx(re,{asChild:!0,children:t.jsx("div",{className:`flex h-7 w-7 items-center justify-center rounded-md cursor-default ${r.color}`,children:t.jsx(n,{className:`h-[18px] w-[18px] ${r.spin?"animate-spin":""}`,"aria-hidden":"true"})})}),t.jsx(ne,{children:r.tip})]})}function D(e){return`widget:${e}`}function ve(e){return e.startsWith("widget:")?e.slice(7):""}function Ie({id:e,hostUrl:r,registerFrame:n}){return t.jsx("div",{className:"mx-auto w-full max-w-[85%] rounded-lg border border-border overflow-hidden bg-card/60",style:{height:480},children:t.jsx("iframe",{ref:l=>n(e,l),sandbox:"allow-scripts allow-same-origin",src:r,referrerPolicy:"no-referrer",title:"interactive widget",className:"w-full h-full border-0 block bg-white"})})}function Y(e){return e==="done"||e==="complete"||e==="completed"||e==="skipped"}function Se(e,r){if(Y(e))return t.jsx(K,{className:"h-3.5 w-3.5 shrink-0 text-success","aria-hidden":"true"});switch(e){case"in_progress":case"active":case"running":return t.jsx(P,{className:N("h-3.5 w-3.5 shrink-0 text-primary",r&&"animate-spin"),"aria-hidden":"true"});case"blocked":case"failed":case"error":return t.jsx(pe,{className:"h-3.5 w-3.5 shrink-0 text-destructive","aria-hidden":"true"});default:return t.jsx(me,{className:"h-3.5 w-3.5 shrink-0 text-muted-foreground","aria-hidden":"true"})}}function Te({plan:e,live:r}){const n=e.items??[];return t.jsxs("div",{className:"mx-auto w-full max-w-[85%] rounded-lg border border-border bg-card/60 px-3 py-2",children:[t.jsxs("div",{className:"flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1.5",children:[t.jsx(fe,{className:"h-3.5 w-3.5","aria-hidden":"true"}),t.jsxs("span",{children:["Plan",n.length>0?` · ${n.length} step${n.length===1?"":"s"}`:""]})]}),n.length===0?t.jsx("div",{className:"text-xs text-muted-foreground",children:"Plan updated."}):t.jsx("ul",{className:"flex flex-col gap-1",children:n.map(l=>{const u=Y(l.status);return t.jsxs("li",{className:"flex items-start gap-1.5 text-xs leading-snug",children:[t.jsx("span",{className:"mt-0.5",children:Se(l.status,r)}),t.jsx("span",{className:N("break-words",u?"text-muted-foreground":"text-card-foreground"),children:l.label})]},l.id)})})]})}function Ce({isUser:e,children:r}){return t.jsx("div",{className:N("flex",e?"justify-end":"justify-start"),children:t.jsx("div",{className:N("max-w-[75%] rounded-2xl px-3.5 py-2 text-sm leading-relaxed",e?"bg-primary text-primary-foreground rounded-br-sm":"bg-card border border-border text-card-foreground rounded-bl-sm"),children:r})})}function $e({ns:e,name:r,attachments:n}){return t.jsx("div",{className:"flex flex-wrap gap-1.5",children:n.map((l,u)=>{const x=new URLSearchParams({artifactId:l.artifactId??"",sessionRef:`${e}/${r}`,fn:l.filename});return t.jsxs("a",{href:`/artifact-download?${x.toString()}`,download:l.filename,className:"inline-flex items-center gap-1.5 rounded-lg border border-border bg-card px-2.5 py-1 text-xs text-card-foreground hover:bg-accent hover:text-accent-foreground",children:[t.jsx(xe,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"break-all",children:l.filename})]},u)})})}function Ee({line:e,ns:r,name:n,live:l,registerWidgetFrame:u}){if(e.role==="widget")return e.hostUrl?t.jsx(Ie,{id:e.id,hostUrl:e.hostUrl,registerFrame:u}):null;if(e.role==="plan"&&e.plan)return t.jsx(Te,{plan:e.plan,live:l});if(e.role==="system"||e.role==="error")return t.jsx("div",{className:N("mx-auto max-w-[85%] text-center text-xs py-1",e.role==="error"?"text-destructive":"text-muted-foreground"),children:t.jsx("span",{className:"whitespace-pre-wrap break-words",children:e.text})});const x=e.attachments??[];return t.jsxs("div",{className:N("flex flex-col gap-1",e.role==="user"?"items-end":"items-start"),children:[t.jsx(Ce,{isUser:e.role==="user",children:t.jsx(he,{children:e.text})}),x.length>0&&t.jsx($e,{ns:r,name:n,attachments:x})]})}function Re(){return t.jsx("div",{className:"flex justify-start",role:"status","aria-label":"Agent is working",children:t.jsx("div",{className:"flex items-center gap-2 rounded-2xl rounded-bl-sm border border-border bg-card px-3.5 py-2.5",children:t.jsx("div",{className:"flex items-center gap-1",children:[0,150,300].map(e=>t.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-muted-foreground animate-bounce",style:{animationDelay:`${e}ms`}},e))})})})}function Oe({text:e}){return t.jsx("div",{className:"flex items-center justify-center text-[11px] text-muted-foreground py-0.5",children:t.jsx("span",{className:"animate-pulse",children:e})})}const Le={critical:{Icon:le,color:"text-destructive"},privacy:{Icon:ce,color:"text-warning"},degraded:{Icon:q,color:"text-warning"},waiting:{Icon:V,color:"text-muted-foreground"},routine:{Icon:W,color:"text-muted-foreground"},housekeeping:{Icon:W,color:"text-muted-foreground"},resolved:{Icon:K,color:"text-success"}},_e={Icon:W,color:"text-muted-foreground"},Fe={money:de,clock:V};function Ue({notice:e}){var l;const r=Le[e.tone??""]??_e,n=Fe[e.glyph??""]??r.Icon;return t.jsxs("div",{className:"flex flex-col gap-1.5 px-4 py-2 text-xs border-t border-border bg-card","data-testid":"notice-banner","data-tone":e.tone||"",children:[t.jsxs("div",{className:N("flex items-center gap-2 font-medium",r.color),children:[t.jsx(n,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"break-words text-card-foreground",children:e.lead})]}),e.body&&t.jsx("div",{className:"text-muted-foreground",children:e.body}),(l=e.fields)==null?void 0:l.map((u,x)=>t.jsxs("div",{className:"leading-snug",children:[t.jsxs("span",{className:"font-medium text-card-foreground",children:[u.label,":"]})," ",t.jsx("span",{className:"text-muted-foreground",children:u.value})]},`${u.label}:${x}`)),e.excerpt&&t.jsx("pre",{className:"max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-background px-2 py-1.5 text-[11px] leading-snug text-card-foreground",children:t.jsxs("code",{children:[e.excerpt.label?`${e.excerpt.label}: +`:"",e.excerpt.content]})}),e.nextStep&&t.jsx("div",{className:"font-medium text-card-foreground",children:e.nextStep})]})}function Ae({disabled:e,placeholder:r,onSend:n}){const[l,u]=c.useState(""),x=()=>{const m=l.trim();!m||e||(n(m),u(""))},p=m=>{m.key==="Enter"&&!m.shiftKey&&(m.preventDefault(),x())};return t.jsxs("div",{className:"flex items-end gap-2 px-4 py-3 border-t border-border bg-card",children:[t.jsx("textarea",{rows:1,value:l,disabled:e,placeholder:r,onChange:m=>u(m.target.value),onKeyDown:p,className:N("flex-1 resize-none max-h-40 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm","placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring","disabled:cursor-not-allowed disabled:opacity-50")}),t.jsx(se,{size:"icon",className:"h-9 w-9 shrink-0",disabled:e||!l.trim(),onClick:x,title:"Send",children:t.jsx(ue,{className:"h-4 w-4"})})]})}function De(e,r){return e.map(n=>n.kind==="plan"&&n.plan?{id:`plan:${n.plan.planName}`,role:"plan",text:"",plan:n.plan}:{id:r(),role:n.role==="user"?"user":"agent",text:n.text??""})}function Pe(e){const{ns:r,name:n}=e,[l,u]=c.useState([]),[x,p]=c.useState(!1),[m,y]=c.useState(""),[j,v]=c.useState(!1),[I,S]=c.useState(null),[T,_]=c.useState(null),g=c.useCallback(s=>{_(null),S(s)},[]),k=c.useCallback(s=>{S(null),_(s!=null&&s.lead?s:null)},[]),C=c.useRef(!1),G=c.useRef(0),E=c.useCallback(()=>`l${G.current++}`,[]),$=c.useCallback((s,i,a)=>{u(o=>[...o,{id:E(),role:s,text:i,attachments:a}])},[E]),X=c.useCallback(s=>{if(s.type==="snapshot"){u(a=>[...De(s.history??[],E),...a.filter(o=>o.role==="widget")]),C.current=!1,p(!1),y("");return}const i=s.event;if(i)switch(i.kind){case"user_message":{const a=i.payload,o=(a.attachments??[]).filter(d=>d.artifactId);$("agent",a.text,o.length?o:void 0);break}case"user_echo":{const a=i.payload;$("user",a.text);break}case"plan_update":{const a=i.payload;C.current&&p(!0);const o=`plan:${a.planName}`;u(d=>{const f=d.findIndex(b=>b.role==="plan"&&b.id===o);if(f>=0){const b=d.slice();return b[f]={...d[f],plan:a},b}return[...d,{id:o,role:"plan",text:"",plan:a}]});break}case"notification":{if(!C.current)break;const a=i.payload;p(!0),y(a.text);break}case"turn_activity":{i.payload.active?(C.current=!0,p(!0)):(C.current=!1,p(!1),y(""));break}case"widget_offer":{const a=i.payload;if(!a.hostUrl)break;const o=D(a.artifactId);u(d=>d.some(f=>f.id===o)?d:[...d,{id:o,role:"widget",text:"",hostUrl:a.hostUrl}]);break}}},[$,E]);c.useEffect(()=>{const s=e.activeWidgets??[];s.length!==0&&u(i=>{const a=new Set(i.map(d=>d.id)),o=s.filter(d=>!a.has(D(d.artifactId))).map(d=>({id:D(d.artifactId),role:"widget",text:"",hostUrl:d.hostUrl}));return o.length?[...i,...o]:i})},[]);const F=c.useRef(new Map),Q=c.useCallback((s,i)=>{i?F.current.set(s,i):F.current.delete(s)},[]),{state:R}=je(r,n,X),U=c.useRef(!1);c.useEffect(()=>{if(R==="connected"){U.current=!1;return}R==="offline"&&!U.current&&(U.current=!0,$("system","Connection lost — retrying…"))},[R,$]);const Z=c.useCallback(async s=>{S(null),_(null),v(!0);try{const i=await fetch(`/session/${r}/${n}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:"user_message",payload:{text:s}})}),a=await i.json().catch(()=>({}));if(!i.ok){const o=a.error||`Failed to send (${i.status}).`;g(o);return}k(a.notice)}catch{g("Network error while sending your message.")}finally{v(!1)}},[r,n,g,k]),M=c.useCallback(async(s,i,a,o)=>{var f,b;const d=h=>{if(e.sandboxOrigin)try{a.postMessage({ap:"mcpui",cmd:"appToolCallReply",corrId:i,response:h},e.sandboxOrigin)}catch(O){console.error("app-tool-call reply postMessage failed",O)}};try{const h=await fetch(`/session/${r}/${n}/app-tool-call`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({toolName:(f=s.payload)==null?void 0:f.toolName,args:((b=s.payload)==null?void 0:b.params)??{},artifactId:o})});if(!h.ok){const w=await h.json().catch(()=>({}));console.error("app-tool-call send failed",h.status,w),g(w.error||`The widget action could not be sent (${h.status}).`),d({content:[{type:"text",text:"The action could not be sent."}],isError:!0});return}const O=await h.json();if(O.status==="not_found"){try{const w=await fetch(`/session/${r}/${n}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(H(s))});if(w.ok){const L=await w.json().catch(()=>({}));k(L.notice)}else{const L=await w.json().catch(()=>({}));console.error("widget action send failed (not_found fallback)",w.status,L),g(L.error||`The widget action could not be sent (${w.status}).`)}}catch(w){console.error("widget action send failed (not_found fallback, network)",w),g("The server was unreachable while sending the widget action.")}d({content:[{type:"text",text:"Sent to the agent."}],isError:!1});return}d(be(O))}catch(h){console.error("app-tool-call send failed (network)",h),g("The server was unreachable while sending the widget action."),d({content:[{type:"text",text:"The server was unreachable."}],isError:!0})}},[r,n,e.sandboxOrigin,g,k]);c.useEffect(()=>{const s=i=>{var d;if(!e.sandboxOrigin||i.origin!==e.sandboxOrigin)return;const a=Array.from(F.current.entries()).find(([,f])=>f.contentWindow===i.source);if(!a)return;const o=i.data;if((o==null?void 0:o.ap)==="mcpui"){if(o.cmd==="appToolCall"&&typeof o.corrId=="string"&&((d=o.result)==null?void 0:d.type)==="tool"&&i.source){M(o.result,o.corrId,i.source,ve(a[0]));return}o.cmd!=="action"||!o.result||fetch(`/session/${r}/${n}/interact`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(H(o.result))}).then(async f=>{if(!f.ok){const h=await f.json().catch(()=>({}));console.error("widget action send failed",f.status,h),g(h.error||`The widget action could not be sent (${f.status}).`);return}const b=await f.json().catch(()=>({}));k(b.notice)}).catch(f=>{console.error("widget action send failed (network)",f),g("The server was unreachable while sending the widget action.")})}};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[e.sandboxOrigin,r,n,M,g,k]);const J=c.useRef(null),B=c.useRef(null);return c.useEffect(()=>{var a,o;const s=B.current;!s||!(s.scrollHeight-s.scrollTop-s.clientHeight<120)||(o=(a=J.current)==null?void 0:a.scrollIntoView)==null||o.call(a,{block:"end"})},[l.length,x,m]),t.jsx(ee,{delayDuration:200,children:t.jsxs("div",{className:"h-screen flex flex-col bg-background text-foreground",children:[t.jsxs("div",{className:"flex items-center gap-3 px-3 py-2 min-h-[52px] bg-card border-b border-border",children:[t.jsx(z,{className:"h-4 w-4 text-muted-foreground shrink-0","aria-hidden":"true"}),t.jsxs("span",{className:"text-[13px] font-semibold truncate",title:`${r}/${n}`,children:[r,"/",n]}),t.jsx("div",{className:"ml-auto shrink-0",children:t.jsx(ke,{state:R})})]}),t.jsx("div",{ref:B,className:"flex-1 min-h-0 overflow-y-auto px-4 py-4",children:t.jsxs("div",{className:"flex flex-col gap-2.5 max-w-3xl mx-auto",children:[l.length===0&&t.jsxs("div",{className:"flex flex-1 flex-col items-center justify-center gap-2 py-16 text-muted-foreground",children:[t.jsx(z,{className:"h-8 w-8","aria-hidden":"true"}),t.jsx("span",{className:"text-sm",children:"No messages yet."})]}),l.map(s=>t.jsx("div",{"data-testid":"session-view-line","data-role":s.role,children:t.jsx(Ee,{line:s,ns:r,name:n,live:x,registerWidgetFrame:Q})},s.id)),x&&!m&&t.jsx(Re,{}),x&&m&&t.jsx(Oe,{text:m}),t.jsx("div",{ref:J})]})}),I&&t.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 text-xs text-destructive border-t border-border bg-card",children:[t.jsx(q,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),t.jsx("span",{className:"break-words",children:I})]}),T&&t.jsx(Ue,{notice:T}),t.jsx(Ae,{disabled:j,placeholder:"Message the agent…",onSend:Z})]})})}ae(Pe); diff --git a/pkg/web/webui/webassets/dist/sessions.D94Qg8pw.js b/pkg/web/webui/webassets/dist/sessions.D94Qg8pw.js new file mode 100644 index 00000000..d6a2106e --- /dev/null +++ b/pkg/web/webui/webassets/dist/sessions.D94Qg8pw.js @@ -0,0 +1 @@ +import{c as L,A as W,V as Ie,G as Y,a as G,C as ge,b as bt,d as wt,e as Pe,B,M as oe,T as ts,g as ss,h as ns,i as as,W as rs,N as Ae,j as os,k as is,l as Ge,n as Je,o as ls,p as Xe,D as Oe,t as Me,u as Le,v as pe,w as me,x as cs,L as ds,m as us}from"./mount.Bxdfy1H2.js";import{j as s,R as fs,O as ps,r as i,I as ms,P as hs,z as ve,X as xs,Q as gs,V as vs,Y as bs,Z as ws,_ as js,$ as ys,a0 as Ns,a1 as Ss,a2 as ks,a3 as Cs,a4 as Rs,a5 as Ts,a6 as As,a as Es,a7 as $s,g as Qe,a8 as Is,M as Ps,a9 as Os,aa as Ms,ab as Ls,ac as _s}from"./vendor.BHG48rjj.js";import{M as _e}from"./markdown.CZgLwgiZ.js";import{B as Ds}from"./badge.D9YA1CKv.js";import{s as jt,u as Us,b as qs,i as Fs,c as yt,I as Vs,S as Bs,a as Hs,C as Ws}from"./StartupLine.DKqmGMaA.js";import{M as Ks}from"./model-name.B3Csp-bS.js";const Ze=["hsl(var(--chart-1))","hsl(var(--chart-2))","hsl(var(--chart-3))","hsl(var(--chart-4))","hsl(var(--chart-5))"];function de(e){return Ze[e%Ze.length]}function je({height:e=240,className:t,children:n}){return s.jsx("div",{className:L("w-full text-xs",t),style:{height:e},children:s.jsx(fs,{width:"100%",height:"100%",children:n})})}function zs(e){return s.jsx(ps,{cursor:{stroke:"hsl(var(--border))"},contentStyle:{background:"hsl(var(--popover))",border:"1px solid hsl(var(--border))",borderRadius:"var(--radius)",color:"hsl(var(--popover-foreground))",fontSize:"0.75rem"},...e})}const Ys=new Set(["submitted","awaiting_approval","running"]);function ie(e){return Ys.has(e)}function De(e){if(e.phase==="idle")return null;if(e.phase==="succeeded")return e.message??null;if(e.message)return e.message;switch(e.phase){case"submitted":return"Request submitted.";case"awaiting_approval":return e.approvalAddressedToViewer?"Your approval is needed to continue.":"Waiting on approval from someone else.";case"running":return"In progress.";case"failed":return"The request failed. You can try again.";case"denied":return"The request was denied.";case"expired":return"The approval window expired. You can try again.";case"rate_limited":return"Too many requests right now. Try again shortly.";default:return null}}const Gs={states:{},invoke:()=>{},answer:()=>Promise.reject(new Error("agentui: no ActionsProvider"))},Nt=i.createContext(Gs);function Js({value:e,children:t}){return s.jsx(Nt.Provider,{value:e,children:t})}function le(){return i.useContext(Nt)}const P=e=>e.props??{},m=(e,t,n="")=>{const a=P(e)[t];return typeof a=="string"?a:typeof a=="number"?Number.isFinite(a)?String(a):n:typeof a=="boolean"?String(a):n};function Ue(e){if(typeof e!="string")return null;const t=e.split("/");if(t.length!==2)return null;for(const n of t)if(n===""||n==="."||n==="..")return null;return[t[0],t[1]]}function Xs(e){const t=Ue(e);return t===null?null:`/chat-embed/${encodeURIComponent(t[0])}/${encodeURIComponent(t[1])}`}function St({sessionRef:e}){const t=Xs(e);return t===null?s.jsx(W,{children:"Chat unavailable: no valid session reference."}):s.jsx("iframe",{"data-testid":"ap-chat",src:t,title:"Test chat",className:"w-full rounded-md bg-background",style:{height:"520px",border:"0"}})}function Qs({n:e}){return s.jsx(St,{sessionRef:P(e).sessionRef})}const Zs=/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;function et(e){return e.length>0&&e.length<=63&&Zs.test(e)}function qe(e){const t=m(e,"namespace"),n=m(e,"agentClass"),a=m(e,"label");if(!et(t))throw new Error(`ap:agentlink: namespace ${JSON.stringify(t)} is not a DNS-1123 label`);if(!et(n))throw new Error(`ap:agentlink: agentClass ${JSON.stringify(n)} is not a DNS-1123 label`);if(a==="")throw new Error("ap:agentlink: label is required");const r=new URLSearchParams({new:"1",ns:t,agentClass:n}),o=m(e,"prompt");return o!==""&&r.set("prompt",o),`/sessions?${r.toString()}`}function en(e,t){return`ap-agentlink:${e}/${t}`}function tt(e){try{return window.sessionStorage.getItem(e)}catch(t){return console.error("agentui: ap:agentlink could not read the remembered test session",t),null}}function tn(e,t){try{window.sessionStorage.setItem(e,t)}catch(n){console.error("agentui: ap:agentlink could not remember the test session",n)}}function sn(e){try{window.sessionStorage.removeItem(e)}catch(t){console.error("agentui: ap:agentlink could not forget the remembered test session",t)}}function nn(e){return`/sessions?${new URLSearchParams({session:e}).toString()}`}function an(e){return typeof e!="string"?null:!e.startsWith("/")||e.startsWith("//")||e.startsWith("/\\")?(console.error("agentui: ap:agentlink refused a start href that is not a same-origin path; using the rebuilt address",e),null):e}const rn="The test could not start.",st="We could not confirm whether that session started. Reload before trying again.";function on({n:e}){const t=qe(e),n=m(e,"namespace"),a=m(e,"agentClass"),r=en(n,a),[o,l]=i.useState(()=>tt(r)),[d,f]=i.useState(null),[p,u]=i.useState(!1),[c,h]=i.useState(null);if(i.useEffect(()=>{const x=tt(r);if(x===null)return;let g=!0;const w=()=>{g&&(sn(r),l(null))},C=Ue(x);if(C===null){w();return}return(async()=>{try{(await fetch(`/sessions/api/${encodeURIComponent(C[0])}/${encodeURIComponent(C[1])}/detail`,{credentials:"same-origin"})).ok||w()}catch(N){console.error("agentui: ap:agentlink could not check the remembered test session",N),w()}})(),()=>{g=!1}},[r]),o!==null)return s.jsxs("div",{className:"flex flex-col gap-2",children:[s.jsx(St,{sessionRef:o}),s.jsx("a",{"data-testid":"ap-agentlink-open-tab",href:d??nn(o),target:"_blank",rel:"noopener noreferrer",className:"self-end text-xs text-[hsl(var(--primary))]",children:"Open in a tab"})]});async function b(){u(!0),h(null);let x;try{x=await fetch("/sessions/api/start",{method:"POST",credentials:"same-origin",headers:{"content-type":"application/json"},body:JSON.stringify({ns:n,agentClass:a,prompt:m(e,"prompt")})})}catch(g){console.error("agentui: ap:agentlink embed start did not complete",g),h({text:st,retryable:!1}),u(!1);return}if(!x.ok){let g="";try{const w=await x.json();g=typeof(w==null?void 0:w.error)=="string"?w.error:""}catch{g=""}console.error("agentui: ap:agentlink embed start was refused",{status:x.status}),h({text:g||rn,retryable:!0}),u(!1);return}try{const g=await x.json();if(!g.ns||!g.name)throw new Error("start answered without a session");const w=`${g.ns}/${g.name}`;tn(r,w),f(an(g.href)),l(w)}catch(g){console.error("agentui: ap:agentlink embed start succeeded but its answer could not be addressed",g),h({text:st,retryable:!1})}finally{u(!1)}}return s.jsxs("div",{className:"flex flex-col gap-2",children:[s.jsx("button",{type:"button","data-testid":"ap-agentlink-embed",onClick:()=>void b(),disabled:p||(c==null?void 0:c.retryable)===!1,className:Ie({variant:"default"}),children:m(e,"label")}),c!==null&&s.jsxs("p",{role:"alert",className:"text-sm text-[hsl(var(--destructive))]",children:[c.text,c.retryable&&s.jsxs(s.Fragment,{children:[" ","Try again, or"," ",s.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"underline",children:"open it in a tab"}),"."]})]})]})}function ln({n:e}){return P(e).embed?s.jsx(on,{n:e}):s.jsx("a",{"data-testid":"ap-agentlink",href:qe(e),target:"_blank",rel:"noopener noreferrer",className:Ie({variant:"default"}),children:m(e,"label")})}const kt=i.createContext(null);function cn({value:e,children:t}){return s.jsx(kt.Provider,{value:e,children:t})}function dn(){return i.useContext(kt)}const un="application/vnd.agentprimitives.authzed.com.agent.v1";function fn(e){return e===un?"Agent bundle":e==="text/html"?"Web page":e.startsWith("image/")?"Image":e}function pn(e){const[t,n]=e>=1e6?[e/1e6,"megabyte"]:e>=1e3?[e/1e3,"kilobyte"]:[e,"byte"];return new Intl.NumberFormat(void 0,{style:"unit",unit:n,unitDisplay:"short",maximumFractionDigits:1}).format(t)}function nt({reason:e}){return s.jsxs(W,{variant:"destructive","data-testid":"ap-attachment-unavailable",children:[s.jsx(Y,{children:"Attachment unavailable"}),s.jsx(G,{children:e})]})}function mn({n:e}){const t=dn(),n=m(e,"artifact"),a=m(e,"label"),[r,o]=i.useState(null),[l,d]=i.useState(null);if(i.useEffect(()=>{if(t===null||n==="")return;let p=!1;const u=new URLSearchParams({artifactId:n,sessionRef:`${t.ns}/${t.name}`});return fetch(`/artifact-view/meta?${u.toString()}`,{credentials:"same-origin"}).then(async c=>{if(!c.ok)throw new Error(`${c.status} ${await c.text()}`);return await c.json()}).then(c=>{p||o(c)}).catch(c=>{console.error("agentui: ap:attachment lookup failed",n,c),p||d("This file could not be found in this conversation.")}),()=>{p=!0}},[t,n]),t===null)return s.jsx(nt,{reason:"This page is not attached to a conversation."});if(l!==null)return s.jsx(nt,{reason:l});if(r===null)return s.jsx("div",{"data-testid":"ap-attachment-loading",className:"text-sm text-muted-foreground",children:"Loading file…"});const f=new URLSearchParams({artifactId:n,sessionRef:`${t.ns}/${t.name}`,fn:r.filename||n});return s.jsxs("div",{"data-testid":"ap-attachment",className:"flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2",children:[s.jsxs("div",{className:"min-w-0",children:[s.jsx("div",{className:"truncate text-sm font-medium",children:a||r.filename||r.name}),s.jsxs("div",{className:"text-xs text-muted-foreground",children:[fn(r.mime)," · ",pn(r.size)]})]}),s.jsxs("a",{href:`/artifact-download?${f.toString()}`,download:r.filename||void 0,className:L(Ie({variant:"outline",size:"sm"}),"shrink-0"),"aria-label":`Download ${r.filename||r.name}`,children:[s.jsx(ms,{className:"mr-1.5 h-4 w-4","aria-hidden":"true"}),"Download"]})]})}function Ct({title:e,collapsed:t,testId:n,children:a}){const[r,o]=i.useState(!t);return i.useEffect(()=>{o(!t)},[t]),s.jsxs("details",{"data-testid":n,open:r,className:"w-full",children:[s.jsxs("summary",{className:"flex cursor-pointer select-none items-center gap-2 px-1 py-2 text-sm font-medium [&::-webkit-details-marker]:hidden",onClick:l=>{l.preventDefault(),o(d=>!d)},children:[s.jsx(hs,{className:L("h-4 w-4 shrink-0 transition-transform",r&&"rotate-90"),"aria-hidden":"true"}),e]}),s.jsx("div",{className:"px-1 pb-2",children:a})]})}const hn={layout:"column",selectedStep:null,selectStep:()=>{}},Ee=i.createContext(hn);function Rt(){return i.useContext(Ee)}function xn(e){var t;return(e==null?void 0:e.component)==="oap:page"&&((t=e.props)==null?void 0:t.layout)==="rail"?"rail":"column"}const Tt="oap:generative",gn={composed:new Set,updated:new Map,stale:new Set,waiting:new Set,answered:new Set,collapsed:new Map},At=i.createContext(gn);function vn({value:e,children:t}){return s.jsx(At.Provider,{value:e,children:t})}function Fe(){return i.useContext(At)}function ne(e){var t;return e.component!==Tt?null:typeof((t=e.props)==null?void 0:t.name)=="string"?e.props.name:""}const bn="rounded-md bg-state/[0.05] px-3 py-2",Et=i.createContext(null);function wn(){return i.useContext(Et)}function jn({name:e,title:t,children:n}){const{composed:a,updated:r,stale:o,waiting:l,collapsed:d}=Fe(),{layout:f}=Rt(),p=a.has(e),u=r.has(e),c=o.has(e),h=l.has(e),b=i.Children.count(n)>0,x=d.get(e),g=f!=="rail"&&x!==void 0&&b,w=f==="rail"&&t!==void 0&&t!==""&&b,C=h&&f!=="rail";return s.jsxs("section",{"data-hook":e,"data-agent-composed":p?"true":void 0,"data-hook-updated":u?"true":void 0,"data-hook-waiting":h?"true":void 0,"data-hook-collapsed":g?"true":void 0,"aria-live":"polite","aria-busy":c||void 0,className:L("relative w-full",p&&b&&bn,c&&"opacity-60 transition-opacity"),children:[(C||u||c)&&s.jsxs("div",{"data-testid":"agent-ui-hook-cues",className:"pointer-events-none mb-1 flex min-h-5 items-center justify-between gap-2 text-[11px]",children:[s.jsx("span",{children:C&&s.jsx("span",{"data-testid":"agent-ui-hook-waiting",className:"rounded bg-accent/10 px-1.5 py-0.5 font-medium text-accent-foreground",children:"Waiting on you"})}),u?s.jsx("span",{"data-testid":"agent-ui-hook-updated",className:"rounded bg-accent/10 px-1.5 py-0.5 text-muted-foreground",children:"Updated"}):c?s.jsxs("div",{"data-testid":"agent-ui-hook-refreshing",className:"flex items-center gap-1.5 text-muted-foreground",children:[s.jsx(ve,{className:"h-3.5 w-3.5 animate-spin","aria-hidden":"true"}),s.jsx("span",{children:"Updating…"})]}):null]}),s.jsxs(Et.Provider,{value:e,children:[w&&s.jsx("h3",{"data-testid":"agent-ui-hook-title",className:"mb-2 text-sm font-medium text-foreground",children:t}),g?s.jsx(Ct,{title:x,collapsed:!0,testId:"agent-ui-hook-collapsed",children:n}):n]})]})}const yn=["info","success","warning"],Nn={info:"border-state/40 bg-state/[0.06]",success:"border-success/50 bg-success/[0.08]",warning:"border-warning/50 bg-warning/[0.08]"},Sn=4;function $t(e){return Array.isArray(e)?e.filter(t=>!!t&&typeof t.label=="string"&&typeof t.action=="string"&&t.action!=="").slice(0,Sn):[]}function It(e){const t=m(e,"tone");return yn.includes(t)?t:"info"}function kn(e){return JSON.stringify([m(e,"title"),m(e,"body"),It(e),$t(P(e).buttons)])}const Pt="agentui:notice:dismissed:";function Cn(e){try{return window.localStorage.getItem(Pt+e)==="1"}catch(t){return console.error("agentui: could not read dismissed notices",t),!1}}function Rn(e){try{window.localStorage.setItem(Pt+e,"1")}catch(t){console.error("agentui: could not remember a dismissed notice",t)}}function Tn({n:e}){const t=kn(e);return s.jsx(An,{n:e,identity:t},t)}function An({n:e,identity:t}){const{states:n,invoke:a,busy:r}=le(),[o,l]=i.useState(()=>Cn(t)),[d,f]=i.useState(null);if(o)return null;const p=It(e),u=m(e,"title"),c=$t(P(e).buttons),h=c.some(g=>ie((n[g.action]??{phase:"idle"}).phase)),b=d!==null||h||!!r,x=d!==null?n[d]??{phase:"submitted"}:null;return s.jsxs(ge,{"data-testid":"ap-notice","data-tone":p,className:L("relative",Nn[p]),children:[s.jsx("button",{type:"button","aria-label":"Dismiss",className:"absolute right-2 top-2 rounded p-1 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",onClick:()=>{Rn(t),l(!0)},children:s.jsx(xs,{className:"h-4 w-4","aria-hidden":"true"})}),u&&s.jsx(bt,{className:"pb-2 pr-8",children:s.jsx(wt,{className:"text-base",children:u})}),s.jsxs(Pe,{className:L("flex flex-col gap-3 pr-8",!u&&"pt-6"),children:[s.jsx(_e,{children:m(e,"body")}),c.length>0&&s.jsx("div",{className:"flex flex-wrap gap-2",children:c.map((g,w)=>s.jsx(B,{type:"button",variant:w===0?"default":"secondary",disabled:b,onClick:()=>{f(g.action),a(g.action,void 0)},children:g.label},`${g.action}:${w}`))}),x!==null&&s.jsx("p",{"data-testid":"ap-notice-sent",className:"text-xs text-muted-foreground",children:De(x)??"Sent."})]})]})}function $e(e,t){return e?e.component===t?!0:(e.children??[]).some(n=>$e(n,t)):!1}function En(e,t){const n=new Set,a=(r,o)=>{const l=ne(r)??o;r.component===t&&l!==null&&n.add(l);for(const d of r.children??[])a(d,l)};return e&&a(e,null),n}function $n(e){const t=(n,a)=>{const r=ne(n)??a;return n.component==="ap:question"&&r===null?!0:(n.children??[]).some(o=>t(o,r))};return e?t(e,null):!1}function Ot(e){var o;if(!e)return[];const t=[],n=l=>{l.component==="ap:steps"&&t.push(l);for(const d of l.children??[])n(d)};if(n(e),t.length!==1)return[];const a=(o=t[0].props)==null?void 0:o.steps,r=[];for(const l of Array.isArray(a)?a:[]){if(!l||typeof l.id!="string"||l.id==="")continue;const d=l.state==="done"||l.state==="active"?l.state:"upcoming";r.push({id:l.id,label:typeof l.label=="string"?l.label:l.id,state:d})}return r}function In(e){const t=new Map;if(!e)return t;const n=new Map;for(const r of Ot(e))n.set(r.id,{step:r.id,label:r.label,state:r.state});if(n.size===0)return t;const a=r=>{var d,f;const o=ne(r),l=(d=r.props)==null?void 0:d.step;if(o!==null&&typeof l=="string"){const p=n.get(l);if(p){const u=(f=r.props)==null?void 0:f.title;t.set(o,typeof u=="string"&&u!==""?{...p,title:u}:p)}}for(const p of r.children??[])a(p)};return a(e),t}function Mt(e,t){return e.component===t?!0:(e.children??[]).some(n=>Mt(n,t))}function Pn(e){var n;const t=e.component===Tt?(n=e.props)==null?void 0:n.step:void 0;return typeof t=="string"&&t!==""?t:null}function On({node:e,renderChild:t}){var x;const n=((x=e.props)==null?void 0:x.layout)==="rail"?"rail":"column",a=e.children??[],{waiting:r}=Fe(),o=i.useMemo(()=>Ot(e),[e]),l=i.useMemo(()=>{const g=o.find(C=>C.state==="active");if(g)return g.id;const w=o.filter(C=>C.state==="done");return w.length>0?w[w.length-1].id:o.length>0?o[0].id:null},[o]),[d,f]=i.useState(null);i.useEffect(()=>{f(null)},[l]);const p=d??l,u=i.useMemo(()=>({layout:n,selectedStep:p,selectStep:f}),[n,p]);if(n!=="rail")return s.jsx(Ee.Provider,{value:u,children:s.jsx("div",{className:"flex flex-col gap-2",children:a.map(t)})});const c=a.findIndex(g=>Mt(g,"ap:steps")),h=c>=0?a[c]:null,b=(g,w)=>{if(w===c)return!1;const C=Pn(g);if(C===null||C===p)return!0;const N=ne(g);return N!==null&&r.has(N)};return s.jsx(Ee.Provider,{value:u,children:s.jsxs("div",{className:"grid gap-6 md:grid-cols-[220px_minmax(0,1fr)]",children:[s.jsx("div",{"data-testid":"agent-ui-page-rail",className:"md:sticky md:top-4 md:self-start",children:h?t(h,"rail"):null}),s.jsx("div",{"data-testid":"agent-ui-page-stage",className:"flex min-w-0 flex-col gap-3",children:a.map((g,w)=>b(g,w)?t(g,w):null)})]})})}const Mn={"ap:select":{prop:"param",values:[{valueProp:"value"}]},"ap:daterange":{prop:"param",values:[{suffix:"from",valueProp:"from"},{suffix:"to",valueProp:"to"}]}};function he(e,t){return t?`${e}.${t}`:e}function Ve(e){const t=Mn[e.component];if(!t)return null;const n=(e.props??{})[t.prop];return typeof n!="string"||n===""?null:{name:n,spec:t}}const Ln={params:{},setParam:()=>{}},Lt=i.createContext(Ln);function _n({value:e,children:t}){return s.jsx(Lt.Provider,{value:e,children:t})}function _t(){return i.useContext(Lt)}function Dt(e){const t={};return e.view&&Ut(e.view,t),t}function ye(e,t){const n=Dt(e),a={};for(const r of Dn(e))r in t?a[r]=t[r]:r in n&&(a[r]=n[r]);return a}function Dn(e){const t=[],n=a=>{const r=Ve(a);if(r)for(const o of r.spec.values){const l=he(r.name,o.suffix);t.includes(l)||t.push(l)}for(const o of a.children??[])n(o)};return e.view&&n(e.view),t}function Ut(e,t){const n=Ve(e);if(n){const a=e.props??{};for(const r of n.spec.values){const o=a[r.valueProp];typeof o=="string"&&o!==""&&(t[he(n.name,r.suffix)]=o)}}for(const a of e.children??[])Ut(a,t)}const at=e=>Array.isArray(e)?e.filter(t=>typeof t=="string"):[];function rt({label:e,state:t}){return s.jsxs("li",{"data-state":t,className:"flex items-center gap-2 text-sm",children:[t==="done"&&s.jsx("span",{"aria-hidden":"true",className:"text-[hsl(var(--success))]",children:"✓"}),s.jsx("span",{className:t==="done"?"text-muted-foreground line-through":void 0,children:e})]})}function Un({n:e}){const t=m(e,"now"),n=at(P(e).done),a=at(P(e).next),r=m(e,"step");return s.jsx(ge,{children:s.jsxs("section",{"data-testid":"ap-progress",className:"flex flex-col gap-3 p-6",children:[s.jsx("p",{"data-testid":"ap-progress-now",className:"text-sm font-medium",children:t}),(n.length>0||a.length>0)&&s.jsxs("ul",{className:"flex flex-col gap-1.5",children:[n.map((o,l)=>s.jsx(rt,{label:o,state:"done"},`done-${l}`)),a.map((o,l)=>s.jsx(rt,{label:o,state:"next"},`next-${l}`))]}),r&&s.jsx("p",{className:"text-xs text-muted-foreground",children:r})]})})}function qn(e){return Array.isArray(e)?e.filter(t=>!!t&&typeof t.value=="string"&&typeof t.label=="string"):[]}function Fn({n:e}){const t=JSON.stringify([m(e,"prompt"),m(e,"kind"),P(e).choices??null]);return s.jsx(Vn,{n:e},t)}function Vn({n:e}){const{answer:t,busy:n}=le(),a=wn(),{answered:r}=Fe(),o=a!==null&&r.has(a),l=m(e,"prompt"),d=m(e,"kind")==="choice"?"choice":"text",f=m(e,"placeholder"),p=m(e,"submitLabel","Answer"),u=qn(P(e).choices),c=i.useId(),[h,b]=i.useState(""),[x,g]=i.useState(!1),[w,C]=i.useState(!1),[N,M]=i.useState(!1);if(w||o)return s.jsx("p",{"data-testid":"ap-question-sent",children:"Sent — waiting for the agent."});const D=h.trim()===""||x||!!n;function j(v){v.preventDefault(),!D&&(g(!0),M(!1),t(h).then(()=>C(!0)).catch($=>{console.error("agentui: ap:question answer failed",$),M(!0)}).finally(()=>g(!1)))}return s.jsx(ge,{children:s.jsx(Pe,{className:"pt-6",children:s.jsxs("form",{"data-testid":"ap-question",onSubmit:j,className:"flex flex-col gap-3",children:[s.jsx(oe,{htmlFor:d==="text"?c:void 0,className:"font-normal text-foreground",children:l}),d==="choice"?s.jsx("div",{className:"flex flex-col gap-2",children:u.map(v=>s.jsxs(oe,{className:"flex items-center gap-2 font-normal",children:[s.jsx("input",{type:"radio",name:c,value:v.value,checked:h===v.value,disabled:x||!!n,onChange:()=>b(v.value),className:"h-4 w-4 border-input text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"}),v.label]},v.value))}):s.jsx("textarea",{id:c,placeholder:f,value:h,disabled:x||!!n,onChange:v=>b(v.target.value),className:"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"}),s.jsx(B,{type:"submit",disabled:D,className:"self-start",children:p}),N&&s.jsx("p",{role:"alert",className:"text-xs text-destructive",children:"Could not send your answer. Try again."})]})})})}const ot={idle:"bg-muted-foreground",running:"bg-[hsl(var(--success))] animate-pulse",paused:"bg-[hsl(var(--warning))]",ended:"bg-muted-foreground",unwatched:"bg-[hsl(var(--warning))]"};function Bn(e){if(e==="")return null;const t=new Date(e);return Number.isNaN(t.getTime())?null:t.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function Hn({n:e}){const t=m(e,"state"),n=ot[t]??ot.idle,a=m(e,"since"),r=Bn(a);return s.jsxs("div",{"data-testid":"ap-status","data-state":t,role:"status",title:a||void 0,className:"flex items-center gap-2 text-sm font-medium",children:[s.jsx("span",{"aria-hidden":"true",className:L("inline-block h-2 w-2 shrink-0 rounded-full",n)}),s.jsxs("span",{children:[m(e,"text"),r!==null&&s.jsxs(s.Fragment,{children:[" ",s.jsx("time",{dateTime:a,children:r})]})]})]})}function qt(e,t,n){return t.includes(e)?e:n}const Wn=["default","secondary","destructive","outline"],Kn=["default","secondary","destructive","outline","ghost","link"],ue={none:"gap-0",sm:"gap-2",md:"gap-4",lg:"gap-6"},it={start:"items-start",center:"items-center",end:"items-end",stretch:"items-stretch"},Ne=e=>`apui-${e}`,zn=e=>e==="right"?"text-right":e==="center"?"text-center":void 0,Yn=e=>e==="right"?"text-right tabular-nums":e==="center"?"text-center":void 0;function Gn({n:e}){const{params:t,setParam:n}=_t(),a=m(e,"param"),r=a!=="",o=m(e,"value"),l=r?t[a]??o:o,d=m(e,"placeholder");return s.jsx("div",{className:"flex flex-col gap-1",children:s.jsxs("select",{disabled:!r,className:L("h-9 rounded-md border border-input bg-transparent px-3 text-sm",!r&&"text-muted-foreground"),value:l,onChange:r?f=>n(a,f.target.value):void 0,children:[d&&s.jsx("option",{value:"",disabled:!0,hidden:!0,children:d}),(P(e).options??[]).map(f=>s.jsx("option",{value:f.value,children:f.label??f.value},f.value))]})})}function lt(e){return e===""||e.includes("T")?e:`${e}T00:00:00Z`}function ct(e){const t=e.indexOf("T");return t===-1?e:e.slice(0,t)}function Jn({n:e}){const{params:t,setParam:n}=_t(),a=m(e,"param");if(!(a!==""))return s.jsxs(B,{variant:"secondary",disabled:!0,children:[m(e,"from")||"start"," → ",m(e,"to")||"end"]});const o=he(a,"from"),l=he(a,"to"),d=t[o]??m(e,"from"),f=t[l]??m(e,"to");return s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(Ae,{type:"date","aria-label":"From date",value:ct(d),onChange:p=>n(o,lt(p.target.value)),className:"h-9 w-auto"}),s.jsx("span",{className:"text-muted-foreground","aria-hidden":"true",children:"→"}),s.jsx(Ae,{type:"date","aria-label":"To date",value:ct(f),onChange:p=>n(l,lt(p.target.value)),className:"h-9 w-auto"})]})}function Be({text:e}){return e?s.jsx("span",{"data-testid":"agent-ui-action-status",className:"text-xs text-muted-foreground",children:e}):null}function Xn({n:e,columns:t,rows:n}){const{states:a,invoke:r}=le(),o=m(e,"rowAction"),l=o!=="",d=l?a[o]??{phase:"idle"}:{phase:"idle"},f=ie(d.phase);return n.length===0?s.jsx("p",{className:"py-6 text-center text-sm text-muted-foreground",children:m(e,"empty","Nothing to show.")}):s.jsxs("div",{className:"overflow-x-auto",children:[s.jsxs(os,{children:[s.jsx(is,{children:s.jsxs(Ge,{children:[t.map(p=>s.jsx(Je,{className:zn(p.align),children:p.header??p.key},p.key)),l&&s.jsx(Je,{className:"w-px"})]})}),s.jsx(ls,{children:n.map((p,u)=>s.jsxs(Ge,{children:[t.map(c=>s.jsx(Xe,{className:Yn(c.align),children:String(p[c.key]??"")},c.key)),l&&s.jsx(Xe,{className:"text-right",children:s.jsx(B,{variant:"ghost",size:"sm",disabled:f,"data-testid":"agent-ui-row-action",onClick:()=>r(o,Qn(p)),children:m(e,"rowActionLabel","Details")})})]},u))})]}),s.jsx(Be,{text:l?d.message??null:null})]})}function Se(e,t){if(e==null)return[];if(!Array.isArray(e))throw new TypeError(`${t} must be an array`);return e}function Qn(e){const t={};for(const[n,a]of Object.entries(e))typeof a=="string"?t[n]=a:(typeof a=="number"&&Number.isFinite(a)||typeof a=="boolean")&&(t[n]=String(a));return t}function Zn({n:e}){const{states:t,invoke:n,busy:a}=le(),r=m(e,"action"),o=r!=="",l=o?t[r]??{phase:"idle"}:{phase:"idle"},d=!o||ie(l.phase)||!!a||!!P(e).disabled;return s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(B,{variant:qt(m(e,"variant"),Kn,"default"),disabled:d,onClick:o?()=>n(r,void 0):void 0,children:m(e,"label")}),s.jsx(Be,{text:De(l)})]})}function ea({n:e}){const{states:t,invoke:n,busy:a}=le(),r=m(e,"action"),o=r!=="",l=o?t[r]??{phase:"idle"}:{phase:"idle"},d=!o||ie(l.phase)||!!a||!!P(e).disabled,f=P(e).fields??[],p=P(e).values??{},[u,c]=i.useState(()=>({...p}));return s.jsxs("form",{className:"flex flex-col gap-3",onSubmit:h=>{h.preventDefault(),o&&n(r,Object.fromEntries(f.map(b=>[b.name,u[b.name]??""])))},children:[f.map(h=>s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(oe,{htmlFor:Ne(h.name),children:h.label??h.name}),h.kind==="textarea"?s.jsx("textarea",{id:Ne(h.name),name:h.name,placeholder:h.placeholder,disabled:d,value:u[h.name]??"",onChange:b=>c(x=>({...x,[h.name]:b.target.value})),className:"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"}):s.jsx(Ae,{id:Ne(h.name),name:h.name,type:h.kind==="number"?"number":"text",placeholder:h.placeholder,disabled:d,value:u[h.name]??"",onChange:b=>c(x=>({...x,[h.name]:b.target.value}))})]},h.name)),s.jsx(B,{type:"submit",disabled:d,children:m(e,"submitLabel","Submit")}),s.jsx(Be,{text:De(l)})]})}function ta(e){const t=Ue(e);return t===null?null:`/session-view/${encodeURIComponent(t[0])}/${encodeURIComponent(t[1])}`}function sa(e,t){const n=s.jsx("ol",{className:"flex w-full items-center","data-testid":"agent-ui-steps",children:t.map((a,r)=>{const o=a.state==="done"||a.state==="active"?a.state:"upcoming",l=r===t.length-1;return s.jsxs("li",{className:L("flex items-center",!l&&"flex-1"),"aria-current":o==="active"?"step":void 0,children:[s.jsxs("div",{className:"flex flex-col items-center gap-1.5",children:[s.jsx("span",{className:L("flex h-7 w-7 items-center justify-center rounded-full border text-xs font-semibold tabular-nums transition-colors",o==="done"&&"border-[hsl(var(--primary))] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]",o==="active"&&"border-[hsl(var(--primary))] text-[hsl(var(--primary))] ring-2 ring-[hsl(var(--primary))]/30",o==="upcoming"&&"border-border text-muted-foreground"),children:o==="done"?"✓":r+1}),s.jsx("span",{className:L("whitespace-nowrap text-xs",o==="active"?"font-medium text-foreground":"text-muted-foreground"),children:a.label??""})]}),!l&&s.jsx("span",{className:L("mx-2 mb-5 h-px flex-1 transition-colors",o==="done"?"bg-[hsl(var(--primary))]":"bg-border"),"aria-hidden":"true"})]},r)})});return P(e).pinned?s.jsx("div",{"data-testid":"agent-ui-steps-pinned",className:"sticky top-0 z-10 -mx-4 bg-background px-4 py-2",children:n}):n}function na({node:e,steps:t}){const{layout:n,selectedStep:a,selectStep:r}=Rt();return n!=="rail"?sa(e,t):s.jsx("ol",{className:"flex flex-col","data-testid":"agent-ui-steps","aria-label":"Steps",children:t.map((o,l)=>{const d=o.state==="done"||o.state==="active"?o.state:"upcoming",f=typeof o.id=="string"&&o.id!==""?o.id:null,p=f!==null&&f===a;return s.jsxs("li",{"data-testid":"agent-ui-rail-step","data-step-id":f??void 0,"data-selected":p?"true":void 0,className:"relative",children:[s.jsxs("button",{type:"button",onClick:()=>f!==null&&r(f),disabled:f===null,"aria-current":d==="active"?"step":void 0,"aria-pressed":p,className:L("flex w-full items-start gap-2.5 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-muted/40",p&&"bg-muted/60"),children:[s.jsx("span",{className:L("mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border text-[10px] font-semibold tabular-nums",d==="done"&&"border-[hsl(var(--primary))] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]",d==="active"&&"border-[hsl(var(--primary))] text-[hsl(var(--primary))] ring-2 ring-[hsl(var(--primary))]/30",d==="upcoming"&&"border-border text-muted-foreground"),children:d==="done"?"✓":l+1}),s.jsxs("span",{className:"min-w-0",children:[s.jsx("span",{className:L("block text-sm",d==="active"?"font-medium text-foreground":d==="upcoming"?"text-muted-foreground":"text-foreground"),children:o.label??""}),o.summary?s.jsx("span",{className:"block truncate text-xs text-muted-foreground",children:o.summary}):null]})]}),ls.jsx("div",{className:L("flex",m(e,"direction")==="horizontal"?"flex-row":"flex-col",ue[m(e,"gap","md")]??ue.md,it[m(e,"align","start")]??it.start),children:(e.children??[]).map(t)}),"ap:status":e=>s.jsx(Hn,{n:e}),"ap:grid":(e,t)=>{const n=typeof P(e).columns=="number"?P(e).columns:0,a=n>0?Math.min(Math.max(Math.trunc(n),1),12):0;return s.jsx("div",{className:L("grid",ue[m(e,"gap","md")]??ue.md),style:{gridTemplateColumns:a>0?`repeat(${a}, minmax(0, 1fr))`:"repeat(auto-fit, minmax(16rem, 1fr))"},children:(e.children??[]).map(t)})},"ap:card":(e,t)=>{const n=!!(m(e,"title")||m(e,"description"));return s.jsxs(ge,{children:[n&&s.jsxs(bt,{children:[m(e,"title")&&s.jsx(wt,{children:m(e,"title")}),m(e,"description")&&s.jsx(rs,{children:m(e,"description")})]}),s.jsx(Pe,{className:n?void 0:"pt-6",children:(e.children??[]).map(t)})]})},"ap:collapsible":(e,t)=>s.jsx(Ct,{title:m(e,"title"),collapsed:!!P(e).collapsed,testId:"ap-collapsible",children:(e.children??[]).map(t)}),"ap:heading":e=>{const t=Math.min(Math.max(Number(P(e).level)||2,1),4),n=`h${t}`,a={1:"text-2xl",2:"text-xl",3:"text-lg",4:"text-base"}[t]??"text-xl";return s.jsx(n,{className:L(a,"font-semibold text-foreground"),children:m(e,"text")})},"ap:text":e=>s.jsx("p",{className:L(m(e,"variant")==="muted"&&"text-muted-foreground",m(e,"variant")==="mono"&&"font-mono","text-sm"),children:m(e,"text")}),"ap:markdown":e=>s.jsx(_e,{children:m(e,"body")}),"ap:metric":e=>s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx("span",{className:"text-xs uppercase tracking-wide text-muted-foreground",children:m(e,"label")}),s.jsx("span",{className:"text-2xl font-semibold tabular-nums",children:m(e,"value")}),m(e,"delta")&&s.jsx("span",{className:L("text-xs tabular-nums",m(e,"trend")==="up"&&"text-[hsl(var(--success))]",m(e,"trend")==="down"&&"text-destructive",m(e,"trend")!=="up"&&m(e,"trend")!=="down"&&"text-muted-foreground"),children:m(e,"delta")}),m(e,"caption")&&s.jsx("span",{className:"text-xs text-muted-foreground",children:m(e,"caption")})]}),"ap:progress":e=>s.jsx(Un,{n:e}),"ap:question":e=>s.jsx(Fn,{n:e}),"ap:notice":e=>s.jsx(Tn,{n:e}),"ap:table":e=>{const t=Se(P(e).columns,"ap:table.columns"),n=Se(P(e).rows,"ap:table.rows");return s.jsx(Xn,{n:e,columns:t,rows:n})},"ap:badge":e=>s.jsx(Ds,{variant:qt(m(e,"variant"),Wn,"default"),children:m(e,"text")}),"ap:alert":e=>{const t=m(e,"severity");return s.jsxs(W,{variant:t==="error"?"destructive":"default",className:t==="warning"?"border-warning/50 text-warning [&>svg]:text-warning":void 0,children:[m(e,"title")&&s.jsx(Y,{children:m(e,"title")}),m(e,"body")&&s.jsx(G,{children:m(e,"body")})]})},"ap:empty":e=>s.jsxs("div",{className:"flex flex-col items-center gap-1 py-10 text-center",children:[s.jsx("p",{className:"text-sm font-medium",children:m(e,"title","Nothing here yet")}),m(e,"body")&&s.jsx("p",{className:"text-xs text-muted-foreground",children:m(e,"body")})]}),"ap:steps":e=>{const t=Se(P(e).steps,"ap:steps.steps");return t.forEach((n,a)=>{if(typeof n!="object"||n===null)throw new TypeError(`ap:steps.steps[${a}] must be an object`)}),s.jsx(na,{node:e,steps:t})},"ap:skeleton":e=>s.jsxs("div",{className:"flex flex-col gap-2 py-4","data-testid":"ap-skeleton","aria-busy":"true","aria-live":"polite",children:[m(e,"label")&&s.jsx("p",{className:"text-xs text-muted-foreground",children:m(e,"label")}),s.jsx("div",{className:"h-4 w-1/3 animate-pulse rounded bg-muted"}),s.jsx("div",{className:"h-4 w-full animate-pulse rounded bg-muted"}),s.jsx("div",{className:"h-4 w-5/6 animate-pulse rounded bg-muted"}),!m(e,"label")&&s.jsx("span",{className:"sr-only",children:"Loading…"})]}),"ap:error":e=>s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:m(e,"title","Something went wrong")}),m(e,"body")&&s.jsx(G,{children:m(e,"body")})]}),"ap:chart":e=>{const t=P(e).series??[],n=P(e).data??[],a=m(e,"xKey"),r=m(e,"kind","line");if(n.length===0||t.length===0)return s.jsx("p",{className:"py-6 text-center text-sm text-muted-foreground",children:"No data to chart."});const o=s.jsx(Ns,{stroke:"hsl(var(--border))",strokeDasharray:"3 3",vertical:!1}),l=s.jsx(Ss,{dataKey:a,stroke:"hsl(var(--muted-foreground))",tickLine:!1,axisLine:!1}),d=s.jsx(ks,{stroke:"hsl(var(--muted-foreground))",tickLine:!1,axisLine:!1,width:40}),f=s.jsx(zs,{});return r==="bar"?s.jsx(je,{children:s.jsxs(gs,{data:n,children:[o,l,d,f,t.map((p,u)=>s.jsx(vs,{dataKey:p.key,name:p.label??p.key,fill:de(u)},p.key))]})}):r==="area"?s.jsx(je,{children:s.jsxs(bs,{data:n,children:[o,l,d,f,t.map((p,u)=>s.jsx(ws,{type:"monotone",dataKey:p.key,name:p.label??p.key,stroke:de(u),fill:de(u),fillOpacity:.25},p.key))]})}):s.jsx(je,{children:s.jsxs(js,{data:n,children:[o,l,d,f,t.map((p,u)=>s.jsx(ys,{type:"monotone",dataKey:p.key,name:p.label??p.key,stroke:de(u),strokeWidth:2,dot:!1},p.key))]})})},"ap:chat":e=>s.jsx(Qs,{n:e}),"ap:tabs":(e,t)=>{const n=P(e).tabs??[],a=e.children??[];return n.length===0?s.jsx(s.Fragment,{children:a.map(t)}):s.jsxs(ts,{defaultValue:m(e,"value")||n[0].value,children:[s.jsx(ss,{children:n.map(r=>s.jsx(ns,{value:r.value,children:r.label??r.value},r.value))}),n.map((r,o)=>{const d=o===n.length-1?a.slice(o):a.slice(o,o+1);return s.jsx(as,{value:r.value,children:d.map((f,p)=>t(f,`${r.value}-${p}`))},r.value)})]})},"ap:raw_html":()=>s.jsxs(W,{children:[s.jsx(Y,{children:"Embedded content"}),s.jsx(G,{children:"This section renders once the sandboxed content view is available."})]}),"ap:session_view":e=>{const t=ta(P(e).sessionRef);return t===null?s.jsx(W,{children:"Session view unavailable: no valid session reference."}):s.jsx("iframe",{src:t,title:"Session view",style:{width:"100%",height:"600px",border:"0"}})},"ap:agentlink":e=>(qe(e),s.jsx(ln,{n:e})),"ap:attachment":e=>s.jsx(mn,{n:e}),"ap:button":e=>s.jsx(Zn,{n:e}),"ap:select":e=>s.jsx(Gn,{n:e}),"ap:daterange":e=>s.jsx(Jn,{n:e}),"ap:form":e=>s.jsx(ea,{n:e}),"oap:generative":(e,t)=>{var n;return s.jsx(jn,{name:m(e,"name"),title:typeof((n=e.props)==null?void 0:n.title)=="string"?e.props.title:void 0,children:(e.children??[]).map(t)})},"oap:page":(e,t)=>s.jsx(On,{node:e,renderChild:t})};function ra({type:e}){return s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Cannot render this section"}),s.jsxs(G,{children:["Unknown component ",s.jsx("code",{className:"font-mono",children:e}),". Reload the page; if it persists, this view needs an update."]})]})}function oa({type:e}){return s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Cannot render this section"}),s.jsxs(G,{children:["The ",s.jsx("code",{className:"font-mono",children:e})," section could not be displayed. The rest of this view is unaffected."]})]})}const ia=e=>(t,n)=>Ft(t,n,e);function Ft(e,t,n){const a=aa[e.component];if(!a)return n==null||n(e.component),s.jsx(ra,{type:e.component},t);try{return i.cloneElement(a(e,ia(n)),{key:t})}catch(r){return console.error(`agentui: failed to render ${e.component}`,r),n==null||n(e.component),s.jsx(oa,{type:e.component},t)}}function la(e,t,n){return`${e}/${t.join(".")}#${n}`}function ca(e,t){return Vt(e,ne(e)??"",[],t)}function Vt(e,t,n,a){var c;const r=(c=e.children)==null?void 0:c.map((h,b)=>{const x=ne(h);return Vt(h,x??t,x!==null?[]:[...n,b],a)});if(!e.bindings)return{...e,children:r};const o={...e.props};let l=null,d=null,f=null;for(const h of Object.keys(e.bindings).sort()){const b=a[la(t,n,h)]??{status:"loading"};if(b.status==="error"){l===null&&(l=b.message);continue}if(b.status==="needs_input"){d===null&&(d=b.message);continue}if(b.status==="waking"){f===null&&(f=b.message);continue}if(b.status==="loading"){f===null&&(f="");continue}o[h]=b.value}if(l===null&&d===null&&f===null)return{...e,props:o,children:r};const u=l!==null?{component:"ap:error",props:{title:"Cannot load this section",body:l}}:f!==null?{component:"ap:skeleton",props:f===""?{}:{label:f}}:{component:"ap:empty",props:{title:"Nothing to show yet",body:d}};return Ve(e)?{component:"ap:stack",props:{gap:"sm"},children:[{...e,props:o,children:r},u]}:u}const xe="p.";function dt(e){const t={};return new URLSearchParams(e).forEach((a,r)=>{if(!r.startsWith(xe))return;const o=r.slice(xe.length);o!==""&&(t[o]=a)}),t}function da(e,t){const n=new URLSearchParams(e);for(const r of Array.from(n.keys()))r.startsWith(xe)&&n.delete(r);for(const r of Object.keys(t).sort())n.set(xe+r,t[r]);const a=n.toString();return a===""?"":`?${a}`}function ua({open:e,variant:t,text:n,replySeq:a,onReply:r,onDismiss:o}){const[l,d]=i.useState(""),[f,p]=i.useState(!1),[u,c]=i.useState(!1);i.useEffect(()=>{d(""),c(!1)},[a]);const h=l.trim()===""||f;function b(x){x.preventDefault(),!h&&(p(!0),c(!1),r(l).catch(g=>{console.error("agentui: reply modal send failed",g),c(!0)}).finally(()=>p(!1)))}return s.jsx(Oe,{open:e,onOpenChange:x=>{x||o()},children:s.jsxs(Me,{"data-testid":"agent-ui-reply-modal","data-variant":t,children:[s.jsx(Le,{children:t==="paused"?s.jsxs(s.Fragment,{children:[s.jsx(pe,{children:"The agent paused"}),s.jsx(me,{children:"It replied and stopped without asking anything. Send a message to continue."})]}):s.jsxs(s.Fragment,{children:[s.jsx(pe,{children:"The agent is waiting for your reply"}),s.jsx(me,{children:"Your reply goes to this session's conversation, just as if you had typed it there."})]})}),s.jsx("div",{className:"max-h-48 overflow-y-auto text-sm",children:s.jsx(_e,{children:n})}),s.jsxs("form",{onSubmit:b,className:"flex flex-col gap-3",children:[s.jsx("textarea",{"aria-label":"Your reply",value:l,disabled:f,onChange:x=>d(x.target.value),placeholder:t==="paused"?"Tell it how to continue…":void 0,className:"flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"}),u&&s.jsx("p",{role:"alert",className:"text-xs text-destructive",children:"Could not send your reply. Try again."}),s.jsxs(cs,{children:[s.jsx(B,{type:"button",variant:"ghost",onClick:o,disabled:f,children:"Later"}),s.jsx(B,{type:"submit",disabled:h,children:f?"Sending…":t==="paused"?"Continue":"Reply"})]})]})]})})}function fa(e){var t;return e.turnActive?e.composingUpdate?"Composing a page update…":(t=e.activity)!=null&&t.compactLine?e.activity.compactLine:e.notice?e.notice:"Working…":null}function pa(e){switch(e){case"done":return"done";case"in_progress":return"current";default:return"next"}}function ma({label:e,state:t,live:n}){return s.jsxs("li",{"data-state":t,className:"flex items-center gap-2 text-xs",children:[t==="done"&&s.jsx("span",{"aria-hidden":"true",className:"text-[hsl(var(--success))]",children:"✓"}),t==="current"&&s.jsx(ve,{className:L("h-3 w-3 shrink-0 text-primary",n&&"animate-spin"),"aria-hidden":"true"}),s.jsx("span",{className:L(t==="done"&&"text-muted-foreground line-through",t==="next"&&"text-muted-foreground"),children:e})]})}function ha({signals:e}){var a;if(!e.turnActive&&e.plan===null)return null;const t=fa(e),n=((a=e.plan)==null?void 0:a.items)??[];return s.jsxs("section",{"data-testid":"agent-ui-default-progress","aria-live":"polite",className:"flex flex-col gap-2 border-b border-border bg-card/40 px-4 py-2",children:[t!==null&&s.jsxs("p",{"data-testid":"agent-ui-progress-now","data-composing":e.composingUpdate?"true":void 0,className:"flex items-center gap-2 text-xs font-medium text-foreground",children:[!e.composingUpdate&&s.jsx(ve,{className:"h-3 w-3 shrink-0 animate-spin text-muted-foreground","aria-hidden":"true"}),t]}),n.length>0&&s.jsx("ul",{className:"flex flex-col gap-1",children:n.map((r,o)=>s.jsx(ma,{label:r.label,state:pa(r.status),live:e.turnActive},`${o}-${r.id}`))})]})}const Bt="$transport",xa="This view's data could not be loaded. Check your connection and reload.";function ut(e){return{status:"error",message:e}}class ke extends Error{constructor(t,n){super(`agent-ui bindings request failed (${t})`),this.name="BindingsRequestError",this.authored=n,this.status=t}}const ga=410;async function va(e){try{const t=await e.json();return typeof(t==null?void 0:t.error)=="string"?t.error:""}catch{return""}}const ba=250,wa=2e3,ja=15;function ya(e,t,n,a,r){const[o,l]=i.useState({}),[d,f]=i.useState(!1),p=i.useRef(r);p.current=r;const u=i.useRef(0),c=i.useRef(!0),[h,b]=i.useState(0),x=i.useRef(0),g=i.useRef(null);return i.useEffect(()=>{let w=!0;u.current+=1;const C=u.current,N=()=>w&&C===u.current,M=new AbortController,D=c.current?0:ba;c.current=!1;const j=setTimeout(()=>{D>0&&f(!0),fetch(`/agent-ui/${encodeURIComponent(e)}/${encodeURIComponent(t)}/bindings`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({params:n}),signal:M.signal}).then(async v=>{if(!v.ok)throw new ke(v.status,await va(v));return await v.json()}).then(v=>{if(!N())return;const $={};let k=!1;for(const[y,T]of Object.entries(v.bindings??{})){if(T.status==="ok"){$[y]={status:"ok",value:T.value};continue}if(T.status==="needs_input"){$[y]={status:"needs_input",message:T.message??"Choose a value above to load this data."};continue}if(T.status==="waking"){$[y]={status:"waking",message:T.message??"Waking this agent…"},k=!0;continue}$[y]={status:"error",message:T.message??"This view's data is not available."}}l($),k&&x.current{w&&(l(y=>y),b(y=>y+1))},wa)):k?l(y=>{const T={};for(const[U,H]of Object.entries(y))T[U]=H.status==="waking"?{status:"error",message:"the agent for this session isn't responding"}:H;return T}):x.current=0,f(!1)}).catch(v=>{var k;if(M.signal.aborted||!N())return;f(!1),console.error("agent-ui bindings: request failed",v),v instanceof ke&&v.status===ga&&((k=p.current)==null||k.call(p));const $=v instanceof ke&&v.authored!==""?v.authored:xa;l(y=>{const T={};for(const U of Object.keys(y))T[U]=ut($);return T[Bt]=ut($),T})})},D);return()=>{w=!1,clearTimeout(j),M.abort(),g.current!==null&&(clearTimeout(g.current),g.current=null)}},[e,t,JSON.stringify(n),a,h]),{states:o,refreshing:d}}const Na=2e4,Sa=5*6e4,ft=["pointerdown","keydown","scroll","wheel"];function ka({send:e,enabled:t=!0}){const n=i.useRef(e);n.current=e,i.useEffect(()=>{if(!t||typeof window>"u"||typeof document>"u")return;let a=Date.now();const r=()=>{a=Date.now()};for(const d of ft)window.addEventListener(d,r,{passive:!0});const o=()=>document.visibilityState!=="visible"||typeof document.hasFocus=="function"&&!document.hasFocus()?!1:Date.now()-a{o()&&n.current()},Na);return()=>{window.clearInterval(l);for(const d of ft)window.removeEventListener(d,r)}},[t])}function Ca(e,t){return e.replace(/\{([^{}]*)\}/g,(n,a)=>a!==""&&Object.hasOwn(t,a)?t[a]:n)}const Ra=2e3,Ta="This action may not have completed. Check your connection, then check the result before retrying.",Aa="Live updates for this view are unavailable right now.";function Ea(e,t,n){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/agent-ui/${encodeURIComponent(t)}/${encodeURIComponent(n)}/live`}function pt(e){return{phase:e.state,requestId:e.requestId,message:e.message,approvalAddressedToViewer:e.approvalAddressedToViewer}}function Ce(e){return!ie(e)}function $a(e,t){return e!==void 0&&e.phase===t.phase&&e.requestId===t.requestId&&e.message===t.message&&!!e.approvalAddressedToViewer==!!t.approvalAddressedToViewer}function Ia(e,t,n,a,r,o=[]){const[l,d]=i.useState({}),[f,p]=i.useState(null),u=i.useRef(!0),c=i.useRef(n);c.current=n;const h=i.useRef(a);h.current=a;const b=i.useRef(r);b.current=r;const x=i.useRef(o);x.current=o;const g=i.useRef(l);g.current=l;const w=i.useRef(new Set),C=i.useRef(null),N=i.useRef(0),M=i.useRef({});i.useEffect(()=>(u.current=!0,()=>{u.current=!1}),[]),i.useEffect(()=>{let j=null,v,$=!1,k=0;const y=O=>{N.current+=1,M.current[O]=N.current},T=O=>{if(!Ce(O.state))return!1;const A=g.current[O.action];return A===void 0||!Ce(A.phase)},U=O=>{const A=new Set,K={};let z=!1,se=!1;for(const q of O){if(A.has(q.action)||(A.add(q.action),w.current.has(q.action)))continue;T(q)&&(z=!0);const te=pt(q);$a(g.current[q.action],te)||(se=!0),K[q.action]=te,y(q.action)}se&&(d(q=>({...q,...K})),z&&h.current())},H=O=>{const A=T(O);y(O.action),d(K=>({...K,[O.action]:pt(O)})),A&&h.current()},ee=O=>{let A;try{A=JSON.parse(O)}catch(K){console.error("agent-ui live: malformed frame",{ns:e,name:t,err:K});return}if(u.current){if(A.type==="snapshot"){p(null),U(A.actions??[]);return}if(A.type==="event"&&A.action){H(A.action);return}if(A.type==="error"){console.error("agent-ui live: server reported live updates unavailable",{ns:e,name:t,msg:A}),p(A.message||Aa);return}if(A.type==="view"){if(!A.declaration||typeof A.declaration.view!="object"||A.declaration.view===null||typeof A.declaration.view.component!="string"){console.error("agent-ui live: malformed view frame; keeping the previous declaration",{ns:e,name:t,msg:A});return}b.current(A.declaration,A.agentParams,A.hook);return}console.error("agent-ui live: unhandled frame",{ns:e,name:t,msg:A})}},J=()=>{$||(k+=1,console.error("agent-ui live: socket closed; reconnecting",{ns:e,name:t,attempt:k}),v=window.setTimeout(X,Ra))},X=()=>{if(!$){try{j=new WebSocket(Ea(window.location,e,t))}catch(O){console.error("agent-ui live: failed to open socket",{ns:e,name:t,err:O}),J();return}j.onopen=()=>{k=0},C.current=j,j.onmessage=O=>ee(O.data),j.onerror=()=>{},j.onclose=J}};return X(),()=>{$=!0,v&&window.clearTimeout(v),j&&j.close(),C.current=null}},[e,t]),ka({enabled:!0,send:i.useCallback(()=>{const j=C.current;if(!(!j||j.readyState!==WebSocket.OPEN))try{j.send(JSON.stringify({type:"presence"}))}catch(v){console.error("agent-ui live: presence heartbeat send failed",{ns:e,name:t,err:v})}},[e,t])});const D=i.useCallback((j,v)=>{if(w.current.has(j))return;w.current.add(j),N.current+=1;const $=N.current;d(y=>({...y,[j]:{phase:"submitted"}}));const k=x.current.find(y=>y.name===j);if(k!=null&&k.prompt){jt(e,t,Ca(k.prompt,{...c.current,...v??{}})).then(()=>{var y;u.current&&(d(T=>({...T,[j]:{phase:"succeeded",message:"Asked the agent."}})),(y=h.current)==null||y.call(h))}).catch(y=>{console.error("agent-ui actions: asking the agent failed",{ns:e,name:t,action:j,err:y}),u.current&&d(T=>({...T,[j]:{phase:"failed",message:"Could not reach the agent."}}))}).finally(()=>{w.current.delete(j)});return}fetch(`/agent-ui/${encodeURIComponent(e)}/${encodeURIComponent(t)}/actions`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:j,params:c.current,inputs:v})}).then(async y=>{if(!y.ok)throw new Error(`agent-ui actions: request failed (${y.status})`);return await y.json()}).then(y=>{if(!u.current||(M.current[j]??0)>$)return;const T=y.state;d(U=>{var H;return{...U,[j]:{phase:T,requestId:y.requestId,message:y.message,approvalAddressedToViewer:(H=U[j])==null?void 0:H.approvalAddressedToViewer}}}),Ce(T)&&h.current()}).catch(y=>{console.error("agent-ui actions: invoke failed",{ns:e,name:t,action:j,err:y}),u.current&&d(T=>{var U;return{...T,[j]:{phase:"failed",message:Ta,approvalAddressedToViewer:(U=T[j])==null?void 0:U.approvalAddressedToViewer}}})}).finally(()=>{w.current.delete(j)})},[e,t]);return{states:l,invoke:D,liveError:f}}const Pa={approvals:[],trustEvents:[]},Oa=4e3;function Ma(e,t){if(e===t)return!0;if(e.approvals.length!==t.approvals.length||e.trustEvents.length!==t.trustEvents.length)return!1;for(let n=0;nLa(t,n),[t,n]),d=i.useMemo(()=>{const b=new Map;for(const[x,g]of In(e==null?void 0:e.view))g.state==="done"&&b.set(x,g.title??g.label);return b},[e==null?void 0:e.view]),f=i.useMemo(()=>({composed:new Set((e==null?void 0:e.agentComposed)??[]),updated:a,stale:l,waiting:r,answered:new Set((e==null?void 0:e.answered)??[]),collapsed:d}),[e==null?void 0:e.agentComposed,e==null?void 0:e.answered,a,l,r,d]),p=[],u=e!=null&&e.view?Ft(ca(e.view,t),void 0,b=>p.push(b)):null,c=p.length>0?p[0]:null;if(i.useEffect(()=>{c!==null&&o(c)},[c,o]),!(e!=null&&e.view))return s.jsx("div",{className:"p-4 text-sm text-muted-foreground",children:"This agent has not declared any content for this session."});const h=t[Bt];return s.jsx(vn,{value:f,children:s.jsxs("div",{"data-testid":"agent-ui-root",className:L("flex flex-col gap-2 p-4 mx-auto",xn(e==null?void 0:e.view)==="rail"?"max-w-6xl":"max-w-3xl"),"aria-busy":l.has("")||void 0,children:[(h==null?void 0:h.status)==="error"&&s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Data unavailable"}),s.jsx(G,{children:h.message})]}),s.jsxs("div",{className:L("relative",l.has("")&&"opacity-60 transition-opacity"),children:[u,l.has("")&&s.jsxs("div",{"data-testid":"agent-ui-region-refreshing",className:"pointer-events-none absolute right-2 top-2 flex items-center gap-1.5 text-[11px] text-muted-foreground",children:[s.jsx(ve,{className:"h-3.5 w-3.5 animate-spin","aria-hidden":"true"}),s.jsx("span",{children:"Updating…"})]})]})]})})}function Da(){const[e,t]=i.useState(()=>new Map),n=i.useRef(new Map),a=i.useCallback(r=>{t(l=>new Map(l).set(r,Date.now()));const o=n.current.get(r);o!==void 0&&clearTimeout(o),n.current.set(r,setTimeout(()=>{n.current.delete(r),t(l=>{if(!l.has(r))return l;const d=new Map(l);return d.delete(r),d})},Oa))},[]);return i.useEffect(()=>{const r=n.current;return()=>{for(const o of r.values())clearTimeout(o);r.clear()}},[]),{updatedHooks:e,markUpdated:a}}function Ua({ns:e,name:t,declaration:n,agentParams:a,shown:r,onChromeSignals:o,onSessionEnded:l}){var Ke,ze;const[d,f]=i.useState(null),p=i.useCallback(S=>{const E=`render-error:${S}`;f(I=>(I==null?void 0:I.id)===E?I:{kind:"hard_error",id:E})},[]),[u,c]=i.useState(n),h=Us(),{questionShowing:b,progressShowing:x,waiting:g}=i.useMemo(()=>{const S=En(u==null?void 0:u.view,"ap:question"),E=new Set((u==null?void 0:u.answered)??[]),I=new Set([...S].filter(V=>!E.has(V)));return{questionShowing:I.size>0||$n(u==null?void 0:u.view)||$e(u==null?void 0:u.view,"ap:notice"),progressShowing:$e(u==null?void 0:u.view,"ap:progress"),waiting:I}},[u==null?void 0:u.view,u==null?void 0:u.answered]),[w,C]=i.useState(0),[N,M]=i.useState(()=>{const E={...Dt(u),...a??{}};return typeof window>"u"?E:ye(u,{...E,...dt(window.location.search)})}),D=i.useRef(new Set(typeof window>"u"?[]:Object.keys(dt(window.location.search)))),j=i.useCallback((S,E)=>{D.current.add(S),M(I=>I[S]===E?I:{...I,[S]:E})},[]),v=i.useCallback((S,E)=>{M(I=>{const V={...I};let Q=!1;for(const[we,Ye]of Object.entries(ye(E,S)))D.current.has(we)||V[we]===Ye||(V[we]=Ye,Q=!0);return Q?V:I})},[]);i.useEffect(()=>{if(typeof window>"u")return;const S=da(window.location.search,N);S!==window.location.search&&window.history.replaceState(window.history.state,"",`${window.location.pathname}${S}${window.location.hash}`)},[N]);const $=i.useMemo(()=>({params:N,setParam:j}),[N,j]),k=i.useMemo(()=>({ns:e,name:t}),[e,t]),[y,T]=i.useState(0),U=i.useCallback(()=>{T(S=>S+1)},[]),{updatedHooks:H,markUpdated:ee}=Da(),J=i.useRef(JSON.stringify(n)),X=i.useCallback((S,E,I)=>{E&&Object.keys(E).length>0&&v(E,S),I&&ee(I);const V=JSON.stringify(S);V!==J.current&&(J.current=V,c(S),M(Q=>ye(S,Q)),T(Q=>Q+1))},[v,ee]),{states:O,invoke:A,liveError:K}=Ia(e,t,N,U,X,(u==null?void 0:u.actions)??[]),z=i.useCallback(S=>jt(e,t,S),[e,t]),se=i.useMemo(()=>({states:O,invoke:A,answer:z,busy:h.turnActive}),[O,A,z,h.turnActive]),{states:q,refreshing:te}=ya(e,t,N,y,l),ce=Object.entries(O).filter(([,S])=>S.phase==="awaiting_approval").map(([S,E])=>`${S}:${E.requestId??""}:${E.approvalAddressedToViewer?"1":"0"}`).sort().join("|"),{approvalNotices:ae,approvalTrustEvents:R}=i.useMemo(()=>{const S=[],E=[];for(const[I,V]of Object.entries(O)){if(V.phase!=="awaiting_approval")continue;const Q=V.requestId??I;S.push({id:Q,label:V.approvalAddressedToViewer?"Your approval is needed to continue.":"Waiting on approval from someone else.",addressedToViewer:!!V.approvalAddressedToViewer}),V.approvalAddressedToViewer&&E.push({kind:"approval_addressed_to_viewer",id:Q})}return{approvalNotices:S,approvalTrustEvents:E}},[ce]),_=i.useMemo(()=>({approvals:ae,trustEvents:[d,...R]}),[ae,R,d]);i.useEffect(()=>{o==null||o(_)},[_,o]);const F=((Ke=h.lastAgentReply)==null?void 0:Ke.seq)??0,Yt=r&&(h.awaitingReply||h.pausedIdle)&&h.lastAgentReply!==null&&F>w&&!b&&!h.ended,Gt=i.useCallback(async S=>{await z(S),C(F)},[z,F]),Jt=i.useCallback(()=>C(F),[F]),[He,be]=i.useState(null),We=qs(e,t,be),Xt=i.useCallback((S,E,I)=>{be(null),We(S,E,I)},[We]),[Qt,Zt]=i.useState({}),es=i.useCallback(S=>{if(S.type!=="interaction_decision_rejected")return;const E=S.payload.payload;h.pendingInteractions.some(I=>I.requestRef===E.requestRef)&&(be(Fs(E)),Zt(I=>({...I,[E.requestRef]:(I[E.requestRef]??0)+1})))},[h.pendingInteractions]);return yt(es),s.jsx(cn,{value:k,children:s.jsx(_n,{value:$,children:s.jsx(Js,{value:se,children:s.jsxs("div",{className:"flex min-h-full flex-col",children:[!x&&s.jsx(ha,{signals:h}),K!==null&&s.jsx("div",{className:"px-4 pt-4 max-w-3xl mx-auto","data-testid":"agent-ui-live-error",children:s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Live updates unavailable"}),s.jsx(G,{children:K})]})}),r&&h.pendingInteractions.length>0&&s.jsxs("section",{"data-testid":"agent-ui-decisions","aria-label":"Needs your decision",className:"flex flex-col gap-2 border-b border-border bg-card/60 px-3 py-2",children:[s.jsx("p",{className:"text-xs font-medium text-muted-foreground",children:"Needs your decision"}),h.pendingInteractions.map(S=>s.jsx(Vs,{request:S,onDecision:Xt},`${S.requestRef}:${Qt[S.requestRef]??0}`)),He&&s.jsx("p",{role:"alert",className:"text-xs text-destructive",children:He})]}),s.jsx(_a,{declaration:u,states:q,refreshing:te,updatedHooks:H,waiting:g,onRenderError:p}),s.jsx(ua,{open:Yt,variant:h.awaitingReply?"asked":"paused",text:((ze=h.lastAgentReply)==null?void 0:ze.text)??"",replySeq:F,onReply:Gt,onDismiss:Jt})]})})})})}function qa(e,t){switch(e){case"attached":return{text:"Resumed your session.",Icon:Qe};case"asleep":return{text:"Your session is asleep; it resumes when you send a message.",Icon:Ps};case"ended":return{text:t?`This session has ended: ${t}`:"This session has ended. You can start a new one.",Icon:Is};default:return{text:"Connected to your session.",Icon:Qe}}}function Ht(e){return e==="collapsed"}function mt(e,t,n,a){a&&(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||(e.preventDefault(),a(t,n)))}function ht(e,t,n,a){e.currentTarget.href=re(t,n,a)}function xt(e){const t="inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-[12px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";return e?`${t} bg-secondary text-secondary-foreground`:`${t} text-muted-foreground hover:bg-accent/50 hover:text-foreground`}function re(e,t,n){const a=new URLSearchParams(typeof window>"u"?"":window.location.search);return a.delete("new"),a.delete("ns"),a.delete("agentClass"),a.delete("prompt"),a.set("session",`${e}/${t}`),a.set("view",n),`/sessions?${a.toString()}`}function Fa(e){return{collapsed:Ht(e),userToggled:!1,trustRevealed:!1}}function Va(e,t){switch(t.type){case"ui_requested":return e.userToggled||e.trustRevealed||e.collapsed===t.collapsed?e:{...e,collapsed:t.collapsed};case"user_toggled":return{collapsed:!e.collapsed,userToggled:!0,trustRevealed:!1};case"trust_event":return{...e,collapsed:!1,trustRevealed:!0}}}function Ba({ns:e,name:t,subject:n,sessionOrigin:a,endedReason:r,currentView:o,offersAgentUI:l,onSelectView:d,initialState:f,trustEvents:p,approvals:u,headerActions:c,sidebar:h,children:b}){const x=e!==void 0&&t!==void 0?{ns:e,name:t}:null,g=qa(a??"",r),[w,C]=i.useReducer(Va,f,Fa),N=w.collapsed;i.useEffect(()=>{C({type:"ui_requested",collapsed:Ht(f)})},[f]);const M=i.useRef(new Set),D=(p??[]).flatMap(v=>v?[v.id]:[]);i.useEffect(()=>{const v=D.filter($=>!M.current.has($));if(v.length!==0){for(const $ of v)M.current.add($);C({type:"trust_event"})}},[D.join("|")]);function j(){C({type:"user_toggled"})}return s.jsxs("div",{className:"h-screen flex flex-col bg-background text-foreground",children:[s.jsxs("header",{className:"flex items-center gap-2.5 px-3 py-2 min-h-[52px] bg-card border-b border-border",children:[s.jsx(B,{type:"button",variant:"ghost",size:"icon","aria-expanded":!N,"aria-label":N?"Show chrome":"Hide chrome","data-testid":"session-shell-chrome-toggle",onClick:j,children:N?s.jsx(Cs,{className:"h-4 w-4","aria-hidden":"true"}):s.jsx(Rs,{className:"h-4 w-4","aria-hidden":"true"})}),x&&s.jsxs("div",{className:"flex items-center gap-2 min-w-0",hidden:N,children:[s.jsx(g.Icon,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0","aria-hidden":"true"}),s.jsx("span",{className:"text-[13px] text-muted-foreground truncate",title:g.text,"data-testid":"session-shell-session-origin",children:g.text})]}),s.jsxs("div",{className:"ml-auto flex items-center gap-2 shrink-0",children:[c&&s.jsx("div",{className:"flex items-center gap-1","data-testid":"session-shell-header-actions",hidden:N,children:c}),s.jsxs("div",{className:"flex items-center gap-2 min-w-0",hidden:N,children:[s.jsx(Ts,{className:"h-4 w-4 text-muted-foreground shrink-0","aria-hidden":"true"}),s.jsxs("span",{className:"text-[13px] font-medium truncate","data-testid":"session-shell-identity",children:["Acting as ",n]})]})]})]}),s.jsxs("div",{className:"flex items-center gap-2 px-3 py-1 min-h-[36px] border-b border-border bg-card/60 text-[12px] text-muted-foreground","data-testid":"session-shell-approval-surface",hidden:N,children:[u&&u.length>0&&s.jsxs(s.Fragment,{children:[s.jsx(As,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),s.jsx("ul",{className:"flex flex-col gap-0.5",children:u.map(v=>s.jsx("li",{children:v.label},v.id))})]}),x&&s.jsxs("div",{className:"ml-auto flex items-center gap-0.5 rounded-md border border-border bg-background/60 p-0.5 shrink-0","data-testid":"session-shell-view-tabs",children:[l&&s.jsxs("a",{href:re(x.ns,x.name,"ui"),"aria-current":o==="ui"?"page":void 0,"data-testid":"session-shell-agent-view-switch",className:xt(o==="ui"),onMouseDown:v=>ht(v,x.ns,x.name,"ui"),onClick:v=>mt(v,"ui",re(x.ns,x.name,"ui"),d),children:[s.jsx(Es,{className:"h-3.5 w-3.5","aria-hidden":"true"}),"Agent view"]}),s.jsxs("a",{href:re(x.ns,x.name,"chat"),"aria-current":o==="chat"?"page":void 0,"data-testid":"session-shell-chat-escape-hatch",className:xt(o==="chat"),onMouseDown:v=>ht(v,x.ns,x.name,"chat"),onClick:v=>mt(v,"chat",re(x.ns,x.name,"chat"),d),children:[s.jsx($s,{className:"h-3.5 w-3.5","aria-hidden":"true"}),"Chat"]})]})]}),s.jsxs("div",{className:"flex-1 min-h-0 flex",children:[s.jsx("aside",{className:h?"w-64 shrink-0 min-h-0 overflow-y-auto border-r border-border bg-card/40":"w-0 shrink-0 min-h-0 overflow-hidden","data-testid":"session-shell-sidebar",hidden:N,children:h}),s.jsx("main",{className:"flex min-h-0 flex-1 flex-col overflow-y-auto","data-testid":"session-shell-content-region",children:b})]})]})}function Ha(e){const t=e;if(!t||typeof t.ns!="string"||typeof t.name!="string"||typeof t.href!="string"||!t.href)throw new Error("the server's answer did not address a session");return{ns:t.ns,name:t.name,href:t.href}}class fe extends Error{constructor(t,n){super(t),this.name="StartError",this.indeterminate=n}}async function Wt(e,t){let n;try{n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}catch(a){throw console.error("sessions: start request did not complete",{url:e,err:a}),new fe("We could not confirm whether that session started. Reload your sessions before trying again.",!0)}if(!n.ok){let a="";try{const r=await n.json();a=typeof(r==null?void 0:r.error)=="string"?r.error:""}catch{a=""}throw console.error("sessions: start request failed",{url:e,status:n.status}),new fe(a||"That session could not be started right now.",!1)}try{return Ha(await n.json())}catch{throw console.error("sessions: start succeeded but its answer could not be addressed",{url:e}),new fe("That session started, but we could not open it. Reload your sessions to find it.",!0)}}function Kt(e){return!(e instanceof fe)||e.indeterminate}const Wa="This server cannot start new sessions for that agent. You can still open the sessions you already have with it, or ask an administrator to enable starting here.",Ka=" — cannot start here";function za(e){const t=new Map;for(const n of e)t.set(n.title,(t.get(n.title)??0)+1);return e.map(n=>{const a=(t.get(n.title)??0)>1?`${n.title} (in ${n.ns})`:n.title;return n.startable?a:a+Ka})}function zt(e){window.location.assign(e)}function Ya(){return s.jsx("p",{className:"text-sm text-muted-foreground","data-testid":"session-shell-no-startable-classes",children:"You do not have access to any agents yet. Access arrives by being added to a session someone else started, or through a channel an agent is connected to."})}function Ga({classes:e,onNavigate:t=zt,openInitially:n=!1,preselect:a,initialPrompt:r}){const[o,l]=i.useState(n),d=a?e.findIndex(k=>k.ns===a.ns&&k.class===a.agentClass):-1,[f,p]=i.useState(!1),u=a!==void 0&&d===-1&&!f,[c,h]=i.useState(d===-1?0:d),[b,x]=i.useState(r??""),[g,w]=i.useState(!1),[C,N]=i.useState(null);if(e.length===0)return null;const M=e[Math.min(c,e.length-1)],D=za(e),j=!M.startable,v=M.offersUI===!0;async function $(){w(!0),N(null);try{const k=await Wt("/sessions/api/start",{ns:M.ns,agentClass:M.class,prompt:b});t(v?`${k.href}&view=ui`:k.href)}catch(k){N(k.message),Kt(k)||w(!1)}}return s.jsxs(s.Fragment,{children:[s.jsx(B,{type:"button",size:"sm","data-testid":"session-shell-new-session",onClick:()=>{N(null),l(!0)},children:"+ New session"}),s.jsx(Oe,{open:o,onOpenChange:l,children:s.jsxs(Me,{children:[s.jsxs(Le,{children:[s.jsx(pe,{children:"Start a new session"}),s.jsx(me,{children:v?"Pick an agent. This one opens its own view.":"Pick an agent and say what you need."})]}),s.jsxs("div",{className:"flex flex-col gap-3",children:[u&&s.jsx("p",{className:"text-sm text-destructive","data-testid":"session-shell-preselect-refused",children:"You can't start that agent from here."}),s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(oe,{htmlFor:"new-session-class",children:"Agent"}),s.jsx("select",{id:"new-session-class",className:"rounded-md border border-input bg-background px-3 py-2 text-sm","data-testid":"session-shell-new-session-class",value:c,onChange:k=>{h(Number(k.target.value)),p(!0)},children:e.map((k,y)=>s.jsx("option",{value:y,children:D[y]},`${k.ns}/${k.class}`))})]}),v?s.jsxs("details",{className:"flex flex-col gap-1","data-testid":"session-shell-new-session-prompt-disclosure",children:[s.jsx("summary",{className:"cursor-pointer text-sm text-muted-foreground",children:"Add a message (optional — otherwise the agent is just told you opened the view)"}),s.jsx("textarea",{id:"new-session-prompt",rows:3,"aria-label":"Message",className:"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm","data-testid":"session-shell-new-session-prompt",value:b,onChange:k=>x(k.target.value)})]}):s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(oe,{htmlFor:"new-session-prompt",children:"Message"}),s.jsx("textarea",{id:"new-session-prompt",rows:4,className:"rounded-md border border-input bg-background px-3 py-2 text-sm","data-testid":"session-shell-new-session-prompt",value:b,onChange:k=>x(k.target.value)})]}),j&&s.jsx("p",{className:"text-sm text-muted-foreground","data-testid":"session-shell-new-session-unstartable",children:Wa}),C&&s.jsx("p",{className:"text-sm text-destructive","data-testid":"session-shell-new-session-error",children:C}),s.jsx("div",{className:"flex justify-end",children:s.jsx(B,{type:"button","data-testid":"session-shell-new-session-submit",disabled:g||j||u||!v&&b.trim()==="",onClick:()=>void $(),children:g?v?"Opening…":"Starting…":v?"Open UI":"Start session"})})]})]})})]})}function Ja({ns:e,name:t,onNavigate:n=zt}){const[a,r]=i.useState(!1),[o,l]=i.useState(null);async function d(){r(!0),l(null);try{const f=await Wt(`/agent-ui/${encodeURIComponent(e)}/${encodeURIComponent(t)}/start`,{prompt:"Continue where we left off."});n(f.href)}catch(f){l(f.message),Kt(f)||r(!1)}}return s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(B,{type:"button",size:"sm",variant:"outline","data-testid":"session-shell-start-replacement",disabled:a,onClick:()=>void d(),children:a?"Starting…":"Start a new session"}),o&&s.jsx("span",{className:"text-sm text-destructive","data-testid":"session-shell-start-replacement-error",children:o})]})}async function Xa(e,t){const n=await fetch(`/sessions/api/${encodeURIComponent(e)}/${encodeURIComponent(t)}/detail`);if(!n.ok)throw console.error("sessions: load session detail failed",{ns:e,name:t,status:n.status}),new Error(n.status===403?"you do not have access to this session":n.status===401?"you are no longer signed in":"these settings could not be loaded right now");return await n.json()}function Z({label:e,value:t,children:n}){return s.jsxs("div",{className:"flex items-start justify-between gap-4 py-1.5 text-sm",children:[s.jsx("span",{className:"shrink-0 text-muted-foreground",children:e}),s.jsx("span",{className:"break-all text-right font-mono text-[13px]",children:n??t})]})}function Qa({ns:e,name:t,open:n,onOpenChange:a}){const[r,o]=i.useState(null),[l,d]=i.useState(null);return i.useEffect(()=>{if(!n)return;let f=!1;return o(null),d(null),Xa(e,t).then(p=>{f||o(p)}).catch(p=>{f||d(p.message||"these settings could not be loaded right now")}),()=>{f=!0}},[n,e,t]),s.jsx(Oe,{open:n,onOpenChange:a,children:s.jsxs(Me,{children:[s.jsxs(Le,{children:[s.jsx(pe,{children:"Session details"}),s.jsx(me,{children:"Resolved settings for this conversation."})]}),l?s.jsxs("div",{className:"text-sm text-destructive","data-testid":"session-shell-session-info-error",children:["Couldn't load details: ",l]}):r?s.jsxs("div",{className:"divide-y divide-border",children:[s.jsx(Z,{label:"Agent class",value:r.agentClass}),s.jsx(Z,{label:"Model",children:r.model.name?s.jsx(Ks,{model:`${r.model.provider||"?"}/${r.model.name}`}):"not resolved"}),s.jsx(Z,{label:"Max tokens",value:r.maxTokens?String(r.maxTokens):"—"}),s.jsx(Z,{label:"Max turns",value:r.maxTurns?String(r.maxTurns):"—"}),s.jsx(Z,{label:"Max duration",value:r.maxDuration||"—"}),s.jsx(Z,{label:"Owner",value:r.owner}),s.jsx(Z,{label:"Phase",value:r.phase||"—"}),s.jsx(Z,{label:"Created",value:new Date(r.createdAt).toLocaleString()})]}):s.jsx("div",{className:"text-sm text-muted-foreground",children:"Loading…"})]})})}function Za(e,t=Date.now()){if(!e)return"";const n=Date.parse(e);if(Number.isNaN(n))return"";const a=Math.max(0,Math.floor((t-n)/1e3));if(a<60)return"just now";const r=Math.floor(a/60);if(r<60)return`${r}m`;const o=Math.floor(r/60);return o<24?`${o}h`:`${Math.floor(o/24)}d`}function er(e,t){return`/sessions?session=${encodeURIComponent(`${e}/${t}`)}`}function tr(e){const t=[];return e&&(e.unavailable>0&&t.push(e.unavailable===1?"1 session could not be loaded and is not shown.":`${e.unavailable} sessions could not be loaded and are not shown.`),e.truncated&&t.push("Showing your most recent sessions only."),e.bootstrapUnavailable&&t.push("Some agents may be missing from New chat. Try again in a moment.")),t}function sr(e){const t=e.lastIndexOf("-");return t>0&&tc.ns===n&&c.name===a,f=e.filter(c=>c.ended&&!d(c)).length,p=o?e:e.filter(c=>!c.ended||d(c)),u=new Map;for(const c of p)u.set(c.title,(u.get(c.title)??0)+1);return s.jsxs("nav",{className:"flex flex-col","aria-label":"Your sessions","data-testid":"session-shell-session-list",children:[r.length>0&&s.jsx("div",{className:"px-3 py-2 text-[11px] text-muted-foreground border-b border-border","data-testid":"session-shell-list-notices",children:r.map(c=>s.jsx("div",{children:c},c))}),e.length===0?s.jsx("div",{className:"px-3 py-4 text-[12px] text-muted-foreground","data-testid":"session-shell-empty-list",children:"You have no sessions yet."}):p.length===0?s.jsx("div",{className:"px-3 py-4 text-[12px] text-muted-foreground","data-testid":"session-shell-empty-list",children:"No active sessions."}):s.jsx("ul",{className:"flex flex-col",children:p.map(c=>{const h=d(c),b=(u.get(c.title)??0)>1,x=c.startedAt?Za(c.startedAt):"";return s.jsx("li",{children:s.jsxs("a",{href:er(c.ns,c.name),"aria-current":h?"page":void 0,"data-testid":`session-shell-session-row-${c.ns}/${c.name}`,className:`flex flex-col gap-0.5 px-3 py-2 border-b border-border hover:bg-surface-2 ${h?"bg-surface-active shadow-[inset_3px_0_0_hsl(var(--state))]":""}`,children:[s.jsxs("span",{className:"flex items-baseline gap-1.5 min-w-0",children:[s.jsx("span",{className:"text-[13px] font-medium truncate",children:c.title}),b&&s.jsx("span",{className:"shrink-0 font-mono text-[10px] text-muted-foreground","data-testid":`session-shell-session-row-tag-${c.ns}/${c.name}`,children:sr(c.name)})]}),s.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] text-muted-foreground",children:[c.awaitingHuman?s.jsx(Os,{className:"h-3 w-3 shrink-0","aria-hidden":"true"}):c.ended?s.jsx(Ms,{className:"h-3 w-3 shrink-0","aria-hidden":"true"}):s.jsx(Ls,{className:"h-3 w-3 shrink-0","aria-hidden":"true"}),s.jsx("span",{className:"truncate","data-testid":`session-shell-session-row-phase-${c.ns}/${c.name}`,children:c.phase}),x&&s.jsxs(s.Fragment,{children:[s.jsx("span",{"aria-hidden":"true",children:"·"}),s.jsx("span",{className:"shrink-0","data-testid":`session-shell-session-row-age-${c.ns}/${c.name}`,children:x})]})]})]})},`${c.ns}/${c.name}`)})}),f>0||o&&e.some(c=>c.ended)?s.jsx("button",{type:"button",onClick:()=>l(c=>!c),"aria-expanded":o,className:"px-3 py-2 text-left text-[11px] text-muted-foreground hover:text-foreground hover:bg-surface-2","data-testid":"session-shell-toggle-ended",children:o?"Hide ended sessions":`Show ${f} ended session${f===1?"":"s"}`}):null]})}function ar(e,t){const n="oap sessions",a=e?`${e} · ${n}`:n;return t>0?`(${t}) ${a}`:a}function gt(){return typeof window<"u"&&"Notification"in window}function rr(e){const[t,n]=i.useState(0);i.useEffect(()=>{e&&n(0)},[e]);const a=i.useRef(!1),r=i.useCallback(()=>{a.current||!gt()||(a.current=!0,Notification.permission==="default"&&Notification.requestPermission())},[]),o=i.useCallback((l,d)=>{if(!e&&(n(f=>f+1),!(!gt()||Notification.permission!=="granted")))try{const f=new Notification(l,{body:d});f.onclick=()=>{window.focus(),f.close()}}catch{}},[e]);return{unread:t,notify:o,requestPermission:r}}function vt(){if(typeof document>"u")return!0;const e=document.visibilityState==="visible",t=typeof document.hasFocus=="function"?document.hasFocus():!0;return e&&t}function or(){const[e,t]=i.useState(vt);return i.useEffect(()=>{const n=()=>t(vt());return document.addEventListener("visibilitychange",n),window.addEventListener("focus",n),window.addEventListener("blur",n),()=>{document.removeEventListener("visibilitychange",n),window.removeEventListener("focus",n),window.removeEventListener("blur",n)}},[]),e}const ir=3e4;function lr(e,t){const n=[];for(const a of e)a.awaitingHuman&&(t&&a.ns===t.ns&&a.name===t.name||n.push({id:`session:${a.ns}/${a.name}`,label:`${a.title} is waiting on a person in another session.`,addressedToViewer:!1}));return n}function cr({selected:e,shown:t,ended:n,onChromeSignals:a,onUserSend:r,onAgentReply:o,onAwaitingDecision:l,onSessionEnded:d,uiEverShownRef:f}){const p=e.view.notice?s.jsx("div",{className:"p-4 max-w-3xl mx-auto","data-testid":"session-shell-view-notice",children:s.jsxs(W,{children:[s.jsx(Y,{children:e.view.notice.title}),s.jsx(G,{children:e.view.notice.message})]})}):null;t==="ui"&&(f.current=!0);const u=e.view.ui,c=u&&f.current?s.jsxs("div",{className:t==="ui"?"h-full":"hidden","data-testid":"session-shell-ui-region",children:[t==="ui"&&p,s.jsx(Ua,{ns:u.ns,name:u.name,declaration:u.declaration,agentParams:u.agentParams,shown:t==="ui",onChromeSignals:a,onSessionEnded:d})]}):null;function h(){var x,g;switch(t){case"ui":return u?null:s.jsxs(s.Fragment,{children:[p,!e.view.unavailable&&Re("agent view shown but no ui or unavailable payload",e),s.jsx(Te,{title:((x=e.view.unavailable)==null?void 0:x.title)??"View unavailable",message:((g=e.view.unavailable)==null?void 0:g.message)??"This agent's view could not be loaded."})]});case"chat":return e.view.chat?s.jsxs(s.Fragment,{children:[p,n&&s.jsx("div",{className:"p-4 max-w-3xl mx-auto","data-testid":"session-shell-replacement-region",children:s.jsx(Ja,{ns:e.view.chat.ns,name:e.view.chat.name})}),s.jsx(Ws,{ns:e.view.chat.ns,name:e.view.chat.name,readOnly:n,onUserSend:r,onSessionEnded:d})]}):s.jsxs(s.Fragment,{children:[p,Re("kind is chat but the view payload is absent",e),s.jsx(Te,{title:"View unavailable",message:"This conversation could not be opened."})]});default:return s.jsxs(s.Fragment,{children:[p,Re("unrecognized shown view",e),s.jsx(Te,{title:"View unavailable",message:"This view could not be shown."})]})}}const b=e.view.chat??{ns:e.ns,name:e.name};return s.jsxs(Bs,{ns:b.ns,name:b.name,children:[s.jsx(dr,{onAgentReply:o,onAwaitingDecision:l,onSessionEnded:d}),s.jsx(Hs,{}),c,h()]},`${b.ns}/${b.name}`)}function dr({onAgentReply:e,onAwaitingDecision:t,onSessionEnded:n}){const a=i.useRef(new Set);return yt(r=>{if(r.type==="user_message"){e(r.payload.text);return}if(r.type==="interaction_request"){const o=r.payload.payload;if(a.current.has(o.requestRef))return;a.current.add(o.requestRef),t(o.lead);return}r.type==="session_ended"&&n()}),null}function Re(e,t){return console.error("sessions: cannot render the selected view",{reason:e,ns:t.ns,name:t.name,kind:t.view.kind}),null}function Te({title:e,message:t}){return s.jsx("div",{className:"p-4 max-w-3xl mx-auto","data-testid":"session-shell-view-unavailable",children:s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:e}),s.jsx(G,{children:t})]})})}function ur(e){var q,te,ce,ae;const{selected:t}=e,[n,a]=i.useState(e.sessions??[]),[r,o]=i.useState(e.notices),[l,d]=i.useState(Pa),[f,p]=i.useState(!1),[u,c]=i.useState(!1),h=or(),{unread:b,notify:x,requestPermission:g}=rr(h),w=i.useCallback(R=>{d(_=>Ma(_,R)?_:R)},[]),C=i.useRef(null);C.current===null&&(C.current=new AbortController);const N=i.useCallback(async R=>{try{const _=await fetch("/sessions/api/sessions",{signal:R});if(!_.ok){console.error("sessions: session-list refresh failed",{status:_.status});return}const F=await _.json();if(R.aborted)return;if(!Array.isArray(F==null?void 0:F.sessions)){console.error("sessions: session-list refresh returned an unexpected body; keeping the current list");return}a(F.sessions),o(F.notices)}catch(_){if(R.aborted)return;console.error("sessions: session-list refresh errored",_)}},[]);i.useEffect(()=>{const R=C.current,_=setInterval(()=>void N(R.signal),ir);return()=>{R.abort(),clearInterval(_)}},[N]);const M=i.useRef(!1),D=i.useCallback(()=>{M.current||(M.current=!0,c(!0),N(C.current.signal))},[N]),j=i.useMemo(()=>[...lr(n,t),...l.approvals],[n,t,l.approvals]),v=i.useMemo(()=>{var R;return(R=n.find(_=>t&&_.ns===t.ns&&_.name===t.name))==null?void 0:R.title},[n,t]),$=i.useRef("oap sessions");$.current=v||"oap sessions",i.useEffect(()=>{document.title=ar(v??null,b)},[v,b]);const k=i.useCallback(R=>{x($.current,R)},[x]),y=i.useCallback(R=>{x(`${$.current} needs your decision`,R)},[x]),T=t?u?"ended":t.sessionOrigin:void 0,U=T==="ended",H=e.canStartSessions&&(((q=e.startableClasses)==null?void 0:q.length)??0)>0,[ee,J]=i.useState(()=>{var R;return((R=e.selected)==null?void 0:R.view.kind)==="chat"?"chat":"ui"}),X=e.selected?`${e.selected.ns}/${e.selected.name}`:"",O=i.useRef(X),A=i.useRef(!1);i.useEffect(()=>{var R;O.current!==X&&(O.current=X,A.current=!1,J(((R=e.selected)==null?void 0:R.view.kind)==="chat"?"chat":"ui"))},[X,(te=e.selected)==null?void 0:te.view.kind]);const K=i.useCallback((R,_)=>{J(R),typeof window<"u"&&window.history.pushState(window.history.state,"",_)},[]);i.useEffect(()=>{if(typeof window>"u")return;const R=()=>{const _=new URLSearchParams(window.location.search).get("view");J(_==="chat"?"chat":"ui")};return window.addEventListener("popstate",R),()=>window.removeEventListener("popstate",R)},[]);const z=i.useMemo(()=>{if(typeof window>"u")return{open:!1};const R=new URLSearchParams(window.location.search),_=R.get("ns")??"",F=R.get("agentClass")??"";return{open:R.get("new")==="1",preselect:_&&F?{ns:_,agentClass:F}:void 0,initialPrompt:R.get("prompt")??void 0}},[]),se=t?{ns:t.ns,name:t.name,sessionOrigin:T??t.sessionOrigin,currentView:ee,onSelectView:K,endedReason:U?t.endedReason:void 0,offersAgentUI:t.offersAgentUI&&!U}:{};return s.jsx(Ba,{...se,subject:e.subject,initialState:(ce=t==null?void 0:t.chrome)==null?void 0:ce.initialState,approvals:j,trustEvents:l.trustEvents,headerActions:H||t?s.jsxs(s.Fragment,{children:[H&&s.jsx(Ga,{classes:e.startableClasses,openInitially:z.open,preselect:z.preselect,initialPrompt:z.initialPrompt}),t&&s.jsxs(s.Fragment,{children:[s.jsx(B,{type:"button",variant:"ghost",size:"icon","aria-label":"Session details","data-testid":"session-shell-session-info",onClick:()=>p(!0),children:s.jsx(_s,{className:"h-4 w-4","aria-hidden":"true"})}),s.jsx(Qa,{ns:t.ns,name:t.name,open:f,onOpenChange:p})]})]}):void 0,sidebar:s.jsx(nr,{sessions:n,notices:r,selectedNs:t==null?void 0:t.ns,selectedName:t==null?void 0:t.name}),children:t?s.jsx(cr,{selected:t,shown:ee,ended:U,onChromeSignals:w,onUserSend:g,onAgentReply:k,onAwaitingDecision:y,onSessionEnded:D,uiEverShownRef:A}):s.jsxs("div",{className:"p-6 flex flex-col gap-3","data-testid":"session-shell-no-selection",children:[s.jsx("span",{className:"text-sm text-muted-foreground",children:"Pick a session to open it."}),(((ae=e.startableClasses)==null?void 0:ae.length)??0)===0&&s.jsx(Ya,{})]})})}ds();us(ur); diff --git a/pkg/web/webui/webassets/dist/sessions.DrV0Blkf.js b/pkg/web/webui/webassets/dist/sessions.DrV0Blkf.js deleted file mode 100644 index bc1bd817..00000000 --- a/pkg/web/webui/webassets/dist/sessions.DrV0Blkf.js +++ /dev/null @@ -1 +0,0 @@ -import{c as L,A as W,V as Ie,G as Y,a as J,C as ge,b as bt,d as wt,e as Pe,B,M as oe,T as ts,g as ss,h as ns,i as as,W as rs,N as Ae,j as os,k as is,l as Je,n as Ge,o as ls,p as Xe,D as Oe,t as Me,u as Le,v as pe,w as me,x as cs,L as ds,m as us}from"./mount.BOEH1oNx.js";import{j as s,R as fs,J as ps,r as i,E as ms,O as hs,y as ve,X as xs,P as gs,Q as vs,V as bs,Y as ws,Z as js,_ as ys,$ as Ns,a0 as Ss,a1 as ks,a2 as Cs,a3 as Rs,a4 as Ts,a5 as As,a as Es,a6 as $s,f as Qe,a7 as Is,M as Ps,a8 as Os,a9 as Ms,aa as Ls,ab as _s}from"./vendor.C5MRNvUt.js";import{M as _e}from"./markdown.FOTo-3Mz.js";import{B as Ds}from"./badge.CIlGQKPh.js";import{s as jt,u as Us,b as qs,i as Fs,c as yt,I as Vs,S as Bs,a as Hs,C as Ws}from"./StartupLine.D55RHpak.js";import{M as Ks}from"./model-name.DtAiu0oG.js";const Ze=["hsl(var(--chart-1))","hsl(var(--chart-2))","hsl(var(--chart-3))","hsl(var(--chart-4))","hsl(var(--chart-5))"];function de(e){return Ze[e%Ze.length]}function je({height:e=240,className:t,children:n}){return s.jsx("div",{className:L("w-full text-xs",t),style:{height:e},children:s.jsx(fs,{width:"100%",height:"100%",children:n})})}function zs(e){return s.jsx(ps,{cursor:{stroke:"hsl(var(--border))"},contentStyle:{background:"hsl(var(--popover))",border:"1px solid hsl(var(--border))",borderRadius:"var(--radius)",color:"hsl(var(--popover-foreground))",fontSize:"0.75rem"},...e})}const Ys=new Set(["submitted","awaiting_approval","running"]);function ie(e){return Ys.has(e)}function De(e){if(e.phase==="idle")return null;if(e.phase==="succeeded")return e.message??null;if(e.message)return e.message;switch(e.phase){case"submitted":return"Request submitted.";case"awaiting_approval":return e.approvalAddressedToViewer?"Your approval is needed to continue.":"Waiting on approval from someone else.";case"running":return"In progress.";case"failed":return"The request failed. You can try again.";case"denied":return"The request was denied.";case"expired":return"The approval window expired. You can try again.";case"rate_limited":return"Too many requests right now. Try again shortly.";default:return null}}const Js={states:{},invoke:()=>{},answer:()=>Promise.reject(new Error("agentui: no ActionsProvider"))},Nt=i.createContext(Js);function Gs({value:e,children:t}){return s.jsx(Nt.Provider,{value:e,children:t})}function le(){return i.useContext(Nt)}const P=e=>e.props??{},m=(e,t,n="")=>{const a=P(e)[t];return typeof a=="string"?a:typeof a=="number"?Number.isFinite(a)?String(a):n:typeof a=="boolean"?String(a):n};function Ue(e){if(typeof e!="string")return null;const t=e.split("/");if(t.length!==2)return null;for(const n of t)if(n===""||n==="."||n==="..")return null;return[t[0],t[1]]}function Xs(e){const t=Ue(e);return t===null?null:`/chat-embed/${encodeURIComponent(t[0])}/${encodeURIComponent(t[1])}`}function St({sessionRef:e}){const t=Xs(e);return t===null?s.jsx(W,{children:"Chat unavailable: no valid session reference."}):s.jsx("iframe",{"data-testid":"ap-chat",src:t,title:"Test chat",className:"w-full rounded-md bg-background",style:{height:"520px",border:"0"}})}function Qs({n:e}){return s.jsx(St,{sessionRef:P(e).sessionRef})}const Zs=/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;function et(e){return e.length>0&&e.length<=63&&Zs.test(e)}function qe(e){const t=m(e,"namespace"),n=m(e,"agentClass"),a=m(e,"label");if(!et(t))throw new Error(`ap:agentlink: namespace ${JSON.stringify(t)} is not a DNS-1123 label`);if(!et(n))throw new Error(`ap:agentlink: agentClass ${JSON.stringify(n)} is not a DNS-1123 label`);if(a==="")throw new Error("ap:agentlink: label is required");const r=new URLSearchParams({new:"1",ns:t,agentClass:n}),o=m(e,"prompt");return o!==""&&r.set("prompt",o),`/sessions?${r.toString()}`}function en(e,t){return`ap-agentlink:${e}/${t}`}function tt(e){try{return window.sessionStorage.getItem(e)}catch(t){return console.error("agentui: ap:agentlink could not read the remembered test session",t),null}}function tn(e,t){try{window.sessionStorage.setItem(e,t)}catch(n){console.error("agentui: ap:agentlink could not remember the test session",n)}}function sn(e){try{window.sessionStorage.removeItem(e)}catch(t){console.error("agentui: ap:agentlink could not forget the remembered test session",t)}}function nn(e){return`/sessions?${new URLSearchParams({session:e}).toString()}`}function an(e){return typeof e!="string"?null:!e.startsWith("/")||e.startsWith("//")||e.startsWith("/\\")?(console.error("agentui: ap:agentlink refused a start href that is not a same-origin path; using the rebuilt address",e),null):e}const rn="The test could not start.",st="We could not confirm whether that session started. Reload before trying again.";function on({n:e}){const t=qe(e),n=m(e,"namespace"),a=m(e,"agentClass"),r=en(n,a),[o,l]=i.useState(()=>tt(r)),[d,f]=i.useState(null),[p,u]=i.useState(!1),[c,h]=i.useState(null);if(i.useEffect(()=>{const x=tt(r);if(x===null)return;let g=!0;const w=()=>{g&&(sn(r),l(null))},C=Ue(x);if(C===null){w();return}return(async()=>{try{(await fetch(`/sessions/api/${encodeURIComponent(C[0])}/${encodeURIComponent(C[1])}/detail`,{credentials:"same-origin"})).ok||w()}catch(N){console.error("agentui: ap:agentlink could not check the remembered test session",N),w()}})(),()=>{g=!1}},[r]),o!==null)return s.jsxs("div",{className:"flex flex-col gap-2",children:[s.jsx(St,{sessionRef:o}),s.jsx("a",{"data-testid":"ap-agentlink-open-tab",href:d??nn(o),target:"_blank",rel:"noopener noreferrer",className:"self-end text-xs text-[hsl(var(--primary))]",children:"Open in a tab"})]});async function b(){u(!0),h(null);let x;try{x=await fetch("/sessions/api/start",{method:"POST",credentials:"same-origin",headers:{"content-type":"application/json"},body:JSON.stringify({ns:n,agentClass:a,prompt:m(e,"prompt")})})}catch(g){console.error("agentui: ap:agentlink embed start did not complete",g),h({text:st,retryable:!1}),u(!1);return}if(!x.ok){let g="";try{const w=await x.json();g=typeof(w==null?void 0:w.error)=="string"?w.error:""}catch{g=""}console.error("agentui: ap:agentlink embed start was refused",{status:x.status}),h({text:g||rn,retryable:!0}),u(!1);return}try{const g=await x.json();if(!g.ns||!g.name)throw new Error("start answered without a session");const w=`${g.ns}/${g.name}`;tn(r,w),f(an(g.href)),l(w)}catch(g){console.error("agentui: ap:agentlink embed start succeeded but its answer could not be addressed",g),h({text:st,retryable:!1})}finally{u(!1)}}return s.jsxs("div",{className:"flex flex-col gap-2",children:[s.jsx("button",{type:"button","data-testid":"ap-agentlink-embed",onClick:()=>void b(),disabled:p||(c==null?void 0:c.retryable)===!1,className:Ie({variant:"default"}),children:m(e,"label")}),c!==null&&s.jsxs("p",{role:"alert",className:"text-sm text-[hsl(var(--destructive))]",children:[c.text,c.retryable&&s.jsxs(s.Fragment,{children:[" ","Try again, or"," ",s.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"underline",children:"open it in a tab"}),"."]})]})]})}function ln({n:e}){return P(e).embed?s.jsx(on,{n:e}):s.jsx("a",{"data-testid":"ap-agentlink",href:qe(e),target:"_blank",rel:"noopener noreferrer",className:Ie({variant:"default"}),children:m(e,"label")})}const kt=i.createContext(null);function cn({value:e,children:t}){return s.jsx(kt.Provider,{value:e,children:t})}function dn(){return i.useContext(kt)}const un="application/vnd.agentprimitives.authzed.com.agent.v1";function fn(e){return e===un?"Agent bundle":e==="text/html"?"Web page":e.startsWith("image/")?"Image":e}function pn(e){const[t,n]=e>=1e6?[e/1e6,"megabyte"]:e>=1e3?[e/1e3,"kilobyte"]:[e,"byte"];return new Intl.NumberFormat(void 0,{style:"unit",unit:n,unitDisplay:"short",maximumFractionDigits:1}).format(t)}function nt({reason:e}){return s.jsxs(W,{variant:"destructive","data-testid":"ap-attachment-unavailable",children:[s.jsx(Y,{children:"Attachment unavailable"}),s.jsx(J,{children:e})]})}function mn({n:e}){const t=dn(),n=m(e,"artifact"),a=m(e,"label"),[r,o]=i.useState(null),[l,d]=i.useState(null);if(i.useEffect(()=>{if(t===null||n==="")return;let p=!1;const u=new URLSearchParams({artifactId:n,sessionRef:`${t.ns}/${t.name}`});return fetch(`/artifact-view/meta?${u.toString()}`,{credentials:"same-origin"}).then(async c=>{if(!c.ok)throw new Error(`${c.status} ${await c.text()}`);return await c.json()}).then(c=>{p||o(c)}).catch(c=>{console.error("agentui: ap:attachment lookup failed",n,c),p||d("This file could not be found in this conversation.")}),()=>{p=!0}},[t,n]),t===null)return s.jsx(nt,{reason:"This page is not attached to a conversation."});if(l!==null)return s.jsx(nt,{reason:l});if(r===null)return s.jsx("div",{"data-testid":"ap-attachment-loading",className:"text-sm text-muted-foreground",children:"Loading file…"});const f=new URLSearchParams({artifactId:n,sessionRef:`${t.ns}/${t.name}`,fn:r.filename||n});return s.jsxs("div",{"data-testid":"ap-attachment",className:"flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2",children:[s.jsxs("div",{className:"min-w-0",children:[s.jsx("div",{className:"truncate text-sm font-medium",children:a||r.filename||r.name}),s.jsxs("div",{className:"text-xs text-muted-foreground",children:[fn(r.mime)," · ",pn(r.size)]})]}),s.jsxs("a",{href:`/artifact-download?${f.toString()}`,download:r.filename||void 0,className:L(Ie({variant:"outline",size:"sm"}),"shrink-0"),"aria-label":`Download ${r.filename||r.name}`,children:[s.jsx(ms,{className:"mr-1.5 h-4 w-4","aria-hidden":"true"}),"Download"]})]})}function Ct({title:e,collapsed:t,testId:n,children:a}){const[r,o]=i.useState(!t);return i.useEffect(()=>{o(!t)},[t]),s.jsxs("details",{"data-testid":n,open:r,className:"w-full",children:[s.jsxs("summary",{className:"flex cursor-pointer select-none items-center gap-2 px-1 py-2 text-sm font-medium [&::-webkit-details-marker]:hidden",onClick:l=>{l.preventDefault(),o(d=>!d)},children:[s.jsx(hs,{className:L("h-4 w-4 shrink-0 transition-transform",r&&"rotate-90"),"aria-hidden":"true"}),e]}),s.jsx("div",{className:"px-1 pb-2",children:a})]})}const hn={layout:"column",selectedStep:null,selectStep:()=>{}},Ee=i.createContext(hn);function Rt(){return i.useContext(Ee)}function xn(e){var t;return(e==null?void 0:e.component)==="oap:page"&&((t=e.props)==null?void 0:t.layout)==="rail"?"rail":"column"}const Tt="oap:generative",gn={composed:new Set,updated:new Map,stale:new Set,waiting:new Set,answered:new Set,collapsed:new Map},At=i.createContext(gn);function vn({value:e,children:t}){return s.jsx(At.Provider,{value:e,children:t})}function Fe(){return i.useContext(At)}function ne(e){var t;return e.component!==Tt?null:typeof((t=e.props)==null?void 0:t.name)=="string"?e.props.name:""}const bn="rounded-md bg-state/[0.05] px-3 py-2",Et=i.createContext(null);function wn(){return i.useContext(Et)}function jn({name:e,title:t,children:n}){const{composed:a,updated:r,stale:o,waiting:l,collapsed:d}=Fe(),{layout:f}=Rt(),p=a.has(e),u=r.has(e),c=o.has(e),h=l.has(e),b=i.Children.count(n)>0,x=d.get(e),g=f!=="rail"&&x!==void 0&&b,w=f==="rail"&&t!==void 0&&t!==""&&b,C=h&&f!=="rail";return s.jsxs("section",{"data-hook":e,"data-agent-composed":p?"true":void 0,"data-hook-updated":u?"true":void 0,"data-hook-waiting":h?"true":void 0,"data-hook-collapsed":g?"true":void 0,"aria-live":"polite","aria-busy":c||void 0,className:L("relative w-full",p&&b&&bn,c&&"opacity-60 transition-opacity"),children:[(C||u||c)&&s.jsxs("div",{"data-testid":"agent-ui-hook-cues",className:"pointer-events-none mb-1 flex min-h-5 items-center justify-between gap-2 text-[11px]",children:[s.jsx("span",{children:C&&s.jsx("span",{"data-testid":"agent-ui-hook-waiting",className:"rounded bg-accent/10 px-1.5 py-0.5 font-medium text-accent-foreground",children:"Waiting on you"})}),u?s.jsx("span",{"data-testid":"agent-ui-hook-updated",className:"rounded bg-accent/10 px-1.5 py-0.5 text-muted-foreground",children:"Updated"}):c?s.jsxs("div",{"data-testid":"agent-ui-hook-refreshing",className:"flex items-center gap-1.5 text-muted-foreground",children:[s.jsx(ve,{className:"h-3.5 w-3.5 animate-spin","aria-hidden":"true"}),s.jsx("span",{children:"Updating…"})]}):null]}),s.jsxs(Et.Provider,{value:e,children:[w&&s.jsx("h3",{"data-testid":"agent-ui-hook-title",className:"mb-2 text-sm font-medium text-foreground",children:t}),g?s.jsx(Ct,{title:x,collapsed:!0,testId:"agent-ui-hook-collapsed",children:n}):n]})]})}const yn=["info","success","warning"],Nn={info:"border-state/40 bg-state/[0.06]",success:"border-success/50 bg-success/[0.08]",warning:"border-warning/50 bg-warning/[0.08]"},Sn=4;function $t(e){return Array.isArray(e)?e.filter(t=>!!t&&typeof t.label=="string"&&typeof t.action=="string"&&t.action!=="").slice(0,Sn):[]}function It(e){const t=m(e,"tone");return yn.includes(t)?t:"info"}function kn(e){return JSON.stringify([m(e,"title"),m(e,"body"),It(e),$t(P(e).buttons)])}const Pt="agentui:notice:dismissed:";function Cn(e){try{return window.localStorage.getItem(Pt+e)==="1"}catch(t){return console.error("agentui: could not read dismissed notices",t),!1}}function Rn(e){try{window.localStorage.setItem(Pt+e,"1")}catch(t){console.error("agentui: could not remember a dismissed notice",t)}}function Tn({n:e}){const t=kn(e);return s.jsx(An,{n:e,identity:t},t)}function An({n:e,identity:t}){const{states:n,invoke:a,busy:r}=le(),[o,l]=i.useState(()=>Cn(t)),[d,f]=i.useState(null);if(o)return null;const p=It(e),u=m(e,"title"),c=$t(P(e).buttons),h=c.some(g=>ie((n[g.action]??{phase:"idle"}).phase)),b=d!==null||h||!!r,x=d!==null?n[d]??{phase:"submitted"}:null;return s.jsxs(ge,{"data-testid":"ap-notice","data-tone":p,className:L("relative",Nn[p]),children:[s.jsx("button",{type:"button","aria-label":"Dismiss",className:"absolute right-2 top-2 rounded p-1 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",onClick:()=>{Rn(t),l(!0)},children:s.jsx(xs,{className:"h-4 w-4","aria-hidden":"true"})}),u&&s.jsx(bt,{className:"pb-2 pr-8",children:s.jsx(wt,{className:"text-base",children:u})}),s.jsxs(Pe,{className:L("flex flex-col gap-3 pr-8",!u&&"pt-6"),children:[s.jsx(_e,{children:m(e,"body")}),c.length>0&&s.jsx("div",{className:"flex flex-wrap gap-2",children:c.map((g,w)=>s.jsx(B,{type:"button",variant:w===0?"default":"secondary",disabled:b,onClick:()=>{f(g.action),a(g.action,void 0)},children:g.label},`${g.action}:${w}`))}),x!==null&&s.jsx("p",{"data-testid":"ap-notice-sent",className:"text-xs text-muted-foreground",children:De(x)??"Sent."})]})]})}function $e(e,t){return e?e.component===t?!0:(e.children??[]).some(n=>$e(n,t)):!1}function En(e,t){const n=new Set,a=(r,o)=>{const l=ne(r)??o;r.component===t&&l!==null&&n.add(l);for(const d of r.children??[])a(d,l)};return e&&a(e,null),n}function $n(e){const t=(n,a)=>{const r=ne(n)??a;return n.component==="ap:question"&&r===null?!0:(n.children??[]).some(o=>t(o,r))};return e?t(e,null):!1}function Ot(e){var o;if(!e)return[];const t=[],n=l=>{l.component==="ap:steps"&&t.push(l);for(const d of l.children??[])n(d)};if(n(e),t.length!==1)return[];const a=(o=t[0].props)==null?void 0:o.steps,r=[];for(const l of Array.isArray(a)?a:[]){if(!l||typeof l.id!="string"||l.id==="")continue;const d=l.state==="done"||l.state==="active"?l.state:"upcoming";r.push({id:l.id,label:typeof l.label=="string"?l.label:l.id,state:d})}return r}function In(e){const t=new Map;if(!e)return t;const n=new Map;for(const r of Ot(e))n.set(r.id,{step:r.id,label:r.label,state:r.state});if(n.size===0)return t;const a=r=>{var d,f;const o=ne(r),l=(d=r.props)==null?void 0:d.step;if(o!==null&&typeof l=="string"){const p=n.get(l);if(p){const u=(f=r.props)==null?void 0:f.title;t.set(o,typeof u=="string"&&u!==""?{...p,title:u}:p)}}for(const p of r.children??[])a(p)};return a(e),t}function Mt(e,t){return e.component===t?!0:(e.children??[]).some(n=>Mt(n,t))}function Pn(e){var n;const t=e.component===Tt?(n=e.props)==null?void 0:n.step:void 0;return typeof t=="string"&&t!==""?t:null}function On({node:e,renderChild:t}){var x;const n=((x=e.props)==null?void 0:x.layout)==="rail"?"rail":"column",a=e.children??[],{waiting:r}=Fe(),o=i.useMemo(()=>Ot(e),[e]),l=i.useMemo(()=>{const g=o.find(C=>C.state==="active");if(g)return g.id;const w=o.filter(C=>C.state==="done");return w.length>0?w[w.length-1].id:o.length>0?o[0].id:null},[o]),[d,f]=i.useState(null);i.useEffect(()=>{f(null)},[l]);const p=d??l,u=i.useMemo(()=>({layout:n,selectedStep:p,selectStep:f}),[n,p]);if(n!=="rail")return s.jsx(Ee.Provider,{value:u,children:s.jsx("div",{className:"flex flex-col gap-2",children:a.map(t)})});const c=a.findIndex(g=>Mt(g,"ap:steps")),h=c>=0?a[c]:null,b=(g,w)=>{if(w===c)return!1;const C=Pn(g);if(C===null||C===p)return!0;const N=ne(g);return N!==null&&r.has(N)};return s.jsx(Ee.Provider,{value:u,children:s.jsxs("div",{className:"grid gap-6 md:grid-cols-[220px_minmax(0,1fr)]",children:[s.jsx("div",{"data-testid":"agent-ui-page-rail",className:"md:sticky md:top-4 md:self-start",children:h?t(h,"rail"):null}),s.jsx("div",{"data-testid":"agent-ui-page-stage",className:"flex min-w-0 flex-col gap-3",children:a.map((g,w)=>b(g,w)?t(g,w):null)})]})})}const Mn={"ap:select":{prop:"param",values:[{valueProp:"value"}]},"ap:daterange":{prop:"param",values:[{suffix:"from",valueProp:"from"},{suffix:"to",valueProp:"to"}]}};function he(e,t){return t?`${e}.${t}`:e}function Ve(e){const t=Mn[e.component];if(!t)return null;const n=(e.props??{})[t.prop];return typeof n!="string"||n===""?null:{name:n,spec:t}}const Ln={params:{},setParam:()=>{}},Lt=i.createContext(Ln);function _n({value:e,children:t}){return s.jsx(Lt.Provider,{value:e,children:t})}function _t(){return i.useContext(Lt)}function Dt(e){const t={};return e.view&&Ut(e.view,t),t}function ye(e,t){const n=Dt(e),a={};for(const r of Dn(e))r in t?a[r]=t[r]:r in n&&(a[r]=n[r]);return a}function Dn(e){const t=[],n=a=>{const r=Ve(a);if(r)for(const o of r.spec.values){const l=he(r.name,o.suffix);t.includes(l)||t.push(l)}for(const o of a.children??[])n(o)};return e.view&&n(e.view),t}function Ut(e,t){const n=Ve(e);if(n){const a=e.props??{};for(const r of n.spec.values){const o=a[r.valueProp];typeof o=="string"&&o!==""&&(t[he(n.name,r.suffix)]=o)}}for(const a of e.children??[])Ut(a,t)}const at=e=>Array.isArray(e)?e.filter(t=>typeof t=="string"):[];function rt({label:e,state:t}){return s.jsxs("li",{"data-state":t,className:"flex items-center gap-2 text-sm",children:[t==="done"&&s.jsx("span",{"aria-hidden":"true",className:"text-[hsl(var(--success))]",children:"✓"}),s.jsx("span",{className:t==="done"?"text-muted-foreground line-through":void 0,children:e})]})}function Un({n:e}){const t=m(e,"now"),n=at(P(e).done),a=at(P(e).next),r=m(e,"step");return s.jsx(ge,{children:s.jsxs("section",{"data-testid":"ap-progress",className:"flex flex-col gap-3 p-6",children:[s.jsx("p",{"data-testid":"ap-progress-now",className:"text-sm font-medium",children:t}),(n.length>0||a.length>0)&&s.jsxs("ul",{className:"flex flex-col gap-1.5",children:[n.map((o,l)=>s.jsx(rt,{label:o,state:"done"},`done-${l}`)),a.map((o,l)=>s.jsx(rt,{label:o,state:"next"},`next-${l}`))]}),r&&s.jsx("p",{className:"text-xs text-muted-foreground",children:r})]})})}function qn(e){return Array.isArray(e)?e.filter(t=>!!t&&typeof t.value=="string"&&typeof t.label=="string"):[]}function Fn({n:e}){const t=JSON.stringify([m(e,"prompt"),m(e,"kind"),P(e).choices??null]);return s.jsx(Vn,{n:e},t)}function Vn({n:e}){const{answer:t,busy:n}=le(),a=wn(),{answered:r}=Fe(),o=a!==null&&r.has(a),l=m(e,"prompt"),d=m(e,"kind")==="choice"?"choice":"text",f=m(e,"placeholder"),p=m(e,"submitLabel","Answer"),u=qn(P(e).choices),c=i.useId(),[h,b]=i.useState(""),[x,g]=i.useState(!1),[w,C]=i.useState(!1),[N,M]=i.useState(!1);if(w||o)return s.jsx("p",{"data-testid":"ap-question-sent",children:"Sent — waiting for the agent."});const D=h.trim()===""||x||!!n;function j(v){v.preventDefault(),!D&&(g(!0),M(!1),t(h).then(()=>C(!0)).catch($=>{console.error("agentui: ap:question answer failed",$),M(!0)}).finally(()=>g(!1)))}return s.jsx(ge,{children:s.jsx(Pe,{className:"pt-6",children:s.jsxs("form",{"data-testid":"ap-question",onSubmit:j,className:"flex flex-col gap-3",children:[s.jsx(oe,{htmlFor:d==="text"?c:void 0,className:"font-normal text-foreground",children:l}),d==="choice"?s.jsx("div",{className:"flex flex-col gap-2",children:u.map(v=>s.jsxs(oe,{className:"flex items-center gap-2 font-normal",children:[s.jsx("input",{type:"radio",name:c,value:v.value,checked:h===v.value,disabled:x||!!n,onChange:()=>b(v.value),className:"h-4 w-4 border-input text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"}),v.label]},v.value))}):s.jsx("textarea",{id:c,placeholder:f,value:h,disabled:x||!!n,onChange:v=>b(v.target.value),className:"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"}),s.jsx(B,{type:"submit",disabled:D,className:"self-start",children:p}),N&&s.jsx("p",{role:"alert",className:"text-xs text-destructive",children:"Could not send your answer. Try again."})]})})})}const ot={idle:"bg-muted-foreground",running:"bg-[hsl(var(--success))] animate-pulse",paused:"bg-[hsl(var(--warning))]",ended:"bg-muted-foreground",unwatched:"bg-[hsl(var(--warning))]"};function Bn(e){if(e==="")return null;const t=new Date(e);return Number.isNaN(t.getTime())?null:t.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function Hn({n:e}){const t=m(e,"state"),n=ot[t]??ot.idle,a=m(e,"since"),r=Bn(a);return s.jsxs("div",{"data-testid":"ap-status","data-state":t,role:"status",title:a||void 0,className:"flex items-center gap-2 text-sm font-medium",children:[s.jsx("span",{"aria-hidden":"true",className:L("inline-block h-2 w-2 shrink-0 rounded-full",n)}),s.jsxs("span",{children:[m(e,"text"),r!==null&&s.jsxs(s.Fragment,{children:[" ",s.jsx("time",{dateTime:a,children:r})]})]})]})}function qt(e,t,n){return t.includes(e)?e:n}const Wn=["default","secondary","destructive","outline"],Kn=["default","secondary","destructive","outline","ghost","link"],ue={none:"gap-0",sm:"gap-2",md:"gap-4",lg:"gap-6"},it={start:"items-start",center:"items-center",end:"items-end",stretch:"items-stretch"},Ne=e=>`apui-${e}`,zn=e=>e==="right"?"text-right":e==="center"?"text-center":void 0,Yn=e=>e==="right"?"text-right tabular-nums":e==="center"?"text-center":void 0;function Jn({n:e}){const{params:t,setParam:n}=_t(),a=m(e,"param"),r=a!=="",o=m(e,"value"),l=r?t[a]??o:o,d=m(e,"placeholder");return s.jsx("div",{className:"flex flex-col gap-1",children:s.jsxs("select",{disabled:!r,className:L("h-9 rounded-md border border-input bg-transparent px-3 text-sm",!r&&"text-muted-foreground"),value:l,onChange:r?f=>n(a,f.target.value):void 0,children:[d&&s.jsx("option",{value:"",disabled:!0,hidden:!0,children:d}),(P(e).options??[]).map(f=>s.jsx("option",{value:f.value,children:f.label??f.value},f.value))]})})}function lt(e){return e===""||e.includes("T")?e:`${e}T00:00:00Z`}function ct(e){const t=e.indexOf("T");return t===-1?e:e.slice(0,t)}function Gn({n:e}){const{params:t,setParam:n}=_t(),a=m(e,"param");if(!(a!==""))return s.jsxs(B,{variant:"secondary",disabled:!0,children:[m(e,"from")||"start"," → ",m(e,"to")||"end"]});const o=he(a,"from"),l=he(a,"to"),d=t[o]??m(e,"from"),f=t[l]??m(e,"to");return s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(Ae,{type:"date","aria-label":"From date",value:ct(d),onChange:p=>n(o,lt(p.target.value)),className:"h-9 w-auto"}),s.jsx("span",{className:"text-muted-foreground","aria-hidden":"true",children:"→"}),s.jsx(Ae,{type:"date","aria-label":"To date",value:ct(f),onChange:p=>n(l,lt(p.target.value)),className:"h-9 w-auto"})]})}function Be({text:e}){return e?s.jsx("span",{"data-testid":"agent-ui-action-status",className:"text-xs text-muted-foreground",children:e}):null}function Xn({n:e,columns:t,rows:n}){const{states:a,invoke:r}=le(),o=m(e,"rowAction"),l=o!=="",d=l?a[o]??{phase:"idle"}:{phase:"idle"},f=ie(d.phase);return n.length===0?s.jsx("p",{className:"py-6 text-center text-sm text-muted-foreground",children:m(e,"empty","Nothing to show.")}):s.jsxs("div",{className:"overflow-x-auto",children:[s.jsxs(os,{children:[s.jsx(is,{children:s.jsxs(Je,{children:[t.map(p=>s.jsx(Ge,{className:zn(p.align),children:p.header??p.key},p.key)),l&&s.jsx(Ge,{className:"w-px"})]})}),s.jsx(ls,{children:n.map((p,u)=>s.jsxs(Je,{children:[t.map(c=>s.jsx(Xe,{className:Yn(c.align),children:String(p[c.key]??"")},c.key)),l&&s.jsx(Xe,{className:"text-right",children:s.jsx(B,{variant:"ghost",size:"sm",disabled:f,"data-testid":"agent-ui-row-action",onClick:()=>r(o,Qn(p)),children:m(e,"rowActionLabel","Details")})})]},u))})]}),s.jsx(Be,{text:l?d.message??null:null})]})}function Se(e,t){if(e==null)return[];if(!Array.isArray(e))throw new TypeError(`${t} must be an array`);return e}function Qn(e){const t={};for(const[n,a]of Object.entries(e))typeof a=="string"?t[n]=a:(typeof a=="number"&&Number.isFinite(a)||typeof a=="boolean")&&(t[n]=String(a));return t}function Zn({n:e}){const{states:t,invoke:n,busy:a}=le(),r=m(e,"action"),o=r!=="",l=o?t[r]??{phase:"idle"}:{phase:"idle"},d=!o||ie(l.phase)||!!a||!!P(e).disabled;return s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(B,{variant:qt(m(e,"variant"),Kn,"default"),disabled:d,onClick:o?()=>n(r,void 0):void 0,children:m(e,"label")}),s.jsx(Be,{text:De(l)})]})}function ea({n:e}){const{states:t,invoke:n,busy:a}=le(),r=m(e,"action"),o=r!=="",l=o?t[r]??{phase:"idle"}:{phase:"idle"},d=!o||ie(l.phase)||!!a||!!P(e).disabled,f=P(e).fields??[],p=P(e).values??{},[u,c]=i.useState(()=>({...p}));return s.jsxs("form",{className:"flex flex-col gap-3",onSubmit:h=>{h.preventDefault(),o&&n(r,Object.fromEntries(f.map(b=>[b.name,u[b.name]??""])))},children:[f.map(h=>s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(oe,{htmlFor:Ne(h.name),children:h.label??h.name}),h.kind==="textarea"?s.jsx("textarea",{id:Ne(h.name),name:h.name,placeholder:h.placeholder,disabled:d,value:u[h.name]??"",onChange:b=>c(x=>({...x,[h.name]:b.target.value})),className:"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"}):s.jsx(Ae,{id:Ne(h.name),name:h.name,type:h.kind==="number"?"number":"text",placeholder:h.placeholder,disabled:d,value:u[h.name]??"",onChange:b=>c(x=>({...x,[h.name]:b.target.value}))})]},h.name)),s.jsx(B,{type:"submit",disabled:d,children:m(e,"submitLabel","Submit")}),s.jsx(Be,{text:De(l)})]})}function ta(e){const t=Ue(e);return t===null?null:`/session-view/${encodeURIComponent(t[0])}/${encodeURIComponent(t[1])}`}function sa(e,t){const n=s.jsx("ol",{className:"flex w-full items-center","data-testid":"agent-ui-steps",children:t.map((a,r)=>{const o=a.state==="done"||a.state==="active"?a.state:"upcoming",l=r===t.length-1;return s.jsxs("li",{className:L("flex items-center",!l&&"flex-1"),"aria-current":o==="active"?"step":void 0,children:[s.jsxs("div",{className:"flex flex-col items-center gap-1.5",children:[s.jsx("span",{className:L("flex h-7 w-7 items-center justify-center rounded-full border text-xs font-semibold tabular-nums transition-colors",o==="done"&&"border-[hsl(var(--primary))] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]",o==="active"&&"border-[hsl(var(--primary))] text-[hsl(var(--primary))] ring-2 ring-[hsl(var(--primary))]/30",o==="upcoming"&&"border-border text-muted-foreground"),children:o==="done"?"✓":r+1}),s.jsx("span",{className:L("whitespace-nowrap text-xs",o==="active"?"font-medium text-foreground":"text-muted-foreground"),children:a.label??""})]}),!l&&s.jsx("span",{className:L("mx-2 mb-5 h-px flex-1 transition-colors",o==="done"?"bg-[hsl(var(--primary))]":"bg-border"),"aria-hidden":"true"})]},r)})});return P(e).pinned?s.jsx("div",{"data-testid":"agent-ui-steps-pinned",className:"sticky top-0 z-10 -mx-4 bg-background px-4 py-2",children:n}):n}function na({node:e,steps:t}){const{layout:n,selectedStep:a,selectStep:r}=Rt();return n!=="rail"?sa(e,t):s.jsx("ol",{className:"flex flex-col","data-testid":"agent-ui-steps","aria-label":"Steps",children:t.map((o,l)=>{const d=o.state==="done"||o.state==="active"?o.state:"upcoming",f=typeof o.id=="string"&&o.id!==""?o.id:null,p=f!==null&&f===a;return s.jsxs("li",{"data-testid":"agent-ui-rail-step","data-step-id":f??void 0,"data-selected":p?"true":void 0,className:"relative",children:[s.jsxs("button",{type:"button",onClick:()=>f!==null&&r(f),disabled:f===null,"aria-current":d==="active"?"step":void 0,"aria-pressed":p,className:L("flex w-full items-start gap-2.5 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-muted/40",p&&"bg-muted/60"),children:[s.jsx("span",{className:L("mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border text-[10px] font-semibold tabular-nums",d==="done"&&"border-[hsl(var(--primary))] bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]",d==="active"&&"border-[hsl(var(--primary))] text-[hsl(var(--primary))] ring-2 ring-[hsl(var(--primary))]/30",d==="upcoming"&&"border-border text-muted-foreground"),children:d==="done"?"✓":l+1}),s.jsxs("span",{className:"min-w-0",children:[s.jsx("span",{className:L("block text-sm",d==="active"?"font-medium text-foreground":d==="upcoming"?"text-muted-foreground":"text-foreground"),children:o.label??""}),o.summary?s.jsx("span",{className:"block truncate text-xs text-muted-foreground",children:o.summary}):null]})]}),ls.jsx("div",{className:L("flex",m(e,"direction")==="horizontal"?"flex-row":"flex-col",ue[m(e,"gap","md")]??ue.md,it[m(e,"align","start")]??it.start),children:(e.children??[]).map(t)}),"ap:status":e=>s.jsx(Hn,{n:e}),"ap:grid":(e,t)=>{const n=typeof P(e).columns=="number"?P(e).columns:0,a=n>0?Math.min(Math.max(Math.trunc(n),1),12):0;return s.jsx("div",{className:L("grid",ue[m(e,"gap","md")]??ue.md),style:{gridTemplateColumns:a>0?`repeat(${a}, minmax(0, 1fr))`:"repeat(auto-fit, minmax(16rem, 1fr))"},children:(e.children??[]).map(t)})},"ap:card":(e,t)=>{const n=!!(m(e,"title")||m(e,"description"));return s.jsxs(ge,{children:[n&&s.jsxs(bt,{children:[m(e,"title")&&s.jsx(wt,{children:m(e,"title")}),m(e,"description")&&s.jsx(rs,{children:m(e,"description")})]}),s.jsx(Pe,{className:n?void 0:"pt-6",children:(e.children??[]).map(t)})]})},"ap:collapsible":(e,t)=>s.jsx(Ct,{title:m(e,"title"),collapsed:!!P(e).collapsed,testId:"ap-collapsible",children:(e.children??[]).map(t)}),"ap:heading":e=>{const t=Math.min(Math.max(Number(P(e).level)||2,1),4),n=`h${t}`,a={1:"text-2xl",2:"text-xl",3:"text-lg",4:"text-base"}[t]??"text-xl";return s.jsx(n,{className:L(a,"font-semibold text-foreground"),children:m(e,"text")})},"ap:text":e=>s.jsx("p",{className:L(m(e,"variant")==="muted"&&"text-muted-foreground",m(e,"variant")==="mono"&&"font-mono","text-sm"),children:m(e,"text")}),"ap:markdown":e=>s.jsx(_e,{children:m(e,"body")}),"ap:metric":e=>s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx("span",{className:"text-xs uppercase tracking-wide text-muted-foreground",children:m(e,"label")}),s.jsx("span",{className:"text-2xl font-semibold tabular-nums",children:m(e,"value")}),m(e,"delta")&&s.jsx("span",{className:L("text-xs tabular-nums",m(e,"trend")==="up"&&"text-[hsl(var(--success))]",m(e,"trend")==="down"&&"text-destructive",m(e,"trend")!=="up"&&m(e,"trend")!=="down"&&"text-muted-foreground"),children:m(e,"delta")}),m(e,"caption")&&s.jsx("span",{className:"text-xs text-muted-foreground",children:m(e,"caption")})]}),"ap:progress":e=>s.jsx(Un,{n:e}),"ap:question":e=>s.jsx(Fn,{n:e}),"ap:notice":e=>s.jsx(Tn,{n:e}),"ap:table":e=>{const t=Se(P(e).columns,"ap:table.columns"),n=Se(P(e).rows,"ap:table.rows");return s.jsx(Xn,{n:e,columns:t,rows:n})},"ap:badge":e=>s.jsx(Ds,{variant:qt(m(e,"variant"),Wn,"default"),children:m(e,"text")}),"ap:alert":e=>{const t=m(e,"severity");return s.jsxs(W,{variant:t==="error"?"destructive":"default",className:t==="warning"?"border-warning/50 text-warning [&>svg]:text-warning":void 0,children:[m(e,"title")&&s.jsx(Y,{children:m(e,"title")}),m(e,"body")&&s.jsx(J,{children:m(e,"body")})]})},"ap:empty":e=>s.jsxs("div",{className:"flex flex-col items-center gap-1 py-10 text-center",children:[s.jsx("p",{className:"text-sm font-medium",children:m(e,"title","Nothing here yet")}),m(e,"body")&&s.jsx("p",{className:"text-xs text-muted-foreground",children:m(e,"body")})]}),"ap:steps":e=>{const t=Se(P(e).steps,"ap:steps.steps");return t.forEach((n,a)=>{if(typeof n!="object"||n===null)throw new TypeError(`ap:steps.steps[${a}] must be an object`)}),s.jsx(na,{node:e,steps:t})},"ap:skeleton":e=>s.jsxs("div",{className:"flex flex-col gap-2 py-4","data-testid":"ap-skeleton","aria-busy":"true","aria-live":"polite",children:[m(e,"label")&&s.jsx("p",{className:"text-xs text-muted-foreground",children:m(e,"label")}),s.jsx("div",{className:"h-4 w-1/3 animate-pulse rounded bg-muted"}),s.jsx("div",{className:"h-4 w-full animate-pulse rounded bg-muted"}),s.jsx("div",{className:"h-4 w-5/6 animate-pulse rounded bg-muted"}),!m(e,"label")&&s.jsx("span",{className:"sr-only",children:"Loading…"})]}),"ap:error":e=>s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:m(e,"title","Something went wrong")}),m(e,"body")&&s.jsx(J,{children:m(e,"body")})]}),"ap:chart":e=>{const t=P(e).series??[],n=P(e).data??[],a=m(e,"xKey"),r=m(e,"kind","line");if(n.length===0||t.length===0)return s.jsx("p",{className:"py-6 text-center text-sm text-muted-foreground",children:"No data to chart."});const o=s.jsx(Ns,{stroke:"hsl(var(--border))",strokeDasharray:"3 3",vertical:!1}),l=s.jsx(Ss,{dataKey:a,stroke:"hsl(var(--muted-foreground))",tickLine:!1,axisLine:!1}),d=s.jsx(ks,{stroke:"hsl(var(--muted-foreground))",tickLine:!1,axisLine:!1,width:40}),f=s.jsx(zs,{});return r==="bar"?s.jsx(je,{children:s.jsxs(gs,{data:n,children:[o,l,d,f,t.map((p,u)=>s.jsx(vs,{dataKey:p.key,name:p.label??p.key,fill:de(u)},p.key))]})}):r==="area"?s.jsx(je,{children:s.jsxs(bs,{data:n,children:[o,l,d,f,t.map((p,u)=>s.jsx(ws,{type:"monotone",dataKey:p.key,name:p.label??p.key,stroke:de(u),fill:de(u),fillOpacity:.25},p.key))]})}):s.jsx(je,{children:s.jsxs(js,{data:n,children:[o,l,d,f,t.map((p,u)=>s.jsx(ys,{type:"monotone",dataKey:p.key,name:p.label??p.key,stroke:de(u),strokeWidth:2,dot:!1},p.key))]})})},"ap:chat":e=>s.jsx(Qs,{n:e}),"ap:tabs":(e,t)=>{const n=P(e).tabs??[],a=e.children??[];return n.length===0?s.jsx(s.Fragment,{children:a.map(t)}):s.jsxs(ts,{defaultValue:m(e,"value")||n[0].value,children:[s.jsx(ss,{children:n.map(r=>s.jsx(ns,{value:r.value,children:r.label??r.value},r.value))}),n.map((r,o)=>{const d=o===n.length-1?a.slice(o):a.slice(o,o+1);return s.jsx(as,{value:r.value,children:d.map((f,p)=>t(f,`${r.value}-${p}`))},r.value)})]})},"ap:raw_html":()=>s.jsxs(W,{children:[s.jsx(Y,{children:"Embedded content"}),s.jsx(J,{children:"This section renders once the sandboxed content view is available."})]}),"ap:session_view":e=>{const t=ta(P(e).sessionRef);return t===null?s.jsx(W,{children:"Session view unavailable: no valid session reference."}):s.jsx("iframe",{src:t,title:"Session view",style:{width:"100%",height:"600px",border:"0"}})},"ap:agentlink":e=>(qe(e),s.jsx(ln,{n:e})),"ap:attachment":e=>s.jsx(mn,{n:e}),"ap:button":e=>s.jsx(Zn,{n:e}),"ap:select":e=>s.jsx(Jn,{n:e}),"ap:daterange":e=>s.jsx(Gn,{n:e}),"ap:form":e=>s.jsx(ea,{n:e}),"oap:generative":(e,t)=>{var n;return s.jsx(jn,{name:m(e,"name"),title:typeof((n=e.props)==null?void 0:n.title)=="string"?e.props.title:void 0,children:(e.children??[]).map(t)})},"oap:page":(e,t)=>s.jsx(On,{node:e,renderChild:t})};function ra({type:e}){return s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Cannot render this section"}),s.jsxs(J,{children:["Unknown component ",s.jsx("code",{className:"font-mono",children:e}),". Reload the page; if it persists, this view needs an update."]})]})}function oa({type:e}){return s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Cannot render this section"}),s.jsxs(J,{children:["The ",s.jsx("code",{className:"font-mono",children:e})," section could not be displayed. The rest of this view is unaffected."]})]})}const ia=e=>(t,n)=>Ft(t,n,e);function Ft(e,t,n){const a=aa[e.component];if(!a)return n==null||n(e.component),s.jsx(ra,{type:e.component},t);try{return i.cloneElement(a(e,ia(n)),{key:t})}catch(r){return console.error(`agentui: failed to render ${e.component}`,r),n==null||n(e.component),s.jsx(oa,{type:e.component},t)}}function la(e,t,n){return`${e}/${t.join(".")}#${n}`}function ca(e,t){return Vt(e,ne(e)??"",[],t)}function Vt(e,t,n,a){var c;const r=(c=e.children)==null?void 0:c.map((h,b)=>{const x=ne(h);return Vt(h,x??t,x!==null?[]:[...n,b],a)});if(!e.bindings)return{...e,children:r};const o={...e.props};let l=null,d=null,f=null;for(const h of Object.keys(e.bindings).sort()){const b=a[la(t,n,h)]??{status:"loading"};if(b.status==="error"){l===null&&(l=b.message);continue}if(b.status==="needs_input"){d===null&&(d=b.message);continue}if(b.status==="waking"){f===null&&(f=b.message);continue}if(b.status==="loading"){f===null&&(f="");continue}o[h]=b.value}if(l===null&&d===null&&f===null)return{...e,props:o,children:r};const u=l!==null?{component:"ap:error",props:{title:"Cannot load this section",body:l}}:f!==null?{component:"ap:skeleton",props:f===""?{}:{label:f}}:{component:"ap:empty",props:{title:"Nothing to show yet",body:d}};return Ve(e)?{component:"ap:stack",props:{gap:"sm"},children:[{...e,props:o,children:r},u]}:u}const xe="p.";function dt(e){const t={};return new URLSearchParams(e).forEach((a,r)=>{if(!r.startsWith(xe))return;const o=r.slice(xe.length);o!==""&&(t[o]=a)}),t}function da(e,t){const n=new URLSearchParams(e);for(const r of Array.from(n.keys()))r.startsWith(xe)&&n.delete(r);for(const r of Object.keys(t).sort())n.set(xe+r,t[r]);const a=n.toString();return a===""?"":`?${a}`}function ua({open:e,variant:t,text:n,replySeq:a,onReply:r,onDismiss:o}){const[l,d]=i.useState(""),[f,p]=i.useState(!1),[u,c]=i.useState(!1);i.useEffect(()=>{d(""),c(!1)},[a]);const h=l.trim()===""||f;function b(x){x.preventDefault(),!h&&(p(!0),c(!1),r(l).catch(g=>{console.error("agentui: reply modal send failed",g),c(!0)}).finally(()=>p(!1)))}return s.jsx(Oe,{open:e,onOpenChange:x=>{x||o()},children:s.jsxs(Me,{"data-testid":"agent-ui-reply-modal","data-variant":t,children:[s.jsx(Le,{children:t==="paused"?s.jsxs(s.Fragment,{children:[s.jsx(pe,{children:"The agent paused"}),s.jsx(me,{children:"It replied and stopped without asking anything. Send a message to continue."})]}):s.jsxs(s.Fragment,{children:[s.jsx(pe,{children:"The agent is waiting for your reply"}),s.jsx(me,{children:"Your reply goes to this session's conversation, just as if you had typed it there."})]})}),s.jsx("div",{className:"max-h-48 overflow-y-auto text-sm",children:s.jsx(_e,{children:n})}),s.jsxs("form",{onSubmit:b,className:"flex flex-col gap-3",children:[s.jsx("textarea",{"aria-label":"Your reply",value:l,disabled:f,onChange:x=>d(x.target.value),placeholder:t==="paused"?"Tell it how to continue…":void 0,className:"flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"}),u&&s.jsx("p",{role:"alert",className:"text-xs text-destructive",children:"Could not send your reply. Try again."}),s.jsxs(cs,{children:[s.jsx(B,{type:"button",variant:"ghost",onClick:o,disabled:f,children:"Later"}),s.jsx(B,{type:"submit",disabled:h,children:f?"Sending…":t==="paused"?"Continue":"Reply"})]})]})]})})}function fa(e){var t;return e.turnActive?e.composingUpdate?"Composing a page update…":(t=e.activity)!=null&&t.compactLine?e.activity.compactLine:e.notice?e.notice:"Working…":null}function pa(e){switch(e){case"done":return"done";case"in_progress":return"current";default:return"next"}}function ma({label:e,state:t,live:n}){return s.jsxs("li",{"data-state":t,className:"flex items-center gap-2 text-xs",children:[t==="done"&&s.jsx("span",{"aria-hidden":"true",className:"text-[hsl(var(--success))]",children:"✓"}),t==="current"&&s.jsx(ve,{className:L("h-3 w-3 shrink-0 text-primary",n&&"animate-spin"),"aria-hidden":"true"}),s.jsx("span",{className:L(t==="done"&&"text-muted-foreground line-through",t==="next"&&"text-muted-foreground"),children:e})]})}function ha({signals:e}){var a;if(!e.turnActive&&e.plan===null)return null;const t=fa(e),n=((a=e.plan)==null?void 0:a.items)??[];return s.jsxs("section",{"data-testid":"agent-ui-default-progress","aria-live":"polite",className:"flex flex-col gap-2 border-b border-border bg-card/40 px-4 py-2",children:[t!==null&&s.jsxs("p",{"data-testid":"agent-ui-progress-now","data-composing":e.composingUpdate?"true":void 0,className:"flex items-center gap-2 text-xs font-medium text-foreground",children:[!e.composingUpdate&&s.jsx(ve,{className:"h-3 w-3 shrink-0 animate-spin text-muted-foreground","aria-hidden":"true"}),t]}),n.length>0&&s.jsx("ul",{className:"flex flex-col gap-1",children:n.map((r,o)=>s.jsx(ma,{label:r.label,state:pa(r.status),live:e.turnActive},`${o}-${r.id}`))})]})}const Bt="$transport",xa="This view's data could not be loaded. Check your connection and reload.";function ut(e){return{status:"error",message:e}}class ke extends Error{constructor(t,n){super(`agent-ui bindings request failed (${t})`),this.name="BindingsRequestError",this.authored=n,this.status=t}}const ga=410;async function va(e){try{const t=await e.json();return typeof(t==null?void 0:t.error)=="string"?t.error:""}catch{return""}}const ba=250,wa=2e3,ja=15;function ya(e,t,n,a,r){const[o,l]=i.useState({}),[d,f]=i.useState(!1),p=i.useRef(r);p.current=r;const u=i.useRef(0),c=i.useRef(!0),[h,b]=i.useState(0),x=i.useRef(0),g=i.useRef(null);return i.useEffect(()=>{let w=!0;u.current+=1;const C=u.current,N=()=>w&&C===u.current,M=new AbortController,D=c.current?0:ba;c.current=!1;const j=setTimeout(()=>{D>0&&f(!0),fetch(`/agent-ui/${encodeURIComponent(e)}/${encodeURIComponent(t)}/bindings`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({params:n}),signal:M.signal}).then(async v=>{if(!v.ok)throw new ke(v.status,await va(v));return await v.json()}).then(v=>{if(!N())return;const $={};let k=!1;for(const[y,T]of Object.entries(v.bindings??{})){if(T.status==="ok"){$[y]={status:"ok",value:T.value};continue}if(T.status==="needs_input"){$[y]={status:"needs_input",message:T.message??"Choose a value above to load this data."};continue}if(T.status==="waking"){$[y]={status:"waking",message:T.message??"Waking this agent…"},k=!0;continue}$[y]={status:"error",message:T.message??"This view's data is not available."}}l($),k&&x.current{w&&(l(y=>y),b(y=>y+1))},wa)):k?l(y=>{const T={};for(const[U,H]of Object.entries(y))T[U]=H.status==="waking"?{status:"error",message:"the agent for this session isn't responding"}:H;return T}):x.current=0,f(!1)}).catch(v=>{var k;if(M.signal.aborted||!N())return;f(!1),console.error("agent-ui bindings: request failed",v),v instanceof ke&&v.status===ga&&((k=p.current)==null||k.call(p));const $=v instanceof ke&&v.authored!==""?v.authored:xa;l(y=>{const T={};for(const U of Object.keys(y))T[U]=ut($);return T[Bt]=ut($),T})})},D);return()=>{w=!1,clearTimeout(j),M.abort(),g.current!==null&&(clearTimeout(g.current),g.current=null)}},[e,t,JSON.stringify(n),a,h]),{states:o,refreshing:d}}const Na=2e4,Sa=5*6e4,ft=["pointerdown","keydown","scroll","wheel"];function ka({send:e,enabled:t=!0}){const n=i.useRef(e);n.current=e,i.useEffect(()=>{if(!t||typeof window>"u"||typeof document>"u")return;let a=Date.now();const r=()=>{a=Date.now()};for(const d of ft)window.addEventListener(d,r,{passive:!0});const o=()=>document.visibilityState!=="visible"||typeof document.hasFocus=="function"&&!document.hasFocus()?!1:Date.now()-a{o()&&n.current()},Na);return()=>{window.clearInterval(l);for(const d of ft)window.removeEventListener(d,r)}},[t])}function Ca(e,t){return e.replace(/\{([^{}]*)\}/g,(n,a)=>a!==""&&Object.hasOwn(t,a)?t[a]:n)}const Ra=2e3,Ta="This action may not have completed. Check your connection, then check the result before retrying.",Aa="Live updates for this view are unavailable right now.";function Ea(e,t,n){return`${e.protocol==="https:"?"wss:":"ws:"}//${e.host}/agent-ui/${encodeURIComponent(t)}/${encodeURIComponent(n)}/live`}function pt(e){return{phase:e.state,requestId:e.requestId,message:e.message,approvalAddressedToViewer:e.approvalAddressedToViewer}}function Ce(e){return!ie(e)}function $a(e,t){return e!==void 0&&e.phase===t.phase&&e.requestId===t.requestId&&e.message===t.message&&!!e.approvalAddressedToViewer==!!t.approvalAddressedToViewer}function Ia(e,t,n,a,r,o=[]){const[l,d]=i.useState({}),[f,p]=i.useState(null),u=i.useRef(!0),c=i.useRef(n);c.current=n;const h=i.useRef(a);h.current=a;const b=i.useRef(r);b.current=r;const x=i.useRef(o);x.current=o;const g=i.useRef(l);g.current=l;const w=i.useRef(new Set),C=i.useRef(null),N=i.useRef(0),M=i.useRef({});i.useEffect(()=>(u.current=!0,()=>{u.current=!1}),[]),i.useEffect(()=>{let j=null,v,$=!1,k=0;const y=O=>{N.current+=1,M.current[O]=N.current},T=O=>{if(!Ce(O.state))return!1;const A=g.current[O.action];return A===void 0||!Ce(A.phase)},U=O=>{const A=new Set,K={};let z=!1,se=!1;for(const q of O){if(A.has(q.action)||(A.add(q.action),w.current.has(q.action)))continue;T(q)&&(z=!0);const te=pt(q);$a(g.current[q.action],te)||(se=!0),K[q.action]=te,y(q.action)}se&&(d(q=>({...q,...K})),z&&h.current())},H=O=>{const A=T(O);y(O.action),d(K=>({...K,[O.action]:pt(O)})),A&&h.current()},ee=O=>{let A;try{A=JSON.parse(O)}catch(K){console.error("agent-ui live: malformed frame",{ns:e,name:t,err:K});return}if(u.current){if(A.type==="snapshot"){p(null),U(A.actions??[]);return}if(A.type==="event"&&A.action){H(A.action);return}if(A.type==="error"){console.error("agent-ui live: server reported live updates unavailable",{ns:e,name:t,msg:A}),p(A.message||Aa);return}if(A.type==="view"){if(!A.declaration||typeof A.declaration.view!="object"||A.declaration.view===null||typeof A.declaration.view.component!="string"){console.error("agent-ui live: malformed view frame; keeping the previous declaration",{ns:e,name:t,msg:A});return}b.current(A.declaration,A.agentParams,A.hook);return}console.error("agent-ui live: unhandled frame",{ns:e,name:t,msg:A})}},G=()=>{$||(k+=1,console.error("agent-ui live: socket closed; reconnecting",{ns:e,name:t,attempt:k}),v=window.setTimeout(X,Ra))},X=()=>{if(!$){try{j=new WebSocket(Ea(window.location,e,t))}catch(O){console.error("agent-ui live: failed to open socket",{ns:e,name:t,err:O}),G();return}j.onopen=()=>{k=0},C.current=j,j.onmessage=O=>ee(O.data),j.onerror=()=>{},j.onclose=G}};return X(),()=>{$=!0,v&&window.clearTimeout(v),j&&j.close(),C.current=null}},[e,t]),ka({enabled:!0,send:i.useCallback(()=>{const j=C.current;if(!(!j||j.readyState!==WebSocket.OPEN))try{j.send(JSON.stringify({type:"presence"}))}catch(v){console.error("agent-ui live: presence heartbeat send failed",{ns:e,name:t,err:v})}},[e,t])});const D=i.useCallback((j,v)=>{if(w.current.has(j))return;w.current.add(j),N.current+=1;const $=N.current;d(y=>({...y,[j]:{phase:"submitted"}}));const k=x.current.find(y=>y.name===j);if(k!=null&&k.prompt){jt(e,t,Ca(k.prompt,{...c.current,...v??{}})).then(()=>{var y;u.current&&(d(T=>({...T,[j]:{phase:"succeeded",message:"Asked the agent."}})),(y=h.current)==null||y.call(h))}).catch(y=>{console.error("agent-ui actions: asking the agent failed",{ns:e,name:t,action:j,err:y}),u.current&&d(T=>({...T,[j]:{phase:"failed",message:"Could not reach the agent."}}))}).finally(()=>{w.current.delete(j)});return}fetch(`/agent-ui/${encodeURIComponent(e)}/${encodeURIComponent(t)}/actions`,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:j,params:c.current,inputs:v})}).then(async y=>{if(!y.ok)throw new Error(`agent-ui actions: request failed (${y.status})`);return await y.json()}).then(y=>{if(!u.current||(M.current[j]??0)>$)return;const T=y.state;d(U=>{var H;return{...U,[j]:{phase:T,requestId:y.requestId,message:y.message,approvalAddressedToViewer:(H=U[j])==null?void 0:H.approvalAddressedToViewer}}}),Ce(T)&&h.current()}).catch(y=>{console.error("agent-ui actions: invoke failed",{ns:e,name:t,action:j,err:y}),u.current&&d(T=>{var U;return{...T,[j]:{phase:"failed",message:Ta,approvalAddressedToViewer:(U=T[j])==null?void 0:U.approvalAddressedToViewer}}})}).finally(()=>{w.current.delete(j)})},[e,t]);return{states:l,invoke:D,liveError:f}}const Pa={approvals:[],trustEvents:[]},Oa=4e3;function Ma(e,t){if(e===t)return!0;if(e.approvals.length!==t.approvals.length||e.trustEvents.length!==t.trustEvents.length)return!1;for(let n=0;nLa(t,n),[t,n]),d=i.useMemo(()=>{const b=new Map;for(const[x,g]of In(e==null?void 0:e.view))g.state==="done"&&b.set(x,g.title??g.label);return b},[e==null?void 0:e.view]),f=i.useMemo(()=>({composed:new Set((e==null?void 0:e.agentComposed)??[]),updated:a,stale:l,waiting:r,answered:new Set((e==null?void 0:e.answered)??[]),collapsed:d}),[e==null?void 0:e.agentComposed,e==null?void 0:e.answered,a,l,r,d]),p=[],u=e!=null&&e.view?Ft(ca(e.view,t),void 0,b=>p.push(b)):null,c=p.length>0?p[0]:null;if(i.useEffect(()=>{c!==null&&o(c)},[c,o]),!(e!=null&&e.view))return s.jsx("div",{className:"p-4 text-sm text-muted-foreground",children:"This agent has not declared any content for this session."});const h=t[Bt];return s.jsx(vn,{value:f,children:s.jsxs("div",{"data-testid":"agent-ui-root",className:L("flex flex-col gap-2 p-4 mx-auto",xn(e==null?void 0:e.view)==="rail"?"max-w-6xl":"max-w-3xl"),"aria-busy":l.has("")||void 0,children:[(h==null?void 0:h.status)==="error"&&s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Data unavailable"}),s.jsx(J,{children:h.message})]}),s.jsxs("div",{className:L("relative",l.has("")&&"opacity-60 transition-opacity"),children:[u,l.has("")&&s.jsxs("div",{"data-testid":"agent-ui-region-refreshing",className:"pointer-events-none absolute right-2 top-2 flex items-center gap-1.5 text-[11px] text-muted-foreground",children:[s.jsx(ve,{className:"h-3.5 w-3.5 animate-spin","aria-hidden":"true"}),s.jsx("span",{children:"Updating…"})]})]})]})})}function Da(){const[e,t]=i.useState(()=>new Map),n=i.useRef(new Map),a=i.useCallback(r=>{t(l=>new Map(l).set(r,Date.now()));const o=n.current.get(r);o!==void 0&&clearTimeout(o),n.current.set(r,setTimeout(()=>{n.current.delete(r),t(l=>{if(!l.has(r))return l;const d=new Map(l);return d.delete(r),d})},Oa))},[]);return i.useEffect(()=>{const r=n.current;return()=>{for(const o of r.values())clearTimeout(o);r.clear()}},[]),{updatedHooks:e,markUpdated:a}}function Ua({ns:e,name:t,declaration:n,agentParams:a,shown:r,onChromeSignals:o,onSessionEnded:l}){var Ke,ze;const[d,f]=i.useState(null),p=i.useCallback(S=>{const E=`render-error:${S}`;f(I=>(I==null?void 0:I.id)===E?I:{kind:"hard_error",id:E})},[]),[u,c]=i.useState(n),h=Us(),{questionShowing:b,progressShowing:x,waiting:g}=i.useMemo(()=>{const S=En(u==null?void 0:u.view,"ap:question"),E=new Set((u==null?void 0:u.answered)??[]),I=new Set([...S].filter(V=>!E.has(V)));return{questionShowing:I.size>0||$n(u==null?void 0:u.view)||$e(u==null?void 0:u.view,"ap:notice"),progressShowing:$e(u==null?void 0:u.view,"ap:progress"),waiting:I}},[u==null?void 0:u.view,u==null?void 0:u.answered]),[w,C]=i.useState(0),[N,M]=i.useState(()=>{const E={...Dt(u),...a??{}};return typeof window>"u"?E:ye(u,{...E,...dt(window.location.search)})}),D=i.useRef(new Set(typeof window>"u"?[]:Object.keys(dt(window.location.search)))),j=i.useCallback((S,E)=>{D.current.add(S),M(I=>I[S]===E?I:{...I,[S]:E})},[]),v=i.useCallback((S,E)=>{M(I=>{const V={...I};let Q=!1;for(const[we,Ye]of Object.entries(ye(E,S)))D.current.has(we)||V[we]===Ye||(V[we]=Ye,Q=!0);return Q?V:I})},[]);i.useEffect(()=>{if(typeof window>"u")return;const S=da(window.location.search,N);S!==window.location.search&&window.history.replaceState(window.history.state,"",`${window.location.pathname}${S}${window.location.hash}`)},[N]);const $=i.useMemo(()=>({params:N,setParam:j}),[N,j]),k=i.useMemo(()=>({ns:e,name:t}),[e,t]),[y,T]=i.useState(0),U=i.useCallback(()=>{T(S=>S+1)},[]),{updatedHooks:H,markUpdated:ee}=Da(),G=i.useRef(JSON.stringify(n)),X=i.useCallback((S,E,I)=>{E&&Object.keys(E).length>0&&v(E,S),I&&ee(I);const V=JSON.stringify(S);V!==G.current&&(G.current=V,c(S),M(Q=>ye(S,Q)),T(Q=>Q+1))},[v,ee]),{states:O,invoke:A,liveError:K}=Ia(e,t,N,U,X,(u==null?void 0:u.actions)??[]),z=i.useCallback(S=>jt(e,t,S),[e,t]),se=i.useMemo(()=>({states:O,invoke:A,answer:z,busy:h.turnActive}),[O,A,z,h.turnActive]),{states:q,refreshing:te}=ya(e,t,N,y,l),ce=Object.entries(O).filter(([,S])=>S.phase==="awaiting_approval").map(([S,E])=>`${S}:${E.requestId??""}:${E.approvalAddressedToViewer?"1":"0"}`).sort().join("|"),{approvalNotices:ae,approvalTrustEvents:R}=i.useMemo(()=>{const S=[],E=[];for(const[I,V]of Object.entries(O)){if(V.phase!=="awaiting_approval")continue;const Q=V.requestId??I;S.push({id:Q,label:V.approvalAddressedToViewer?"Your approval is needed to continue.":"Waiting on approval from someone else.",addressedToViewer:!!V.approvalAddressedToViewer}),V.approvalAddressedToViewer&&E.push({kind:"approval_addressed_to_viewer",id:Q})}return{approvalNotices:S,approvalTrustEvents:E}},[ce]),_=i.useMemo(()=>({approvals:ae,trustEvents:[d,...R]}),[ae,R,d]);i.useEffect(()=>{o==null||o(_)},[_,o]);const F=((Ke=h.lastAgentReply)==null?void 0:Ke.seq)??0,Yt=r&&(h.awaitingReply||h.pausedIdle)&&h.lastAgentReply!==null&&F>w&&!b&&!h.ended,Jt=i.useCallback(async S=>{await z(S),C(F)},[z,F]),Gt=i.useCallback(()=>C(F),[F]),[He,be]=i.useState(null),We=qs(e,t,be),Xt=i.useCallback((S,E,I)=>{be(null),We(S,E,I)},[We]),[Qt,Zt]=i.useState({}),es=i.useCallback(S=>{if(S.type!=="interaction_decision_rejected")return;const E=S.payload.payload;h.pendingInteractions.some(I=>I.requestRef===E.requestRef)&&(be(Fs(E)),Zt(I=>({...I,[E.requestRef]:(I[E.requestRef]??0)+1})))},[h.pendingInteractions]);return yt(es),s.jsx(cn,{value:k,children:s.jsx(_n,{value:$,children:s.jsx(Gs,{value:se,children:s.jsxs("div",{className:"flex min-h-full flex-col",children:[!x&&s.jsx(ha,{signals:h}),K!==null&&s.jsx("div",{className:"px-4 pt-4 max-w-3xl mx-auto","data-testid":"agent-ui-live-error",children:s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:"Live updates unavailable"}),s.jsx(J,{children:K})]})}),r&&h.pendingInteractions.length>0&&s.jsxs("section",{"data-testid":"agent-ui-decisions","aria-label":"Needs your decision",className:"flex flex-col gap-2 border-b border-border bg-card/60 px-3 py-2",children:[s.jsx("p",{className:"text-xs font-medium text-muted-foreground",children:"Needs your decision"}),h.pendingInteractions.map(S=>s.jsx(Vs,{request:S,onDecision:Xt},`${S.requestRef}:${Qt[S.requestRef]??0}`)),He&&s.jsx("p",{role:"alert",className:"text-xs text-destructive",children:He})]}),s.jsx(_a,{declaration:u,states:q,refreshing:te,updatedHooks:H,waiting:g,onRenderError:p}),s.jsx(ua,{open:Yt,variant:h.awaitingReply?"asked":"paused",text:((ze=h.lastAgentReply)==null?void 0:ze.text)??"",replySeq:F,onReply:Jt,onDismiss:Gt})]})})})})}function qa(e,t){switch(e){case"attached":return{text:"Resumed your session.",Icon:Qe};case"asleep":return{text:"Your session is asleep; it resumes when you send a message.",Icon:Ps};case"ended":return{text:t?`This session has ended: ${t}`:"This session has ended. You can start a new one.",Icon:Is};default:return{text:"Connected to your session.",Icon:Qe}}}function Ht(e){return e==="collapsed"}function mt(e,t,n,a){a&&(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||(e.preventDefault(),a(t,n)))}function ht(e,t,n,a){e.currentTarget.href=re(t,n,a)}function xt(e){const t="inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-[12px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";return e?`${t} bg-secondary text-secondary-foreground`:`${t} text-muted-foreground hover:bg-accent/50 hover:text-foreground`}function re(e,t,n){const a=new URLSearchParams(typeof window>"u"?"":window.location.search);return a.delete("new"),a.delete("ns"),a.delete("agentClass"),a.delete("prompt"),a.set("session",`${e}/${t}`),a.set("view",n),`/sessions?${a.toString()}`}function Fa(e){return{collapsed:Ht(e),userToggled:!1,trustRevealed:!1}}function Va(e,t){switch(t.type){case"ui_requested":return e.userToggled||e.trustRevealed||e.collapsed===t.collapsed?e:{...e,collapsed:t.collapsed};case"user_toggled":return{collapsed:!e.collapsed,userToggled:!0,trustRevealed:!1};case"trust_event":return{...e,collapsed:!1,trustRevealed:!0}}}function Ba({ns:e,name:t,subject:n,sessionOrigin:a,endedReason:r,currentView:o,offersAgentUI:l,onSelectView:d,initialState:f,trustEvents:p,approvals:u,headerActions:c,sidebar:h,children:b}){const x=e!==void 0&&t!==void 0?{ns:e,name:t}:null,g=qa(a??"",r),[w,C]=i.useReducer(Va,f,Fa),N=w.collapsed;i.useEffect(()=>{C({type:"ui_requested",collapsed:Ht(f)})},[f]);const M=i.useRef(new Set),D=(p??[]).flatMap(v=>v?[v.id]:[]);i.useEffect(()=>{const v=D.filter($=>!M.current.has($));if(v.length!==0){for(const $ of v)M.current.add($);C({type:"trust_event"})}},[D.join("|")]);function j(){C({type:"user_toggled"})}return s.jsxs("div",{className:"h-screen flex flex-col bg-background text-foreground",children:[s.jsxs("header",{className:"flex items-center gap-2.5 px-3 py-2 min-h-[52px] bg-card border-b border-border",children:[s.jsx(B,{type:"button",variant:"ghost",size:"icon","aria-expanded":!N,"aria-label":N?"Show chrome":"Hide chrome","data-testid":"session-shell-chrome-toggle",onClick:j,children:N?s.jsx(Cs,{className:"h-4 w-4","aria-hidden":"true"}):s.jsx(Rs,{className:"h-4 w-4","aria-hidden":"true"})}),x&&s.jsxs("div",{className:"flex items-center gap-2 min-w-0",hidden:N,children:[s.jsx(g.Icon,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0","aria-hidden":"true"}),s.jsx("span",{className:"text-[13px] text-muted-foreground truncate",title:g.text,"data-testid":"session-shell-session-origin",children:g.text})]}),s.jsxs("div",{className:"ml-auto flex items-center gap-2 shrink-0",children:[c&&s.jsx("div",{className:"flex items-center gap-1","data-testid":"session-shell-header-actions",hidden:N,children:c}),s.jsxs("div",{className:"flex items-center gap-2 min-w-0",hidden:N,children:[s.jsx(Ts,{className:"h-4 w-4 text-muted-foreground shrink-0","aria-hidden":"true"}),s.jsxs("span",{className:"text-[13px] font-medium truncate","data-testid":"session-shell-identity",children:["Acting as ",n]})]})]})]}),s.jsxs("div",{className:"flex items-center gap-2 px-3 py-1 min-h-[36px] border-b border-border bg-card/60 text-[12px] text-muted-foreground","data-testid":"session-shell-approval-surface",hidden:N,children:[u&&u.length>0&&s.jsxs(s.Fragment,{children:[s.jsx(As,{className:"h-3.5 w-3.5 shrink-0","aria-hidden":"true"}),s.jsx("ul",{className:"flex flex-col gap-0.5",children:u.map(v=>s.jsx("li",{children:v.label},v.id))})]}),x&&s.jsxs("div",{className:"ml-auto flex items-center gap-0.5 rounded-md border border-border bg-background/60 p-0.5 shrink-0","data-testid":"session-shell-view-tabs",children:[l&&s.jsxs("a",{href:re(x.ns,x.name,"ui"),"aria-current":o==="ui"?"page":void 0,"data-testid":"session-shell-agent-view-switch",className:xt(o==="ui"),onMouseDown:v=>ht(v,x.ns,x.name,"ui"),onClick:v=>mt(v,"ui",re(x.ns,x.name,"ui"),d),children:[s.jsx(Es,{className:"h-3.5 w-3.5","aria-hidden":"true"}),"Agent view"]}),s.jsxs("a",{href:re(x.ns,x.name,"chat"),"aria-current":o==="chat"?"page":void 0,"data-testid":"session-shell-chat-escape-hatch",className:xt(o==="chat"),onMouseDown:v=>ht(v,x.ns,x.name,"chat"),onClick:v=>mt(v,"chat",re(x.ns,x.name,"chat"),d),children:[s.jsx($s,{className:"h-3.5 w-3.5","aria-hidden":"true"}),"Chat"]})]})]}),s.jsxs("div",{className:"flex-1 min-h-0 flex",children:[s.jsx("aside",{className:h?"w-64 shrink-0 min-h-0 overflow-y-auto border-r border-border bg-card/40":"w-0 shrink-0 min-h-0 overflow-hidden","data-testid":"session-shell-sidebar",hidden:N,children:h}),s.jsx("main",{className:"flex min-h-0 flex-1 flex-col overflow-y-auto","data-testid":"session-shell-content-region",children:b})]})]})}function Ha(e){const t=e;if(!t||typeof t.ns!="string"||typeof t.name!="string"||typeof t.href!="string"||!t.href)throw new Error("the server's answer did not address a session");return{ns:t.ns,name:t.name,href:t.href}}class fe extends Error{constructor(t,n){super(t),this.name="StartError",this.indeterminate=n}}async function Wt(e,t){let n;try{n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}catch(a){throw console.error("sessions: start request did not complete",{url:e,err:a}),new fe("We could not confirm whether that session started. Reload your sessions before trying again.",!0)}if(!n.ok){let a="";try{const r=await n.json();a=typeof(r==null?void 0:r.error)=="string"?r.error:""}catch{a=""}throw console.error("sessions: start request failed",{url:e,status:n.status}),new fe(a||"That session could not be started right now.",!1)}try{return Ha(await n.json())}catch{throw console.error("sessions: start succeeded but its answer could not be addressed",{url:e}),new fe("That session started, but we could not open it. Reload your sessions to find it.",!0)}}function Kt(e){return!(e instanceof fe)||e.indeterminate}const Wa="This server cannot start new sessions for that agent. You can still open the sessions you already have with it, or ask an administrator to enable starting here.",Ka=" — cannot start here";function za(e){const t=new Map;for(const n of e)t.set(n.title,(t.get(n.title)??0)+1);return e.map(n=>{const a=(t.get(n.title)??0)>1?`${n.title} (in ${n.ns})`:n.title;return n.startable?a:a+Ka})}function zt(e){window.location.assign(e)}function Ya(){return s.jsx("p",{className:"text-sm text-muted-foreground","data-testid":"session-shell-no-startable-classes",children:"You do not have access to any agents yet. Access arrives by being added to a session someone else started, or through a channel an agent is connected to."})}function Ja({classes:e,onNavigate:t=zt,openInitially:n=!1,preselect:a,initialPrompt:r}){const[o,l]=i.useState(n),d=a?e.findIndex(k=>k.ns===a.ns&&k.class===a.agentClass):-1,[f,p]=i.useState(!1),u=a!==void 0&&d===-1&&!f,[c,h]=i.useState(d===-1?0:d),[b,x]=i.useState(r??""),[g,w]=i.useState(!1),[C,N]=i.useState(null);if(e.length===0)return null;const M=e[Math.min(c,e.length-1)],D=za(e),j=!M.startable,v=M.offersUI===!0;async function $(){w(!0),N(null);try{const k=await Wt("/sessions/api/start",{ns:M.ns,agentClass:M.class,prompt:b});t(v?`${k.href}&view=ui`:k.href)}catch(k){N(k.message),Kt(k)||w(!1)}}return s.jsxs(s.Fragment,{children:[s.jsx(B,{type:"button",size:"sm","data-testid":"session-shell-new-session",onClick:()=>{N(null),l(!0)},children:"+ New session"}),s.jsx(Oe,{open:o,onOpenChange:l,children:s.jsxs(Me,{children:[s.jsxs(Le,{children:[s.jsx(pe,{children:"Start a new session"}),s.jsx(me,{children:v?"Pick an agent. This one opens its own view.":"Pick an agent and say what you need."})]}),s.jsxs("div",{className:"flex flex-col gap-3",children:[u&&s.jsx("p",{className:"text-sm text-destructive","data-testid":"session-shell-preselect-refused",children:"You can't start that agent from here."}),s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(oe,{htmlFor:"new-session-class",children:"Agent"}),s.jsx("select",{id:"new-session-class",className:"rounded-md border border-input bg-background px-3 py-2 text-sm","data-testid":"session-shell-new-session-class",value:c,onChange:k=>{h(Number(k.target.value)),p(!0)},children:e.map((k,y)=>s.jsx("option",{value:y,children:D[y]},`${k.ns}/${k.class}`))})]}),v?s.jsxs("details",{className:"flex flex-col gap-1","data-testid":"session-shell-new-session-prompt-disclosure",children:[s.jsx("summary",{className:"cursor-pointer text-sm text-muted-foreground",children:"Add a message (optional — otherwise the agent is just told you opened the view)"}),s.jsx("textarea",{id:"new-session-prompt",rows:3,"aria-label":"Message",className:"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm","data-testid":"session-shell-new-session-prompt",value:b,onChange:k=>x(k.target.value)})]}):s.jsxs("div",{className:"flex flex-col gap-1",children:[s.jsx(oe,{htmlFor:"new-session-prompt",children:"Message"}),s.jsx("textarea",{id:"new-session-prompt",rows:4,className:"rounded-md border border-input bg-background px-3 py-2 text-sm","data-testid":"session-shell-new-session-prompt",value:b,onChange:k=>x(k.target.value)})]}),j&&s.jsx("p",{className:"text-sm text-muted-foreground","data-testid":"session-shell-new-session-unstartable",children:Wa}),C&&s.jsx("p",{className:"text-sm text-destructive","data-testid":"session-shell-new-session-error",children:C}),s.jsx("div",{className:"flex justify-end",children:s.jsx(B,{type:"button","data-testid":"session-shell-new-session-submit",disabled:g||j||u||!v&&b.trim()==="",onClick:()=>void $(),children:g?v?"Opening…":"Starting…":v?"Open UI":"Start session"})})]})]})})]})}function Ga({ns:e,name:t,onNavigate:n=zt}){const[a,r]=i.useState(!1),[o,l]=i.useState(null);async function d(){r(!0),l(null);try{const f=await Wt(`/agent-ui/${encodeURIComponent(e)}/${encodeURIComponent(t)}/start`,{prompt:"Continue where we left off."});n(f.href)}catch(f){l(f.message),Kt(f)||r(!1)}}return s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(B,{type:"button",size:"sm",variant:"outline","data-testid":"session-shell-start-replacement",disabled:a,onClick:()=>void d(),children:a?"Starting…":"Start a new session"}),o&&s.jsx("span",{className:"text-sm text-destructive","data-testid":"session-shell-start-replacement-error",children:o})]})}async function Xa(e,t){const n=await fetch(`/sessions/api/${encodeURIComponent(e)}/${encodeURIComponent(t)}/detail`);if(!n.ok)throw console.error("sessions: load session detail failed",{ns:e,name:t,status:n.status}),new Error(n.status===403?"you do not have access to this session":n.status===401?"you are no longer signed in":"these settings could not be loaded right now");return await n.json()}function Z({label:e,value:t,children:n}){return s.jsxs("div",{className:"flex items-start justify-between gap-4 py-1.5 text-sm",children:[s.jsx("span",{className:"shrink-0 text-muted-foreground",children:e}),s.jsx("span",{className:"break-all text-right font-mono text-[13px]",children:n??t})]})}function Qa({ns:e,name:t,open:n,onOpenChange:a}){const[r,o]=i.useState(null),[l,d]=i.useState(null);return i.useEffect(()=>{if(!n)return;let f=!1;return o(null),d(null),Xa(e,t).then(p=>{f||o(p)}).catch(p=>{f||d(p.message||"these settings could not be loaded right now")}),()=>{f=!0}},[n,e,t]),s.jsx(Oe,{open:n,onOpenChange:a,children:s.jsxs(Me,{children:[s.jsxs(Le,{children:[s.jsx(pe,{children:"Session details"}),s.jsx(me,{children:"Resolved settings for this conversation."})]}),l?s.jsxs("div",{className:"text-sm text-destructive","data-testid":"session-shell-session-info-error",children:["Couldn't load details: ",l]}):r?s.jsxs("div",{className:"divide-y divide-border",children:[s.jsx(Z,{label:"Agent class",value:r.agentClass}),s.jsx(Z,{label:"Model",children:r.model.name?s.jsx(Ks,{model:`${r.model.provider||"?"}/${r.model.name}`}):"not resolved"}),s.jsx(Z,{label:"Max tokens",value:r.maxTokens?String(r.maxTokens):"—"}),s.jsx(Z,{label:"Max turns",value:r.maxTurns?String(r.maxTurns):"—"}),s.jsx(Z,{label:"Max duration",value:r.maxDuration||"—"}),s.jsx(Z,{label:"Owner",value:r.owner}),s.jsx(Z,{label:"Phase",value:r.phase||"—"}),s.jsx(Z,{label:"Created",value:new Date(r.createdAt).toLocaleString()})]}):s.jsx("div",{className:"text-sm text-muted-foreground",children:"Loading…"})]})})}function Za(e,t=Date.now()){if(!e)return"";const n=Date.parse(e);if(Number.isNaN(n))return"";const a=Math.max(0,Math.floor((t-n)/1e3));if(a<60)return"just now";const r=Math.floor(a/60);if(r<60)return`${r}m`;const o=Math.floor(r/60);return o<24?`${o}h`:`${Math.floor(o/24)}d`}function er(e,t){return`/sessions?session=${encodeURIComponent(`${e}/${t}`)}`}function tr(e){const t=[];return e&&(e.unavailable>0&&t.push(e.unavailable===1?"1 session could not be loaded and is not shown.":`${e.unavailable} sessions could not be loaded and are not shown.`),e.truncated&&t.push("Showing your most recent sessions only."),e.bootstrapUnavailable&&t.push("Some agents may be missing from New chat. Try again in a moment.")),t}function sr(e){const t=e.lastIndexOf("-");return t>0&&tc.ns===n&&c.name===a,f=e.filter(c=>c.ended&&!d(c)).length,p=o?e:e.filter(c=>!c.ended||d(c)),u=new Map;for(const c of p)u.set(c.title,(u.get(c.title)??0)+1);return s.jsxs("nav",{className:"flex flex-col","aria-label":"Your sessions","data-testid":"session-shell-session-list",children:[r.length>0&&s.jsx("div",{className:"px-3 py-2 text-[11px] text-muted-foreground border-b border-border","data-testid":"session-shell-list-notices",children:r.map(c=>s.jsx("div",{children:c},c))}),e.length===0?s.jsx("div",{className:"px-3 py-4 text-[12px] text-muted-foreground","data-testid":"session-shell-empty-list",children:"You have no sessions yet."}):p.length===0?s.jsx("div",{className:"px-3 py-4 text-[12px] text-muted-foreground","data-testid":"session-shell-empty-list",children:"No active sessions."}):s.jsx("ul",{className:"flex flex-col",children:p.map(c=>{const h=d(c),b=(u.get(c.title)??0)>1,x=c.startedAt?Za(c.startedAt):"";return s.jsx("li",{children:s.jsxs("a",{href:er(c.ns,c.name),"aria-current":h?"page":void 0,"data-testid":`session-shell-session-row-${c.ns}/${c.name}`,className:`flex flex-col gap-0.5 px-3 py-2 border-b border-border hover:bg-surface-2 ${h?"bg-surface-active shadow-[inset_3px_0_0_hsl(var(--state))]":""}`,children:[s.jsxs("span",{className:"flex items-baseline gap-1.5 min-w-0",children:[s.jsx("span",{className:"text-[13px] font-medium truncate",children:c.title}),b&&s.jsx("span",{className:"shrink-0 font-mono text-[10px] text-muted-foreground","data-testid":`session-shell-session-row-tag-${c.ns}/${c.name}`,children:sr(c.name)})]}),s.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] text-muted-foreground",children:[c.awaitingHuman?s.jsx(Os,{className:"h-3 w-3 shrink-0","aria-hidden":"true"}):c.ended?s.jsx(Ms,{className:"h-3 w-3 shrink-0","aria-hidden":"true"}):s.jsx(Ls,{className:"h-3 w-3 shrink-0","aria-hidden":"true"}),s.jsx("span",{className:"truncate","data-testid":`session-shell-session-row-phase-${c.ns}/${c.name}`,children:c.phase}),x&&s.jsxs(s.Fragment,{children:[s.jsx("span",{"aria-hidden":"true",children:"·"}),s.jsx("span",{className:"shrink-0","data-testid":`session-shell-session-row-age-${c.ns}/${c.name}`,children:x})]})]})]})},`${c.ns}/${c.name}`)})}),f>0||o&&e.some(c=>c.ended)?s.jsx("button",{type:"button",onClick:()=>l(c=>!c),"aria-expanded":o,className:"px-3 py-2 text-left text-[11px] text-muted-foreground hover:text-foreground hover:bg-surface-2","data-testid":"session-shell-toggle-ended",children:o?"Hide ended sessions":`Show ${f} ended session${f===1?"":"s"}`}):null]})}function ar(e,t){const n="oap sessions",a=e?`${e} · ${n}`:n;return t>0?`(${t}) ${a}`:a}function gt(){return typeof window<"u"&&"Notification"in window}function rr(e){const[t,n]=i.useState(0);i.useEffect(()=>{e&&n(0)},[e]);const a=i.useRef(!1),r=i.useCallback(()=>{a.current||!gt()||(a.current=!0,Notification.permission==="default"&&Notification.requestPermission())},[]),o=i.useCallback((l,d)=>{if(!e&&(n(f=>f+1),!(!gt()||Notification.permission!=="granted")))try{const f=new Notification(l,{body:d});f.onclick=()=>{window.focus(),f.close()}}catch{}},[e]);return{unread:t,notify:o,requestPermission:r}}function vt(){if(typeof document>"u")return!0;const e=document.visibilityState==="visible",t=typeof document.hasFocus=="function"?document.hasFocus():!0;return e&&t}function or(){const[e,t]=i.useState(vt);return i.useEffect(()=>{const n=()=>t(vt());return document.addEventListener("visibilitychange",n),window.addEventListener("focus",n),window.addEventListener("blur",n),()=>{document.removeEventListener("visibilitychange",n),window.removeEventListener("focus",n),window.removeEventListener("blur",n)}},[]),e}const ir=3e4;function lr(e,t){const n=[];for(const a of e)a.awaitingHuman&&(t&&a.ns===t.ns&&a.name===t.name||n.push({id:`session:${a.ns}/${a.name}`,label:`${a.title} is waiting on a person in another session.`,addressedToViewer:!1}));return n}function cr({selected:e,shown:t,ended:n,onChromeSignals:a,onUserSend:r,onAgentReply:o,onAwaitingDecision:l,onSessionEnded:d,uiEverShownRef:f}){const p=e.view.notice?s.jsx("div",{className:"p-4 max-w-3xl mx-auto","data-testid":"session-shell-view-notice",children:s.jsxs(W,{children:[s.jsx(Y,{children:e.view.notice.title}),s.jsx(J,{children:e.view.notice.message})]})}):null;t==="ui"&&(f.current=!0);const u=e.view.ui,c=u&&f.current?s.jsxs("div",{className:t==="ui"?"h-full":"hidden","data-testid":"session-shell-ui-region",children:[t==="ui"&&p,s.jsx(Ua,{ns:u.ns,name:u.name,declaration:u.declaration,agentParams:u.agentParams,shown:t==="ui",onChromeSignals:a,onSessionEnded:d})]}):null;function h(){var x,g;switch(t){case"ui":return u?null:s.jsxs(s.Fragment,{children:[p,!e.view.unavailable&&Re("agent view shown but no ui or unavailable payload",e),s.jsx(Te,{title:((x=e.view.unavailable)==null?void 0:x.title)??"View unavailable",message:((g=e.view.unavailable)==null?void 0:g.message)??"This agent's view could not be loaded."})]});case"chat":return e.view.chat?s.jsxs(s.Fragment,{children:[p,n&&s.jsx("div",{className:"p-4 max-w-3xl mx-auto","data-testid":"session-shell-replacement-region",children:s.jsx(Ga,{ns:e.view.chat.ns,name:e.view.chat.name})}),s.jsx(Ws,{ns:e.view.chat.ns,name:e.view.chat.name,readOnly:n,onUserSend:r,onSessionEnded:d})]}):s.jsxs(s.Fragment,{children:[p,Re("kind is chat but the view payload is absent",e),s.jsx(Te,{title:"View unavailable",message:"This conversation could not be opened."})]});default:return s.jsxs(s.Fragment,{children:[p,Re("unrecognized shown view",e),s.jsx(Te,{title:"View unavailable",message:"This view could not be shown."})]})}}const b=e.view.chat??{ns:e.ns,name:e.name};return s.jsxs(Bs,{ns:b.ns,name:b.name,children:[s.jsx(dr,{onAgentReply:o,onAwaitingDecision:l,onSessionEnded:d}),s.jsx(Hs,{}),c,h()]},`${b.ns}/${b.name}`)}function dr({onAgentReply:e,onAwaitingDecision:t,onSessionEnded:n}){const a=i.useRef(new Set);return yt(r=>{if(r.type==="user_message"){e(r.payload.text);return}if(r.type==="interaction_request"){const o=r.payload.payload;if(a.current.has(o.requestRef))return;a.current.add(o.requestRef),t(o.lead);return}r.type==="session_ended"&&n()}),null}function Re(e,t){return console.error("sessions: cannot render the selected view",{reason:e,ns:t.ns,name:t.name,kind:t.view.kind}),null}function Te({title:e,message:t}){return s.jsx("div",{className:"p-4 max-w-3xl mx-auto","data-testid":"session-shell-view-unavailable",children:s.jsxs(W,{variant:"destructive",children:[s.jsx(Y,{children:e}),s.jsx(J,{children:t})]})})}function ur(e){var q,te,ce,ae;const{selected:t}=e,[n,a]=i.useState(e.sessions??[]),[r,o]=i.useState(e.notices),[l,d]=i.useState(Pa),[f,p]=i.useState(!1),[u,c]=i.useState(!1),h=or(),{unread:b,notify:x,requestPermission:g}=rr(h),w=i.useCallback(R=>{d(_=>Ma(_,R)?_:R)},[]),C=i.useRef(null);C.current===null&&(C.current=new AbortController);const N=i.useCallback(async R=>{try{const _=await fetch("/sessions/api/sessions",{signal:R});if(!_.ok){console.error("sessions: session-list refresh failed",{status:_.status});return}const F=await _.json();if(R.aborted)return;if(!Array.isArray(F==null?void 0:F.sessions)){console.error("sessions: session-list refresh returned an unexpected body; keeping the current list");return}a(F.sessions),o(F.notices)}catch(_){if(R.aborted)return;console.error("sessions: session-list refresh errored",_)}},[]);i.useEffect(()=>{const R=C.current,_=setInterval(()=>void N(R.signal),ir);return()=>{R.abort(),clearInterval(_)}},[N]);const M=i.useRef(!1),D=i.useCallback(()=>{M.current||(M.current=!0,c(!0),N(C.current.signal))},[N]),j=i.useMemo(()=>[...lr(n,t),...l.approvals],[n,t,l.approvals]),v=i.useMemo(()=>{var R;return(R=n.find(_=>t&&_.ns===t.ns&&_.name===t.name))==null?void 0:R.title},[n,t]),$=i.useRef("oap sessions");$.current=v||"oap sessions",i.useEffect(()=>{document.title=ar(v??null,b)},[v,b]);const k=i.useCallback(R=>{x($.current,R)},[x]),y=i.useCallback(R=>{x(`${$.current} needs your decision`,R)},[x]),T=t?u?"ended":t.sessionOrigin:void 0,U=T==="ended",H=e.canStartSessions&&(((q=e.startableClasses)==null?void 0:q.length)??0)>0,[ee,G]=i.useState(()=>{var R;return((R=e.selected)==null?void 0:R.view.kind)==="chat"?"chat":"ui"}),X=e.selected?`${e.selected.ns}/${e.selected.name}`:"",O=i.useRef(X),A=i.useRef(!1);i.useEffect(()=>{var R;O.current!==X&&(O.current=X,A.current=!1,G(((R=e.selected)==null?void 0:R.view.kind)==="chat"?"chat":"ui"))},[X,(te=e.selected)==null?void 0:te.view.kind]);const K=i.useCallback((R,_)=>{G(R),typeof window<"u"&&window.history.pushState(window.history.state,"",_)},[]);i.useEffect(()=>{if(typeof window>"u")return;const R=()=>{const _=new URLSearchParams(window.location.search).get("view");G(_==="chat"?"chat":"ui")};return window.addEventListener("popstate",R),()=>window.removeEventListener("popstate",R)},[]);const z=i.useMemo(()=>{if(typeof window>"u")return{open:!1};const R=new URLSearchParams(window.location.search),_=R.get("ns")??"",F=R.get("agentClass")??"";return{open:R.get("new")==="1",preselect:_&&F?{ns:_,agentClass:F}:void 0,initialPrompt:R.get("prompt")??void 0}},[]),se=t?{ns:t.ns,name:t.name,sessionOrigin:T??t.sessionOrigin,currentView:ee,onSelectView:K,endedReason:U?t.endedReason:void 0,offersAgentUI:t.offersAgentUI&&!U}:{};return s.jsx(Ba,{...se,subject:e.subject,initialState:(ce=t==null?void 0:t.chrome)==null?void 0:ce.initialState,approvals:j,trustEvents:l.trustEvents,headerActions:H||t?s.jsxs(s.Fragment,{children:[H&&s.jsx(Ja,{classes:e.startableClasses,openInitially:z.open,preselect:z.preselect,initialPrompt:z.initialPrompt}),t&&s.jsxs(s.Fragment,{children:[s.jsx(B,{type:"button",variant:"ghost",size:"icon","aria-label":"Session details","data-testid":"session-shell-session-info",onClick:()=>p(!0),children:s.jsx(_s,{className:"h-4 w-4","aria-hidden":"true"})}),s.jsx(Qa,{ns:t.ns,name:t.name,open:f,onOpenChange:p})]})]}):void 0,sidebar:s.jsx(nr,{sessions:n,notices:r,selectedNs:t==null?void 0:t.ns,selectedName:t==null?void 0:t.name}),children:t?s.jsx(cr,{selected:t,shown:ee,ended:U,onChromeSignals:w,onUserSend:g,onAgentReply:k,onAwaitingDecision:y,onSessionEnded:D,uiEverShownRef:A}):s.jsxs("div",{className:"p-6 flex flex-col gap-3","data-testid":"session-shell-no-selection",children:[s.jsx("span",{className:"text-sm text-muted-foreground",children:"Pick a session to open it."}),(((ae=e.startableClasses)==null?void 0:ae.length)??0)===0&&s.jsx(Ya,{})]})})}ds();us(ur); diff --git a/pkg/web/webui/webassets/dist/settings.Bs9xT9hr.js b/pkg/web/webui/webassets/dist/settings.DdEnJSsa.js similarity index 99% rename from pkg/web/webui/webassets/dist/settings.Bs9xT9hr.js rename to pkg/web/webui/webassets/dist/settings.DdEnJSsa.js index f0a6a4ef..61a744be 100644 --- a/pkg/web/webui/webassets/dist/settings.Bs9xT9hr.js +++ b/pkg/web/webui/webassets/dist/settings.DdEnJSsa.js @@ -1,3 +1,3 @@ -import{A as b,a as N,M as v,N as V,B as $,O as se,P as le,Q as ae,R as ne,U as J,j as he,k as ge,l as K,n as _,o as pe,p as I,D as Ge,t as Ue,u as Ke,v as Je,x as Ye,T as ye,g as we,h as Te,i as H,m as qe}from"./mount.BOEH1oNx.js";import{r,j as e}from"./vendor.C5MRNvUt.js";import{B as Ce}from"./badge.CIlGQKPh.js";class ie extends Error{constructor(s,l){super(s),this.status=l}}async function ce(t){if(!t.ok){const s=await t.text();let l=s.trim()||`request failed (${t.status})`;try{const i=JSON.parse(s);i.error&&(l=i.error)}catch{}throw new ie(l,t.status)}return await t.json()}async function ve(t){const s=await fetch(t,{method:"POST"});if(!s.ok){await ce(s);return}}const ue=async t=>ce(await fetch(t)),He=async(t,s)=>ce(await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})),Ee=async(t,s)=>ce(await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})),We=t=>ue(`${t}/state`),te=t=>ue(`${t}/config`),Ae=(t,s)=>Ee(`${t}/config`,s),Pe=t=>ue(`${t}/cluster/settings`),De=(t,s)=>He(`${t}/cluster/settings/validate`,s),Me=(t,s)=>Ee(`${t}/cluster/settings`,s),Qe=t=>ue(`${t}/cluster/install-info`);function Fe(t){if(!(t instanceof ie))return null;try{const s=JSON.parse(t.message);if(Array.isArray(s.errors))return s}catch{}return null}function Oe(t){if(!(t instanceof ie))return null;try{const s=JSON.parse(t.message);if(s.validation&&Array.isArray(s.validation.errors))return s}catch{}return null}const Xe=t=>ve(`${t}/kubectl/use`),Ze=t=>ve(`${t}/reveal/config`),Be=t=>ve(`${t}/reveal/logs`);function et(t){const[s,l]=r.useState(null),[i,o]=r.useState(null);return r.useEffect(()=>{let a=!1;We(t).then(u=>{a||(l(u),o(null))}).catch(u=>{a||o(u.message)});const n=new EventSource(`${t}/events`);return n.onmessage=u=>{if(!a)try{l(JSON.parse(u.data)),o(null)}catch{}},n.onerror=()=>{a||o("settings: live update stream stalled")},()=>{a=!0,n.close()}},[t]),{state:s,error:i}}const tt={applied:"Applied","next-start":"Applies at next start",failed:"Failed"},st={applied:"✓","next-start":"⏱",failed:"✕"},lt={model:"Model",password:"Password",ngrok:"ngrok",healthNotifications:"Health notifications"};function Le({outcomes:t}){return t.length===0?null:e.jsx("ul",{"aria-label":"Save results",className:"flex flex-col gap-1 text-sm",children:t.map(s=>e.jsxs("li",{className:"flex items-start gap-2",children:[e.jsx("span",{"aria-hidden":"true",children:st[s.outcome]??"•"}),e.jsxs("span",{children:[e.jsxs("span",{className:"font-medium",children:[lt[s.field]??s.field,":"]})," ",tt[s.outcome]??s.outcome,s.message&&e.jsxs("span",{className:"text-muted-foreground",children:[" — ",s.message]})]})]},s.field))})}const xe=8;function at({apiBase:t,running:s,kubectlCurrent:l}){const[i,o]=r.useState(null),[a,n]=r.useState(null),[u,g]=r.useState(!0),[c,x]=r.useState(""),[w,T]=r.useState(""),[h,P]=r.useState(""),[R,D]=r.useState(null),[d,f]=r.useState(!1),[S,E]=r.useState([]),[y,A]=r.useState(!1),[m,O]=r.useState(null),[Y,U]=r.useState(null);r.useEffect(()=>{let p=!1;return te(t).then(k=>{p||(o(k),g(k.healthNotifications))}).catch(k=>{p||n(k.message)}),()=>{p=!0}},[t]);async function q(){D(null),E([]);const p=w!==""||h!=="";if(p){if(w.lengthg(p.target.checked),className:"h-4 w-4 rounded border-border"}),e.jsx(v,{htmlFor:"general-health-notifications",children:"Notify on cluster health issues"})]})}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Admin password"}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"general-current-password",children:"Current password"}),e.jsx(V,{id:"general-current-password",type:"password",value:c,onChange:p=>x(p.target.value)})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"general-new-password",children:"New password"}),e.jsx(V,{id:"general-new-password",type:"password",value:w,onChange:p=>T(p.target.value)}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["Must be at least ",xe," characters."]})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"general-confirm-password",children:"Confirm new password"}),e.jsx(V,{id:"general-confirm-password",type:"password",value:h,onChange:p=>P(p.target.value)})]})]}),R&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:R})}),e.jsx(Le,{outcomes:S}),e.jsx("div",{children:e.jsx($,{onClick:()=>void q(),disabled:d||!i,children:d?"Saving…":"Save general settings"})}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"kubectl"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:l?"kubectl is pointing at this cluster.":"kubectl is not pointing at this cluster."}),e.jsxs("div",{children:[e.jsx($,{variant:"outline",onClick:()=>void G(),disabled:!s||y,title:s?void 0:"Start the cluster first.",children:y?"Switching…":"Point kubectl at this cluster"}),!s&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"Start the cluster first."})]}),m&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:m})})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Diagnostics"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx($,{variant:"outline",onClick:()=>void Q(Ze),children:"Reveal config file"}),e.jsx($,{variant:"outline",onClick:()=>void Q(Be),children:"View logs"})]}),Y&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:Y})})]})]})}function nt({apiBase:t,running:s}){const[l,i]=r.useState(null),[o,a]=r.useState(null),[n,u]=r.useState(""),[g,c]=r.useState(""),[x,w]=r.useState(""),[T,h]=r.useState(""),[P,R]=r.useState(!1),[D,d]=r.useState(null),[f,S]=r.useState(!1),[E,y]=r.useState([]);r.useEffect(()=>{let m=!1;return te(t).then(O=>{m||(i(O),u(O.model.provider),w(O.model.name))}).catch(O=>{m||a(O.message)}),()=>{m=!0}},[t]);async function A(){d(null),y([]),S(!0);try{const m=await te(t),O=await Ae(t,{model:{provider:n,apiKey:g,name:x},ngrokAuthToken:P?"":T,clearNgrok:P,healthNotifications:m.healthNotifications});y(O.outcomes),c(""),h(""),R(!1);const Y=await te(t);i(Y)}catch(m){d(m instanceof ie||m instanceof Error?m.message:String(m))}finally{S(!1)}}return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx("h2",{className:"text-base font-semibold",children:"Model settings"}),o&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:o})}),l&&e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx(v,{htmlFor:"model-provider",children:"Provider"}),e.jsxs(se,{value:n,onValueChange:u,children:[e.jsx(le,{id:"model-provider",children:e.jsx(ae,{})}),e.jsx(ne,{children:l.providers.map(m=>e.jsx(J,{value:m,children:m},m))})]})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx(v,{htmlFor:"model-api-key",children:"API key"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:l.model.apiKeyMasked?l.model.apiKeyMasked:"Not set"}),e.jsx(V,{id:"model-api-key",type:"password",placeholder:"unchanged",value:g,onChange:m=>c(m.target.value)})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx(v,{htmlFor:"model-name",children:"Model name"}),e.jsx(V,{id:"model-name",value:x,onChange:m=>w(m.target.value)}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["Leave blank to use ",n||"the selected provider","'s recommended default model."]})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"ngrok"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:l.ngrokConfigured?"Configured":"Not configured"}),e.jsx(v,{htmlFor:"model-ngrok-token",children:"ngrok auth token"}),e.jsx(V,{id:"model-ngrok-token",type:"password",placeholder:"unchanged",value:T,onChange:m=>h(m.target.value),disabled:P}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{id:"model-clear-ngrok",type:"checkbox",checked:P,onChange:m=>R(m.target.checked),className:"h-4 w-4 rounded border-border"}),e.jsx(v,{htmlFor:"model-clear-ngrok",children:"Clear ngrok token"})]})]}),!s&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"Changes apply at the next start — the cluster isn't running."}),D&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:D})}),e.jsx(Le,{outcomes:E}),e.jsx("div",{children:e.jsx($,{onClick:()=>void A(),disabled:f,children:f?"Saving…":"Save"})})]})]})}function de({result:t}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[t.errors.map((s,l)=>e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:s})},`err-${l}`)),t.violations.map((s,l)=>e.jsx(b,{children:e.jsxs(N,{className:"flex items-center gap-2",children:[e.jsx(Ce,{variant:s.fatal?"destructive":"secondary",children:s.reason}),e.jsx("span",{children:s.message})]})},`violation-${l}`)),t.effective!==void 0&&e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Effective settings (preview)"}),e.jsx("pre",{className:"max-h-64 overflow-auto rounded-md border border-border bg-muted/30 p-2 text-xs",children:JSON.stringify(t.effective,null,2)})]})]})}function Ie({drift:t,onTakeOwnership:s,busy:l,afterTakeOwnership:i}){return i?e.jsx(b,{children:e.jsxs(N,{className:"flex flex-col gap-2",children:[e.jsx("p",{children:"Drift remains even after taking full ownership at these paths — a defaulting webhook may be re-adding them:"}),e.jsx("ul",{className:"list-inside list-disc text-xs",children:t.map(o=>e.jsx("li",{children:o},o))})]})}):e.jsx(b,{children:e.jsxs(N,{className:"flex flex-col gap-2",children:[e.jsx("p",{children:"The saved spec differs from the cluster's at these paths — a different field manager may own them (SSA cannot remove a field it doesn't own by omission):"}),e.jsx("ul",{className:"list-inside list-disc text-xs",children:t.map(o=>e.jsx("li",{children:o},o))}),e.jsx("div",{children:e.jsx($,{size:"sm",onClick:s,disabled:l,children:"Take full ownership and re-apply"})})]})})}function $e({dirty:t,busy:s=!1,disabled:l=!1,note:i,onValidate:o,onSave:a,saveLabel:n}){const u=s||l;return e.jsx("div",{className:"flex shrink-0 flex-col gap-2 border-t border-border bg-background py-3",children:e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-x-4 gap-y-2",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx($,{variant:"outline",onClick:o,disabled:u,children:"Validate"}),e.jsx($,{onClick:a,disabled:u,children:n??"Save"}),t&&e.jsx("span",{role:"status",className:"text-xs text-muted-foreground",children:"Unsaved changes"})]}),i&&e.jsx("span",{className:"text-xs text-muted-foreground",children:i})]})})}function Re(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function F(t,s,l){if(s.length===0)throw new Error("updateSpec: path must not be empty");return Ve(t,s,l)}function ee(t,s){let l=t;for(const i of s){if(!Re(l))return;l=l[i]}return l}function Ve(t,s,l){const[i,...o]=s,a={...t};if(o.length===0)return l===void 0?delete a[i]:a[i]=l,a;const n=a[i],u=Re(n)?n:{};return a[i]=Ve(u,o,l),a}function it(t){return t===void 0?"unset":t?"true":"false"}function rt(t){if(t!=="unset")return t==="true"}function ot(t){return t===void 0?"unrestricted":t.length===0?"deny-all":"restricted"}function dt(t){return t.split(` +import{A as b,a as N,M as v,N as V,B as $,O as se,P as le,Q as ae,R as ne,U as J,j as he,k as ge,l as K,n as _,o as pe,p as I,D as Ge,t as Ue,u as Ke,v as Je,x as Ye,T as ye,g as we,h as Te,i as H,m as qe}from"./mount.Bxdfy1H2.js";import{r,j as e}from"./vendor.BHG48rjj.js";import{B as Ce}from"./badge.D9YA1CKv.js";class ie extends Error{constructor(s,l){super(s),this.status=l}}async function ce(t){if(!t.ok){const s=await t.text();let l=s.trim()||`request failed (${t.status})`;try{const i=JSON.parse(s);i.error&&(l=i.error)}catch{}throw new ie(l,t.status)}return await t.json()}async function ve(t){const s=await fetch(t,{method:"POST"});if(!s.ok){await ce(s);return}}const ue=async t=>ce(await fetch(t)),He=async(t,s)=>ce(await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})),Ee=async(t,s)=>ce(await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})),We=t=>ue(`${t}/state`),te=t=>ue(`${t}/config`),Ae=(t,s)=>Ee(`${t}/config`,s),Pe=t=>ue(`${t}/cluster/settings`),De=(t,s)=>He(`${t}/cluster/settings/validate`,s),Me=(t,s)=>Ee(`${t}/cluster/settings`,s),Qe=t=>ue(`${t}/cluster/install-info`);function Fe(t){if(!(t instanceof ie))return null;try{const s=JSON.parse(t.message);if(Array.isArray(s.errors))return s}catch{}return null}function Oe(t){if(!(t instanceof ie))return null;try{const s=JSON.parse(t.message);if(s.validation&&Array.isArray(s.validation.errors))return s}catch{}return null}const Xe=t=>ve(`${t}/kubectl/use`),Ze=t=>ve(`${t}/reveal/config`),Be=t=>ve(`${t}/reveal/logs`);function et(t){const[s,l]=r.useState(null),[i,o]=r.useState(null);return r.useEffect(()=>{let a=!1;We(t).then(u=>{a||(l(u),o(null))}).catch(u=>{a||o(u.message)});const n=new EventSource(`${t}/events`);return n.onmessage=u=>{if(!a)try{l(JSON.parse(u.data)),o(null)}catch{}},n.onerror=()=>{a||o("settings: live update stream stalled")},()=>{a=!0,n.close()}},[t]),{state:s,error:i}}const tt={applied:"Applied","next-start":"Applies at next start",failed:"Failed"},st={applied:"✓","next-start":"⏱",failed:"✕"},lt={model:"Model",password:"Password",ngrok:"ngrok",healthNotifications:"Health notifications"};function Le({outcomes:t}){return t.length===0?null:e.jsx("ul",{"aria-label":"Save results",className:"flex flex-col gap-1 text-sm",children:t.map(s=>e.jsxs("li",{className:"flex items-start gap-2",children:[e.jsx("span",{"aria-hidden":"true",children:st[s.outcome]??"•"}),e.jsxs("span",{children:[e.jsxs("span",{className:"font-medium",children:[lt[s.field]??s.field,":"]})," ",tt[s.outcome]??s.outcome,s.message&&e.jsxs("span",{className:"text-muted-foreground",children:[" — ",s.message]})]})]},s.field))})}const xe=8;function at({apiBase:t,running:s,kubectlCurrent:l}){const[i,o]=r.useState(null),[a,n]=r.useState(null),[u,g]=r.useState(!0),[c,x]=r.useState(""),[w,T]=r.useState(""),[h,P]=r.useState(""),[R,D]=r.useState(null),[d,f]=r.useState(!1),[S,E]=r.useState([]),[y,A]=r.useState(!1),[m,O]=r.useState(null),[Y,U]=r.useState(null);r.useEffect(()=>{let p=!1;return te(t).then(k=>{p||(o(k),g(k.healthNotifications))}).catch(k=>{p||n(k.message)}),()=>{p=!0}},[t]);async function q(){D(null),E([]);const p=w!==""||h!=="";if(p){if(w.lengthg(p.target.checked),className:"h-4 w-4 rounded border-border"}),e.jsx(v,{htmlFor:"general-health-notifications",children:"Notify on cluster health issues"})]})}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Admin password"}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"general-current-password",children:"Current password"}),e.jsx(V,{id:"general-current-password",type:"password",value:c,onChange:p=>x(p.target.value)})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"general-new-password",children:"New password"}),e.jsx(V,{id:"general-new-password",type:"password",value:w,onChange:p=>T(p.target.value)}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["Must be at least ",xe," characters."]})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"general-confirm-password",children:"Confirm new password"}),e.jsx(V,{id:"general-confirm-password",type:"password",value:h,onChange:p=>P(p.target.value)})]})]}),R&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:R})}),e.jsx(Le,{outcomes:S}),e.jsx("div",{children:e.jsx($,{onClick:()=>void q(),disabled:d||!i,children:d?"Saving…":"Save general settings"})}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"kubectl"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:l?"kubectl is pointing at this cluster.":"kubectl is not pointing at this cluster."}),e.jsxs("div",{children:[e.jsx($,{variant:"outline",onClick:()=>void G(),disabled:!s||y,title:s?void 0:"Start the cluster first.",children:y?"Switching…":"Point kubectl at this cluster"}),!s&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"Start the cluster first."})]}),m&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:m})})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Diagnostics"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx($,{variant:"outline",onClick:()=>void Q(Ze),children:"Reveal config file"}),e.jsx($,{variant:"outline",onClick:()=>void Q(Be),children:"View logs"})]}),Y&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:Y})})]})]})}function nt({apiBase:t,running:s}){const[l,i]=r.useState(null),[o,a]=r.useState(null),[n,u]=r.useState(""),[g,c]=r.useState(""),[x,w]=r.useState(""),[T,h]=r.useState(""),[P,R]=r.useState(!1),[D,d]=r.useState(null),[f,S]=r.useState(!1),[E,y]=r.useState([]);r.useEffect(()=>{let m=!1;return te(t).then(O=>{m||(i(O),u(O.model.provider),w(O.model.name))}).catch(O=>{m||a(O.message)}),()=>{m=!0}},[t]);async function A(){d(null),y([]),S(!0);try{const m=await te(t),O=await Ae(t,{model:{provider:n,apiKey:g,name:x},ngrokAuthToken:P?"":T,clearNgrok:P,healthNotifications:m.healthNotifications});y(O.outcomes),c(""),h(""),R(!1);const Y=await te(t);i(Y)}catch(m){d(m instanceof ie||m instanceof Error?m.message:String(m))}finally{S(!1)}}return e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx("h2",{className:"text-base font-semibold",children:"Model settings"}),o&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:o})}),l&&e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx(v,{htmlFor:"model-provider",children:"Provider"}),e.jsxs(se,{value:n,onValueChange:u,children:[e.jsx(le,{id:"model-provider",children:e.jsx(ae,{})}),e.jsx(ne,{children:l.providers.map(m=>e.jsx(J,{value:m,children:m},m))})]})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx(v,{htmlFor:"model-api-key",children:"API key"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:l.model.apiKeyMasked?l.model.apiKeyMasked:"Not set"}),e.jsx(V,{id:"model-api-key",type:"password",placeholder:"unchanged",value:g,onChange:m=>c(m.target.value)})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx(v,{htmlFor:"model-name",children:"Model name"}),e.jsx(V,{id:"model-name",value:x,onChange:m=>w(m.target.value)}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["Leave blank to use ",n||"the selected provider","'s recommended default model."]})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"ngrok"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:l.ngrokConfigured?"Configured":"Not configured"}),e.jsx(v,{htmlFor:"model-ngrok-token",children:"ngrok auth token"}),e.jsx(V,{id:"model-ngrok-token",type:"password",placeholder:"unchanged",value:T,onChange:m=>h(m.target.value),disabled:P}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{id:"model-clear-ngrok",type:"checkbox",checked:P,onChange:m=>R(m.target.checked),className:"h-4 w-4 rounded border-border"}),e.jsx(v,{htmlFor:"model-clear-ngrok",children:"Clear ngrok token"})]})]}),!s&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"Changes apply at the next start — the cluster isn't running."}),D&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:D})}),e.jsx(Le,{outcomes:E}),e.jsx("div",{children:e.jsx($,{onClick:()=>void A(),disabled:f,children:f?"Saving…":"Save"})})]})]})}function de({result:t}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[t.errors.map((s,l)=>e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:s})},`err-${l}`)),t.violations.map((s,l)=>e.jsx(b,{children:e.jsxs(N,{className:"flex items-center gap-2",children:[e.jsx(Ce,{variant:s.fatal?"destructive":"secondary",children:s.reason}),e.jsx("span",{children:s.message})]})},`violation-${l}`)),t.effective!==void 0&&e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Effective settings (preview)"}),e.jsx("pre",{className:"max-h-64 overflow-auto rounded-md border border-border bg-muted/30 p-2 text-xs",children:JSON.stringify(t.effective,null,2)})]})]})}function Ie({drift:t,onTakeOwnership:s,busy:l,afterTakeOwnership:i}){return i?e.jsx(b,{children:e.jsxs(N,{className:"flex flex-col gap-2",children:[e.jsx("p",{children:"Drift remains even after taking full ownership at these paths — a defaulting webhook may be re-adding them:"}),e.jsx("ul",{className:"list-inside list-disc text-xs",children:t.map(o=>e.jsx("li",{children:o},o))})]})}):e.jsx(b,{children:e.jsxs(N,{className:"flex flex-col gap-2",children:[e.jsx("p",{children:"The saved spec differs from the cluster's at these paths — a different field manager may own them (SSA cannot remove a field it doesn't own by omission):"}),e.jsx("ul",{className:"list-inside list-disc text-xs",children:t.map(o=>e.jsx("li",{children:o},o))}),e.jsx("div",{children:e.jsx($,{size:"sm",onClick:s,disabled:l,children:"Take full ownership and re-apply"})})]})})}function $e({dirty:t,busy:s=!1,disabled:l=!1,note:i,onValidate:o,onSave:a,saveLabel:n}){const u=s||l;return e.jsx("div",{className:"flex shrink-0 flex-col gap-2 border-t border-border bg-background py-3",children:e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-x-4 gap-y-2",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx($,{variant:"outline",onClick:o,disabled:u,children:"Validate"}),e.jsx($,{onClick:a,disabled:u,children:n??"Save"}),t&&e.jsx("span",{role:"status",className:"text-xs text-muted-foreground",children:"Unsaved changes"})]}),i&&e.jsx("span",{className:"text-xs text-muted-foreground",children:i})]})})}function Re(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function F(t,s,l){if(s.length===0)throw new Error("updateSpec: path must not be empty");return Ve(t,s,l)}function ee(t,s){let l=t;for(const i of s){if(!Re(l))return;l=l[i]}return l}function Ve(t,s,l){const[i,...o]=s,a={...t};if(o.length===0)return l===void 0?delete a[i]:a[i]=l,a;const n=a[i],u=Re(n)?n:{};return a[i]=Ve(u,o,l),a}function it(t){return t===void 0?"unset":t?"true":"false"}function rt(t){if(t!=="unset")return t==="true"}function ot(t){return t===void 0?"unrestricted":t.length===0?"deny-all":"restricted"}function dt(t){return t.split(` `).map(s=>s.trim()).filter(s=>s.length>0)}function ct(t){return(t??[]).join(` `)}function be(t){return{namespace:"agentprimitives-system",name:`model-default-token-${t}`,key:"token"}}function ut(t,s){const l=t.modelCatalog??[],i=l.findIndex(n=>n.name===s.name),o=l.slice();i>=0?o[i]=s:o.push(s);const a=s.default?o.map(n=>n.name===s.name?n:{...n,default:!1}):o;return F(t,["modelCatalog"],a)}function ze(t,s){const l=t.modelCatalog??[];return F(t,["modelCatalog"],l.filter(i=>i.name!==s))}function xt(t,s,l){return ut(ze(t,s),l)}function mt(t){const s=(t==null?void 0:t.rules)??[],l=(t==null?void 0:t.bypass)??[];if(s.length===0&&l.length===0)return"—";let o=s.map(a=>`${a.kind}(min=${a.minStrength||"any"},mode=${a.mode||"approve"})`).join(", ");return l.length>0&&(o+=` · ${l.length} bypass`),o}function ft(t){if(!t)return"—";const s=[];return t.maxFailureThreshold!==void 0&&s.push(`maxFailureThreshold=${t.maxFailureThreshold}`),t.minInitialCoolOff!==void 0&&s.push(`minInitialCoolOff=${t.minInitialCoolOff}`),t.minAction!==void 0&&s.push(`minAction=${t.minAction}`),t.maxCallsPerTurn!==void 0&&s.push(`maxCallsPerTurn=${t.maxCallsPerTurn}`),t.maxCalls!==void 0&&t.window!==void 0&&s.push(`maxCalls=${t.maxCalls}/${t.window}`),t.maxEgressBytes!==void 0&&s.push(`maxEgressBytes=${t.maxEgressBytes}`),t.maxIngressBytes!==void 0&&s.push(`maxIngressBytes=${t.maxIngressBytes}`),s.length===0?"—":s.join(", ")}function ht(t){return t===void 0?"not set":t.length===0?"(none)":t.map(s=>s.id).join(", ")}function gt(t){return t===void 0?"no constraint":t.length===0?"deny-all":t.map(s=>s.name).join(", ")}const pt=["anthropic","openai","openrouter"];function Ne(){return{name:"",provider:"anthropic",isDefault:!1,inputPerMTok:0,outputPerMTok:0,tokenInput:""}}function jt(t){return{name:t.name,provider:t.provider??"anthropic",isDefault:!!t.default,inputPerMTok:t.inputPerMTok??0,outputPerMTok:t.outputPerMTok??0,tokenInput:""}}function ke(t){return t===void 0||t===0?"—":String(t)}function vt({spec:t,onChange:s,tokens:l,onTokensChange:i}){const o=t.modelCatalog??[],[a,n]=r.useState(!1),[u,g]=r.useState(null),[c,x]=r.useState(Ne()),[w,T]=r.useState(null);function h(){g(null),x(Ne()),n(!0)}function P(d){g(d.name),x(jt(d)),n(!0)}function R(){const d=c.name.trim();if(!d)return;const f=u?o.find(A=>A.name===u):void 0,S=(f==null?void 0:f.tokenRef)??be(d),E={...f,name:d,provider:c.provider,default:c.isDefault,inputPerMTok:c.inputPerMTok,outputPerMTok:c.outputPerMTok,tokenRef:S};s(xt(t,u??d,E));const y=c.tokenInput.trim();if(y!==""){const A=l.filter(m=>!(m.namespace===S.namespace&&m.name===S.name&&m.key===S.key));i([...A,{...S,value:y}])}n(!1)}function D(d){s(ze(t,d));const f=o.find(y=>y.name===d),S=(f==null?void 0:f.tokenRef)??be(d),E=l.filter(y=>!(y.namespace===S.namespace&&y.name===S.name&&y.key===S.key));E.length!==l.length&&i(E),T(null)}return e.jsxs("section",{className:"flex flex-col gap-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Model catalog"}),e.jsx($,{size:"sm",onClick:h,children:"Add model"})]}),e.jsxs(he,{children:[e.jsx(ge,{children:e.jsxs(K,{children:[e.jsx(_,{children:"Name"}),e.jsx(_,{children:"Provider"}),e.jsx(_,{children:"Default"}),e.jsx(_,{children:"Input $/MTok"}),e.jsx(_,{children:"Output $/MTok"}),e.jsx(_,{children:"Actions"})]})}),e.jsxs(pe,{children:[o.length===0&&e.jsx(K,{children:e.jsx(I,{colSpan:6,className:"text-muted-foreground",children:"No catalog entries."})}),o.map(d=>e.jsxs(K,{children:[e.jsx(I,{children:d.name}),e.jsx(I,{children:d.provider||"—"}),e.jsx(I,{children:d.default?"Yes":"No"}),e.jsx(I,{children:ke(d.inputPerMTok)}),e.jsx(I,{children:ke(d.outputPerMTok)}),e.jsx(I,{children:w===d.name?e.jsxs("div",{className:"flex gap-2",children:[e.jsx($,{variant:"destructive",size:"sm",onClick:()=>D(d.name),children:"Confirm delete"}),e.jsx($,{variant:"outline",size:"sm",onClick:()=>T(null),children:"Cancel"})]}):e.jsxs("div",{className:"flex gap-2",children:[e.jsx($,{variant:"outline",size:"sm",onClick:()=>P(d),children:"Edit"}),e.jsx($,{variant:"outline",size:"sm",onClick:()=>T(d.name),children:"Delete"})]})})]},d.name))]})]}),e.jsx(Ge,{open:a,onOpenChange:n,children:e.jsxs(Ue,{children:[e.jsx(Ke,{children:e.jsx(Je,{children:u?"Edit model":"Add model"})}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"catalog-name",children:"Name"}),e.jsx(V,{id:"catalog-name",value:c.name,onChange:d=>x({...c,name:d.target.value})})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"catalog-provider",children:"Provider"}),e.jsxs(se,{value:c.provider,onValueChange:d=>x({...c,provider:d}),children:[e.jsx(le,{id:"catalog-provider",children:e.jsx(ae,{})}),e.jsx(ne,{children:pt.map(d=>e.jsx(J,{value:d,children:d},d))})]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("input",{id:"catalog-default",type:"checkbox",checked:c.isDefault,onChange:d=>x({...c,isDefault:d.target.checked}),className:"h-4 w-4 rounded border-border"}),e.jsx(v,{htmlFor:"catalog-default",children:"Default model"})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"catalog-input-price",children:"Input $/MTok"}),e.jsx(V,{id:"catalog-input-price",type:"number",value:c.inputPerMTok,onChange:d=>x({...c,inputPerMTok:Number(d.target.value)||0})})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"catalog-output-price",children:"Output $/MTok"}),e.jsx(V,{id:"catalog-output-price",type:"number",value:c.outputPerMTok,onChange:d=>x({...c,outputPerMTok:Number(d.target.value)||0})})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"catalog-token",children:"API token"}),e.jsx(V,{id:"catalog-token",type:"password",placeholder:"unchanged",value:c.tokenInput,onChange:d=>x({...c,tokenInput:d.target.value})}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"Leave blank to keep the existing token for this entry."})]})]}),e.jsx(Ye,{children:e.jsx($,{onClick:R,disabled:c.name.trim()==="",children:"Save"})})]})})]})}function W({spec:t,onChange:s,path:l,id:i,label:o,hint:a}){const n=ee(t,l),u=typeof n=="string"?n:"";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:i,children:o}),e.jsx(V,{id:i,placeholder:"e.g. 30m",value:u,onChange:g=>{const c=g.target.value;s(F(t,l,c.trim()===""?void 0:c))}}),a&&e.jsx("p",{className:"text-xs text-muted-foreground",children:a})]})}function B({spec:t,onChange:s,path:l,id:i,label:o,hint:a}){const n=ee(t,l),u=typeof n=="number"?n:"";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:i,children:o}),e.jsx(V,{id:i,type:"number",value:u,onChange:g=>{const c=g.target.value;if(c.trim()===""){s(F(t,l,void 0));return}const x=Number(c);Number.isNaN(x)||s(F(t,l,x))}}),a&&e.jsx("p",{className:"text-xs text-muted-foreground",children:a})]})}function Se({spec:t,onChange:s,path:l,id:i,label:o,hint:a}){const n=ee(t,l),u=typeof n=="string"?n:"";return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:i,children:o}),e.jsx(V,{id:i,value:u,onChange:g=>{const c=g.target.value;s(F(t,l,c.trim()===""?void 0:c))}}),a&&e.jsx("p",{className:"text-xs text-muted-foreground",children:a})]})}function oe({spec:t,onChange:s,path:l,id:i,label:o,hint:a}){const n=ee(t,l),u=it(typeof n=="boolean"?n:void 0);return e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:i,children:o}),e.jsxs(se,{value:u,onValueChange:g=>s(F(t,l,rt(g))),children:[e.jsx(le,{id:i,children:e.jsx(ae,{})}),e.jsxs(ne,{children:[e.jsx(J,{value:"unset",children:"Unset (no requirement)"}),e.jsx(J,{value:"true",children:"True"}),e.jsx(J,{value:"false",children:"False"})]})]}),a&&e.jsx("p",{className:"text-xs text-muted-foreground",children:a})]})}function je({id:t,initial:s,onCommit:l}){const[i,o]=r.useState(()=>ct(s));return e.jsx("textarea",{id:t,rows:4,className:"flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",value:i,onChange:a=>{o(a.target.value),l(dt(a.target.value))}})}function re({label:t,value:s}){return e.jsxs("div",{className:"flex flex-col gap-0.5 rounded-md border border-border p-3",children:[e.jsx("span",{className:"text-xs font-medium text-muted-foreground",children:t}),e.jsx("span",{className:"text-sm",children:s}),e.jsx("span",{className:"text-xs text-muted-foreground",children:"Edit in Advanced tab"})]})}const bt=[{value:"unset",label:"Unset (no floor)"},{value:"disabled",label:"disabled"},{value:"logging",label:"logging"},{value:"enforcing",label:"enforcing"}];function me({spec:t,onChange:s,path:l,fieldLabel:i,idPrefix:o}){const a=ee(t,l),n=ot(a),u=n!=="unrestricted";return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx(v,{children:i}),e.jsxs("div",{className:"flex flex-col gap-1 text-sm",children:[e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"radio",name:o,checked:!u,onChange:()=>s(F(t,l,void 0))}),i,": Unrestricted"]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"radio",name:o,checked:u,onChange:()=>s(F(t,l,a??[]))}),i,": Restrict to list"]})]}),u&&e.jsxs(e.Fragment,{children:[e.jsx(je,{id:`${o}-list`,initial:a??[],onCommit:g=>s(F(t,l,g))}),n==="deny-all"&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:"Empty list — deny-all: no entries are permitted."})})]})]})}function Nt({spec:t,onChange:s}){var i,o,a,n,u,g,c;const l=((i=t.limits)==null?void 0:i.minPlanGateMode)||"unset";return e.jsxs("section",{className:"flex flex-col gap-6",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Limits (ceilings)"}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Budget ceiling"}),e.jsx(B,{spec:t,onChange:s,path:["limits","budget","maxTurns"],id:"limits-budget-max-turns",label:"Ceiling: max turns"}),e.jsx(B,{spec:t,onChange:s,path:["limits","budget","maxTokens"],id:"limits-budget-max-tokens",label:"Ceiling: max tokens"}),e.jsx(W,{spec:t,onChange:s,path:["limits","budget","maxDuration"],id:"limits-budget-max-duration",label:"Ceiling: max duration"}),e.jsx(W,{spec:t,onChange:s,path:["limits","budget","sessionExpiration"],id:"limits-budget-session-expiration",label:"Ceiling: session expiration"}),e.jsx(B,{spec:t,onChange:s,path:["limits","budget","maxDelegatedAgents"],id:"limits-budget-max-delegated-agents",label:"Ceiling: max delegated agents"})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Authz ceiling"}),e.jsx(W,{spec:t,onChange:s,path:["limits","authz","maxApprovalTimeout"],id:"limits-authz-max-approval-timeout",label:"Ceiling: max approval timeout"}),e.jsx(W,{spec:t,onChange:s,path:["limits","authz","maxInformationLeakageApprovalTTL"],id:"limits-authz-max-leakage-ttl",label:"Ceiling: max information-leakage approval TTL"})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"limits-min-plan-gate-mode",children:"Minimum plan-gate mode"}),e.jsxs(se,{value:l,onValueChange:x=>s(F(t,["limits","minPlanGateMode"],x==="unset"?void 0:x)),children:[e.jsx(le,{id:"limits-min-plan-gate-mode",children:e.jsx(ae,{})}),e.jsx(ne,{children:bt.map(x=>e.jsx(J,{value:x.value,children:x.label},x.value))})]})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"limits-denied-models",children:"Denied models"}),e.jsx(je,{id:"limits-denied-models",initial:((o=t.limits)==null?void 0:o.deniedModels)??[],onCommit:x=>s(F(t,["limits","deniedModels"],x.length>0?x:void 0))})]}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"limits-denied-skills",children:"Denied skills"}),e.jsx(je,{id:"limits-denied-skills",initial:((a=t.limits)==null?void 0:a.deniedSkills)??[],onCommit:x=>s(F(t,["limits","deniedSkills"],x.length>0?x:void 0))})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx(oe,{spec:t,onChange:s,path:["limits","allowModelOverride"],id:"limits-allow-model-override",label:"Allow model override",hint:"Top-down grant: nil/false means catalog-only."}),e.jsx(oe,{spec:t,onChange:s,path:["limits","nativeFileHandling"],id:"limits-native-file-handling",label:"Native file handling"}),e.jsx(oe,{spec:t,onChange:s,path:["limits","requireSubagentDigestPins"],id:"limits-require-subagent-digest-pins",label:"Require subagent digest pins"})]}),e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsx(me,{spec:t,onChange:s,path:["limits","allowedToolkits"],fieldLabel:"Allowed toolkits",idPrefix:"limits-allowed-toolkits"}),e.jsx(me,{spec:t,onChange:s,path:["limits","allowedSkills"],fieldLabel:"Allowed skills",idPrefix:"limits-allowed-skills"}),e.jsx(me,{spec:t,onChange:s,path:["limits","allowedSandboxKinds"],fieldLabel:"Allowed sandbox kinds",idPrefix:"limits-allowed-sandbox-kinds"})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-3 sm:grid-cols-2",children:[e.jsx(re,{label:"Pinning",value:mt((n=t.limits)==null?void 0:n.pinning)}),e.jsx(re,{label:"Tool guard ceiling",value:ft((u=t.limits)==null?void 0:u.toolGuard)}),e.jsx(re,{label:"Content inspectors",value:ht((g=t.limits)==null?void 0:g.contentInspectors)}),e.jsx(re,{label:"Allowed MCP servers",value:gt((c=t.limits)==null?void 0:c.allowedMCPServers)})]})]})}const kt=["anthropic","openai","openrouter"],fe="__none__";function St({spec:t,onChange:s}){var g,c,x,w,T;const l=(c=(g=t.defaults)==null?void 0:g.model)==null?void 0:c.provider,i=((w=(x=t.defaults)==null?void 0:x.model)==null?void 0:w.fromCatalog)||fe,o=(t.modelCatalog??[]).map(h=>h.name),a=(T=t.defaults)==null?void 0:T.reportSessionCost,n=ee(t,["defaults","authz","planGate","mode"]),u=ee(t,["defaults","authz","metaagent","trigger"]);return e.jsxs("section",{className:"flex flex-col gap-6",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Defaults (fallbacks)"}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Default model"}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"defaults-model-provider",children:"Provider"}),e.jsxs(se,{value:l,onValueChange:h=>s(F(t,["defaults","model","provider"],h)),children:[e.jsx(le,{id:"defaults-model-provider",children:e.jsx(ae,{placeholder:"Select a provider…"})}),e.jsx(ne,{children:kt.map(h=>e.jsx(J,{value:h,children:h},h))})]})]}),e.jsx(Se,{spec:t,onChange:s,path:["defaults","model","name"],id:"defaults-model-name",label:"Model name"}),e.jsxs("div",{className:"flex flex-col gap-1",children:[e.jsx(v,{htmlFor:"defaults-model-from-catalog",children:"From catalog"}),e.jsxs(se,{value:i,onValueChange:h=>s(F(t,["defaults","model","fromCatalog"],h===fe?void 0:h)),children:[e.jsx(le,{id:"defaults-model-from-catalog",children:e.jsx(ae,{})}),e.jsxs(ne,{children:[e.jsx(J,{value:fe,children:"(use provider/name above)"}),o.map(h=>e.jsx(J,{value:h,children:h},h))]})]})]})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Default budget"}),e.jsx(B,{spec:t,onChange:s,path:["defaults","budget","maxTurns"],id:"defaults-budget-max-turns",label:"Default: max turns"}),e.jsx(B,{spec:t,onChange:s,path:["defaults","budget","maxTokens"],id:"defaults-budget-max-tokens",label:"Default: max tokens"}),e.jsx(W,{spec:t,onChange:s,path:["defaults","budget","maxDuration"],id:"defaults-budget-max-duration",label:"Default: max duration"}),e.jsx(W,{spec:t,onChange:s,path:["defaults","budget","sessionExpiration"],id:"defaults-budget-session-expiration",label:"Default: session expiration"}),e.jsx(B,{spec:t,onChange:s,path:["defaults","budget","maxDelegatedAgents"],id:"defaults-budget-max-delegated-agents",label:"Default: max delegated agents"})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx("h4",{className:"text-xs font-semibold uppercase text-muted-foreground",children:"Default authz"}),e.jsx(W,{spec:t,onChange:s,path:["defaults","authz","approvalTimeout"],id:"defaults-authz-approval-timeout",label:"Default: approval timeout"}),e.jsx(W,{spec:t,onChange:s,path:["defaults","authz","informationLeakageApprovalTTL"],id:"defaults-authz-leakage-ttl",label:"Default: information-leakage approval TTL"}),e.jsx(B,{spec:t,onChange:s,path:["defaults","authz","scopeMaxLlmLatencyMs"],id:"defaults-authz-scope-max-llm-latency-ms",label:"Default: scope max LLM latency (ms)"}),e.jsxs("div",{className:"flex flex-col gap-0.5 text-xs text-muted-foreground",children:[e.jsxs("span",{children:["Plan-gate mode: ",typeof n=="string"&&n?n:"not set"]}),e.jsxs("span",{children:["Metaagent trigger: ",typeof u=="string"&&u?u:"not set"]}),e.jsx("span",{children:"Edit in Advanced tab"})]})]}),e.jsxs("div",{className:"flex flex-col gap-3",children:[e.jsx(oe,{spec:t,onChange:s,path:["defaults","reportSessionCost"],id:"defaults-report-session-cost",label:"Report session cost",hint:"Unset means on: the end-of-session cost estimate is shown by default."}),e.jsx(Se,{spec:t,onChange:s,path:["defaults","sandbox","kind"],id:"defaults-sandbox-kind",label:"Sandbox kind",hint:'Defaults to "pod" when unset.'})]}),a===void 0&&e.jsx("p",{className:"text-xs text-muted-foreground","data-testid":"report-session-cost-default-note",children:"No value set — cost reporting is ON by default."})]})}const yt=[{id:"catalog",label:"Catalog"},{id:"limits",label:"Limits"},{id:"defaults",label:"Defaults"}];function wt({apiBase:t,running:s}){const[l,i]=r.useState(null),[o,a]=r.useState(null),[n,u]=r.useState({}),[g,c]=r.useState(!1),[x,w]=r.useState([]),[T,h]=r.useState(null),[P,R]=r.useState(!1),[D,d]=r.useState(null),[f,S]=r.useState(null),[E,y]=r.useState(!1),[A,m]=r.useState(null),[O,Y]=r.useState(!1);r.useEffect(()=>{let j=!1;return Pe(t).then(M=>{j||(i(M),u(M.spec??{}),c(!1))}).catch(M=>{j||a(M.message)}),()=>{j=!0}},[t]);async function U(){d(null),R(!0);try{const j=await De(t,{spec:n});h(j)}catch(j){const M=Fe(j);M?h(M):d(j instanceof Error?j.message:String(j))}finally{R(!1)}}async function q(j){m(null),y(!0),Y(j);try{const Z=await Me(t,j?{spec:n,takeOwnership:!0}:{spec:n,tokens:x});S(Z),Z.applied&&(c(!1),j||w([]))}catch(M){const Z=Oe(M);Z?S(Z):m(M instanceof Error?M.message:String(M))}finally{y(!1)}}const G=()=>q(!1),Q=()=>q(!0);function p(j){u(j),c(!0)}function k(j){w(j),c(!0)}const X=g||x.length>0;return e.jsxs("div",{className:"flex h-full flex-col gap-6",children:[e.jsx("h2",{className:"text-base font-semibold",children:"Cluster settings"}),o&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:o})}),(l==null?void 0:l.clusterDown)&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:"The desktop cluster isn't running. Start it from the General tab to edit cluster settings."})}),l&&!l.clusterDown&&e.jsxs(e.Fragment,{children:[!s&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"The desktop isn't running — Validate still works, but Save will fail until it's back up."}),e.jsxs(ye,{defaultValue:"catalog",orientation:"vertical",className:"flex min-h-0 flex-1 gap-6",children:[e.jsx(we,{className:"h-auto w-40 shrink-0 flex-col items-stretch justify-start gap-1 bg-transparent p-0",children:yt.map(j=>e.jsx(Te,{value:j.id,className:"justify-start data-[state=active]:bg-muted",children:j.label},j.id))}),e.jsxs("div",{className:"min-w-0 flex-1 overflow-y-auto",children:[e.jsx(H,{value:"catalog",className:"mt-0",children:e.jsx(vt,{spec:n,onChange:p,tokens:x,onTokensChange:k})}),e.jsx(H,{value:"limits",className:"mt-0",children:e.jsx(Nt,{spec:n,onChange:p})}),e.jsx(H,{value:"defaults",className:"mt-0",children:e.jsx(St,{spec:n,onChange:p})})]})]}),(D||T||A||f)&&e.jsxs("div",{className:"flex max-h-48 shrink-0 flex-col gap-2 overflow-y-auto",children:[D&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:D})}),T&&e.jsx(de,{result:T}),A&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:A})}),f&&e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("p",{className:"text-sm",children:f.applied?"Applied.":"Not applied — see validation below."}),e.jsx(de,{result:f.validation}),f.drift&&f.drift.length>0&&e.jsx(Ie,{drift:f.drift,onTakeOwnership:()=>void Q(),busy:E,afterTakeOwnership:O})]})]}),e.jsx($e,{dirty:X,busy:P||E,onValidate:()=>void U(),onSave:()=>void G()})]})]})}const Tt=1e4;function Ct({apiBase:t,running:s}){const[l,i]=r.useState(null),[o,a]=r.useState(null),[n,u]=r.useState(""),[g,c]=r.useState(""),[x,w]=r.useState(null),[T,h]=r.useState(!1),[P,R]=r.useState(null),[D,d]=r.useState(null),[f,S]=r.useState(null),[E,y]=r.useState(!1),[A,m]=r.useState(null),[O,Y]=r.useState(!1),[U,q]=r.useState(!1),G=r.useRef(null);r.useEffect(()=>()=>{G.current&&clearTimeout(G.current)},[]),r.useEffect(()=>{let L=!1;return Pe(t).then(C=>{if(L)return;i(C);const z=C.yaml??"";u(z),c(z)}).catch(C=>{L||a(C.message)}),()=>{L=!0}},[t]);function Q(){G.current&&(clearTimeout(G.current),G.current=null),q(!1)}function p(L){u(L),U&&Q()}async function k(){R(null),h(!0);const L=n;try{const C=await De(t,{yaml:L});w(C),d(L)}catch(C){const z=Fe(C);z?(w(z),d(L)):R(C instanceof Error?C.message:String(C))}finally{h(!1)}}async function X(L){m(null),y(!0),Y(L);try{const z=await Me(t,L?{yaml:n,takeOwnership:!0}:{yaml:n});S(z),z.applied&&c(n)}catch(C){const z=Oe(C);z?S(z):m(C instanceof Error?C.message:String(C))}finally{y(!1)}}const j=()=>X(!0),M=n!==g;function Z(){if(D===n){X(!1);return}if(U){Q(),X(!1);return}q(!0),G.current=setTimeout(()=>q(!1),Tt)}const _e=E?"Applying…":U?"Apply without validating?":"Apply";return e.jsxs("div",{className:"flex h-full flex-col gap-6",children:[e.jsx("h2",{className:"text-base font-semibold",children:"Advanced (raw YAML)"}),o&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:o})}),(l==null?void 0:l.clusterDown)&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:"The desktop cluster isn't running. Start it from the General tab to edit cluster settings."})}),l&&!l.clusterDown&&e.jsxs(e.Fragment,{children:[!s&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"The desktop isn't running — Validate still works, but Apply will fail until it's back up."}),e.jsx("textarea",{"aria-label":"cluster settings yaml",spellCheck:!1,className:"min-h-0 w-full flex-1 rounded-md border border-border bg-background p-3 font-mono text-xs",value:n,onChange:L=>p(L.target.value)}),U&&e.jsx("p",{className:"text-xs text-muted-foreground",children:"This text hasn't been validated. Click Apply again within 10 seconds to apply it anyway."}),(P||x||A||f)&&e.jsxs("div",{className:"flex max-h-48 shrink-0 flex-col gap-2 overflow-y-auto",children:[P&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:P})}),x&&e.jsx(de,{result:x}),A&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:A})}),f&&e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsx("p",{className:"text-sm",children:f.applied?"Applied.":"Not applied — see validation below."}),e.jsx(de,{result:f.validation}),f.drift&&f.drift.length>0&&e.jsx(Ie,{drift:f.drift,onTakeOwnership:()=>void j(),busy:E,afterTakeOwnership:O})]})]}),e.jsx($e,{dirty:M,busy:T||E,onValidate:()=>void k(),onSave:Z,saveLabel:_e})]})]})}function Et({apiBase:t}){const[s,l]=r.useState(null),[i,o]=r.useState(null);return r.useEffect(()=>{let a=!1;return Qe(t).then(n=>{a||l(n)}).catch(n=>{a||o(n.message)}),()=>{a=!0}},[t]),e.jsxs("div",{className:"flex flex-col gap-6",children:[e.jsx("h2",{className:"text-base font-semibold",children:"About"}),i&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:i})}),(s==null?void 0:s.clusterDown)&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:"The desktop cluster isn't running. Start it from the General tab to see install info."})}),s&&!s.clusterDown&&e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"flex items-center gap-2","data-testid":"about-cluster-kind",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Cluster kind"}),e.jsx(Ce,{variant:"secondary",children:s.clusterKind})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Components"}),e.jsxs(he,{"data-testid":"about-components-table",children:[e.jsx(ge,{children:e.jsxs(K,{children:[e.jsx(_,{children:"Name"}),e.jsx(_,{children:"Image"}),e.jsx(_,{children:"Ready"})]})}),e.jsxs(pe,{children:[s.components.length===0&&e.jsx(K,{children:e.jsx(I,{colSpan:3,className:"text-muted-foreground",children:"No components reported."})}),s.components.map(a=>e.jsxs(K,{children:[e.jsx(I,{children:a.name}),e.jsx(I,{className:"font-mono text-xs",children:a.image}),e.jsx(I,{children:a.ready})]},a.name))]})]})]}),e.jsxs("section",{className:"flex flex-col gap-2",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Operator environment"}),e.jsxs(he,{"data-testid":"about-env-table",children:[e.jsx(ge,{children:e.jsxs(K,{children:[e.jsx(_,{children:"Name"}),e.jsx(_,{children:"Value"})]})}),e.jsxs(pe,{children:[s.operatorEnv.length===0&&e.jsx(K,{children:e.jsx(I,{colSpan:2,className:"text-muted-foreground",children:"No environment variables reported."})}),s.operatorEnv.map(a=>e.jsxs(K,{children:[e.jsx(I,{className:"font-mono text-xs",children:a.name}),e.jsx(I,{className:"font-mono text-xs",children:a.value})]},a.name))]})]})]}),e.jsxs("section",{className:"flex flex-col gap-1","data-testid":"about-node",children:[e.jsx("h3",{className:"text-sm font-medium",children:"Node"}),s.node?e.jsxs("p",{className:"text-sm text-muted-foreground",children:[s.node.name," — kubelet ",s.node.kubeletVersion," —"," ",s.node.ready?"Ready":"Not ready"]}):e.jsx("p",{className:"text-sm text-muted-foreground",children:"No node reported."})]}),e.jsxs("footer",{className:"text-xs text-muted-foreground","data-testid":"about-app-version",children:["oap ",s.appVersion]})]})]})}const At=[{id:"general",label:"General"},{id:"model",label:"Model"},{id:"cluster",label:"Cluster"},{id:"advanced",label:"Advanced"},{id:"about",label:"About"}];function Pt({apiBase:t}){const{state:s,error:l}=et(t);return e.jsxs("div",{className:"mx-auto flex h-screen max-w-3xl flex-col gap-4 p-6",children:[e.jsxs("header",{className:"flex items-center justify-between gap-4",children:[e.jsx("h1",{className:"text-lg font-semibold",children:"Desktop settings"}),e.jsx(Dt,{phase:s==null?void 0:s.phase,step:s==null?void 0:s.step})]}),l&&e.jsx(b,{variant:"destructive",children:e.jsx(N,{children:l})}),e.jsxs(ye,{defaultValue:"general",className:"flex min-h-0 flex-1 flex-col",children:[e.jsx(we,{className:"shrink-0 self-start",children:At.map(i=>e.jsx(Te,{value:i.id,children:i.label},i.id))}),e.jsx(H,{value:"general",className:"min-h-0 flex-1 overflow-y-auto",children:e.jsx(at,{apiBase:t,running:(s==null?void 0:s.running)??!1,kubectlCurrent:(s==null?void 0:s.kubectlCurrent)??!1})}),e.jsx(H,{value:"model",className:"min-h-0 flex-1 overflow-y-auto",children:e.jsx(nt,{apiBase:t,running:(s==null?void 0:s.running)??!1})}),e.jsx(H,{value:"cluster",className:"min-h-0 flex-1",children:e.jsx(wt,{apiBase:t,running:(s==null?void 0:s.running)??!1})}),e.jsx(H,{value:"advanced",className:"min-h-0 flex-1",children:e.jsx(Ct,{apiBase:t,running:(s==null?void 0:s.running)??!1})}),e.jsx(H,{value:"about",className:"min-h-0 flex-1 overflow-y-auto",children:e.jsx(Et,{apiBase:t})})]})]})}function Dt({phase:t,step:s}){return t?e.jsxs("div",{className:"text-sm text-muted-foreground",children:[e.jsx("span",{className:"font-medium text-foreground",children:t}),t==="starting"&&s&&e.jsxs("span",{children:[" — ",s]})]}):null}qe(Pt); diff --git a/pkg/web/webui/webassets/dist/system.DT_ax8I5.js b/pkg/web/webui/webassets/dist/system.BiW1Avlo.js similarity index 77% rename from pkg/web/webui/webassets/dist/system.DT_ax8I5.js rename to pkg/web/webui/webassets/dist/system.BiW1Avlo.js index 96b34fad..01dd64a4 100644 --- a/pkg/web/webui/webassets/dist/system.DT_ax8I5.js +++ b/pkg/web/webui/webassets/dist/system.BiW1Avlo.js @@ -1 +1 @@ -import{C as c,b as l,c as x,d as m,e as u,B as f,m as h}from"./mount.BOEH1oNx.js";import{j as e,an as a,ae as g,ag as j,ay as p,az as C,c as N}from"./vendor.C5MRNvUt.js";const b={unauthorized:N,forbidden:C,notFound:p,expired:j,error:g,info:a},w={unauthorized:"text-warning",forbidden:"text-destructive",notFound:"text-muted-foreground",expired:"text-warning",error:"text-destructive",info:"text-primary"};function y({status:n,kind:t,title:d,message:o,actions:r}){const i=b[t]??a;return e.jsx("div",{className:"min-h-screen flex items-center justify-center p-6 bg-background text-foreground",children:e.jsxs(c,{className:"max-w-md w-full",children:[e.jsxs(l,{className:"flex flex-row items-center gap-3",children:[e.jsx(i,{className:x("h-6 w-6 shrink-0",w[t])}),e.jsx(m,{children:d})]}),e.jsxs(u,{className:"space-y-4",children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:o}),r&&r.length>0&&e.jsx("div",{className:"flex gap-2",children:r.map(s=>e.jsx(f,{asChild:!0,variant:"secondary",children:e.jsx("a",{href:s.href,children:s.label})},s.href))}),e.jsxs("p",{className:"text-xs text-muted-foreground/60",children:["Status ",n]})]})]})})}h(y); +import{C as c,b as l,c as x,d as m,e as u,B as f,m as h}from"./mount.Bxdfy1H2.js";import{j as e,ao as a,af as j,ah as g,az as p,aA as C,c as N}from"./vendor.BHG48rjj.js";const b={unauthorized:N,forbidden:C,notFound:p,expired:g,error:j,info:a},w={unauthorized:"text-warning",forbidden:"text-destructive",notFound:"text-muted-foreground",expired:"text-warning",error:"text-destructive",info:"text-primary"};function v({status:n,kind:t,title:d,message:o,actions:r}){const i=b[t]??a;return e.jsx("div",{className:"min-h-screen flex items-center justify-center p-6 bg-background text-foreground",children:e.jsxs(c,{className:"max-w-md w-full",children:[e.jsxs(l,{className:"flex flex-row items-center gap-3",children:[e.jsx(i,{className:x("h-6 w-6 shrink-0",w[t])}),e.jsx(m,{children:d})]}),e.jsxs(u,{className:"space-y-4",children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:o}),r&&r.length>0&&e.jsx("div",{className:"flex gap-2",children:r.map(s=>e.jsx(f,{asChild:!0,variant:"secondary",children:e.jsx("a",{href:s.href,children:s.label})},s.href))}),e.jsxs("p",{className:"text-xs text-muted-foreground/60",children:["Status ",n]})]})]})})}h(v); diff --git a/pkg/web/webui/webassets/dist/vendor.C5MRNvUt.js b/pkg/web/webui/webassets/dist/vendor.BHG48rjj.js similarity index 99% rename from pkg/web/webui/webassets/dist/vendor.C5MRNvUt.js rename to pkg/web/webui/webassets/dist/vendor.BHG48rjj.js index d9ba551a..4ec8289d 100644 --- a/pkg/web/webui/webassets/dist/vendor.C5MRNvUt.js +++ b/pkg/web/webui/webassets/dist/vendor.BHG48rjj.js @@ -320,172 +320,177 @@ Error generating stack: `+o.message+` * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const O6e=le("Laptop",[["path",{d:"M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16",key:"tarvll"}]]);/** + */const O6e=le("KeySquare",[["path",{d:"M12.4 2.7a2.5 2.5 0 0 1 3.4 0l5.5 5.5a2.5 2.5 0 0 1 0 3.4l-3.7 3.7a2.5 2.5 0 0 1-3.4 0L8.7 9.8a2.5 2.5 0 0 1 0-3.4z",key:"165ttr"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"m9.4 10.6-6.814 6.814A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814",key:"1ubxi2"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const T6e=le("LayoutDashboard",[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]]);/** + */const T6e=le("Laptop",[["path",{d:"M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16",key:"tarvll"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const I6e=le("LayoutGrid",[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1",key:"1g98yp"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1",key:"6d4xhi"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1",key:"nxv5o0"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1",key:"1bb6yr"}]]);/** + */const I6e=le("LayoutDashboard",[["rect",{width:"7",height:"9",x:"3",y:"3",rx:"1",key:"10lvy0"}],["rect",{width:"7",height:"5",x:"14",y:"3",rx:"1",key:"16une8"}],["rect",{width:"7",height:"9",x:"14",y:"12",rx:"1",key:"1hutg5"}],["rect",{width:"7",height:"5",x:"3",y:"16",rx:"1",key:"ldoo1y"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const A6e=le("ListChecks",[["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"m3 7 2 2 4-4",key:"1obspn"}],["path",{d:"M13 6h8",key:"15sg57"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 18h8",key:"oe0vm4"}]]);/** + */const A6e=le("LayoutGrid",[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1",key:"1g98yp"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1",key:"6d4xhi"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1",key:"nxv5o0"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1",key:"1bb6yr"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const C6e=le("LoaderCircle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** + */const C6e=le("ListChecks",[["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"m3 7 2 2 4-4",key:"1obspn"}],["path",{d:"M13 6h8",key:"15sg57"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 18h8",key:"oe0vm4"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const R6e=le("Lock",[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]]);/** + */const R6e=le("LoaderCircle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const N6e=le("LogOut",[["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}],["polyline",{points:"16 17 21 12 16 7",key:"1gabdz"}],["line",{x1:"21",x2:"9",y1:"12",y2:"12",key:"1uyos4"}]]);/** + */const N6e=le("Lock",[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const j6e=le("Menu",[["line",{x1:"4",x2:"20",y1:"12",y2:"12",key:"1e0a9i"}],["line",{x1:"4",x2:"20",y1:"6",y2:"6",key:"1owob3"}],["line",{x1:"4",x2:"20",y1:"18",y2:"18",key:"yk5zj1"}]]);/** + */const j6e=le("LogOut",[["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}],["polyline",{points:"16 17 21 12 16 7",key:"1gabdz"}],["line",{x1:"21",x2:"9",y1:"12",y2:"12",key:"1uyos4"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const D6e=le("MessageCircle",[["path",{d:"M7.9 20A9 9 0 1 0 4 16.1L2 22Z",key:"vv11sd"}]]);/** + */const D6e=le("Menu",[["line",{x1:"4",x2:"20",y1:"12",y2:"12",key:"1e0a9i"}],["line",{x1:"4",x2:"20",y1:"6",y2:"6",key:"1owob3"}],["line",{x1:"4",x2:"20",y1:"18",y2:"18",key:"yk5zj1"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const M6e=le("MessageSquare",[["path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",key:"1lielz"}]]);/** + */const M6e=le("MessageCircle",[["path",{d:"M7.9 20A9 9 0 1 0 4 16.1L2 22Z",key:"vv11sd"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const z6e=le("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** + */const z6e=le("MessageSquare",[["path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",key:"1lielz"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const L6e=le("Network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]);/** + */const L6e=le("Moon",[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const U6e=le("PanelTopClose",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"m9 16 3-3 3 3",key:"1idcnm"}]]);/** + */const U6e=le("Network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const F6e=le("PanelTopOpen",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"m15 14-3 3-3-3",key:"g215vf"}]]);/** + */const F6e=le("PanelTopClose",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"m9 16 3-3 3 3",key:"1idcnm"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const B6e=le("Pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);/** + */const B6e=le("PanelTopOpen",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"m15 14-3 3-3-3",key:"g215vf"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Z6e=le("RotateCw",[["path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",key:"1p45f6"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}]]);/** + */const Z6e=le("Pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const W6e=le("ScrollText",[["path",{d:"M15 12h-5",key:"r7krc0"}],["path",{d:"M15 8h-5",key:"1khuty"}],["path",{d:"M19 17V5a2 2 0 0 0-2-2H4",key:"zz82l3"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3",key:"1ph1d7"}]]);/** + */const W6e=le("RotateCw",[["path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",key:"1p45f6"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const H6e=le("Search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);/** + */const H6e=le("ScrollText",[["path",{d:"M15 12h-5",key:"r7krc0"}],["path",{d:"M15 8h-5",key:"1khuty"}],["path",{d:"M19 17V5a2 2 0 0 0-2-2H4",key:"zz82l3"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3",key:"1ph1d7"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const V6e=le("SendHorizontal",[["path",{d:"M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z",key:"117uat"}],["path",{d:"M6 12h16",key:"s4cdu5"}]]);/** + */const V6e=le("Search",[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const q6e=le("ShieldCheck",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]);/** + */const q6e=le("SendHorizontal",[["path",{d:"M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z",key:"117uat"}],["path",{d:"M6 12h16",key:"s4cdu5"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const G6e=le("Shield",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]]);/** + */const G6e=le("ShieldCheck",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const K6e=le("SlidersHorizontal",[["line",{x1:"21",x2:"14",y1:"4",y2:"4",key:"obuewd"}],["line",{x1:"10",x2:"3",y1:"4",y2:"4",key:"1q6298"}],["line",{x1:"21",x2:"12",y1:"12",y2:"12",key:"1iu8h1"}],["line",{x1:"8",x2:"3",y1:"12",y2:"12",key:"ntss68"}],["line",{x1:"21",x2:"16",y1:"20",y2:"20",key:"14d8ph"}],["line",{x1:"12",x2:"3",y1:"20",y2:"20",key:"m0wm8r"}],["line",{x1:"14",x2:"14",y1:"2",y2:"6",key:"14e1ph"}],["line",{x1:"8",x2:"8",y1:"10",y2:"14",key:"1i6ji0"}],["line",{x1:"16",x2:"16",y1:"18",y2:"22",key:"1lctlv"}]]);/** + */const K6e=le("Shield",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Y6e=le("Sparkles",[["path",{d:"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",key:"4pj2yx"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M22 5h-4",key:"1gvqau"}],["path",{d:"M4 17v2",key:"vumght"}],["path",{d:"M5 18H3",key:"zchphs"}]]);/** + */const Y6e=le("SlidersHorizontal",[["line",{x1:"21",x2:"14",y1:"4",y2:"4",key:"obuewd"}],["line",{x1:"10",x2:"3",y1:"4",y2:"4",key:"1q6298"}],["line",{x1:"21",x2:"12",y1:"12",y2:"12",key:"1iu8h1"}],["line",{x1:"8",x2:"3",y1:"12",y2:"12",key:"ntss68"}],["line",{x1:"21",x2:"16",y1:"20",y2:"20",key:"14d8ph"}],["line",{x1:"12",x2:"3",y1:"20",y2:"20",key:"m0wm8r"}],["line",{x1:"14",x2:"14",y1:"2",y2:"6",key:"14e1ph"}],["line",{x1:"8",x2:"8",y1:"10",y2:"14",key:"1i6ji0"}],["line",{x1:"16",x2:"16",y1:"18",y2:"22",key:"1lctlv"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const X6e=le("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]);/** + */const X6e=le("Sparkles",[["path",{d:"M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z",key:"4pj2yx"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M22 5h-4",key:"1gvqau"}],["path",{d:"M4 17v2",key:"vumght"}],["path",{d:"M5 18H3",key:"zchphs"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const J6e=le("Table",[["path",{d:"M12 3v18",key:"108xh3"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"M3 15h18",key:"5xshup"}]]);/** + */const J6e=le("Sun",[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const Q6e=le("Terminal",[["polyline",{points:"4 17 10 11 4 5",key:"akl6gq"}],["line",{x1:"12",x2:"20",y1:"19",y2:"19",key:"q2wloq"}]]);/** + */const Q6e=le("Table",[["path",{d:"M12 3v18",key:"108xh3"}],["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"M3 15h18",key:"5xshup"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const eUe=le("TriangleAlert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);/** + */const eUe=le("Terminal",[["polyline",{points:"4 17 10 11 4 5",key:"akl6gq"}],["line",{x1:"12",x2:"20",y1:"19",y2:"19",key:"q2wloq"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const tUe=le("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** + */const tUe=le("TriangleAlert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const nUe=le("Users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["path",{d:"M16 3.13a4 4 0 0 1 0 7.75",key:"1da9ce"}]]);/** + */const nUe=le("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const rUe=le("Wallet",[["path",{d:"M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1",key:"18etb6"}],["path",{d:"M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4",key:"xoc0q4"}]]);/** + */const rUe=le("Users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["path",{d:"M16 3.13a4 4 0 0 1 0 7.75",key:"1da9ce"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const iUe=le("WifiOff",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69",key:"1dl1wf"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523",key:"4k23kn"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643",key:"1grhjp"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764",key:"z3jwby"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]]);/** + */const iUe=le("Wallet",[["path",{d:"M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1",key:"18etb6"}],["path",{d:"M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4",key:"xoc0q4"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const oUe=le("Wifi",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]]);/** + */const oUe=le("WifiOff",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69",key:"1dl1wf"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523",key:"4k23kn"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643",key:"1grhjp"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764",key:"z3jwby"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const aUe=le("Wrench",[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",key:"cbrjhi"}]]);/** + */const aUe=le("Wifi",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]]);/** * @license lucide-react v0.460.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const sUe=le("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]);function qT(e,[t,n]){return Math.min(n,Math.max(t,e))}var lre="Arrow",Pz=S.forwardRef((e,t)=>{const{children:n,width:r=10,height:i=5,...o}=e;return z.jsx(He.svg,{...o,ref:t,width:r,height:i,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?n:z.jsx("polygon",{points:"0,0 30,0 15,10"})})});Pz.displayName=lre;var ure=Pz,bS="Popper",[Oz,Tz]=ra(bS),[cre,Iz]=Oz(bS),Az=e=>{const{__scopePopper:t,children:n}=e,[r,i]=S.useState(null),[o,a]=S.useState(void 0);return z.jsx(cre,{scope:t,anchor:r,onAnchorChange:i,placementState:o,setPlacementState:a,children:n})};Az.displayName=bS;var Cz="PopperAnchor",Rz=S.forwardRef((e,t)=>{const{__scopePopper:n,virtualRef:r,...i}=e,o=Iz(Cz,n),a=S.useRef(null),s=o.onAnchorChange,l=S.useCallback(h=>{a.current=h,h&&s(h)},[s]),u=Xe(t,l),f=S.useRef(null);S.useEffect(()=>{if(!r)return;const h=f.current;f.current=r.current,h!==f.current&&s(f.current)});const c=o.placementState&&xS(o.placementState),d=c==null?void 0:c[0],p=c==null?void 0:c[1];return r?null:z.jsx(He.div,{"data-radix-popper-side":d,"data-radix-popper-align":p,...i,ref:u})});Rz.displayName=Cz;var wS="PopperContent",[fre,dre]=Oz(wS),Nz=S.forwardRef((e,t)=>{var oe,Y,ne,Q,Z,$e;const{__scopePopper:n,side:r="bottom",sideOffset:i=0,align:o="center",alignOffset:a=0,arrowPadding:s=0,avoidCollisions:l=!0,collisionBoundary:u=[],collisionPadding:f=0,sticky:c="partial",hideWhenDetached:d=!1,updatePositionStrategy:p="optimized",onPlaced:h,...m}=e,g=Iz(wS,n),[v,y]=S.useState(null),b=Xe(t,ve=>y(ve)),[_,x]=S.useState(null),w=kM(_),$=(w==null?void 0:w.width)??0,k=(w==null?void 0:w.height)??0,T=r+(o!=="center"?"-"+o:""),E=typeof f=="number"?f:{top:0,right:0,bottom:0,left:0,...f},I=Array.isArray(u)?u:[u],O=I.length>0,j={padding:E,boundary:I.filter(hre),altBoundary:O},{refs:D,floatingStyles:M,placement:L,isPositioned:B,middlewareData:R}=X_({strategy:"fixed",placement:T,whileElementsMounted:(...ve)=>Y_(...ve,{animationFrame:p==="always"}),elements:{reference:g.anchor},middleware:[J_({mainAxis:i+k,alignmentAxis:a}),l&&Q_({mainAxis:!0,crossAxis:!1,limiter:c==="partial"?eS():void 0,...j}),l&&tS({...j}),nS({...j,apply:({elements:ve,rects:Me,availableWidth:Ue,availableHeight:Qe})=>{const{width:fn,height:Sn}=Me.reference,Ft=ve.floating.style;Ft.setProperty("--radix-popper-available-width",`${Ue}px`),Ft.setProperty("--radix-popper-available-height",`${Qe}px`),Ft.setProperty("--radix-popper-anchor-width",`${fn}px`),Ft.setProperty("--radix-popper-anchor-height",`${Sn}px`)}}),_&&iS({element:_,padding:s}),mre({arrowWidth:$,arrowHeight:k}),d&&rS({strategy:"referenceHidden",...j,boundary:O?j.boundary:void 0})]}),U=g.setPlacementState;Dt(()=>(U(L),()=>{U(void 0)}),[L,U]);const[P,G]=xS(L),V=ei(h);Dt(()=>{B&&(V==null||V())},[B,V]);const A=(oe=R.arrow)==null?void 0:oe.x,pe=(Y=R.arrow)==null?void 0:Y.y,me=((ne=R.arrow)==null?void 0:ne.centerOffset)!==0,[ce,ie]=S.useState();return Dt(()=>{v&&ie(window.getComputedStyle(v).zIndex)},[v]),z.jsx("div",{ref:D.setFloating,"data-radix-popper-content-wrapper":"",style:{...M,transform:B?M.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:ce,"--radix-popper-transform-origin":[(Q=R.transformOrigin)==null?void 0:Q.x,(Z=R.transformOrigin)==null?void 0:Z.y].join(" "),...(($e=R.hide)==null?void 0:$e.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:z.jsx(fre,{scope:n,placedSide:P,placedAlign:G,onArrowChange:x,arrowX:A,arrowY:pe,shouldHideArrow:me,children:z.jsx(He.div,{"data-side":P,"data-align":G,...m,ref:b,style:{...m.style,animation:B?void 0:"none"}})})})});Nz.displayName=wS;var jz="PopperArrow",pre={top:"bottom",right:"left",bottom:"top",left:"right"},Dz=S.forwardRef(function(t,n){const{__scopePopper:r,...i}=t,o=dre(jz,r),a=pre[o.placedSide];return z.jsx("span",{ref:o.onArrowChange,style:{position:"absolute",left:o.arrowX,top:o.arrowY,[a]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[o.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[o.placedSide],visibility:o.shouldHideArrow?"hidden":void 0},children:z.jsx(ure,{...i,ref:n,style:{...i.style,display:"block"}})})});Dz.displayName=jz;function hre(e){return e!==null}var mre=e=>({name:"transformOrigin",options:e,fn(t){var g,v,y;const{placement:n,rects:r,middlewareData:i}=t,a=((g=i.arrow)==null?void 0:g.centerOffset)!==0,s=a?0:e.arrowWidth,l=a?0:e.arrowHeight,[u,f]=xS(n),c={start:"0%",center:"50%",end:"100%"}[f],d=(((v=i.arrow)==null?void 0:v.x)??0)+s/2,p=(((y=i.arrow)==null?void 0:y.y)??0)+l/2;let h="",m="";return u==="bottom"?(h=a?c:`${d}px`,m=`${-l}px`):u==="top"?(h=a?c:`${d}px`,m=`${r.floating.height+l}px`):u==="right"?(h=`${-l}px`,m=a?c:`${p}px`):u==="left"&&(h=`${r.floating.width+l}px`,m=a?c:`${p}px`),{data:{x:h,y:m}}}});function xS(e){const[t,n="center"]=e.split("-");return[t,n]}var vre=Az,gre=Rz,yre=Nz,bre=Dz;function wre(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}var Mz=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"}),xre="VisuallyHidden",_re=S.forwardRef((e,t)=>z.jsx(He.span,{...e,ref:t,style:{...Mz,...e.style}}));_re.displayName=xre;var Sre=[" ","Enter","ArrowUp","ArrowDown"],$re=[" ","Enter"],ts="Select",[Yv,Xv,kre]=HM(ts),[hs]=ra(ts,[kre,Tz]),Jv=Tz(),[Ere,aa]=hs(ts),[Pre,Ore]=hs(ts),Tre="SelectProvider";function zz(e){const{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:o,value:a,defaultValue:s,onValueChange:l,dir:u,name:f,autoComplete:c,disabled:d,required:p,form:h,internal_do_not_use_render:m}=e,g=Jv(t),[v,y]=S.useState(null),[b,_]=S.useState(null),[x,w]=S.useState(!1),$=cS(u),[k,T]=$l({prop:r,defaultProp:i??!1,onChange:o,caller:ts}),[E,I]=$l({prop:a,defaultProp:s,onChange:l,caller:ts}),O=S.useRef(null),j=v?!!h||!!v.closest("form"):!0,[D,M]=S.useState(new Set),L=Bi(),B=Array.from(D).map(G=>G.props.value).join(";"),R=S.useCallback(G=>{M(V=>new Set(V).add(G))},[]),U=S.useCallback(G=>{M(V=>{const A=new Set(V);return A.delete(G),A})},[]),P={required:p,trigger:v,onTriggerChange:y,valueNode:b,onValueNodeChange:_,valueNodeHasChildren:x,onValueNodeHasChildrenChange:w,contentId:L,value:E,onValueChange:I,open:k,onOpenChange:T,dir:$,triggerPointerDownPosRef:O,disabled:d,name:f,autoComplete:c,form:h,nativeOptions:D,nativeSelectKey:B,isFormControl:j};return z.jsx(vre,{...g,children:z.jsx(Ere,{scope:t,...P,children:z.jsx(Yv.Provider,{scope:t,children:z.jsx(Pre,{scope:t,onNativeOptionAdd:R,onNativeOptionRemove:U,children:iie(m)?m(P):n})})})})}zz.displayName=Tre;var Ire=e=>{const{__scopeSelect:t,children:n,...r}=e;return z.jsx(zz,{__scopeSelect:t,...r,internal_do_not_use_render:({isFormControl:i})=>z.jsxs(z.Fragment,{children:[n,i?z.jsx(Qz,{__scopeSelect:t}):null]})})};Ire.displayName=ts;var Lz="SelectTrigger",Are=S.forwardRef((e,t)=>{const{__scopeSelect:n,disabled:r=!1,...i}=e,o=Jv(n),a=aa(Lz,n),s=a.disabled||r,l=Xe(t,a.onTriggerChange),u=Xv(n),f=S.useRef("touch"),[c,d,p]=eL(m=>{const g=u().filter(b=>!b.disabled),v=g.find(b=>b.value===a.value),y=tL(g,m,v);y!==void 0&&a.onValueChange(y.value)}),h=m=>{s||(a.onOpenChange(!0),p()),m&&(a.triggerPointerDownPosRef.current={x:Math.round(m.pageX),y:Math.round(m.pageY)})};return z.jsx(gre,{asChild:!0,...o,children:z.jsx(He.button,{type:"button",role:"combobox","aria-controls":a.open?a.contentId:void 0,"aria-expanded":a.open,"aria-required":a.required,"aria-autocomplete":"none",dir:a.dir,"data-state":a.open?"open":"closed",disabled:s,"data-disabled":s?"":void 0,"data-placeholder":Qv(a.value)?"":void 0,...i,ref:l,onClick:Ee(i.onClick,m=>{m.currentTarget.focus(),f.current!=="mouse"&&h(m)}),onPointerDown:Ee(i.onPointerDown,m=>{f.current=m.pointerType;const g=m.target;g.hasPointerCapture(m.pointerId)&&g.releasePointerCapture(m.pointerId),m.button===0&&m.ctrlKey===!1&&m.pointerType==="mouse"&&(h(m),m.preventDefault())}),onKeyDown:Ee(i.onKeyDown,m=>{const g=c.current!=="";!(m.ctrlKey||m.altKey||m.metaKey)&&m.key.length===1&&d(m.key),!(g&&m.key===" ")&&Sre.includes(m.key)&&(h(),m.preventDefault())})})})});Are.displayName=Lz;var Uz="SelectValue",Cre=S.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,children:o,placeholder:a="",...s}=e,l=aa(Uz,n),{onValueNodeHasChildrenChange:u}=l,f=o!==void 0,c=Xe(t,l.onValueNodeChange);Dt(()=>{u(f)},[u,f]);const d=Qv(l.value);return z.jsx(He.span,{...s,asChild:d?!1:s.asChild,ref:c,style:{pointerEvents:"none"},children:z.jsx(S.Fragment,{children:d?a:o},d?"placeholder":"value")})});Cre.displayName=Uz;var Rre="SelectIcon",Nre=S.forwardRef((e,t)=>{const{__scopeSelect:n,children:r,...i}=e;return z.jsx(He.span,{"aria-hidden":!0,...i,ref:t,children:r||"▼"})});Nre.displayName=Rre;var Fz="SelectPortal",[jre,Dre]=hs(Fz,{forceMount:void 0}),Mre=e=>{const{__scopeSelect:t,forceMount:n,...r}=e;return z.jsx(jre,{scope:e.__scopeSelect,forceMount:n,children:z.jsx(mS,{asChild:!0,...r})})};Mre.displayName=Fz;var qo="SelectContent",zre=S.forwardRef((e,t)=>{const n=Dre(qo,e.__scopeSelect),{forceMount:r=n.forceMount,...i}=e,o=aa(qo,e.__scopeSelect),[a,s]=S.useState();return Dt(()=>{s(new DocumentFragment)},[]),z.jsx(oa,{present:r||o.open,children:({present:l})=>l?z.jsx(Wz,{...i,ref:t}):z.jsx(Bz,{...i,fragment:a})})});zre.displayName=qo;var Bz=S.forwardRef((e,t)=>{const{__scopeSelect:n,children:r,fragment:i}=e;return i?$i.createPortal(z.jsx(Zz,{scope:n,children:z.jsx(Yv.Slot,{scope:n,children:z.jsx("div",{ref:t,children:r})})}),i):null});Bz.displayName="SelectContentFragment";var Ur=10,[Zz,sa]=hs(qo),Lre="SelectContentImpl",Ure=of("SelectContent.RemoveScroll"),Wz=S.forwardRef((e,t)=>{const{__scopeSelect:n}=e,{position:r="item-aligned",onCloseAutoFocus:i,onEscapeKeyDown:o,onPointerDownOutside:a,side:s,sideOffset:l,align:u,alignOffset:f,arrowPadding:c,collisionBoundary:d,collisionPadding:p,sticky:h,hideWhenDetached:m,avoidCollisions:g,...v}=e,y=aa(qo,n),[b,_]=S.useState(null),[x,w]=S.useState(null),$=Xe(t,ie=>_(ie)),[k,T]=S.useState(null),[E,I]=S.useState(null),O=Xv(n),[j,D]=S.useState(!1),M=S.useRef(!1);S.useEffect(()=>{if(b)return vS(b)},[b]),cz();const L=S.useCallback(ie=>{const[oe,...Y]=O().map(Z=>Z.ref.current),[ne]=Y.slice(-1),Q=document.activeElement;for(const Z of ie)if(Z===Q||(Z==null||Z.scrollIntoView({block:"nearest"}),Z===oe&&x&&(x.scrollTop=0),Z===ne&&x&&(x.scrollTop=x.scrollHeight),Z==null||Z.focus(),document.activeElement!==Q))return},[O,x]),B=S.useCallback(()=>L([k,b]),[L,k,b]);S.useEffect(()=>{j&&B()},[j,B]);const{onOpenChange:R,triggerPointerDownPosRef:U}=y;S.useEffect(()=>{if(b){let ie={x:0,y:0};const oe=ne=>{var Q,Z;ie={x:Math.abs(Math.round(ne.pageX)-(((Q=U.current)==null?void 0:Q.x)??0)),y:Math.abs(Math.round(ne.pageY)-(((Z=U.current)==null?void 0:Z.y)??0))}},Y=ne=>{ie.x<=10&&ie.y<=10?ne.preventDefault():ne.composedPath().includes(b)||R(!1),document.removeEventListener("pointermove",oe),U.current=null};return U.current!==null&&(document.addEventListener("pointermove",oe),document.addEventListener("pointerup",Y,{capture:!0,once:!0})),()=>{document.removeEventListener("pointermove",oe),document.removeEventListener("pointerup",Y,{capture:!0})}}},[b,R,U]),S.useEffect(()=>{const ie=()=>R(!1);return window.addEventListener("blur",ie),window.addEventListener("resize",ie),()=>{window.removeEventListener("blur",ie),window.removeEventListener("resize",ie)}},[R]);const[P,G]=eL(ie=>{const oe=O().filter(Q=>!Q.disabled),Y=oe.find(Q=>Q.ref.current===document.activeElement),ne=tL(oe,ie,Y);ne&&setTimeout(()=>{var Q;return(Q=ne.ref.current)==null?void 0:Q.focus()})}),V=S.useCallback((ie,oe,Y)=>{const ne=!M.current&&!Y;(y.value!==void 0&&y.value===oe||ne)&&(T(ie),ne&&(M.current=!0))},[y.value]),A=S.useCallback(()=>b==null?void 0:b.focus(),[b]),pe=S.useCallback((ie,oe,Y)=>{const ne=!M.current&&!Y;(y.value!==void 0&&y.value===oe||ne)&&I(ie)},[y.value]),me=r==="popper"?Cw:Hz,ce=me===Cw?{side:s,sideOffset:l,align:u,alignOffset:f,arrowPadding:c,collisionBoundary:d,collisionPadding:p,sticky:h,hideWhenDetached:m,avoidCollisions:g}:{};return z.jsx(Zz,{scope:n,content:b,viewport:x,onViewportChange:w,itemRefCallback:V,selectedItem:k,onItemLeave:A,itemTextRefCallback:pe,focusSelectedItem:B,selectedItemText:E,position:r,isPositioned:j,searchRef:P,children:z.jsx(Gv,{as:Ure,allowPinchZoom:!0,children:z.jsx(hS,{asChild:!0,trapped:y.open,onMountAutoFocus:ie=>{ie.preventDefault()},onUnmountAutoFocus:Ee(i,ie=>{var oe;(oe=y.trigger)==null||oe.focus({preventScroll:!0}),ie.preventDefault()}),children:z.jsx(pS,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:o,onPointerDownOutside:a,onFocusOutside:ie=>ie.preventDefault(),onDismiss:()=>y.onOpenChange(!1),children:z.jsx(me,{role:"listbox",id:y.contentId,"data-state":y.open?"open":"closed",dir:y.dir,onContextMenu:ie=>ie.preventDefault(),...v,...ce,onPlaced:()=>D(!0),ref:$,style:{display:"flex",flexDirection:"column",outline:"none",...v.style},onKeyDown:Ee(v.onKeyDown,ie=>{const oe=ie.ctrlKey||ie.altKey||ie.metaKey;if(ie.key==="Tab"&&ie.preventDefault(),!oe&&ie.key.length===1&&G(ie.key),["ArrowUp","ArrowDown","Home","End"].includes(ie.key)){let ne=O().filter(Q=>!Q.disabled).map(Q=>Q.ref.current);if(["ArrowUp","End"].includes(ie.key)&&(ne=ne.slice().reverse()),["ArrowUp","ArrowDown"].includes(ie.key)){const Q=ie.target,Z=ne.indexOf(Q);ne=ne.slice(Z+1)}setTimeout(()=>L(ne)),ie.preventDefault()}})})})})})})});Wz.displayName=Lre;var Fre="SelectItemAlignedPosition",Hz=S.forwardRef((e,t)=>{const{__scopeSelect:n,onPlaced:r,...i}=e,o=aa(qo,n),a=sa(qo,n),[s,l]=S.useState(null),[u,f]=S.useState(null),c=Xe(t,$=>f($)),d=Xv(n),p=S.useRef(!1),h=S.useRef(!0),{viewport:m,selectedItem:g,selectedItemText:v,focusSelectedItem:y}=a,b=S.useCallback(()=>{if(o.trigger&&o.valueNode&&s&&u&&m&&g&&v){const $=o.trigger.getBoundingClientRect(),k=u.getBoundingClientRect(),T=o.valueNode.getBoundingClientRect(),E=v.getBoundingClientRect();if(o.dir!=="rtl"){const Q=E.left-k.left,Z=T.left-Q,$e=$.left-Z,ve=$.width+$e,Me=Math.max(ve,k.width),Ue=window.innerWidth-Ur,Qe=qT(Z,[Ur,Math.max(Ur,Ue-Me)]);s.style.minWidth=ve+"px",s.style.left=Qe+"px"}else{const Q=k.right-E.right,Z=window.innerWidth-T.right-Q,$e=window.innerWidth-$.right-Z,ve=$.width+$e,Me=Math.max(ve,k.width),Ue=window.innerWidth-Ur,Qe=qT(Z,[Ur,Math.max(Ur,Ue-Me)]);s.style.minWidth=ve+"px",s.style.right=Qe+"px"}const I=d(),O=window.innerHeight-Ur*2,j=m.scrollHeight,D=window.getComputedStyle(u),M=parseInt(D.borderTopWidth,10),L=parseInt(D.paddingTop,10),B=parseInt(D.borderBottomWidth,10),R=parseInt(D.paddingBottom,10),U=M+L+j+R+B,P=Math.min(g.offsetHeight*5,U),G=window.getComputedStyle(m),V=parseInt(G.paddingTop,10),A=parseInt(G.paddingBottom,10),pe=$.top+$.height/2-Ur,me=O-pe,ce=g.offsetHeight/2,ie=g.offsetTop+ce,oe=M+L+ie,Y=U-oe;if(oe<=pe){const Q=I.length>0&&g===I[I.length-1].ref.current;s.style.bottom="0px";const Z=u.clientHeight-m.offsetTop-m.offsetHeight,$e=Math.max(me,ce+(Q?A:0)+Z+B),ve=oe+$e;s.style.height=ve+"px"}else{const Q=I.length>0&&g===I[0].ref.current;s.style.top="0px";const $e=Math.max(pe,M+m.offsetTop+(Q?V:0)+ce)+Y;s.style.height=$e+"px",m.scrollTop=oe-pe+m.offsetTop}s.style.margin=`${Ur}px 0`,s.style.minHeight=P+"px",s.style.maxHeight=O+"px",r==null||r(),requestAnimationFrame(()=>p.current=!0)}},[d,o.trigger,o.valueNode,s,u,m,g,v,o.dir,r]);Dt(()=>b(),[b]);const[_,x]=S.useState();Dt(()=>{u&&x(window.getComputedStyle(u).zIndex)},[u]);const w=S.useCallback($=>{$&&h.current===!0&&(b(),y==null||y(),h.current=!1)},[b,y]);return z.jsx(Zre,{scope:n,contentWrapper:s,shouldExpandOnScrollRef:p,onScrollButtonChange:w,children:z.jsx("div",{ref:l,style:{display:"flex",flexDirection:"column",position:"fixed",zIndex:_},children:z.jsx(He.div,{...i,ref:c,style:{boxSizing:"border-box",maxHeight:"100%",...i.style}})})})});Hz.displayName=Fre;var Bre="SelectPopperPosition",Cw=S.forwardRef((e,t)=>{const{__scopeSelect:n,align:r="start",collisionPadding:i=Ur,...o}=e,a=Jv(n);return z.jsx(yre,{...a,...o,ref:t,align:r,collisionPadding:i,style:{boxSizing:"border-box",...o.style,"--radix-select-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-select-content-available-width":"var(--radix-popper-available-width)","--radix-select-content-available-height":"var(--radix-popper-available-height)","--radix-select-trigger-width":"var(--radix-popper-anchor-width)","--radix-select-trigger-height":"var(--radix-popper-anchor-height)"}})});Cw.displayName=Bre;var[Zre,_S]=hs(qo,{}),Rw="SelectViewport",Wre=S.forwardRef((e,t)=>{const{__scopeSelect:n,nonce:r,...i}=e,o=sa(Rw,n),a=_S(Rw,n),s=Xe(t,o.onViewportChange),l=S.useRef(0);return z.jsxs(z.Fragment,{children:[z.jsx("style",{dangerouslySetInnerHTML:{__html:"[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}"},nonce:r}),z.jsx(Yv.Slot,{scope:n,children:z.jsx(He.div,{"data-radix-select-viewport":"",role:"presentation",...i,ref:s,style:{position:"relative",flex:1,overflow:"hidden auto",...i.style},onScroll:Ee(i.onScroll,u=>{const f=u.currentTarget,{contentWrapper:c,shouldExpandOnScrollRef:d}=a;if(d!=null&&d.current&&c){const p=Math.abs(l.current-f.scrollTop);if(p>0){const h=window.innerHeight-Ur*2,m=parseFloat(c.style.minHeight),g=parseFloat(c.style.height),v=Math.max(m,g);if(v0?_:0,c.style.justifyContent="flex-end")}}}l.current=f.scrollTop})})})]})});Wre.displayName=Rw;var Vz="SelectGroup",[Hre,Vre]=hs(Vz),qre=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Bi();return z.jsx(Hre,{scope:n,id:i,children:z.jsx(He.div,{role:"group","aria-labelledby":i,...r,ref:t})})});qre.displayName=Vz;var qz="SelectLabel",Gre=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Vre(qz,n);return z.jsx(He.div,{id:i.id,...r,ref:t})});Gre.displayName=qz;var em="SelectItem",[Kre,Gz]=hs(em),Yre=S.forwardRef((e,t)=>{const{__scopeSelect:n,value:r,disabled:i=!1,textValue:o,...a}=e,s=aa(em,n),l=sa(em,n),u=s.value===r,[f,c]=S.useState(o??""),[d,p]=S.useState(!1),h=Xe(t,y=>{var b;return(b=l.itemRefCallback)==null?void 0:b.call(l,y,r,i)}),m=Bi(),g=S.useRef("touch"),v=()=>{i||(s.onValueChange(r),s.onOpenChange(!1))};return z.jsx(Kre,{scope:n,value:r,disabled:i,textId:m,isSelected:u,onItemTextChange:S.useCallback(y=>{c(b=>b||((y==null?void 0:y.textContent)??"").trim())},[]),children:z.jsx(Yv.ItemSlot,{scope:n,value:r,disabled:i,textValue:f,children:z.jsx(He.div,{role:"option","aria-labelledby":m,"data-highlighted":d?"":void 0,"aria-selected":u&&d,"data-state":u?"checked":"unchecked","aria-disabled":i||void 0,"data-disabled":i?"":void 0,tabIndex:i?void 0:-1,...a,ref:h,onFocus:Ee(a.onFocus,()=>p(!0)),onBlur:Ee(a.onBlur,()=>p(!1)),onClick:Ee(a.onClick,()=>{g.current!=="mouse"&&v()}),onPointerUp:Ee(a.onPointerUp,()=>{g.current==="mouse"&&v()}),onPointerDown:Ee(a.onPointerDown,y=>{g.current=y.pointerType}),onPointerMove:Ee(a.onPointerMove,y=>{var b;g.current=y.pointerType,i?(b=l.onItemLeave)==null||b.call(l):g.current==="mouse"&&y.currentTarget.focus({preventScroll:!0})}),onPointerLeave:Ee(a.onPointerLeave,y=>{var b;y.currentTarget===document.activeElement&&((b=l.onItemLeave)==null||b.call(l))}),onKeyDown:Ee(a.onKeyDown,y=>{var _;((_=l.searchRef)==null?void 0:_.current)!==""&&y.key===" "||($re.includes(y.key)&&v(),y.key===" "&&y.preventDefault())})})})})});Yre.displayName=em;var fc="SelectItemText",Xre=S.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,...o}=e,a=aa(fc,n),s=sa(fc,n),l=Gz(fc,n),u=Ore(fc,n),[f,c]=S.useState(null),d=Xe(t,v=>c(v),l.onItemTextChange,v=>{var y;return(y=s.itemTextRefCallback)==null?void 0:y.call(s,v,l.value,l.disabled)}),p=f==null?void 0:f.textContent,h=S.useMemo(()=>z.jsx("option",{value:l.value,disabled:l.disabled,children:p},l.value),[l.disabled,l.value,p]),{onNativeOptionAdd:m,onNativeOptionRemove:g}=u;return Dt(()=>(m(h),()=>g(h)),[m,g,h]),z.jsxs(z.Fragment,{children:[z.jsx(He.span,{id:l.textId,...o,ref:d}),l.isSelected&&a.valueNode&&!a.valueNodeHasChildren&&!Qv(a.value)?$i.createPortal(o.children,a.valueNode):null]})});Xre.displayName=fc;var Kz="SelectItemIndicator",Jre=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return Gz(Kz,n).isSelected?z.jsx(He.span,{"aria-hidden":!0,...r,ref:t}):null});Jre.displayName=Kz;var Nw="SelectScrollUpButton",Qre=S.forwardRef((e,t)=>{const n=sa(Nw,e.__scopeSelect),r=_S(Nw,e.__scopeSelect),[i,o]=S.useState(!1),a=Xe(t,r.onScrollButtonChange);return Dt(()=>{if(n.viewport&&n.isPositioned){let s=function(){const u=l.scrollTop>0;o(u)};const l=n.viewport;return s(),l.addEventListener("scroll",s),()=>l.removeEventListener("scroll",s)}},[n.viewport,n.isPositioned]),i?z.jsx(Yz,{...e,ref:a,onAutoScroll:()=>{const{viewport:s,selectedItem:l}=n;s&&l&&(s.scrollTop=s.scrollTop-l.offsetHeight)}}):null});Qre.displayName=Nw;var jw="SelectScrollDownButton",eie=S.forwardRef((e,t)=>{const n=sa(jw,e.__scopeSelect),r=_S(jw,e.__scopeSelect),[i,o]=S.useState(!1),a=Xe(t,r.onScrollButtonChange);return Dt(()=>{if(n.viewport&&n.isPositioned){let s=function(){const u=l.scrollHeight-l.clientHeight,f=Math.ceil(l.scrollTop)l.removeEventListener("scroll",s)}},[n.viewport,n.isPositioned]),i?z.jsx(Yz,{...e,ref:a,onAutoScroll:()=>{const{viewport:s,selectedItem:l}=n;s&&l&&(s.scrollTop=s.scrollTop+l.offsetHeight)}}):null});eie.displayName=jw;var Yz=S.forwardRef((e,t)=>{const{__scopeSelect:n,onAutoScroll:r,...i}=e,o=sa("SelectScrollButton",n),a=S.useRef(null),s=Xv(n),l=S.useCallback(()=>{a.current!==null&&(window.clearInterval(a.current),a.current=null)},[]);return S.useEffect(()=>()=>l(),[l]),Dt(()=>{var f;const u=s().find(c=>c.ref.current===document.activeElement);(f=u==null?void 0:u.ref.current)==null||f.scrollIntoView({block:"nearest"})},[s]),z.jsx(He.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:Ee(i.onPointerDown,()=>{a.current===null&&(a.current=window.setInterval(r,50))}),onPointerMove:Ee(i.onPointerMove,()=>{var u;(u=o.onItemLeave)==null||u.call(o),a.current===null&&(a.current=window.setInterval(r,50))}),onPointerLeave:Ee(i.onPointerLeave,()=>{l()})})}),tie="SelectSeparator",nie=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return z.jsx(He.div,{"aria-hidden":!0,...r,ref:t})});nie.displayName=tie;var Xz="SelectArrow",rie=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Jv(n);return sa(Xz,n).position==="popper"?z.jsx(bre,{...i,...r,ref:t}):null});rie.displayName=Xz;var Jz="SelectBubbleInput",Qz=S.forwardRef(({__scopeSelect:e,...t},n)=>{const r=aa(Jz,e),{value:i,onValueChange:o,required:a,disabled:s,name:l,autoComplete:u,form:f}=r,{nativeOptions:c,nativeSelectKey:d}=r,p=S.useRef(null),h=Xe(n,p),m=i??"",g=wre(m),v=Array.from(c).some(y=>(y.props.value??"")==="");return S.useEffect(()=>{const y=p.current;if(!y)return;const b=window.HTMLSelectElement.prototype,x=Object.getOwnPropertyDescriptor(b,"value").set;if(g!==m&&x){const w=new Event("change",{bubbles:!0});x.call(y,m),y.dispatchEvent(w)}},[g,m]),z.jsxs(He.select,{"aria-hidden":!0,required:a,tabIndex:-1,name:l,autoComplete:u,disabled:s,form:f,onChange:y=>o(y.target.value),...t,style:{...Mz,...t.style},ref:h,defaultValue:m,children:[Qv(i)&&!v?z.jsx("option",{value:""}):null,Array.from(c)]},d)});Qz.displayName=Jz;function iie(e){return typeof e=="function"}function Qv(e){return e===""||e===void 0}function eL(e){const t=ei(e),n=S.useRef(""),r=S.useRef(0),i=S.useCallback(a=>{const s=n.current+a;t(s),function l(u){n.current=u,window.clearTimeout(r.current),u!==""&&(r.current=window.setTimeout(()=>l(""),1e3))}(s)},[t]),o=S.useCallback(()=>{n.current="",window.clearTimeout(r.current)},[]);return S.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,o]}function tL(e,t,n){const i=t.length>1&&Array.from(t).every(u=>u===t[0])?t[0]:t,o=n?e.indexOf(n):-1;let a=oie(e,Math.max(o,0));i.length===1&&(a=a.filter(u=>u!==n));const l=a.find(u=>u.textValue.toLowerCase().startsWith(i.toLowerCase()));return l!==n?l:void 0}function oie(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var aie=Array.isArray,Wn=aie,sie=typeof To=="object"&&To&&To.Object===Object&&To,nL=sie,lie=nL,uie=typeof self=="object"&&self&&self.Object===Object&&self,cie=lie||uie||Function("return this")(),Pi=cie,fie=Pi,die=fie.Symbol,ld=die,GT=ld,rL=Object.prototype,pie=rL.hasOwnProperty,hie=rL.toString,Ku=GT?GT.toStringTag:void 0;function mie(e){var t=pie.call(e,Ku),n=e[Ku];try{e[Ku]=void 0;var r=!0}catch{}var i=hie.call(e);return r&&(t?e[Ku]=n:delete e[Ku]),i}var vie=mie,gie=Object.prototype,yie=gie.toString;function bie(e){return yie.call(e)}var wie=bie,KT=ld,xie=vie,_ie=wie,Sie="[object Null]",$ie="[object Undefined]",YT=KT?KT.toStringTag:void 0;function kie(e){return e==null?e===void 0?$ie:Sie:YT&&YT in Object(e)?xie(e):_ie(e)}var so=kie;function Eie(e){return e!=null&&typeof e=="object"}var lo=Eie,Pie=so,Oie=lo,Tie="[object Symbol]";function Iie(e){return typeof e=="symbol"||Oie(e)&&Pie(e)==Tie}var pu=Iie,Aie=Wn,Cie=pu,Rie=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Nie=/^\w*$/;function jie(e,t){if(Aie(e))return!1;var n=typeof e;return n=="number"||n=="symbol"||n=="boolean"||e==null||Cie(e)?!0:Nie.test(e)||!Rie.test(e)||t!=null&&e in Object(t)}var SS=jie;function Die(e){var t=typeof e;return e!=null&&(t=="object"||t=="function")}var la=Die;const hu=Ge(la);var Mie=so,zie=la,Lie="[object AsyncFunction]",Uie="[object Function]",Fie="[object GeneratorFunction]",Bie="[object Proxy]";function Zie(e){if(!zie(e))return!1;var t=Mie(e);return t==Uie||t==Fie||t==Lie||t==Bie}var $S=Zie;const Pe=Ge($S);var Wie=Pi,Hie=Wie["__core-js_shared__"],Vie=Hie,zb=Vie,XT=function(){var e=/[^.]+$/.exec(zb&&zb.keys&&zb.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}();function qie(e){return!!XT&&XT in e}var Gie=qie,Kie=Function.prototype,Yie=Kie.toString;function Xie(e){if(e!=null){try{return Yie.call(e)}catch{}try{return e+""}catch{}}return""}var iL=Xie,Jie=$S,Qie=Gie,eoe=la,toe=iL,noe=/[\\^$.*+?()[\]{}|]/g,roe=/^\[object .+?Constructor\]$/,ioe=Function.prototype,ooe=Object.prototype,aoe=ioe.toString,soe=ooe.hasOwnProperty,loe=RegExp("^"+aoe.call(soe).replace(noe,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function uoe(e){if(!eoe(e)||Qie(e))return!1;var t=Jie(e)?loe:roe;return t.test(toe(e))}var coe=uoe;function foe(e,t){return e==null?void 0:e[t]}var doe=foe,poe=coe,hoe=doe;function moe(e,t){var n=hoe(e,t);return poe(n)?n:void 0}var ms=moe,voe=ms,goe=voe(Object,"create"),eg=goe,JT=eg;function yoe(){this.__data__=JT?JT(null):{},this.size=0}var boe=yoe;function woe(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}var xoe=woe,_oe=eg,Soe="__lodash_hash_undefined__",$oe=Object.prototype,koe=$oe.hasOwnProperty;function Eoe(e){var t=this.__data__;if(_oe){var n=t[e];return n===Soe?void 0:n}return koe.call(t,e)?t[e]:void 0}var Poe=Eoe,Ooe=eg,Toe=Object.prototype,Ioe=Toe.hasOwnProperty;function Aoe(e){var t=this.__data__;return Ooe?t[e]!==void 0:Ioe.call(t,e)}var Coe=Aoe,Roe=eg,Noe="__lodash_hash_undefined__";function joe(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=Roe&&t===void 0?Noe:t,this}var Doe=joe,Moe=boe,zoe=xoe,Loe=Poe,Uoe=Coe,Foe=Doe;function mu(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t-1}var iae=rae,oae=tg;function aae(e,t){var n=this.__data__,r=oae(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}var sae=aae,lae=Woe,uae=Joe,cae=tae,fae=iae,dae=sae;function vu(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t{const{children:n,width:r=10,height:i=5,...o}=e;return z.jsx(He.svg,{...o,ref:t,width:r,height:i,viewBox:"0 0 30 10",preserveAspectRatio:"none",children:e.asChild?n:z.jsx("polygon",{points:"0,0 30,0 15,10"})})});Pz.displayName=lre;var ure=Pz,bS="Popper",[Oz,Tz]=ra(bS),[cre,Iz]=Oz(bS),Az=e=>{const{__scopePopper:t,children:n}=e,[r,i]=S.useState(null),[o,a]=S.useState(void 0);return z.jsx(cre,{scope:t,anchor:r,onAnchorChange:i,placementState:o,setPlacementState:a,children:n})};Az.displayName=bS;var Cz="PopperAnchor",Rz=S.forwardRef((e,t)=>{const{__scopePopper:n,virtualRef:r,...i}=e,o=Iz(Cz,n),a=S.useRef(null),s=o.onAnchorChange,l=S.useCallback(h=>{a.current=h,h&&s(h)},[s]),u=Xe(t,l),f=S.useRef(null);S.useEffect(()=>{if(!r)return;const h=f.current;f.current=r.current,h!==f.current&&s(f.current)});const c=o.placementState&&xS(o.placementState),d=c==null?void 0:c[0],p=c==null?void 0:c[1];return r?null:z.jsx(He.div,{"data-radix-popper-side":d,"data-radix-popper-align":p,...i,ref:u})});Rz.displayName=Cz;var wS="PopperContent",[fre,dre]=Oz(wS),Nz=S.forwardRef((e,t)=>{var oe,Y,ne,Q,Z,$e;const{__scopePopper:n,side:r="bottom",sideOffset:i=0,align:o="center",alignOffset:a=0,arrowPadding:s=0,avoidCollisions:l=!0,collisionBoundary:u=[],collisionPadding:f=0,sticky:c="partial",hideWhenDetached:d=!1,updatePositionStrategy:p="optimized",onPlaced:h,...m}=e,g=Iz(wS,n),[v,y]=S.useState(null),b=Xe(t,ve=>y(ve)),[_,x]=S.useState(null),w=kM(_),$=(w==null?void 0:w.width)??0,k=(w==null?void 0:w.height)??0,T=r+(o!=="center"?"-"+o:""),E=typeof f=="number"?f:{top:0,right:0,bottom:0,left:0,...f},I=Array.isArray(u)?u:[u],O=I.length>0,j={padding:E,boundary:I.filter(hre),altBoundary:O},{refs:D,floatingStyles:M,placement:L,isPositioned:B,middlewareData:R}=X_({strategy:"fixed",placement:T,whileElementsMounted:(...ve)=>Y_(...ve,{animationFrame:p==="always"}),elements:{reference:g.anchor},middleware:[J_({mainAxis:i+k,alignmentAxis:a}),l&&Q_({mainAxis:!0,crossAxis:!1,limiter:c==="partial"?eS():void 0,...j}),l&&tS({...j}),nS({...j,apply:({elements:ve,rects:Me,availableWidth:Ue,availableHeight:Qe})=>{const{width:fn,height:Sn}=Me.reference,Ft=ve.floating.style;Ft.setProperty("--radix-popper-available-width",`${Ue}px`),Ft.setProperty("--radix-popper-available-height",`${Qe}px`),Ft.setProperty("--radix-popper-anchor-width",`${fn}px`),Ft.setProperty("--radix-popper-anchor-height",`${Sn}px`)}}),_&&iS({element:_,padding:s}),mre({arrowWidth:$,arrowHeight:k}),d&&rS({strategy:"referenceHidden",...j,boundary:O?j.boundary:void 0})]}),U=g.setPlacementState;Dt(()=>(U(L),()=>{U(void 0)}),[L,U]);const[P,G]=xS(L),V=ei(h);Dt(()=>{B&&(V==null||V())},[B,V]);const A=(oe=R.arrow)==null?void 0:oe.x,pe=(Y=R.arrow)==null?void 0:Y.y,me=((ne=R.arrow)==null?void 0:ne.centerOffset)!==0,[ce,ie]=S.useState();return Dt(()=>{v&&ie(window.getComputedStyle(v).zIndex)},[v]),z.jsx("div",{ref:D.setFloating,"data-radix-popper-content-wrapper":"",style:{...M,transform:B?M.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:ce,"--radix-popper-transform-origin":[(Q=R.transformOrigin)==null?void 0:Q.x,(Z=R.transformOrigin)==null?void 0:Z.y].join(" "),...(($e=R.hide)==null?void 0:$e.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:e.dir,children:z.jsx(fre,{scope:n,placedSide:P,placedAlign:G,onArrowChange:x,arrowX:A,arrowY:pe,shouldHideArrow:me,children:z.jsx(He.div,{"data-side":P,"data-align":G,...m,ref:b,style:{...m.style,animation:B?void 0:"none"}})})})});Nz.displayName=wS;var jz="PopperArrow",pre={top:"bottom",right:"left",bottom:"top",left:"right"},Dz=S.forwardRef(function(t,n){const{__scopePopper:r,...i}=t,o=dre(jz,r),a=pre[o.placedSide];return z.jsx("span",{ref:o.onArrowChange,style:{position:"absolute",left:o.arrowX,top:o.arrowY,[a]:0,transformOrigin:{top:"",right:"0 0",bottom:"center 0",left:"100% 0"}[o.placedSide],transform:{top:"translateY(100%)",right:"translateY(50%) rotate(90deg) translateX(-50%)",bottom:"rotate(180deg)",left:"translateY(50%) rotate(-90deg) translateX(50%)"}[o.placedSide],visibility:o.shouldHideArrow?"hidden":void 0},children:z.jsx(ure,{...i,ref:n,style:{...i.style,display:"block"}})})});Dz.displayName=jz;function hre(e){return e!==null}var mre=e=>({name:"transformOrigin",options:e,fn(t){var g,v,y;const{placement:n,rects:r,middlewareData:i}=t,a=((g=i.arrow)==null?void 0:g.centerOffset)!==0,s=a?0:e.arrowWidth,l=a?0:e.arrowHeight,[u,f]=xS(n),c={start:"0%",center:"50%",end:"100%"}[f],d=(((v=i.arrow)==null?void 0:v.x)??0)+s/2,p=(((y=i.arrow)==null?void 0:y.y)??0)+l/2;let h="",m="";return u==="bottom"?(h=a?c:`${d}px`,m=`${-l}px`):u==="top"?(h=a?c:`${d}px`,m=`${r.floating.height+l}px`):u==="right"?(h=`${-l}px`,m=a?c:`${p}px`):u==="left"&&(h=`${r.floating.width+l}px`,m=a?c:`${p}px`),{data:{x:h,y:m}}}});function xS(e){const[t,n="center"]=e.split("-");return[t,n]}var vre=Az,gre=Rz,yre=Nz,bre=Dz;function wre(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}var Mz=Object.freeze({position:"absolute",border:0,width:1,height:1,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",wordWrap:"normal"}),xre="VisuallyHidden",_re=S.forwardRef((e,t)=>z.jsx(He.span,{...e,ref:t,style:{...Mz,...e.style}}));_re.displayName=xre;var Sre=[" ","Enter","ArrowUp","ArrowDown"],$re=[" ","Enter"],ts="Select",[Yv,Xv,kre]=HM(ts),[hs]=ra(ts,[kre,Tz]),Jv=Tz(),[Ere,aa]=hs(ts),[Pre,Ore]=hs(ts),Tre="SelectProvider";function zz(e){const{__scopeSelect:t,children:n,open:r,defaultOpen:i,onOpenChange:o,value:a,defaultValue:s,onValueChange:l,dir:u,name:f,autoComplete:c,disabled:d,required:p,form:h,internal_do_not_use_render:m}=e,g=Jv(t),[v,y]=S.useState(null),[b,_]=S.useState(null),[x,w]=S.useState(!1),$=cS(u),[k,T]=$l({prop:r,defaultProp:i??!1,onChange:o,caller:ts}),[E,I]=$l({prop:a,defaultProp:s,onChange:l,caller:ts}),O=S.useRef(null),j=v?!!h||!!v.closest("form"):!0,[D,M]=S.useState(new Set),L=Bi(),B=Array.from(D).map(G=>G.props.value).join(";"),R=S.useCallback(G=>{M(V=>new Set(V).add(G))},[]),U=S.useCallback(G=>{M(V=>{const A=new Set(V);return A.delete(G),A})},[]),P={required:p,trigger:v,onTriggerChange:y,valueNode:b,onValueNodeChange:_,valueNodeHasChildren:x,onValueNodeHasChildrenChange:w,contentId:L,value:E,onValueChange:I,open:k,onOpenChange:T,dir:$,triggerPointerDownPosRef:O,disabled:d,name:f,autoComplete:c,form:h,nativeOptions:D,nativeSelectKey:B,isFormControl:j};return z.jsx(vre,{...g,children:z.jsx(Ere,{scope:t,...P,children:z.jsx(Yv.Provider,{scope:t,children:z.jsx(Pre,{scope:t,onNativeOptionAdd:R,onNativeOptionRemove:U,children:iie(m)?m(P):n})})})})}zz.displayName=Tre;var Ire=e=>{const{__scopeSelect:t,children:n,...r}=e;return z.jsx(zz,{__scopeSelect:t,...r,internal_do_not_use_render:({isFormControl:i})=>z.jsxs(z.Fragment,{children:[n,i?z.jsx(Qz,{__scopeSelect:t}):null]})})};Ire.displayName=ts;var Lz="SelectTrigger",Are=S.forwardRef((e,t)=>{const{__scopeSelect:n,disabled:r=!1,...i}=e,o=Jv(n),a=aa(Lz,n),s=a.disabled||r,l=Xe(t,a.onTriggerChange),u=Xv(n),f=S.useRef("touch"),[c,d,p]=eL(m=>{const g=u().filter(b=>!b.disabled),v=g.find(b=>b.value===a.value),y=tL(g,m,v);y!==void 0&&a.onValueChange(y.value)}),h=m=>{s||(a.onOpenChange(!0),p()),m&&(a.triggerPointerDownPosRef.current={x:Math.round(m.pageX),y:Math.round(m.pageY)})};return z.jsx(gre,{asChild:!0,...o,children:z.jsx(He.button,{type:"button",role:"combobox","aria-controls":a.open?a.contentId:void 0,"aria-expanded":a.open,"aria-required":a.required,"aria-autocomplete":"none",dir:a.dir,"data-state":a.open?"open":"closed",disabled:s,"data-disabled":s?"":void 0,"data-placeholder":Qv(a.value)?"":void 0,...i,ref:l,onClick:Ee(i.onClick,m=>{m.currentTarget.focus(),f.current!=="mouse"&&h(m)}),onPointerDown:Ee(i.onPointerDown,m=>{f.current=m.pointerType;const g=m.target;g.hasPointerCapture(m.pointerId)&&g.releasePointerCapture(m.pointerId),m.button===0&&m.ctrlKey===!1&&m.pointerType==="mouse"&&(h(m),m.preventDefault())}),onKeyDown:Ee(i.onKeyDown,m=>{const g=c.current!=="";!(m.ctrlKey||m.altKey||m.metaKey)&&m.key.length===1&&d(m.key),!(g&&m.key===" ")&&Sre.includes(m.key)&&(h(),m.preventDefault())})})})});Are.displayName=Lz;var Uz="SelectValue",Cre=S.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,children:o,placeholder:a="",...s}=e,l=aa(Uz,n),{onValueNodeHasChildrenChange:u}=l,f=o!==void 0,c=Xe(t,l.onValueNodeChange);Dt(()=>{u(f)},[u,f]);const d=Qv(l.value);return z.jsx(He.span,{...s,asChild:d?!1:s.asChild,ref:c,style:{pointerEvents:"none"},children:z.jsx(S.Fragment,{children:d?a:o},d?"placeholder":"value")})});Cre.displayName=Uz;var Rre="SelectIcon",Nre=S.forwardRef((e,t)=>{const{__scopeSelect:n,children:r,...i}=e;return z.jsx(He.span,{"aria-hidden":!0,...i,ref:t,children:r||"▼"})});Nre.displayName=Rre;var Fz="SelectPortal",[jre,Dre]=hs(Fz,{forceMount:void 0}),Mre=e=>{const{__scopeSelect:t,forceMount:n,...r}=e;return z.jsx(jre,{scope:e.__scopeSelect,forceMount:n,children:z.jsx(mS,{asChild:!0,...r})})};Mre.displayName=Fz;var qo="SelectContent",zre=S.forwardRef((e,t)=>{const n=Dre(qo,e.__scopeSelect),{forceMount:r=n.forceMount,...i}=e,o=aa(qo,e.__scopeSelect),[a,s]=S.useState();return Dt(()=>{s(new DocumentFragment)},[]),z.jsx(oa,{present:r||o.open,children:({present:l})=>l?z.jsx(Wz,{...i,ref:t}):z.jsx(Bz,{...i,fragment:a})})});zre.displayName=qo;var Bz=S.forwardRef((e,t)=>{const{__scopeSelect:n,children:r,fragment:i}=e;return i?$i.createPortal(z.jsx(Zz,{scope:n,children:z.jsx(Yv.Slot,{scope:n,children:z.jsx("div",{ref:t,children:r})})}),i):null});Bz.displayName="SelectContentFragment";var Ur=10,[Zz,sa]=hs(qo),Lre="SelectContentImpl",Ure=of("SelectContent.RemoveScroll"),Wz=S.forwardRef((e,t)=>{const{__scopeSelect:n}=e,{position:r="item-aligned",onCloseAutoFocus:i,onEscapeKeyDown:o,onPointerDownOutside:a,side:s,sideOffset:l,align:u,alignOffset:f,arrowPadding:c,collisionBoundary:d,collisionPadding:p,sticky:h,hideWhenDetached:m,avoidCollisions:g,...v}=e,y=aa(qo,n),[b,_]=S.useState(null),[x,w]=S.useState(null),$=Xe(t,ie=>_(ie)),[k,T]=S.useState(null),[E,I]=S.useState(null),O=Xv(n),[j,D]=S.useState(!1),M=S.useRef(!1);S.useEffect(()=>{if(b)return vS(b)},[b]),cz();const L=S.useCallback(ie=>{const[oe,...Y]=O().map(Z=>Z.ref.current),[ne]=Y.slice(-1),Q=document.activeElement;for(const Z of ie)if(Z===Q||(Z==null||Z.scrollIntoView({block:"nearest"}),Z===oe&&x&&(x.scrollTop=0),Z===ne&&x&&(x.scrollTop=x.scrollHeight),Z==null||Z.focus(),document.activeElement!==Q))return},[O,x]),B=S.useCallback(()=>L([k,b]),[L,k,b]);S.useEffect(()=>{j&&B()},[j,B]);const{onOpenChange:R,triggerPointerDownPosRef:U}=y;S.useEffect(()=>{if(b){let ie={x:0,y:0};const oe=ne=>{var Q,Z;ie={x:Math.abs(Math.round(ne.pageX)-(((Q=U.current)==null?void 0:Q.x)??0)),y:Math.abs(Math.round(ne.pageY)-(((Z=U.current)==null?void 0:Z.y)??0))}},Y=ne=>{ie.x<=10&&ie.y<=10?ne.preventDefault():ne.composedPath().includes(b)||R(!1),document.removeEventListener("pointermove",oe),U.current=null};return U.current!==null&&(document.addEventListener("pointermove",oe),document.addEventListener("pointerup",Y,{capture:!0,once:!0})),()=>{document.removeEventListener("pointermove",oe),document.removeEventListener("pointerup",Y,{capture:!0})}}},[b,R,U]),S.useEffect(()=>{const ie=()=>R(!1);return window.addEventListener("blur",ie),window.addEventListener("resize",ie),()=>{window.removeEventListener("blur",ie),window.removeEventListener("resize",ie)}},[R]);const[P,G]=eL(ie=>{const oe=O().filter(Q=>!Q.disabled),Y=oe.find(Q=>Q.ref.current===document.activeElement),ne=tL(oe,ie,Y);ne&&setTimeout(()=>{var Q;return(Q=ne.ref.current)==null?void 0:Q.focus()})}),V=S.useCallback((ie,oe,Y)=>{const ne=!M.current&&!Y;(y.value!==void 0&&y.value===oe||ne)&&(T(ie),ne&&(M.current=!0))},[y.value]),A=S.useCallback(()=>b==null?void 0:b.focus(),[b]),pe=S.useCallback((ie,oe,Y)=>{const ne=!M.current&&!Y;(y.value!==void 0&&y.value===oe||ne)&&I(ie)},[y.value]),me=r==="popper"?Cw:Hz,ce=me===Cw?{side:s,sideOffset:l,align:u,alignOffset:f,arrowPadding:c,collisionBoundary:d,collisionPadding:p,sticky:h,hideWhenDetached:m,avoidCollisions:g}:{};return z.jsx(Zz,{scope:n,content:b,viewport:x,onViewportChange:w,itemRefCallback:V,selectedItem:k,onItemLeave:A,itemTextRefCallback:pe,focusSelectedItem:B,selectedItemText:E,position:r,isPositioned:j,searchRef:P,children:z.jsx(Gv,{as:Ure,allowPinchZoom:!0,children:z.jsx(hS,{asChild:!0,trapped:y.open,onMountAutoFocus:ie=>{ie.preventDefault()},onUnmountAutoFocus:Ee(i,ie=>{var oe;(oe=y.trigger)==null||oe.focus({preventScroll:!0}),ie.preventDefault()}),children:z.jsx(pS,{asChild:!0,disableOutsidePointerEvents:!0,onEscapeKeyDown:o,onPointerDownOutside:a,onFocusOutside:ie=>ie.preventDefault(),onDismiss:()=>y.onOpenChange(!1),children:z.jsx(me,{role:"listbox",id:y.contentId,"data-state":y.open?"open":"closed",dir:y.dir,onContextMenu:ie=>ie.preventDefault(),...v,...ce,onPlaced:()=>D(!0),ref:$,style:{display:"flex",flexDirection:"column",outline:"none",...v.style},onKeyDown:Ee(v.onKeyDown,ie=>{const oe=ie.ctrlKey||ie.altKey||ie.metaKey;if(ie.key==="Tab"&&ie.preventDefault(),!oe&&ie.key.length===1&&G(ie.key),["ArrowUp","ArrowDown","Home","End"].includes(ie.key)){let ne=O().filter(Q=>!Q.disabled).map(Q=>Q.ref.current);if(["ArrowUp","End"].includes(ie.key)&&(ne=ne.slice().reverse()),["ArrowUp","ArrowDown"].includes(ie.key)){const Q=ie.target,Z=ne.indexOf(Q);ne=ne.slice(Z+1)}setTimeout(()=>L(ne)),ie.preventDefault()}})})})})})})});Wz.displayName=Lre;var Fre="SelectItemAlignedPosition",Hz=S.forwardRef((e,t)=>{const{__scopeSelect:n,onPlaced:r,...i}=e,o=aa(qo,n),a=sa(qo,n),[s,l]=S.useState(null),[u,f]=S.useState(null),c=Xe(t,$=>f($)),d=Xv(n),p=S.useRef(!1),h=S.useRef(!0),{viewport:m,selectedItem:g,selectedItemText:v,focusSelectedItem:y}=a,b=S.useCallback(()=>{if(o.trigger&&o.valueNode&&s&&u&&m&&g&&v){const $=o.trigger.getBoundingClientRect(),k=u.getBoundingClientRect(),T=o.valueNode.getBoundingClientRect(),E=v.getBoundingClientRect();if(o.dir!=="rtl"){const Q=E.left-k.left,Z=T.left-Q,$e=$.left-Z,ve=$.width+$e,Me=Math.max(ve,k.width),Ue=window.innerWidth-Ur,Qe=qT(Z,[Ur,Math.max(Ur,Ue-Me)]);s.style.minWidth=ve+"px",s.style.left=Qe+"px"}else{const Q=k.right-E.right,Z=window.innerWidth-T.right-Q,$e=window.innerWidth-$.right-Z,ve=$.width+$e,Me=Math.max(ve,k.width),Ue=window.innerWidth-Ur,Qe=qT(Z,[Ur,Math.max(Ur,Ue-Me)]);s.style.minWidth=ve+"px",s.style.right=Qe+"px"}const I=d(),O=window.innerHeight-Ur*2,j=m.scrollHeight,D=window.getComputedStyle(u),M=parseInt(D.borderTopWidth,10),L=parseInt(D.paddingTop,10),B=parseInt(D.borderBottomWidth,10),R=parseInt(D.paddingBottom,10),U=M+L+j+R+B,P=Math.min(g.offsetHeight*5,U),G=window.getComputedStyle(m),V=parseInt(G.paddingTop,10),A=parseInt(G.paddingBottom,10),pe=$.top+$.height/2-Ur,me=O-pe,ce=g.offsetHeight/2,ie=g.offsetTop+ce,oe=M+L+ie,Y=U-oe;if(oe<=pe){const Q=I.length>0&&g===I[I.length-1].ref.current;s.style.bottom="0px";const Z=u.clientHeight-m.offsetTop-m.offsetHeight,$e=Math.max(me,ce+(Q?A:0)+Z+B),ve=oe+$e;s.style.height=ve+"px"}else{const Q=I.length>0&&g===I[0].ref.current;s.style.top="0px";const $e=Math.max(pe,M+m.offsetTop+(Q?V:0)+ce)+Y;s.style.height=$e+"px",m.scrollTop=oe-pe+m.offsetTop}s.style.margin=`${Ur}px 0`,s.style.minHeight=P+"px",s.style.maxHeight=O+"px",r==null||r(),requestAnimationFrame(()=>p.current=!0)}},[d,o.trigger,o.valueNode,s,u,m,g,v,o.dir,r]);Dt(()=>b(),[b]);const[_,x]=S.useState();Dt(()=>{u&&x(window.getComputedStyle(u).zIndex)},[u]);const w=S.useCallback($=>{$&&h.current===!0&&(b(),y==null||y(),h.current=!1)},[b,y]);return z.jsx(Zre,{scope:n,contentWrapper:s,shouldExpandOnScrollRef:p,onScrollButtonChange:w,children:z.jsx("div",{ref:l,style:{display:"flex",flexDirection:"column",position:"fixed",zIndex:_},children:z.jsx(He.div,{...i,ref:c,style:{boxSizing:"border-box",maxHeight:"100%",...i.style}})})})});Hz.displayName=Fre;var Bre="SelectPopperPosition",Cw=S.forwardRef((e,t)=>{const{__scopeSelect:n,align:r="start",collisionPadding:i=Ur,...o}=e,a=Jv(n);return z.jsx(yre,{...a,...o,ref:t,align:r,collisionPadding:i,style:{boxSizing:"border-box",...o.style,"--radix-select-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-select-content-available-width":"var(--radix-popper-available-width)","--radix-select-content-available-height":"var(--radix-popper-available-height)","--radix-select-trigger-width":"var(--radix-popper-anchor-width)","--radix-select-trigger-height":"var(--radix-popper-anchor-height)"}})});Cw.displayName=Bre;var[Zre,_S]=hs(qo,{}),Rw="SelectViewport",Wre=S.forwardRef((e,t)=>{const{__scopeSelect:n,nonce:r,...i}=e,o=sa(Rw,n),a=_S(Rw,n),s=Xe(t,o.onViewportChange),l=S.useRef(0);return z.jsxs(z.Fragment,{children:[z.jsx("style",{dangerouslySetInnerHTML:{__html:"[data-radix-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-select-viewport]::-webkit-scrollbar{display:none}"},nonce:r}),z.jsx(Yv.Slot,{scope:n,children:z.jsx(He.div,{"data-radix-select-viewport":"",role:"presentation",...i,ref:s,style:{position:"relative",flex:1,overflow:"hidden auto",...i.style},onScroll:Ee(i.onScroll,u=>{const f=u.currentTarget,{contentWrapper:c,shouldExpandOnScrollRef:d}=a;if(d!=null&&d.current&&c){const p=Math.abs(l.current-f.scrollTop);if(p>0){const h=window.innerHeight-Ur*2,m=parseFloat(c.style.minHeight),g=parseFloat(c.style.height),v=Math.max(m,g);if(v0?_:0,c.style.justifyContent="flex-end")}}}l.current=f.scrollTop})})})]})});Wre.displayName=Rw;var Vz="SelectGroup",[Hre,Vre]=hs(Vz),qre=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Bi();return z.jsx(Hre,{scope:n,id:i,children:z.jsx(He.div,{role:"group","aria-labelledby":i,...r,ref:t})})});qre.displayName=Vz;var qz="SelectLabel",Gre=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Vre(qz,n);return z.jsx(He.div,{id:i.id,...r,ref:t})});Gre.displayName=qz;var em="SelectItem",[Kre,Gz]=hs(em),Yre=S.forwardRef((e,t)=>{const{__scopeSelect:n,value:r,disabled:i=!1,textValue:o,...a}=e,s=aa(em,n),l=sa(em,n),u=s.value===r,[f,c]=S.useState(o??""),[d,p]=S.useState(!1),h=Xe(t,y=>{var b;return(b=l.itemRefCallback)==null?void 0:b.call(l,y,r,i)}),m=Bi(),g=S.useRef("touch"),v=()=>{i||(s.onValueChange(r),s.onOpenChange(!1))};return z.jsx(Kre,{scope:n,value:r,disabled:i,textId:m,isSelected:u,onItemTextChange:S.useCallback(y=>{c(b=>b||((y==null?void 0:y.textContent)??"").trim())},[]),children:z.jsx(Yv.ItemSlot,{scope:n,value:r,disabled:i,textValue:f,children:z.jsx(He.div,{role:"option","aria-labelledby":m,"data-highlighted":d?"":void 0,"aria-selected":u&&d,"data-state":u?"checked":"unchecked","aria-disabled":i||void 0,"data-disabled":i?"":void 0,tabIndex:i?void 0:-1,...a,ref:h,onFocus:Ee(a.onFocus,()=>p(!0)),onBlur:Ee(a.onBlur,()=>p(!1)),onClick:Ee(a.onClick,()=>{g.current!=="mouse"&&v()}),onPointerUp:Ee(a.onPointerUp,()=>{g.current==="mouse"&&v()}),onPointerDown:Ee(a.onPointerDown,y=>{g.current=y.pointerType}),onPointerMove:Ee(a.onPointerMove,y=>{var b;g.current=y.pointerType,i?(b=l.onItemLeave)==null||b.call(l):g.current==="mouse"&&y.currentTarget.focus({preventScroll:!0})}),onPointerLeave:Ee(a.onPointerLeave,y=>{var b;y.currentTarget===document.activeElement&&((b=l.onItemLeave)==null||b.call(l))}),onKeyDown:Ee(a.onKeyDown,y=>{var _;((_=l.searchRef)==null?void 0:_.current)!==""&&y.key===" "||($re.includes(y.key)&&v(),y.key===" "&&y.preventDefault())})})})})});Yre.displayName=em;var fc="SelectItemText",Xre=S.forwardRef((e,t)=>{const{__scopeSelect:n,className:r,style:i,...o}=e,a=aa(fc,n),s=sa(fc,n),l=Gz(fc,n),u=Ore(fc,n),[f,c]=S.useState(null),d=Xe(t,v=>c(v),l.onItemTextChange,v=>{var y;return(y=s.itemTextRefCallback)==null?void 0:y.call(s,v,l.value,l.disabled)}),p=f==null?void 0:f.textContent,h=S.useMemo(()=>z.jsx("option",{value:l.value,disabled:l.disabled,children:p},l.value),[l.disabled,l.value,p]),{onNativeOptionAdd:m,onNativeOptionRemove:g}=u;return Dt(()=>(m(h),()=>g(h)),[m,g,h]),z.jsxs(z.Fragment,{children:[z.jsx(He.span,{id:l.textId,...o,ref:d}),l.isSelected&&a.valueNode&&!a.valueNodeHasChildren&&!Qv(a.value)?$i.createPortal(o.children,a.valueNode):null]})});Xre.displayName=fc;var Kz="SelectItemIndicator",Jre=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return Gz(Kz,n).isSelected?z.jsx(He.span,{"aria-hidden":!0,...r,ref:t}):null});Jre.displayName=Kz;var Nw="SelectScrollUpButton",Qre=S.forwardRef((e,t)=>{const n=sa(Nw,e.__scopeSelect),r=_S(Nw,e.__scopeSelect),[i,o]=S.useState(!1),a=Xe(t,r.onScrollButtonChange);return Dt(()=>{if(n.viewport&&n.isPositioned){let s=function(){const u=l.scrollTop>0;o(u)};const l=n.viewport;return s(),l.addEventListener("scroll",s),()=>l.removeEventListener("scroll",s)}},[n.viewport,n.isPositioned]),i?z.jsx(Yz,{...e,ref:a,onAutoScroll:()=>{const{viewport:s,selectedItem:l}=n;s&&l&&(s.scrollTop=s.scrollTop-l.offsetHeight)}}):null});Qre.displayName=Nw;var jw="SelectScrollDownButton",eie=S.forwardRef((e,t)=>{const n=sa(jw,e.__scopeSelect),r=_S(jw,e.__scopeSelect),[i,o]=S.useState(!1),a=Xe(t,r.onScrollButtonChange);return Dt(()=>{if(n.viewport&&n.isPositioned){let s=function(){const u=l.scrollHeight-l.clientHeight,f=Math.ceil(l.scrollTop)l.removeEventListener("scroll",s)}},[n.viewport,n.isPositioned]),i?z.jsx(Yz,{...e,ref:a,onAutoScroll:()=>{const{viewport:s,selectedItem:l}=n;s&&l&&(s.scrollTop=s.scrollTop+l.offsetHeight)}}):null});eie.displayName=jw;var Yz=S.forwardRef((e,t)=>{const{__scopeSelect:n,onAutoScroll:r,...i}=e,o=sa("SelectScrollButton",n),a=S.useRef(null),s=Xv(n),l=S.useCallback(()=>{a.current!==null&&(window.clearInterval(a.current),a.current=null)},[]);return S.useEffect(()=>()=>l(),[l]),Dt(()=>{var f;const u=s().find(c=>c.ref.current===document.activeElement);(f=u==null?void 0:u.ref.current)==null||f.scrollIntoView({block:"nearest"})},[s]),z.jsx(He.div,{"aria-hidden":!0,...i,ref:t,style:{flexShrink:0,...i.style},onPointerDown:Ee(i.onPointerDown,()=>{a.current===null&&(a.current=window.setInterval(r,50))}),onPointerMove:Ee(i.onPointerMove,()=>{var u;(u=o.onItemLeave)==null||u.call(o),a.current===null&&(a.current=window.setInterval(r,50))}),onPointerLeave:Ee(i.onPointerLeave,()=>{l()})})}),tie="SelectSeparator",nie=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e;return z.jsx(He.div,{"aria-hidden":!0,...r,ref:t})});nie.displayName=tie;var Xz="SelectArrow",rie=S.forwardRef((e,t)=>{const{__scopeSelect:n,...r}=e,i=Jv(n);return sa(Xz,n).position==="popper"?z.jsx(bre,{...i,...r,ref:t}):null});rie.displayName=Xz;var Jz="SelectBubbleInput",Qz=S.forwardRef(({__scopeSelect:e,...t},n)=>{const r=aa(Jz,e),{value:i,onValueChange:o,required:a,disabled:s,name:l,autoComplete:u,form:f}=r,{nativeOptions:c,nativeSelectKey:d}=r,p=S.useRef(null),h=Xe(n,p),m=i??"",g=wre(m),v=Array.from(c).some(y=>(y.props.value??"")==="");return S.useEffect(()=>{const y=p.current;if(!y)return;const b=window.HTMLSelectElement.prototype,x=Object.getOwnPropertyDescriptor(b,"value").set;if(g!==m&&x){const w=new Event("change",{bubbles:!0});x.call(y,m),y.dispatchEvent(w)}},[g,m]),z.jsxs(He.select,{"aria-hidden":!0,required:a,tabIndex:-1,name:l,autoComplete:u,disabled:s,form:f,onChange:y=>o(y.target.value),...t,style:{...Mz,...t.style},ref:h,defaultValue:m,children:[Qv(i)&&!v?z.jsx("option",{value:""}):null,Array.from(c)]},d)});Qz.displayName=Jz;function iie(e){return typeof e=="function"}function Qv(e){return e===""||e===void 0}function eL(e){const t=ei(e),n=S.useRef(""),r=S.useRef(0),i=S.useCallback(a=>{const s=n.current+a;t(s),function l(u){n.current=u,window.clearTimeout(r.current),u!==""&&(r.current=window.setTimeout(()=>l(""),1e3))}(s)},[t]),o=S.useCallback(()=>{n.current="",window.clearTimeout(r.current)},[]);return S.useEffect(()=>()=>window.clearTimeout(r.current),[]),[n,i,o]}function tL(e,t,n){const i=t.length>1&&Array.from(t).every(u=>u===t[0])?t[0]:t,o=n?e.indexOf(n):-1;let a=oie(e,Math.max(o,0));i.length===1&&(a=a.filter(u=>u!==n));const l=a.find(u=>u.textValue.toLowerCase().startsWith(i.toLowerCase()));return l!==n?l:void 0}function oie(e,t){return e.map((n,r)=>e[(t+r)%e.length])}var aie=Array.isArray,Wn=aie,sie=typeof To=="object"&&To&&To.Object===Object&&To,nL=sie,lie=nL,uie=typeof self=="object"&&self&&self.Object===Object&&self,cie=lie||uie||Function("return this")(),Pi=cie,fie=Pi,die=fie.Symbol,ld=die,GT=ld,rL=Object.prototype,pie=rL.hasOwnProperty,hie=rL.toString,Ku=GT?GT.toStringTag:void 0;function mie(e){var t=pie.call(e,Ku),n=e[Ku];try{e[Ku]=void 0;var r=!0}catch{}var i=hie.call(e);return r&&(t?e[Ku]=n:delete e[Ku]),i}var vie=mie,gie=Object.prototype,yie=gie.toString;function bie(e){return yie.call(e)}var wie=bie,KT=ld,xie=vie,_ie=wie,Sie="[object Null]",$ie="[object Undefined]",YT=KT?KT.toStringTag:void 0;function kie(e){return e==null?e===void 0?$ie:Sie:YT&&YT in Object(e)?xie(e):_ie(e)}var so=kie;function Eie(e){return e!=null&&typeof e=="object"}var lo=Eie,Pie=so,Oie=lo,Tie="[object Symbol]";function Iie(e){return typeof e=="symbol"||Oie(e)&&Pie(e)==Tie}var pu=Iie,Aie=Wn,Cie=pu,Rie=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Nie=/^\w*$/;function jie(e,t){if(Aie(e))return!1;var n=typeof e;return n=="number"||n=="symbol"||n=="boolean"||e==null||Cie(e)?!0:Nie.test(e)||!Rie.test(e)||t!=null&&e in Object(t)}var SS=jie;function Die(e){var t=typeof e;return e!=null&&(t=="object"||t=="function")}var la=Die;const hu=Ge(la);var Mie=so,zie=la,Lie="[object AsyncFunction]",Uie="[object Function]",Fie="[object GeneratorFunction]",Bie="[object Proxy]";function Zie(e){if(!zie(e))return!1;var t=Mie(e);return t==Uie||t==Fie||t==Lie||t==Bie}var $S=Zie;const Pe=Ge($S);var Wie=Pi,Hie=Wie["__core-js_shared__"],Vie=Hie,zb=Vie,XT=function(){var e=/[^.]+$/.exec(zb&&zb.keys&&zb.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}();function qie(e){return!!XT&&XT in e}var Gie=qie,Kie=Function.prototype,Yie=Kie.toString;function Xie(e){if(e!=null){try{return Yie.call(e)}catch{}try{return e+""}catch{}}return""}var iL=Xie,Jie=$S,Qie=Gie,eoe=la,toe=iL,noe=/[\\^$.*+?()[\]{}|]/g,roe=/^\[object .+?Constructor\]$/,ioe=Function.prototype,ooe=Object.prototype,aoe=ioe.toString,soe=ooe.hasOwnProperty,loe=RegExp("^"+aoe.call(soe).replace(noe,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function uoe(e){if(!eoe(e)||Qie(e))return!1;var t=Jie(e)?loe:roe;return t.test(toe(e))}var coe=uoe;function foe(e,t){return e==null?void 0:e[t]}var doe=foe,poe=coe,hoe=doe;function moe(e,t){var n=hoe(e,t);return poe(n)?n:void 0}var ms=moe,voe=ms,goe=voe(Object,"create"),eg=goe,JT=eg;function yoe(){this.__data__=JT?JT(null):{},this.size=0}var boe=yoe;function woe(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}var xoe=woe,_oe=eg,Soe="__lodash_hash_undefined__",$oe=Object.prototype,koe=$oe.hasOwnProperty;function Eoe(e){var t=this.__data__;if(_oe){var n=t[e];return n===Soe?void 0:n}return koe.call(t,e)?t[e]:void 0}var Poe=Eoe,Ooe=eg,Toe=Object.prototype,Ioe=Toe.hasOwnProperty;function Aoe(e){var t=this.__data__;return Ooe?t[e]!==void 0:Ioe.call(t,e)}var Coe=Aoe,Roe=eg,Noe="__lodash_hash_undefined__";function joe(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=Roe&&t===void 0?Noe:t,this}var Doe=joe,Moe=boe,zoe=xoe,Loe=Poe,Uoe=Coe,Foe=Doe;function mu(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t-1}var iae=rae,oae=tg;function aae(e,t){var n=this.__data__,r=oae(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}var sae=aae,lae=Woe,uae=Joe,cae=tae,fae=iae,dae=sae;function vu(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t`);var y=p.inactive?u:p.color;return N.createElement("li",Kw({className:g,style:c,key:"legend-item-".concat(h)},rm(r.props,p,h)),N.createElement(Uw,{width:a,height:a,viewBox:f,style:d},r.renderIcon(p)),N.createElement("span",{className:"recharts-legend-item-text",style:{color:y}},m?m(v,p,h):v))})}},{key:"render",value:function(){var r=this.props,i=r.payload,o=r.layout,a=r.align;if(!i||!i.length)return null;var s={padding:0,margin:0,textAlign:o==="horizontal"?a:"left"};return N.createElement("ul",{className:"recharts-default-legend",style:s},this.renderItems())}}])}(S.PureComponent);sf(LS,"displayName","Legend");sf(LS,"defaultProps",{iconSize:14,layout:"horizontal",align:"center",verticalAlign:"middle",inactiveColor:"#ccc"});var Hue=ng;function Vue(){this.__data__=new Hue,this.size=0}var que=Vue;function Gue(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}var Kue=Gue;function Yue(e){return this.__data__.get(e)}var Xue=Yue;function Jue(e){return this.__data__.has(e)}var Que=Jue,ece=ng,tce=ES,nce=PS,rce=200;function ice(e,t){var n=this.__data__;if(n instanceof ece){var r=n.__data__;if(!tce||r.lengths))return!1;var u=o.get(e),f=o.get(t);if(u&&f)return u==t&&f==e;var c=-1,d=!0,p=n&Ece?new _ce:void 0;for(o.set(e,t),o.set(t,e);++c-1&&e%1==0&&e-1&&e%1==0&&e<=Ife}var ZS=Afe,Cfe=so,Rfe=ZS,Nfe=lo,jfe="[object Arguments]",Dfe="[object Array]",Mfe="[object Boolean]",zfe="[object Date]",Lfe="[object Error]",Ufe="[object Function]",Ffe="[object Map]",Bfe="[object Number]",Zfe="[object Object]",Wfe="[object RegExp]",Hfe="[object Set]",Vfe="[object String]",qfe="[object WeakMap]",Gfe="[object ArrayBuffer]",Kfe="[object DataView]",Yfe="[object Float32Array]",Xfe="[object Float64Array]",Jfe="[object Int8Array]",Qfe="[object Int16Array]",ede="[object Int32Array]",tde="[object Uint8Array]",nde="[object Uint8ClampedArray]",rde="[object Uint16Array]",ide="[object Uint32Array]",pt={};pt[Yfe]=pt[Xfe]=pt[Jfe]=pt[Qfe]=pt[ede]=pt[tde]=pt[nde]=pt[rde]=pt[ide]=!0;pt[jfe]=pt[Dfe]=pt[Gfe]=pt[Mfe]=pt[Kfe]=pt[zfe]=pt[Lfe]=pt[Ufe]=pt[Ffe]=pt[Bfe]=pt[Zfe]=pt[Wfe]=pt[Hfe]=pt[Vfe]=pt[qfe]=!1;function ode(e){return Nfe(e)&&Rfe(e.length)&&!!pt[Cfe(e)]}var ade=ode;function sde(e){return function(t){return e(t)}}var KL=sde,dm={exports:{}};dm.exports;(function(e,t){var n=nL,r=t&&!t.nodeType&&t,i=r&&!0&&e&&!e.nodeType&&e,o=i&&i.exports===r,a=o&&n.process,s=function(){try{var l=i&&i.require&&i.require("util").types;return l||a&&a.binding&&a.binding("util")}catch{}}();e.exports=s})(dm,dm.exports);var lde=dm.exports,ude=ade,cde=KL,_I=lde,SI=_I&&_I.isTypedArray,fde=SI?cde(SI):ude,YL=fde,dde=mfe,pde=FS,hde=Wn,mde=GL,vde=BS,gde=YL,yde=Object.prototype,bde=yde.hasOwnProperty;function wde(e,t){var n=hde(e),r=!n&&pde(e),i=!n&&!r&&mde(e),o=!n&&!r&&!i&&gde(e),a=n||r||i||o,s=a?dde(e.length,String):[],l=s.length;for(var u in e)(t||bde.call(e,u))&&!(a&&(u=="length"||i&&(u=="offset"||u=="parent")||o&&(u=="buffer"||u=="byteLength"||u=="byteOffset")||vde(u,l)))&&s.push(u);return s}var xde=wde,_de=Object.prototype;function Sde(e){var t=e&&e.constructor,n=typeof t=="function"&&t.prototype||_de;return e===n}var $de=Sde;function kde(e,t){return function(n){return e(t(n))}}var XL=kde,Ede=XL,Pde=Ede(Object.keys,Object),Ode=Pde,Tde=$de,Ide=Ode,Ade=Object.prototype,Cde=Ade.hasOwnProperty;function Rde(e){if(!Tde(e))return Ide(e);var t=[];for(var n in Object(e))Cde.call(e,n)&&n!="constructor"&&t.push(n);return t}var Nde=Rde,jde=$S,Dde=ZS;function Mde(e){return e!=null&&Dde(e.length)&&!jde(e)}var ud=Mde,zde=xde,Lde=Nde,Ude=ud;function Fde(e){return Ude(e)?zde(e):Lde(e)}var bg=Fde,Bde=rfe,Zde=pfe,Wde=bg;function Hde(e){return Bde(e,Wde,Zde)}var Vde=Hde,$I=Vde,qde=1,Gde=Object.prototype,Kde=Gde.hasOwnProperty;function Yde(e,t,n,r,i,o){var a=n&qde,s=$I(e),l=s.length,u=$I(t),f=u.length;if(l!=f&&!a)return!1;for(var c=l;c--;){var d=s[c];if(!(a?d in t:Kde.call(t,d)))return!1}var p=o.get(e),h=o.get(t);if(p&&h)return p==t&&h==e;var m=!0;o.set(e,t),o.set(t,e);for(var g=a;++c-1}var Ghe=qhe;function Khe(e,t,n){for(var r=-1,i=e==null?0:e.length;++r=cme){var u=t?null:lme(e);if(u)return ume(u);a=!1,i=sme,l=new ime}else l=t?[]:s;e:for(;++r=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function Eme(e,t){if(e==null)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}function Pme(e){return e.value}function Ome(e,t){if(N.isValidElement(e))return N.cloneElement(e,t);if(typeof e=="function")return N.createElement(e,t);t.ref;var n=kme(t,gme);return N.createElement(LS,n)}var UI=1,cl=function(e){function t(){var n;yme(this,t);for(var r=arguments.length,i=new Array(r),o=0;oUI||Math.abs(i.height-this.lastBoundingBox.height)>UI)&&(this.lastBoundingBox.width=i.width,this.lastBoundingBox.height=i.height,r&&r(i)):(this.lastBoundingBox.width!==-1||this.lastBoundingBox.height!==-1)&&(this.lastBoundingBox.width=-1,this.lastBoundingBox.height=-1,r&&r(null))}},{key:"getBBoxSnapshot",value:function(){return this.lastBoundingBox.width>=0&&this.lastBoundingBox.height>=0?Ai({},this.lastBoundingBox):{width:0,height:0}}},{key:"getDefaultPosition",value:function(r){var i=this.props,o=i.layout,a=i.align,s=i.verticalAlign,l=i.margin,u=i.chartWidth,f=i.chartHeight,c,d;if(!r||(r.left===void 0||r.left===null)&&(r.right===void 0||r.right===null))if(a==="center"&&o==="vertical"){var p=this.getBBoxSnapshot();c={left:((u||0)-p.width)/2}}else c=a==="right"?{right:l&&l.right||0}:{left:l&&l.left||0};if(!r||(r.top===void 0||r.top===null)&&(r.bottom===void 0||r.bottom===null))if(s==="middle"){var h=this.getBBoxSnapshot();d={top:((f||0)-h.height)/2}}else d=s==="bottom"?{bottom:l&&l.bottom||0}:{top:l&&l.top||0};return Ai(Ai({},c),d)}},{key:"render",value:function(){var r=this,i=this.props,o=i.content,a=i.width,s=i.height,l=i.wrapperStyle,u=i.payloadUniqBy,f=i.payload,c=Ai(Ai({position:"absolute",width:a||"auto",height:s||"auto"},this.getDefaultPosition(l)),l);return N.createElement("div",{className:"recharts-legend-wrapper",style:c,ref:function(p){r.wrapperNode=p}},Ome(o,Ai(Ai({},this.props),{},{payload:i4(f,u,Pme)})))}}],[{key:"getWithHeight",value:function(r,i){var o=Ai(Ai({},this.defaultProps),r.props),a=o.layout;return a==="vertical"&&re(r.props.height)?{height:r.props.height}:a==="horizontal"?{width:r.props.width||i}:null}}])}(S.PureComponent);wg(cl,"displayName","Legend");wg(cl,"defaultProps",{iconSize:14,layout:"horizontal",align:"center",verticalAlign:"bottom"});var FI=ld,Tme=FS,Ime=Wn,BI=FI?FI.isConcatSpreadable:void 0;function Ame(e){return Ime(e)||Tme(e)||!!(BI&&e&&e[BI])}var Cme=Ame,Rme=VL,Nme=Cme;function s4(e,t,n,r,i){var o=-1,a=e.length;for(n||(n=Nme),i||(i=[]);++o0&&n(s)?t>1?s4(s,t-1,n,r,i):Rme(i,s):r||(i[i.length]=s)}return i}var l4=s4;function jme(e){return function(t,n,r){for(var i=-1,o=Object(t),a=r(t),s=a.length;s--;){var l=a[e?s:++i];if(n(o[l],l,o)===!1)break}return t}}var Dme=jme,Mme=Dme,zme=Mme(),Lme=zme,Ume=Lme,Fme=bg;function Bme(e,t){return e&&Ume(e,t,Fme)}var u4=Bme,Zme=ud;function Wme(e,t){return function(n,r){if(n==null)return n;if(!Zme(n))return e(n,r);for(var i=n.length,o=t?i:-1,a=Object(n);(t?o--:++ot||o&&a&&l&&!s&&!u||r&&a&&l||!n&&l||!i)return 1;if(!r&&!o&&!u&&e=s)return l;var u=n[r];return l*(u=="desc"?-1:1)}}return e.index-t.index}var ive=rve,Vb=TS,ove=IS,ave=ua,sve=c4,lve=Qme,uve=KL,cve=ive,fve=_u,dve=Wn;function pve(e,t,n){t.length?t=Vb(t,function(o){return dve(o)?function(a){return ove(a,o.length===1?o[0]:o)}:o}):t=[fve];var r=-1;t=Vb(t,uve(ave));var i=sve(e,function(o,a,s){var l=Vb(t,function(u){return u(o)});return{criteria:l,index:++r,value:o}});return lve(i,function(o,a){return cve(o,a,n)})}var hve=pve;function mve(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}var vve=mve,gve=vve,WI=Math.max;function yve(e,t,n){return t=WI(t===void 0?e.length-1:t,0),function(){for(var r=arguments,i=-1,o=WI(r.length-t,0),a=Array(o);++i0){if(++t>=Ove)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}var Cve=Ave,Rve=Pve,Nve=Cve,jve=Nve(Rve),Dve=jve,Mve=_u,zve=bve,Lve=Dve;function Uve(e,t){return Lve(zve(e,t,Mve),e+"")}var Fve=Uve,Bve=kS,Zve=ud,Wve=BS,Hve=la;function Vve(e,t,n){if(!Hve(n))return!1;var r=typeof t;return(r=="number"?Zve(n)&&Wve(t,n.length):r=="string"&&t in n)?Bve(n[t],e):!1}var xg=Vve,qve=l4,Gve=hve,Kve=Fve,VI=xg,Yve=Kve(function(e,t){if(e==null)return[];var n=t.length;return n>1&&VI(e,t[0],t[1])?t=[]:n>2&&VI(t[0],t[1],t[2])&&(t=[t[0]]),Gve(e,qve(t,1),[])}),Xve=Yve;const VS=Ge(Xve);function lf(e){"@babel/helpers - typeof";return lf=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},lf(e)}function rx(){return rx=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.x),"".concat(Yu,"-left"),re(n)&&t&&re(t.x)&&n=t.y),"".concat(Yu,"-top"),re(r)&&t&&re(t.y)&&rm?Math.max(f,l[r]):Math.max(c,l[r])}function dge(e){var t=e.translateX,n=e.translateY,r=e.useTranslate3d;return{transform:r?"translate3d(".concat(t,"px, ").concat(n,"px, 0)"):"translate(".concat(t,"px, ").concat(n,"px)")}}function pge(e){var t=e.allowEscapeViewBox,n=e.coordinate,r=e.offsetTopLeft,i=e.position,o=e.reverseDirection,a=e.tooltipBox,s=e.useTranslate3d,l=e.viewBox,u,f,c;return a.height>0&&a.width>0&&n?(f=KI({allowEscapeViewBox:t,coordinate:n,key:"x",offsetTopLeft:r,position:i,reverseDirection:o,tooltipDimension:a.width,viewBox:l,viewBoxDimension:l.width}),c=KI({allowEscapeViewBox:t,coordinate:n,key:"y",offsetTopLeft:r,position:i,reverseDirection:o,tooltipDimension:a.height,viewBox:l,viewBoxDimension:l.height}),u=dge({translateX:f,translateY:c,useTranslate3d:s})):u=cge,{cssProperties:u,cssClasses:fge({translateX:f,translateY:c,coordinate:n})}}function Il(e){"@babel/helpers - typeof";return Il=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Il(e)}function YI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function XI(e){for(var t=1;tJI||Math.abs(r.height-this.state.lastBoundingBox.height)>JI)&&this.setState({lastBoundingBox:{width:r.width,height:r.height}})}else(this.state.lastBoundingBox.width!==-1||this.state.lastBoundingBox.height!==-1)&&this.setState({lastBoundingBox:{width:-1,height:-1}})}},{key:"componentDidMount",value:function(){document.addEventListener("keydown",this.handleKeyDown),this.updateBBox()}},{key:"componentWillUnmount",value:function(){document.removeEventListener("keydown",this.handleKeyDown)}},{key:"componentDidUpdate",value:function(){var r,i;this.props.active&&this.updateBBox(),this.state.dismissed&&(((r=this.props.coordinate)===null||r===void 0?void 0:r.x)!==this.state.dismissedAtCoordinate.x||((i=this.props.coordinate)===null||i===void 0?void 0:i.y)!==this.state.dismissedAtCoordinate.y)&&(this.state.dismissed=!1)}},{key:"render",value:function(){var r=this,i=this.props,o=i.active,a=i.allowEscapeViewBox,s=i.animationDuration,l=i.animationEasing,u=i.children,f=i.coordinate,c=i.hasPayload,d=i.isAnimationActive,p=i.offset,h=i.position,m=i.reverseDirection,g=i.useTranslate3d,v=i.viewBox,y=i.wrapperStyle,b=pge({allowEscapeViewBox:a,coordinate:f,offsetTopLeft:p,position:h,reverseDirection:m,tooltipBox:this.state.lastBoundingBox,useTranslate3d:g,viewBox:v}),_=b.cssClasses,x=b.cssProperties,w=XI(XI({transition:d&&o?"transform ".concat(s,"ms ").concat(l):void 0},x),{},{pointerEvents:"none",visibility:!this.state.dismissed&&o&&c?"visible":"hidden",position:"absolute",top:0,left:0},y);return N.createElement("div",{tabIndex:-1,className:_,style:w,ref:function(k){r.wrapperNode=k}},u)}}])}(S.PureComponent),Sge=function(){return!(typeof window<"u"&&window.document&&window.document.createElement&&window.setTimeout)},vs={isSsr:Sge()};function Al(e){"@babel/helpers - typeof";return Al=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Al(e)}function QI(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function eA(e){for(var t=1;t0;return N.createElement(_ge,{allowEscapeViewBox:a,animationDuration:s,animationEasing:l,isAnimationActive:d,active:o,coordinate:f,hasPayload:w,offset:p,position:g,reverseDirection:v,useTranslate3d:y,viewBox:b,wrapperStyle:_},Rge(u,eA(eA({},this.props),{},{payload:x})))}}])}(S.PureComponent);qS(Ri,"displayName","Tooltip");qS(Ri,"defaultProps",{accessibilityLayer:!1,allowEscapeViewBox:{x:!1,y:!1},animationDuration:400,animationEasing:"ease",contentStyle:{},coordinate:{x:0,y:0},cursor:!0,cursorStyle:{},filterNull:!0,isAnimationActive:!vs.isSsr,itemStyle:{},labelStyle:{},offset:10,reverseDirection:{x:!1,y:!1},separator:" : ",trigger:"hover",useTranslate3d:!1,viewBox:{x:0,y:0,height:0,width:0},wrapperStyle:{}});var Nge=Pi,jge=function(){return Nge.Date.now()},Dge=jge,Mge=/\s/;function zge(e){for(var t=e.length;t--&&Mge.test(e.charAt(t)););return t}var Lge=zge,Uge=Lge,Fge=/^\s+/;function Bge(e){return e&&e.slice(0,Uge(e)+1).replace(Fge,"")}var Zge=Bge,Wge=Zge,tA=la,Hge=pu,nA=NaN,Vge=/^[-+]0x[0-9a-f]+$/i,qge=/^0b[01]+$/i,Gge=/^0o[0-7]+$/i,Kge=parseInt;function Yge(e){if(typeof e=="number")return e;if(Hge(e))return nA;if(tA(e)){var t=typeof e.valueOf=="function"?e.valueOf():e;e=tA(t)?t+"":t}if(typeof e!="string")return e===0?e:+e;e=Wge(e);var n=qge.test(e);return n||Gge.test(e)?Kge(e.slice(2),n?2:8):Vge.test(e)?nA:+e}var v4=Yge,Xge=la,Gb=Dge,rA=v4,Jge="Expected a function",Qge=Math.max,eye=Math.min;function tye(e,t,n){var r,i,o,a,s,l,u=0,f=!1,c=!1,d=!0;if(typeof e!="function")throw new TypeError(Jge);t=rA(t)||0,Xge(n)&&(f=!!n.leading,c="maxWait"in n,o=c?Qge(rA(n.maxWait)||0,t):o,d="trailing"in n?!!n.trailing:d);function p(w){var $=r,k=i;return r=i=void 0,u=w,a=e.apply(k,$),a}function h(w){return u=w,s=setTimeout(v,t),f?p(w):a}function m(w){var $=w-l,k=w-u,T=t-$;return c?eye(T,o-k):T}function g(w){var $=w-l,k=w-u;return l===void 0||$>=t||$<0||c&&k>=o}function v(){var w=Gb();if(g(w))return y(w);s=setTimeout(v,m(w))}function y(w){return s=void 0,d&&r?p(w):(r=i=void 0,a)}function b(){s!==void 0&&clearTimeout(s),u=0,r=l=i=s=void 0}function _(){return s===void 0?a:y(Gb())}function x(){var w=Gb(),$=g(w);if(r=arguments,i=this,l=w,$){if(s===void 0)return h(l);if(c)return clearTimeout(s),s=setTimeout(v,t),p(l)}return s===void 0&&(s=setTimeout(v,t)),a}return x.cancel=b,x.flush=_,x}var nye=tye,rye=nye,iye=la,oye="Expected a function";function aye(e,t,n){var r=!0,i=!0;if(typeof e!="function")throw new TypeError(oye);return iye(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),rye(e,t,{leading:r,maxWait:t,trailing:i})}var sye=aye;const g4=Ge(sye);function cf(e){"@babel/helpers - typeof";return cf=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},cf(e)}function iA(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function Cp(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(j=g4(j,m,{trailing:!0,leading:!1}));var D=new ResizeObserver(j),M=x.current.getBoundingClientRect(),L=M.width,B=M.height;return I(L,B),D.observe(x.current),function(){D.disconnect()}},[I,m]);var O=S.useMemo(function(){var j=T.containerWidth,D=T.containerHeight;if(j<0||D<0)return null;Wi(Ca(a)||Ca(l),`The width(%s) and height(%s) are both fixed numbers, +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function pye(e,t){if(e){if(typeof e=="string")return oA(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return oA(e,t)}}function oA(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&(j=g4(j,m,{trailing:!0,leading:!1}));var D=new ResizeObserver(j),M=x.current.getBoundingClientRect(),L=M.width,B=M.height;return I(L,B),D.observe(x.current),function(){D.disconnect()}},[I,m]);var O=S.useMemo(function(){var j=T.containerWidth,D=T.containerHeight;if(j<0||D<0)return null;Wi(Ca(a)||Ca(l),`The width(%s) and height(%s) are both fixed numbers, maybe you don't need to use a ResponsiveContainer.`,a,l),Wi(!n||n>0,"The aspect(%s) must be greater than zero.",n);var M=Ca(a)?j:a,L=Ca(l)?D:l;n&&n>0&&(M?L=M/n:L&&(M=L*n),d&&L>d&&(L=d)),Wi(M>0||L>0,`The width(%s) and height(%s) of chart should be greater than 0, please check the style of container, or the props width(%s) and height(%s), or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the @@ -553,7 +558,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function OOe(e,t){if(e){if(typeof e=="string")return Kx(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Kx(e,t)}}function TOe(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function IOe(e){if(Array.isArray(e))return Kx(e)}function Kx(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nc){p=[].concat(Rs(l.slice(0,h)),[c-m]);break}var g=p.length%2===0?[0,d]:[d];return[].concat(Rs(t.repeat(l,f)),Rs(p),g).map(function(v){return"".concat(v,"px")}).join(", ")}),Wr(n,"id",bu("recharts-line-")),Wr(n,"pathRef",function(a){n.mainCurve=a}),Wr(n,"handleAnimationEnd",function(){n.setState({isAnimationFinished:!0}),n.props.onAnimationEnd&&n.props.onAnimationEnd()}),Wr(n,"handleAnimationStart",function(){n.setState({isAnimationFinished:!1}),n.props.onAnimationStart&&n.props.onAnimationStart()}),n}return DOe(t,e),COe(t,[{key:"componentDidMount",value:function(){if(this.props.isAnimationActive){var r=this.getTotalLength();this.setState({totalLength:r})}}},{key:"componentDidUpdate",value:function(){if(this.props.isAnimationActive){var r=this.getTotalLength();r!==this.state.totalLength&&this.setState({totalLength:r})}}},{key:"getTotalLength",value:function(){var r=this.mainCurve;try{return r&&r.getTotalLength&&r.getTotalLength()||0}catch{return 0}}},{key:"renderErrorBar",value:function(r,i){if(this.props.isAnimationActive&&!this.state.isAnimationFinished)return null;var o=this.props,a=o.points,s=o.xAxis,l=o.yAxis,u=o.layout,f=o.children,c=$r(f,hd);if(!c)return null;var d=function(m,g){return{x:m.x,y:m.y,value:m.value,errorVal:In(m.payload,g)}},p={clipPath:r?"url(#clipPath-".concat(i,")"):null};return N.createElement(ct,p,c.map(function(h){return N.cloneElement(h,{key:"bar-".concat(h.props.dataKey),data:a,xAxis:s,yAxis:l,layout:u,dataPointFormatter:d})}))}},{key:"renderDots",value:function(r,i,o){var a=this.props.isAnimationActive;if(a&&!this.state.isAnimationFinished)return null;var s=this.props,l=s.dot,u=s.points,f=s.dataKey,c=Ie(this.props,!1),d=Ie(l,!0),p=u.map(function(m,g){var v=Gn(Gn(Gn({key:"dot-".concat(g),r:3},c),d),{},{index:g,cx:m.x,cy:m.y,value:m.value,dataKey:f,payload:m.payload,points:u});return t.renderDotItem(l,v)}),h={clipPath:r?"url(#clipPath-".concat(i?"":"dots-").concat(o,")"):null};return N.createElement(ct,Cc({className:"recharts-line-dots",key:"dots"},h),p)}},{key:"renderCurveStatically",value:function(r,i,o,a){var s=this.props,l=s.type,u=s.layout,f=s.connectNulls;s.ref;var c=$2(s,$Oe),d=Gn(Gn(Gn({},Ie(c,!0)),{},{fill:"none",className:"recharts-line-curve",clipPath:i?"url(#clipPath-".concat(o,")"):null,points:r},a),{},{type:l,layout:u,connectNulls:f});return N.createElement(pl,Cc({},d,{pathRef:this.pathRef}))}},{key:"renderCurveWithAnimation",value:function(r,i){var o=this,a=this.props,s=a.points,l=a.strokeDasharray,u=a.isAnimationActive,f=a.animationBegin,c=a.animationDuration,d=a.animationEasing,p=a.animationId,h=a.animateNewValues,m=a.width,g=a.height,v=this.state,y=v.prevPoints,b=v.totalLength;return N.createElement(Si,{begin:f,duration:c,isActive:u,easing:d,from:{t:0},to:{t:1},key:"line-".concat(p),onAnimationEnd:this.handleAnimationEnd,onAnimationStart:this.handleAnimationStart},function(_){var x=_.t;if(y){var w=y.length/s.length,$=s.map(function(O,j){var D=Math.floor(j*w);if(y[D]){var M=y[D],L=rn(M.x,O.x),B=rn(M.y,O.y);return Gn(Gn({},O),{},{x:L(x),y:B(x)})}if(h){var R=rn(m*2,O.x),U=rn(g/2,O.y);return Gn(Gn({},O),{},{x:R(x),y:U(x)})}return Gn(Gn({},O),{},{x:O.x,y:O.y})});return o.renderCurveStatically($,r,i)}var k=rn(0,b),T=k(x),E;if(l){var I="".concat(l).split(/[,\s]+/gim).map(function(O){return parseFloat(O)});E=o.getStrokeDasharray(T,b,I)}else E=o.generateSimpleStrokeDasharray(b,T);return o.renderCurveStatically(s,r,i,{strokeDasharray:E})})}},{key:"renderCurve",value:function(r,i){var o=this.props,a=o.points,s=o.isAnimationActive,l=this.state,u=l.prevPoints,f=l.totalLength;return s&&a&&a.length&&(!u&&f>0||!jl(u,a))?this.renderCurveWithAnimation(r,i):this.renderCurveStatically(a,r,i)}},{key:"render",value:function(){var r,i=this.props,o=i.hide,a=i.dot,s=i.points,l=i.className,u=i.xAxis,f=i.yAxis,c=i.top,d=i.left,p=i.width,h=i.height,m=i.isAnimationActive,g=i.id;if(o||!s||!s.length)return null;var v=this.state.isAnimationFinished,y=s.length===1,b=De("recharts-line",l),_=u&&u.allowDataOverflow,x=f&&f.allowDataOverflow,w=_||x,$=Ce(g)?this.id:g,k=(r=Ie(a,!1))!==null&&r!==void 0?r:{r:3,strokeWidth:2},T=k.r,E=T===void 0?3:T,I=k.strokeWidth,O=I===void 0?2:I,j=hL(a)?a:{},D=j.clipDot,M=D===void 0?!0:D,L=E*2+O;return N.createElement(ct,{className:b},_||x?N.createElement("defs",null,N.createElement("clipPath",{id:"clipPath-".concat($)},N.createElement("rect",{x:_?d:d-p/2,y:x?c:c-h/2,width:_?p:p*2,height:x?h:h*2})),!M&&N.createElement("clipPath",{id:"clipPath-dots-".concat($)},N.createElement("rect",{x:d-L/2,y:c-L/2,width:p+L,height:h+L}))):null,!y&&this.renderCurve(w,$),this.renderErrorBar(w,$),(y||a)&&this.renderDots(w,M,$),(!m||v)&&Vi.renderCallByParent(this.props,s))}}],[{key:"getDerivedStateFromProps",value:function(r,i){return r.animationId!==i.prevAnimationId?{prevAnimationId:r.animationId,curPoints:r.points,prevPoints:i.curPoints}:r.points!==i.curPoints?{curPoints:r.points}:null}},{key:"repeat",value:function(r,i){for(var o=r.length%2!==0?[].concat(Rs(r),[0]):r,a=[],s=0;s=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function UOe(e,t){if(e==null)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}function Da(){return Da=Object.assign?Object.assign.bind():function(e){for(var t=1;t0||!jl(f,a)||!jl(c,s))?this.renderAreaWithAnimation(r,i):this.renderAreaStatically(a,s,r,i)}},{key:"render",value:function(){var r,i=this.props,o=i.hide,a=i.dot,s=i.points,l=i.className,u=i.top,f=i.left,c=i.xAxis,d=i.yAxis,p=i.width,h=i.height,m=i.isAnimationActive,g=i.id;if(o||!s||!s.length)return null;var v=this.state.isAnimationFinished,y=s.length===1,b=De("recharts-area",l),_=c&&c.allowDataOverflow,x=d&&d.allowDataOverflow,w=_||x,$=Ce(g)?this.id:g,k=(r=Ie(a,!1))!==null&&r!==void 0?r:{r:3,strokeWidth:2},T=k.r,E=T===void 0?3:T,I=k.strokeWidth,O=I===void 0?2:I,j=hL(a)?a:{},D=j.clipDot,M=D===void 0?!0:D,L=E*2+O;return N.createElement(ct,{className:b},_||x?N.createElement("defs",null,N.createElement("clipPath",{id:"clipPath-".concat($)},N.createElement("rect",{x:_?f:f-p/2,y:x?u:u-h/2,width:_?p:p*2,height:x?h:h*2})),!M&&N.createElement("clipPath",{id:"clipPath-dots-".concat($)},N.createElement("rect",{x:f-L/2,y:u-L/2,width:p+L,height:h+L}))):null,y?null:this.renderArea(w,$),(a||y)&&this.renderDots(w,M,$),(!m||v)&&Vi.renderCallByParent(this.props,s))}}],[{key:"getDerivedStateFromProps",value:function(r,i){return r.animationId!==i.prevAnimationId?{prevAnimationId:r.animationId,curPoints:r.points,curBaseLine:r.baseLine,prevPoints:i.curPoints,prevBaseLine:i.curBaseLine}:r.points!==i.curPoints||r.baseLine!==i.curBaseLine?{curPoints:r.points,curBaseLine:r.baseLine}:null}}])}(S.PureComponent);hU=ws;mi(ws,"displayName","Area");mi(ws,"defaultProps",{stroke:"#3182bd",fill:"#3182bd",fillOpacity:.6,xAxisId:0,yAxisId:0,legendType:"line",connectNulls:!1,points:[],dot:!1,activeDot:!0,hide:!1,isAnimationActive:!vs.isSsr,animationBegin:0,animationDuration:1500,animationEasing:"ease"});mi(ws,"getBaseValue",function(e,t,n,r){var i=e.layout,o=e.baseValue,a=t.props.baseValue,s=a??o;if(re(s)&&typeof s=="number")return s;var l=i==="horizontal"?r:n,u=l.scale.domain();if(l.type==="number"){var f=Math.max(u[0],u[1]),c=Math.min(u[0],u[1]);return s==="dataMin"?c:s==="dataMax"||f<0?f:Math.max(Math.min(u[0],u[1]),0)}return s==="dataMin"?u[0]:s==="dataMax"?u[1]:u[0]});mi(ws,"getComposedData",function(e){var t=e.props,n=e.item,r=e.xAxis,i=e.yAxis,o=e.xAxisTicks,a=e.yAxisTicks,s=e.bandSize,l=e.dataKey,u=e.stackedData,f=e.dataStartIndex,c=e.displayedData,d=e.offset,p=t.layout,h=u&&u.length,m=hU.getBaseValue(t,n,r,i),g=p==="horizontal",v=!1,y=c.map(function(_,x){var w;h?w=u[f+x]:(w=In(_,l),Array.isArray(w)?v=!0:w=[m,w]);var $=w[1]==null||h&&In(_,l)==null;return g?{x:Cm({axis:r,ticks:o,bandSize:s,entry:_,index:x}),y:$?null:i.scale(w[1]),value:w,payload:_}:{x:$?null:r.scale(w[1]),y:Cm({axis:i,ticks:a,bandSize:s,entry:_,index:x}),value:w,payload:_}}),b;return h||v?b=y.map(function(_){var x=Array.isArray(_.value)?_.value[0]:null;return g?{x:_.x,y:x!=null&&_.y!=null?i.scale(x):null}:{x:x!=null?r.scale(x):null,y:_.y}}):b=g?i.scale(m):r.scale(m),wo({points:y,baseLine:b,layout:p,isRange:v},d)});mi(ws,"renderDotItem",function(e,t){var n;if(N.isValidElement(e))n=N.cloneElement(e,t);else if(Pe(e))n=e(t);else{var r=De("recharts-area-dot",typeof e!="boolean"?e.className:""),i=t.key,o=mU(t,LOe);n=N.createElement(Ag,Da({},o,{key:i,className:r}))}return n});function ql(e){"@babel/helpers - typeof";return ql=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},ql(e)}function GOe(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function KOe(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function jTe(e,t){if(e==null)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}function DTe(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function MTe(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0?a:t&&t.length&&re(i)&&re(o)?t.slice(i,o+1):[]};function AU(e){return e==="number"?[0,"auto"]:void 0}var a1=function(t,n,r,i){var o=t.graphicalItems,a=t.tooltipAxis,s=Ug(n,t);return r<0||!o||!o.length||r>=s.length?null:o.reduce(function(l,u){var f,c=(f=u.props.data)!==null&&f!==void 0?f:n;c&&t.dataStartIndex+t.dataEndIndex!==0&&t.dataEndIndex-t.dataStartIndex>=r&&(c=c.slice(t.dataStartIndex,t.dataEndIndex+1));var d;if(a.dataKey&&!a.allowDuplicatedCategory){var p=c===void 0?s:c;d=tm(p,a.dataKey,i)}else d=c&&c[r]||s[r];return d?[].concat(Yl(l),[S6(u,d)]):l},[])},N2=function(t,n,r,i){var o=i||{x:t.chartX,y:t.chartY},a=KTe(o,r),s=t.orderedTooltipTicks,l=t.tooltipAxis,u=t.tooltipTicks,f=Yxe(a,s,u,l);if(f>=0&&u){var c=u[f]&&u[f].value,d=a1(t,n,f,c),p=YTe(r,s,f,o);return{activeTooltipIndex:f,activeLabel:c,activePayload:d,activeCoordinate:p}}return null},XTe=function(t,n){var r=n.axes,i=n.graphicalItems,o=n.axisType,a=n.axisIdKey,s=n.stackGroups,l=n.dataStartIndex,u=n.dataEndIndex,f=t.layout,c=t.children,d=t.stackOffset,p=w6(f,o);return r.reduce(function(h,m){var g,v=m.type.defaultProps!==void 0?H(H({},m.type.defaultProps),m.props):m.props,y=v.type,b=v.dataKey,_=v.allowDataOverflow,x=v.allowDuplicatedCategory,w=v.scale,$=v.ticks,k=v.includeHidden,T=v[a];if(h[T])return h;var E=Ug(t.data,{graphicalItems:i.filter(function(V){var A,pe=a in V.props?V.props[a]:(A=V.type.defaultProps)===null||A===void 0?void 0:A[a];return pe===T}),dataStartIndex:l,dataEndIndex:u}),I=E.length,O,j,D;STe(v.domain,_,y)&&(O=Sx(v.domain,null,_),p&&(y==="number"||w!=="auto")&&(D=Ic(E,b,"category")));var M=AU(y);if(!O||O.length===0){var L,B=(L=v.domain)!==null&&L!==void 0?L:M;if(b){if(O=Ic(E,b,y),y==="category"&&p){var R=zse(O);x&&R?(j=O,O=Zm(0,I)):x||(O=pC(B,O,m).reduce(function(V,A){return V.indexOf(A)>=0?V:[].concat(Yl(V),[A])},[]))}else if(y==="category")x?O=O.filter(function(V){return V!==""&&!Ce(V)}):O=pC(B,O,m).reduce(function(V,A){return V.indexOf(A)>=0||A===""||Ce(A)?V:[].concat(Yl(V),[A])},[]);else if(y==="number"){var U=t1e(E,i.filter(function(V){var A,pe,me=a in V.props?V.props[a]:(A=V.type.defaultProps)===null||A===void 0?void 0:A[a],ce="hide"in V.props?V.props.hide:(pe=V.type.defaultProps)===null||pe===void 0?void 0:pe.hide;return me===T&&(k||!ce)}),b,o,f);U&&(O=U)}p&&(y==="number"||w!=="auto")&&(D=Ic(E,b,"category"))}else p?O=Zm(0,I):s&&s[T]&&s[T].hasStack&&y==="number"?O=d==="expand"?[0,1]:_6(s[T].stackGroups,l,u):O=b6(E,i.filter(function(V){var A=a in V.props?V.props[a]:V.type.defaultProps[a],pe="hide"in V.props?V.props.hide:V.type.defaultProps.hide;return A===T&&(k||!pe)}),y,f,!0);if(y==="number")O=r1(c,O,T,o,$),B&&(O=Sx(B,O,_));else if(y==="category"&&B){var P=B,G=O.every(function(V){return P.indexOf(V)>=0});G&&(O=P)}}return H(H({},h),{},be({},T,H(H({},v),{},{axisType:o,domain:O,categoricalDomain:D,duplicateDomain:j,originalDomain:(g=v.domain)!==null&&g!==void 0?g:M,isCategorical:p,layout:f})))},{})},JTe=function(t,n){var r=n.graphicalItems,i=n.Axis,o=n.axisType,a=n.axisIdKey,s=n.stackGroups,l=n.dataStartIndex,u=n.dataEndIndex,f=t.layout,c=t.children,d=Ug(t.data,{graphicalItems:r,dataStartIndex:l,dataEndIndex:u}),p=d.length,h=w6(f,o),m=-1;return r.reduce(function(g,v){var y=v.type.defaultProps!==void 0?H(H({},v.type.defaultProps),v.props):v.props,b=y[a],_=AU("number");if(!g[b]){m++;var x;return h?x=Zm(0,p):s&&s[b]&&s[b].hasStack?(x=_6(s[b].stackGroups,l,u),x=r1(c,x,b,o)):(x=Sx(_,b6(d,r.filter(function(w){var $,k,T=a in w.props?w.props[a]:($=w.type.defaultProps)===null||$===void 0?void 0:$[a],E="hide"in w.props?w.props.hide:(k=w.type.defaultProps)===null||k===void 0?void 0:k.hide;return T===b&&!E}),"number",f),i.defaultProps.allowDataOverflow),x=r1(c,x,b,o)),H(H({},g),{},be({},b,H(H({axisType:o},i.defaultProps),{},{hide:!0,orientation:Sr(qTe,"".concat(o,".").concat(m%2),null),domain:x,originalDomain:_,isCategorical:h,layout:f})))}return g},{})},QTe=function(t,n){var r=n.axisType,i=r===void 0?"xAxis":r,o=n.AxisComp,a=n.graphicalItems,s=n.stackGroups,l=n.dataStartIndex,u=n.dataEndIndex,f=t.children,c="".concat(i,"Id"),d=$r(f,o),p={};return d&&d.length?p=XTe(t,{axes:d,graphicalItems:a,axisType:i,axisIdKey:c,stackGroups:s,dataStartIndex:l,dataEndIndex:u}):a&&a.length&&(p=JTe(t,{Axis:o,graphicalItems:a,axisType:i,axisIdKey:c,stackGroups:s,dataStartIndex:l,dataEndIndex:u})),p},eIe=function(t){var n=Oo(t),r=Ui(n,!1,!0);return{tooltipTicks:r,orderedTooltipTicks:VS(r,function(i){return i.coordinate}),tooltipAxis:n,tooltipAxisBandSize:Rm(n,r)}},j2=function(t){var n=t.children,r=t.defaultShowTooltip,i=Xn(n,Ll),o=0,a=0;return t.data&&t.data.length!==0&&(a=t.data.length-1),i&&i.props&&(i.props.startIndex>=0&&(o=i.props.startIndex),i.props.endIndex>=0&&(a=i.props.endIndex)),{chartX:0,chartY:0,dataStartIndex:o,dataEndIndex:a,activeTooltipIndex:-1,isTooltipActive:!!r}},tIe=function(t){return!t||!t.length?!1:t.some(function(n){var r=Zi(n&&n.type);return r&&r.indexOf("Bar")>=0})},D2=function(t){return t==="horizontal"?{numericAxisName:"yAxis",cateAxisName:"xAxis"}:t==="vertical"?{numericAxisName:"xAxis",cateAxisName:"yAxis"}:t==="centric"?{numericAxisName:"radiusAxis",cateAxisName:"angleAxis"}:{numericAxisName:"angleAxis",cateAxisName:"radiusAxis"}},nIe=function(t,n){var r=t.props,i=t.graphicalItems,o=t.xAxisMap,a=o===void 0?{}:o,s=t.yAxisMap,l=s===void 0?{}:s,u=r.width,f=r.height,c=r.children,d=r.margin||{},p=Xn(c,Ll),h=Xn(c,cl),m=Object.keys(l).reduce(function(x,w){var $=l[w],k=$.orientation;return!$.mirror&&!$.hide?H(H({},x),{},be({},k,x[k]+$.width)):x},{left:d.left||0,right:d.right||0}),g=Object.keys(a).reduce(function(x,w){var $=a[w],k=$.orientation;return!$.mirror&&!$.hide?H(H({},x),{},be({},k,Sr(x,"".concat(k))+$.height)):x},{top:d.top||0,bottom:d.bottom||0}),v=H(H({},g),m),y=v.bottom;p&&(v.bottom+=p.props.height||Ll.defaultProps.height),h&&n&&(v=Qxe(v,i,r,n));var b=u-v.left-v.right,_=f-v.top-v.bottom;return H(H({brushBottom:y},v),{},{width:Math.max(b,0),height:Math.max(_,0)})},rIe=function(t,n){if(n==="xAxis")return t[n].width;if(n==="yAxis")return t[n].height},I$=function(t){var n=t.chartName,r=t.GraphicalChild,i=t.defaultTooltipEventType,o=i===void 0?"axis":i,a=t.validateTooltipEventTypes,s=a===void 0?["axis"]:a,l=t.axisComponents,u=t.legendContent,f=t.formatAxisMap,c=t.defaultProps,d=function(v,y){var b=y.graphicalItems,_=y.stackGroups,x=y.offset,w=y.updateId,$=y.dataStartIndex,k=y.dataEndIndex,T=v.barSize,E=v.layout,I=v.barGap,O=v.barCategoryGap,j=v.maxBarSize,D=D2(E),M=D.numericAxisName,L=D.cateAxisName,B=tIe(b),R=[];return b.forEach(function(U,P){var G=Ug(v.data,{graphicalItems:[U],dataStartIndex:$,dataEndIndex:k}),V=U.type.defaultProps!==void 0?H(H({},U.type.defaultProps),U.props):U.props,A=V.dataKey,pe=V.maxBarSize,me=V["".concat(M,"Id")],ce=V["".concat(L,"Id")],ie={},oe=l.reduce(function(jr,Rn){var Nu=y["".concat(Rn.axisType,"Map")],Xd=V["".concat(Rn.axisType,"Id")];Nu&&Nu[Xd]||Rn.axisType==="zAxis"||os();var Jd=Nu[Xd];return H(H({},jr),{},be(be({},Rn.axisType,Jd),"".concat(Rn.axisType,"Ticks"),Ui(Jd)))},ie),Y=oe[L],ne=oe["".concat(L,"Ticks")],Q=_&&_[me]&&_[me].hasStack&&p1e(U,_[me].stackGroups),Z=Zi(U.type).indexOf("Bar")>=0,$e=Rm(Y,ne),ve=[],Me=B&&Xxe({barSize:T,stackGroups:_,totalSize:rIe(oe,L)});if(Z){var Ue,Qe,fn=Ce(pe)?j:pe,Sn=(Ue=(Qe=Rm(Y,ne,!0))!==null&&Qe!==void 0?Qe:fn)!==null&&Ue!==void 0?Ue:0;ve=Jxe({barGap:I,barCategoryGap:O,bandSize:Sn!==$e?Sn:$e,sizeList:Me[ce],maxBarSize:fn}),Sn!==$e&&(ve=ve.map(function(jr){return H(H({},jr),{},{position:H(H({},jr.position),{},{offset:jr.position.offset-Sn/2})})}))}var Ft=U&&U.type&&U.type.getComposedData;Ft&&R.push({props:H(H({},Ft(H(H({},oe),{},{displayedData:G,props:v,dataKey:A,item:U,bandSize:$e,barPosition:ve,offset:x,stackedData:Q,layout:E,dataStartIndex:$,dataEndIndex:k}))),{},be(be(be({key:U.key||"item-".concat(P)},M,oe[M]),L,oe[L]),"animationId",w)),childIndex:Yse(U,v.children),item:U})}),R},p=function(v,y){var b=v.props,_=v.dataStartIndex,x=v.dataEndIndex,w=v.updateId;if(!sI({props:b}))return null;var $=b.children,k=b.layout,T=b.stackOffset,E=b.data,I=b.reverseStackOrder,O=D2(k),j=O.numericAxisName,D=O.cateAxisName,M=$r($,r),L=c1e(E,M,"".concat(j,"Id"),"".concat(D,"Id"),T,I),B=l.reduce(function(V,A){var pe="".concat(A.axisType,"Map");return H(H({},V),{},be({},pe,QTe(b,H(H({},A),{},{graphicalItems:M,stackGroups:A.axisType===j&&L,dataStartIndex:_,dataEndIndex:x}))))},{}),R=nIe(H(H({},B),{},{props:b,graphicalItems:M}),y==null?void 0:y.legendBBox);Object.keys(B).forEach(function(V){B[V]=f(b,B[V],R,V.replace("Map",""),n)});var U=B["".concat(D,"Map")],P=eIe(U),G=d(b,H(H({},B),{},{dataStartIndex:_,dataEndIndex:x,updateId:w,graphicalItems:M,stackGroups:L,offset:R}));return H(H({formattedGraphicalItems:G,graphicalItems:M,offset:R,stackGroups:L},P),B)},h=function(g){function v(y){var b,_,x;return DTe(this,v),x=LTe(this,v,[y]),be(x,"eventEmitterSymbol",Symbol("rechartsEventEmitter")),be(x,"accessibilityManager",new _Te),be(x,"handleLegendBBoxUpdate",function(w){if(w){var $=x.state,k=$.dataStartIndex,T=$.dataEndIndex,E=$.updateId;x.setState(H({legendBBox:w},p({props:x.props,dataStartIndex:k,dataEndIndex:T,updateId:E},H(H({},x.state),{},{legendBBox:w}))))}}),be(x,"handleReceiveSyncEvent",function(w,$,k){if(x.props.syncId===w){if(k===x.eventEmitterSymbol&&typeof x.props.syncMethod!="function")return;x.applySyncEvent($)}}),be(x,"handleBrushChange",function(w){var $=w.startIndex,k=w.endIndex;if($!==x.state.dataStartIndex||k!==x.state.dataEndIndex){var T=x.state.updateId;x.setState(function(){return H({dataStartIndex:$,dataEndIndex:k},p({props:x.props,dataStartIndex:$,dataEndIndex:k,updateId:T},x.state))}),x.triggerSyncEvent({dataStartIndex:$,dataEndIndex:k})}}),be(x,"handleMouseEnter",function(w){var $=x.getMouseInfo(w);if($){var k=H(H({},$),{},{isTooltipActive:!0});x.setState(k),x.triggerSyncEvent(k);var T=x.props.onMouseEnter;Pe(T)&&T(k,w)}}),be(x,"triggeredAfterMouseMove",function(w){var $=x.getMouseInfo(w),k=$?H(H({},$),{},{isTooltipActive:!0}):{isTooltipActive:!1};x.setState(k),x.triggerSyncEvent(k);var T=x.props.onMouseMove;Pe(T)&&T(k,w)}),be(x,"handleItemMouseEnter",function(w){x.setState(function(){return{isTooltipActive:!0,activeItem:w,activePayload:w.tooltipPayload,activeCoordinate:w.tooltipPosition||{x:w.cx,y:w.cy}}})}),be(x,"handleItemMouseLeave",function(){x.setState(function(){return{isTooltipActive:!1}})}),be(x,"handleMouseMove",function(w){w.persist(),x.throttleTriggeredAfterMouseMove(w)}),be(x,"handleMouseLeave",function(w){x.throttleTriggeredAfterMouseMove.cancel();var $={isTooltipActive:!1};x.setState($),x.triggerSyncEvent($);var k=x.props.onMouseLeave;Pe(k)&&k($,w)}),be(x,"handleOuterEvent",function(w){var $=Kse(w),k=Sr(x.props,"".concat($));if($&&Pe(k)){var T,E;/.*touch.*/i.test($)?E=x.getMouseInfo(w.changedTouches[0]):E=x.getMouseInfo(w),k((T=E)!==null&&T!==void 0?T:{},w)}}),be(x,"handleClick",function(w){var $=x.getMouseInfo(w);if($){var k=H(H({},$),{},{isTooltipActive:!0});x.setState(k),x.triggerSyncEvent(k);var T=x.props.onClick;Pe(T)&&T(k,w)}}),be(x,"handleMouseDown",function(w){var $=x.props.onMouseDown;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"handleMouseUp",function(w){var $=x.props.onMouseUp;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"handleTouchMove",function(w){w.changedTouches!=null&&w.changedTouches.length>0&&x.throttleTriggeredAfterMouseMove(w.changedTouches[0])}),be(x,"handleTouchStart",function(w){w.changedTouches!=null&&w.changedTouches.length>0&&x.handleMouseDown(w.changedTouches[0])}),be(x,"handleTouchEnd",function(w){w.changedTouches!=null&&w.changedTouches.length>0&&x.handleMouseUp(w.changedTouches[0])}),be(x,"handleDoubleClick",function(w){var $=x.props.onDoubleClick;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"handleContextMenu",function(w){var $=x.props.onContextMenu;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"triggerSyncEvent",function(w){x.props.syncId!==void 0&&c0.emit(f0,x.props.syncId,w,x.eventEmitterSymbol)}),be(x,"applySyncEvent",function(w){var $=x.props,k=$.layout,T=$.syncMethod,E=x.state.updateId,I=w.dataStartIndex,O=w.dataEndIndex;if(w.dataStartIndex!==void 0||w.dataEndIndex!==void 0)x.setState(H({dataStartIndex:I,dataEndIndex:O},p({props:x.props,dataStartIndex:I,dataEndIndex:O,updateId:E},x.state)));else if(w.activeTooltipIndex!==void 0){var j=w.chartX,D=w.chartY,M=w.activeTooltipIndex,L=x.state,B=L.offset,R=L.tooltipTicks;if(!B)return;if(typeof T=="function")M=T(R,w);else if(T==="value"){M=-1;for(var U=0;U=0){var Q,Z;if(j.dataKey&&!j.allowDuplicatedCategory){var $e=typeof j.dataKey=="function"?ne:"payload.".concat(j.dataKey.toString());Q=tm(U,$e,M),Z=P&&G&&tm(G,$e,M)}else Q=U==null?void 0:U[D],Z=P&&G&&G[D];if(ce||me){var ve=w.props.activeIndex!==void 0?w.props.activeIndex:D;return[S.cloneElement(w,H(H(H({},T.props),oe),{},{activeIndex:ve})),null,null]}if(!Ce(Q))return[Y].concat(Yl(x.renderActivePoints({item:T,activePoint:Q,basePoint:Z,childIndex:D,isRange:P})))}else{var Me,Ue=(Me=x.getItemByXY(x.state.activeCoordinate))!==null&&Me!==void 0?Me:{graphicalItem:Y},Qe=Ue.graphicalItem,fn=Qe.item,Sn=fn===void 0?w:fn,Ft=Qe.childIndex,jr=H(H(H({},T.props),oe),{},{activeIndex:Ft});return[S.cloneElement(Sn,jr),null,null]}return P?[Y,null,null]:[Y,null]}),be(x,"renderCustomized",function(w,$,k){return S.cloneElement(w,H(H({key:"recharts-customized-".concat(k)},x.props),x.state))}),be(x,"renderMap",{CartesianGrid:{handler:Bp,once:!0},ReferenceArea:{handler:x.renderReferenceElement},ReferenceLine:{handler:Bp},ReferenceDot:{handler:x.renderReferenceElement},XAxis:{handler:Bp},YAxis:{handler:Bp},Brush:{handler:x.renderBrush,once:!0},Bar:{handler:x.renderGraphicChild},Line:{handler:x.renderGraphicChild},Area:{handler:x.renderGraphicChild},Radar:{handler:x.renderGraphicChild},RadialBar:{handler:x.renderGraphicChild},Scatter:{handler:x.renderGraphicChild},Pie:{handler:x.renderGraphicChild},Funnel:{handler:x.renderGraphicChild},Tooltip:{handler:x.renderCursor,once:!0},PolarGrid:{handler:x.renderPolarGrid,once:!0},PolarAngleAxis:{handler:x.renderPolarAxis},PolarRadiusAxis:{handler:x.renderPolarAxis},Customized:{handler:x.renderCustomized}}),x.clipPathId="".concat((b=y.id)!==null&&b!==void 0?b:bu("recharts"),"-clip"),x.throttleTriggeredAfterMouseMove=g4(x.triggeredAfterMouseMove,(_=y.throttleDelay)!==null&&_!==void 0?_:1e3/60),x.state={},x}return BTe(v,g),zTe(v,[{key:"componentDidMount",value:function(){var b,_;this.addListener(),this.accessibilityManager.setDetails({container:this.container,offset:{left:(b=this.props.margin.left)!==null&&b!==void 0?b:0,top:(_=this.props.margin.top)!==null&&_!==void 0?_:0},coordinateList:this.state.tooltipTicks,mouseHandlerCallback:this.triggeredAfterMouseMove,layout:this.props.layout}),this.displayDefaultTooltip()}},{key:"displayDefaultTooltip",value:function(){var b=this.props,_=b.children,x=b.data,w=b.height,$=b.layout,k=Xn(_,Ri);if(k){var T=k.props.defaultIndex;if(!(typeof T!="number"||T<0||T>this.state.tooltipTicks.length-1)){var E=this.state.tooltipTicks[T]&&this.state.tooltipTicks[T].value,I=a1(this.state,x,T,E),O=this.state.tooltipTicks[T].coordinate,j=(this.state.offset.top+w)/2,D=$==="horizontal",M=D?{x:O,y:j}:{y:O,x:j},L=this.state.formattedGraphicalItems.find(function(R){var U=R.item;return U.type.name==="Scatter"});L&&(M=H(H({},M),L.props.points[T].tooltipPosition),I=L.props.points[T].tooltipPayload);var B={activeTooltipIndex:T,isTooltipActive:!0,activeLabel:E,activePayload:I,activeCoordinate:M};this.setState(B),this.renderCursor(k),this.accessibilityManager.setIndex(T)}}}},{key:"getSnapshotBeforeUpdate",value:function(b,_){if(!this.props.accessibilityLayer)return null;if(this.state.tooltipTicks!==_.tooltipTicks&&this.accessibilityManager.setDetails({coordinateList:this.state.tooltipTicks}),this.props.layout!==b.layout&&this.accessibilityManager.setDetails({layout:this.props.layout}),this.props.margin!==b.margin){var x,w;this.accessibilityManager.setDetails({offset:{left:(x=this.props.margin.left)!==null&&x!==void 0?x:0,top:(w=this.props.margin.top)!==null&&w!==void 0?w:0}})}return null}},{key:"componentDidUpdate",value:function(b){zw([Xn(b.children,Ri)],[Xn(this.props.children,Ri)])||this.displayDefaultTooltip()}},{key:"componentWillUnmount",value:function(){this.removeListener(),this.throttleTriggeredAfterMouseMove.cancel()}},{key:"getTooltipEventType",value:function(){var b=Xn(this.props.children,Ri);if(b&&typeof b.props.shared=="boolean"){var _=b.props.shared?"axis":"item";return s.indexOf(_)>=0?_:o}return o}},{key:"getMouseInfo",value:function(b){if(!this.container)return null;var _=this.container,x=_.getBoundingClientRect(),w=_ye(x),$={chartX:Math.round(b.pageX-w.left),chartY:Math.round(b.pageY-w.top)},k=x.width/_.offsetWidth||1,T=this.inRange($.chartX,$.chartY,k);if(!T)return null;var E=this.state,I=E.xAxisMap,O=E.yAxisMap,j=this.getTooltipEventType(),D=N2(this.state,this.props.data,this.props.layout,T);if(j!=="axis"&&I&&O){var M=Oo(I).scale,L=Oo(O).scale,B=M&&M.invert?M.invert($.chartX):null,R=L&&L.invert?L.invert($.chartY):null;return H(H({},$),{},{xValue:B,yValue:R},D)}return D?H(H({},$),D):null}},{key:"inRange",value:function(b,_){var x=arguments.length>2&&arguments[2]!==void 0?arguments[2]:1,w=this.props.layout,$=b/x,k=_/x;if(w==="horizontal"||w==="vertical"){var T=this.state.offset,E=$>=T.left&&$<=T.left+T.width&&k>=T.top&&k<=T.top+T.height;return E?{x:$,y:k}:null}var I=this.state,O=I.angleAxisMap,j=I.radiusAxisMap;if(O&&j){var D=Oo(O);return vC({x:$,y:k},D)}return null}},{key:"parseEventsOfWrapper",value:function(){var b=this.props.children,_=this.getTooltipEventType(),x=Xn(b,Ri),w={};x&&_==="axis"&&(x.props.trigger==="click"?w={onClick:this.handleClick}:w={onMouseEnter:this.handleMouseEnter,onDoubleClick:this.handleDoubleClick,onMouseMove:this.handleMouseMove,onMouseLeave:this.handleMouseLeave,onTouchMove:this.handleTouchMove,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onContextMenu:this.handleContextMenu});var $=nm(this.props,this.handleOuterEvent);return H(H({},$),w)}},{key:"addListener",value:function(){c0.on(f0,this.handleReceiveSyncEvent)}},{key:"removeListener",value:function(){c0.removeListener(f0,this.handleReceiveSyncEvent)}},{key:"filterFormatItem",value:function(b,_,x){for(var w=this.state.formattedGraphicalItems,$=0,k=w.length;$iIe(e,"name",{value:t,configurable:!0}),CU=!!(typeof window<"u"&&window.document&&window.document.createElement);function Ha(e,t,{checkForDefaultPrevented:n=!0}={}){return Ou(function(i){if(e==null||e(i),n===!1||!i||!i.defaultPrevented)return t==null?void 0:t(i)},"handleEvent")}Ou(Ha,"composeEventHandlers");function oIe(e){var t;if(!CU)throw new Error("Cannot access window outside of the DOM");return((t=e==null?void 0:e.ownerDocument)==null?void 0:t.defaultView)??window}Ou(oIe,"getOwnerWindow");function s1(e){if(!CU)throw new Error("Cannot access document outside of the DOM");return(e==null?void 0:e.ownerDocument)??document}Ou(s1,"getOwnerDocument");function RU(e,t=!1){const{activeElement:n}=s1(e);if(!(n!=null&&n.nodeName))return null;if(NU(n)&&n.contentDocument)return RU(n.contentDocument.body,t);if(t){const r=n.getAttribute("aria-activedescendant");if(r){const i=s1(n).getElementById(r);if(i)return i}}return n}Ou(RU,"getActiveElement");function NU(e){return e.tagName==="IFRAME"}Ou(NU,"isFrame");var aIe=Object.defineProperty,A$=(e,t)=>aIe(e,"name",{value:t,configurable:!0});function l1(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}A$(l1,"setRef");function jU(...e){return t=>{let n=!1;const r=e.map(i=>{const o=l1(i,t);return!n&&typeof o=="function"&&(n=!0),o});if(n)return()=>{for(let i=0;isIe(e,"name",{value:t,configurable:!0});function lIe(e,t){const n=S.createContext(t);n.displayName=e+"Context";const r=yr(o=>{const{children:a,...s}=o,l=S.useMemo(()=>s,Object.values(s));return z.jsx(n.Provider,{value:l,children:a})},"Provider");r.displayName=e+"Provider";function i(o,a={}){const{optional:s=!1}=a,l=S.useContext(n);if(l)return l;if(t!==void 0)return t;if(!s)throw new Error(`\`${o}\` must be used within \`${e}\``)}return yr(i,"useContext"),[r,i]}yr(lIe,"createContext");function C$(e,t=[]){let n=[];function r(o,a){const s=S.createContext(a);s.displayName=o+"Context";const l=n.length;n=[...n,a];const u=yr(c=>{var v;const{scope:d,children:p,...h}=c,m=((v=d==null?void 0:d[e])==null?void 0:v[l])||s,g=S.useMemo(()=>h,Object.values(h));return z.jsx(m.Provider,{value:g,children:p})},"Provider");u.displayName=o+"Provider";function f(c,d,p={}){var v;const{optional:h=!1}=p,m=((v=d==null?void 0:d[e])==null?void 0:v[l])||s,g=S.useContext(m);if(g)return g;if(a!==void 0)return a;if(!h)throw new Error(`\`${c}\` must be used within \`${o}\``)}return yr(f,"useContext"),[u,f]}yr(r,"createContext");const i=yr(()=>{const o=n.map(a=>S.createContext(a));return yr(function(s){const l=(s==null?void 0:s[e])||o;return S.useMemo(()=>({[`__scope${e}`]:{...s,[e]:l}}),[s,l])},"useScope")},"createScope");return i.scopeName=e,[r,DU(i,...t)]}yr(C$,"createContextScope");function DU(...e){const t=e[0];if(e.length===1)return t;const n=yr(()=>{const r=e.map(i=>({useScope:i(),scopeName:i.scopeName}));return yr(function(o){const a=r.reduce((s,{useScope:l,scopeName:u})=>{const c=l(o)[`__scope${u}`];return{...s,...c}},{});return S.useMemo(()=>({[`__scope${t.scopeName}`]:a}),[a])},"useComposedScopes")},"createScope");return n.scopeName=t.scopeName,n}yr(DU,"composeContextScopes");var uIe=Object.defineProperty,oi=(e,t)=>uIe(e,"name",{value:t,configurable:!0});function R$(e){const t=S.forwardRef((n,r)=>{let{children:i,...o}=n,a=null,s=!1;const l=[];u1(i)&&typeof Zp=="function"&&(i=Zp(i._payload)),S.Children.forEach(i,d=>{var p;if(UU(d)){s=!0;const h=d;let m="child"in h.props?h.props.child:h.props.children;u1(m)&&typeof Zp=="function"&&(m=Zp(m._payload)),a=fIe(h,m),l.push((p=a==null?void 0:a.props)==null?void 0:p.children)}else l.push(d)}),a?a=S.cloneElement(a,void 0,l):!s&&S.Children.count(i)===1&&S.isValidElement(i)&&(a=i);const u=a?LU(a):void 0,f=Tu(r,u);if(!a){if(i||i===0)throw new Error(s?hIe(e):pIe(e));return i}const c=zU(o,a.props??{});return a.type!==S.Fragment&&(c.ref=r?f:u),S.cloneElement(a,c)});return t.displayName=`${e}.Slot`,t}oi(R$,"createSlot");var MU=Symbol.for("radix.slottable");function cIe(e){const t=oi(n=>"child"in n?n.children(n.child):n.children,"Slottable");return t.displayName=`${e}.Slottable`,t.__radixId=MU,t}oi(cIe,"createSlottable");var fIe=oi((e,t)=>{if("child"in e.props){const n=e.props.child;return S.isValidElement(n)?S.cloneElement(n,void 0,e.props.children(n.props.children)):null}return S.isValidElement(t)?t:null},"getSlottableElementFromSlottable");function zU(e,t){const n={...t};for(const r in t){const i=e[r],o=t[r];/^on[A-Z]/.test(r)?i&&o?n[r]=(...s)=>{const l=o(...s);return i(...s),l}:i&&(n[r]=i):r==="style"?n[r]={...i,...o}:r==="className"&&(n[r]=[i,o].filter(Boolean).join(" "))}return{...e,...n}}oi(zU,"mergeProps");function LU(e){var r,i;let t=(r=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:r.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=(i=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:i.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}oi(LU,"getElementRef");function UU(e){return S.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===MU}oi(UU,"isSlottable");var dIe=Symbol.for("react.lazy");function u1(e){return e!=null&&typeof e=="object"&&"$$typeof"in e&&e.$$typeof===dIe&&"_payload"in e&&FU(e._payload)}oi(u1,"isLazyComponent");function FU(e){return typeof e=="object"&&e!==null&&"then"in e}oi(FU,"isPromiseLike");var pIe=oi(e=>`${e} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`,"createSlotError"),hIe=oi(e=>`${e} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`,"createSlottableError"),Zp=Xf[" use ".trim().toString()],mIe=Object.defineProperty,vIe=(e,t)=>mIe(e,"name",{value:t,configurable:!0}),gIe=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],gd=gIe.reduce((e,t)=>{const n=R$(`Primitive.${t}`),r=S.forwardRef((i,o)=>{const{asChild:a,...s}=i,l=a?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),z.jsx(l,{...s,ref:o})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function BU(e,t){e&&$i.flushSync(()=>e.dispatchEvent(t))}vIe(BU,"dispatchDiscreteCustomEvent");var yIe=Object.defineProperty,bIe=(e,t)=>yIe(e,"name",{value:t,configurable:!0});function ss(e){const t=S.useRef(e);return S.useEffect(()=>{t.current=e}),S.useMemo(()=>(...n)=>{var r;return(r=t.current)==null?void 0:r.call(t,...n)},[])}bIe(ss,"useCallbackRef");var wIe=Object.defineProperty,Xt=(e,t)=>wIe(e,"name",{value:t,configurable:!0}),c1="dismissableLayer.update",xIe="dismissableLayer.pointerDownOutside",_Ie="dismissableLayer.focusOutside",M2,ZU=S.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set,dismissableSurfaces:new Set}),SIe=S.forwardRef(Xt(function(t,n){const{disableOutsidePointerEvents:r=!1,deferPointerDownOutside:i=!1,onEscapeKeyDown:o,onPointerDownOutside:a,onFocusOutside:s,onInteractOutside:l,onDismiss:u,...f}=t,c=S.useContext(ZU),[d,p]=S.useState(null),h=(d==null?void 0:d.ownerDocument)??(globalThis==null?void 0:globalThis.document),[,m]=S.useState({}),g=Tu(n,p),v=Array.from(c.layers),[y]=[...c.layersWithOutsidePointerEventsDisabled].slice(-1),b=y?v.indexOf(y):-1,_=d?v.indexOf(d):-1,x=c.layersWithOutsidePointerEventsDisabled.size>0,w=_>=b,$=S.useRef(!1),k=WU(O=>{a==null||a(O),l==null||l(O),O.defaultPrevented||u==null||u()},{ownerDocument:h,deferPointerDownOutside:i,isDeferredPointerDownOutsideRef:$,dismissableSurfaces:c.dismissableSurfaces,shouldHandlePointerDownOutside:S.useCallback(O=>{if(!(O instanceof Node))return!1;const j=[...c.branches].some(D=>D.contains(O));return w&&!j},[c.branches,w])}),T=HU(O=>{if(i&&$.current)return;const j=O.target;[...c.branches].some(M=>M.contains(j))||(s==null||s(O),l==null||l(O),O.defaultPrevented||u==null||u())},h),E=d?_===v.length-1:!1,I=ss(O=>{O.key==="Escape"&&(o==null||o(O),!O.defaultPrevented&&u&&(O.preventDefault(),u()))});return S.useEffect(()=>{if(E)return h.addEventListener("keydown",I,{capture:!0}),()=>h.removeEventListener("keydown",I,{capture:!0})},[h,E,I]),S.useEffect(()=>{if(d)return r&&(c.layersWithOutsidePointerEventsDisabled.size===0&&(M2=h.body.style.pointerEvents,h.body.style.pointerEvents="none"),c.layersWithOutsidePointerEventsDisabled.add(d)),c.layers.add(d),f1(),()=>{r&&(c.layersWithOutsidePointerEventsDisabled.delete(d),c.layersWithOutsidePointerEventsDisabled.size===0&&(h.body.style.pointerEvents=M2))}},[d,h,r,c]),S.useEffect(()=>()=>{d&&(c.layers.delete(d),c.layersWithOutsidePointerEventsDisabled.delete(d),f1())},[d,c]),S.useEffect(()=>{const O=Xt(()=>m({}),"handleUpdate");return document.addEventListener(c1,O),()=>document.removeEventListener(c1,O)},[]),z.jsx(gd.div,{...f,ref:g,style:{pointerEvents:x?w?"auto":"none":void 0,...t.style},onFocusCapture:Ha(t.onFocusCapture,T.onFocusCapture),onBlurCapture:Ha(t.onBlurCapture,T.onBlurCapture),onPointerDownCapture:Ha(t.onPointerDownCapture,k.onPointerDownCapture)})},"DismissableLayer"));function $Ie(){const e=S.useContext(ZU),[t,n]=S.useState(null);return S.useEffect(()=>{if(t)return e.dismissableSurfaces.add(t),()=>{e.dismissableSurfaces.delete(t)}},[t,e.dismissableSurfaces]),n}Xt($Ie,"useDismissableLayerSurface");var kIe=Xt(()=>!0,"IS_TRUE");function WU(e,t){const{ownerDocument:n=globalThis==null?void 0:globalThis.document,deferPointerDownOutside:r=!1,isDeferredPointerDownOutsideRef:i,dismissableSurfaces:o,shouldHandlePointerDownOutside:a=kIe}=t,s=ss(e),l=S.useRef(!1),u=S.useRef(!1),f=S.useRef(new Map),c=S.useRef(()=>{});return S.useEffect(()=>{function d(){u.current=!1,i.current=!1,f.current.clear()}Xt(d,"resetOutsideInteraction");function p(){return Array.from(f.current.values()).some(Boolean)}Xt(p,"isOutsideInteractionIntercepted");function h(b){if(!u.current)return;const _=b.target;_ instanceof Node&&[...o].some(w=>w.contains(_))||f.current.set(b.type,!0),b.type==="click"&&window.setTimeout(()=>{u.current&&c.current()},0)}Xt(h,"handleInteractionCapture");function m(b){u.current&&f.current.set(b.type,!1)}Xt(m,"handleInteractionBubble");const g=Xt(b=>{if(b.target&&!l.current){let _=function(){n.removeEventListener("click",c.current);const w=p();d(),w||N$(xIe,s,x,{discrete:!0})};if(Xt(_,"handleAndDispatchPointerDownOutsideEvent"),!a(b.target)){n.removeEventListener("click",c.current),d(),l.current=!1;return}const x={originalEvent:b};u.current=!0,i.current=r&&b.button===0,f.current.clear(),!r||b.button!==0?_():(n.removeEventListener("click",c.current),c.current=_,n.addEventListener("click",c.current,{once:!0}))}else n.removeEventListener("click",c.current),d();l.current=!1},"handlePointerDown"),v=["pointerup","mousedown","mouseup","touchstart","touchend","click"];for(const b of v)n.addEventListener(b,h,!0),n.addEventListener(b,m);const y=window.setTimeout(()=>{n.addEventListener("pointerdown",g)},0);return()=>{window.clearTimeout(y),n.removeEventListener("pointerdown",g),n.removeEventListener("click",c.current);for(const b of v)n.removeEventListener(b,h,!0),n.removeEventListener(b,m)}},[n,s,r,i,o,a]),{onPointerDownCapture:Xt(()=>l.current=!0,"onPointerDownCapture")}}Xt(WU,"usePointerDownOutside");function HU(e,t=globalThis==null?void 0:globalThis.document){const n=ss(e),r=S.useRef(!1);return S.useEffect(()=>{const i=Xt(o=>{o.target&&!r.current&&N$(_Ie,n,{originalEvent:o},{discrete:!1})},"handleFocus");return t.addEventListener("focusin",i),()=>t.removeEventListener("focusin",i)},[t,n]),{onFocusCapture:Xt(()=>r.current=!0,"onFocusCapture"),onBlurCapture:Xt(()=>r.current=!1,"onBlurCapture")}}Xt(HU,"useFocusOutside");function f1(){const e=new CustomEvent(c1);document.dispatchEvent(e)}Xt(f1,"dispatchUpdate");function N$(e,t,n,{discrete:r}){const i=n.originalEvent.target,o=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),r?BU(i,o):i.dispatchEvent(o)}Xt(N$,"handleAndDispatchCustomEvent");var EIe=Object.defineProperty,j$=(e,t)=>EIe(e,"name",{value:t,configurable:!0}),Wp=0,li=null;function PIe(e){return D$(),e.children}j$(PIe,"FocusGuards");function D$(){S.useEffect(()=>{li||(li={start:d1(),end:d1()});const{start:e,end:t}=li;return document.body.firstElementChild!==e&&document.body.insertAdjacentElement("afterbegin",e),document.body.lastElementChild!==t&&document.body.insertAdjacentElement("beforeend",t),Wp++,()=>{Wp===1&&(li==null||li.start.remove(),li==null||li.end.remove(),li=null),Wp=Math.max(0,Wp-1)}},[])}j$(D$,"useFocusGuards");function d1(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}j$(d1,"createFocusGuard");var OIe=Object.defineProperty,On=(e,t)=>OIe(e,"name",{value:t,configurable:!0}),p0="focusScope.autoFocusOnMount",h0="focusScope.autoFocusOnUnmount",z2={bubbles:!1,cancelable:!0},TIe=S.forwardRef(On(function(t,n){const{loop:r=!1,trapped:i=!1,onMountAutoFocus:o,onUnmountAutoFocus:a,...s}=t,[l,u]=S.useState(null),f=ss(o),c=ss(a),d=S.useRef(null),p=Tu(n,u),h=S.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;S.useEffect(()=>{if(i){let g=function(_){if(h.paused||!l)return;const x=_.target;l.contains(x)?d.current=x:Ni(d.current,{select:!0})},v=function(_){if(h.paused||!l)return;const x=_.relatedTarget;x!==null&&(l.contains(x)||Ni(d.current,{select:!0}))},y=function(_){if(document.activeElement===document.body)for(const w of _)w.removedNodes.length>0&&Ni(l)};On(g,"handleFocusIn"),On(v,"handleFocusOut"),On(y,"handleMutations"),document.addEventListener("focusin",g),document.addEventListener("focusout",v);const b=new MutationObserver(y);return l&&b.observe(l,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",g),document.removeEventListener("focusout",v),b.disconnect()}}},[i,l,h.paused]),S.useEffect(()=>{if(l){L2.add(h);const g=document.activeElement;if(!l.contains(g)){const y=new CustomEvent(p0,z2);l.addEventListener(p0,f),l.dispatchEvent(y),y.defaultPrevented||(VU(XU(M$(l)),{select:!0}),document.activeElement===g&&Ni(l))}return()=>{l.removeEventListener(p0,f),setTimeout(()=>{const y=new CustomEvent(h0,z2);l.addEventListener(h0,c),l.dispatchEvent(y),y.defaultPrevented||Ni(g??document.body,{select:!0}),l.removeEventListener(h0,c),L2.remove(h)},0)}}},[l,f,c,h]);const m=S.useCallback(g=>{if(!r&&!i||h.paused)return;const v=g.key==="Tab"&&!g.altKey&&!g.ctrlKey&&!g.metaKey,y=document.activeElement;if(v&&y){const b=g.currentTarget,[_,x]=qU(b);_&&x?!g.shiftKey&&y===x?(g.preventDefault(),r&&Ni(_,{select:!0})):g.shiftKey&&y===_&&(g.preventDefault(),r&&Ni(x,{select:!0})):y===b&&g.preventDefault()}},[r,i,h.paused]);return z.jsx(gd.div,{tabIndex:-1,...s,ref:p,onKeyDown:m})},"FocusScope"));function VU(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(Ni(r,{select:t}),document.activeElement!==n)return}On(VU,"focusFirst");function qU(e){const t=M$(e),n=p1(t,e),r=p1(t.reverse(),e);return[n,r]}On(qU,"getTabbableEdges");function M$(e){const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:On(r=>{const i=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||i?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP},"acceptNode")});for(;n.nextNode();)t.push(n.currentNode);return t}On(M$,"getTabbableCandidates");function p1(e,t){const n=typeof t.checkVisibility=="function"&&t.checkVisibility({checkVisibilityCSS:!0});for(const r of e)if(!(n?!r.checkVisibility({checkVisibilityCSS:!0}):GU(r,{upTo:t})))return r}On(p1,"findVisible");function GU(e,{upTo:t}){if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1}On(GU,"isHidden");function KU(e){return e instanceof HTMLInputElement&&"select"in e}On(KU,"isSelectableInput");function Ni(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&KU(e)&&t&&e.select()}}On(Ni,"focus");var L2=YU();function YU(){let e=[];return{add(t){const n=e[0];t!==n&&(n==null||n.pause()),e=h1(e,t),e.unshift(t)},remove(t){var n;e=h1(e,t),(n=e[0])==null||n.resume()}}}On(YU,"createFocusScopesStack");function h1(e,t){const n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}On(h1,"arrayRemove");function XU(e){return e.filter(t=>t.tagName!=="A")}On(XU,"removeLinks");var Va=globalThis!=null&&globalThis.document?S.useLayoutEffect:()=>{},IIe=Object.defineProperty,AIe=(e,t)=>IIe(e,"name",{value:t,configurable:!0});function JU(e){const[t,n]=S.useState(void 0);return Va(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});const r=new ResizeObserver(i=>{if(!Array.isArray(i)||!i.length)return;const o=i[0];let a,s;if("borderBoxSize"in o){const l=o.borderBoxSize,u=Array.isArray(l)?l[0]:l;a=u.inlineSize,s=u.blockSize}else a=e.offsetWidth,s=e.offsetHeight;n({width:a,height:s})});return r.observe(e,{box:"border-box"}),()=>r.unobserve(e)}else n(void 0)},[e]),t}AIe(JU,"useSize");var CIe=Object.defineProperty,hl=(e,t)=>CIe(e,"name",{value:t,configurable:!0}),QU="Popper",[eF,tF]=C$(QU),[dUe,RIe]=eF(QU),nF="PopperContent",[NIe,pUe]=eF(nF),jIe=S.forwardRef(hl(function(t,n){var Y,ne,Q,Z,$e,ve,Me;const{__scopePopper:r,side:i="bottom",sideOffset:o=0,align:a="center",alignOffset:s=0,arrowPadding:l=0,avoidCollisions:u=!0,collisionBoundary:f=[],collisionPadding:c=0,sticky:d="partial",hideWhenDetached:p=!1,updatePositionStrategy:h="optimized",onPlaced:m,...g}=t,v=RIe(nF,r),[y,b]=S.useState(null),_=Tu(n,b),[x,w]=S.useState(null),$=JU(x),k=($==null?void 0:$.width)??0,T=($==null?void 0:$.height)??0,E=i+(a!=="center"?"-"+a:""),I=typeof c=="number"?c:{top:0,right:0,bottom:0,left:0,...c},O=Array.isArray(f)?f:[f],j=O.length>0,D={padding:I,boundary:O.filter(rF),altBoundary:j},{refs:M,floatingStyles:L,placement:B,isPositioned:R,middlewareData:U}=X_({strategy:"fixed",placement:E,whileElementsMounted:hl((...Ue)=>Y_(...Ue,{animationFrame:h==="always"}),"whileElementsMounted"),elements:{reference:v.anchor},middleware:[J_({mainAxis:o+T,alignmentAxis:s}),u&&Q_({mainAxis:!0,crossAxis:!1,limiter:d==="partial"?eS():void 0,...D}),u&&tS({...D}),nS({...D,apply:hl(({elements:Ue,rects:Qe,availableWidth:fn,availableHeight:Sn})=>{const{width:Ft,height:jr}=Qe.reference,Rn=Ue.floating.style;Rn.setProperty("--radix-popper-available-width",`${fn}px`),Rn.setProperty("--radix-popper-available-height",`${Sn}px`),Rn.setProperty("--radix-popper-anchor-width",`${Ft}px`),Rn.setProperty("--radix-popper-anchor-height",`${jr}px`)},"apply")}),x&&iS({element:x,padding:l}),DIe({arrowWidth:k,arrowHeight:T}),p&&rS({strategy:"referenceHidden",...D,boundary:j?D.boundary:void 0})]}),P=v.setPlacementState;Va(()=>(P(B),()=>{P(void 0)}),[B,P]);const[G,V]=z$(B),A=ss(m);Va(()=>{R&&(A==null||A())},[R,A]);const pe=(Y=U.arrow)==null?void 0:Y.x,me=(ne=U.arrow)==null?void 0:ne.y,ce=((Q=U.arrow)==null?void 0:Q.centerOffset)!==0,[ie,oe]=S.useState();return Va(()=>{y&&oe(window.getComputedStyle(y).zIndex)},[y]),z.jsx("div",{ref:M.setFloating,"data-radix-popper-content-wrapper":"",style:{...L,transform:R?L.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:ie,"--radix-popper-transform-origin":[(Z=U.transformOrigin)==null?void 0:Z.x,($e=U.transformOrigin)==null?void 0:$e.y].join(" "),...((ve=U.hide)==null?void 0:ve.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:t.dir,children:z.jsx(NIe,{scope:r,placedSide:G,placedAlign:V,onArrowChange:w,arrowX:pe,arrowY:me,shouldHideArrow:ce,children:z.jsx(gd.div,{"data-side":G,"data-align":V,...g,ref:_,style:{...g.style,animation:R?(Me=g.style)==null?void 0:Me.animation:"none"}})})})},"PopperContent"));function rF(e){return e!==null}hl(rF,"isNotNull");var DIe=hl(e=>({name:"transformOrigin",options:e,fn(t){var g,v,y;const{placement:n,rects:r,middlewareData:i}=t,a=((g=i.arrow)==null?void 0:g.centerOffset)!==0,s=a?0:e.arrowWidth,l=a?0:e.arrowHeight,[u,f]=z$(n),c={start:"0%",center:"50%",end:"100%"}[f],d=(((v=i.arrow)==null?void 0:v.x)??0)+s/2,p=(((y=i.arrow)==null?void 0:y.y)??0)+l/2;let h="",m="";return u==="bottom"?(h=a?c:`${d}px`,m=`${-l}px`):u==="top"?(h=a?c:`${d}px`,m=`${r.floating.height+l}px`):u==="right"?(h=`${-l}px`,m=a?c:`${p}px`):u==="left"&&(h=`${r.floating.width+l}px`,m=a?c:`${p}px`),{data:{x:h,y:m}}}}),"transformOrigin");function z$(e){const[t,n="center"]=e.split("-");return[t,n]}hl(z$,"getSideAndAlignFromPlacement");var MIe=jIe,zIe=Object.defineProperty,LIe=(e,t)=>zIe(e,"name",{value:t,configurable:!0}),UIe=S.forwardRef(LIe(function(t,n){var l;const{container:r,...i}=t,[o,a]=S.useState(!1);Va(()=>a(!0),[]);const s=r||o&&((l=globalThis==null?void 0:globalThis.document)==null?void 0:l.body);return s?$i.createPortal(z.jsx(gd.div,{...i,ref:n}),s):null},"Portal")),FIe=Object.defineProperty,no=(e,t)=>FIe(e,"name",{value:t,configurable:!0});function iF(e,t){return S.useReducer((n,r)=>t[n][r]??n,e)}no(iF,"useStateMachine");var oF=no(e=>{const{present:t,children:n}=e,r=aF(t),i=typeof n=="function"?n({present:r.isPresent}):S.Children.only(n),o=sF(r.ref,lF(i));return typeof n=="function"||r.isPresent?S.cloneElement(i,{ref:o}):null},"Presence");function aF(e){const[t,n]=S.useState(),r=S.useRef(null),i=S.useRef(e),o=S.useRef("none"),a=S.useRef(void 0),s=e?"mounted":"unmounted",[l,u]=iF(s,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return S.useEffect(()=>{l==="mounted"?(o.current=a.current??Ds(r.current),a.current=void 0):o.current="none"},[l]),Va(()=>{const f=r.current,c=i.current;if(c!==e){const p=o.current,h=Ds(f);e?(a.current=h,u("MOUNT")):h==="none"||(f==null?void 0:f.display)==="none"?u("UNMOUNT"):u(c&&p!==h?"ANIMATION_OUT":"UNMOUNT"),i.current=e}},[e,u]),Va(()=>{if(t){let f;const c=t.ownerDocument.defaultView??window,d=no(h=>{const g=Ds(r.current).includes(CSS.escape(h.animationName));if(h.target===t&&g&&(u("ANIMATION_END"),!i.current)){const v=t.style.animationFillMode;t.style.animationFillMode="forwards",f=c.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=v)})}},"handleAnimationEnd"),p=no(h=>{h.target===t&&(o.current=Ds(r.current))},"handleAnimationStart");return t.addEventListener("animationstart",p),t.addEventListener("animationcancel",d),t.addEventListener("animationend",d),()=>{c.clearTimeout(f),t.removeEventListener("animationstart",p),t.removeEventListener("animationcancel",d),t.removeEventListener("animationend",d)}}else u("ANIMATION_END")},[t,u]),{isPresent:["mounted","unmountSuspended"].includes(l),ref:S.useCallback(f=>{if(f){const c=getComputedStyle(f);r.current=c,a.current=Ds(c)}else r.current=null;n(f)},[])}}no(aF,"usePresence");function m1(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}no(m1,"setRef");function sF(...e){const t=S.useRef(e);return t.current=e,S.useCallback(n=>{const r=t.current;let i=!1;const o=r.map(a=>{const s=m1(a,n);return!i&&typeof s=="function"&&(i=!0),s});if(i)return()=>{for(let a=0;aBIe(e,"name",{value:t,configurable:!0}),uF="Popover",[cF,hUe]=C$(uF,[tF]),ZIe=tF(),[mUe,yd]=cF(uF),fF="PopoverPortal",[WIe,HIe]=cF(fF,{forceMount:void 0}),VIe=Iu(e=>{const{__scopePopover:t,forceMount:n,children:r,container:i}=e,o=yd(fF,t);return z.jsx(WIe,{scope:t,forceMount:n,children:z.jsx(oF,{present:n||o.open,children:z.jsx(UIe,{asChild:!0,container:i,children:r})})})},"PopoverPortal"),Ff="PopoverContent",qIe=S.forwardRef(Iu(function(t,n){const r=HIe(Ff,t.__scopePopover),{forceMount:i=r.forceMount,...o}=t,a=yd(Ff,t.__scopePopover);return z.jsx(oF,{present:i||a.open,children:a.modal?z.jsx(KIe,{...o,ref:n}):z.jsx(YIe,{...o,ref:n})})},"PopoverContent")),GIe=R$("PopoverContent.RemoveScroll"),KIe=S.forwardRef(Iu(function(t,n){const r=yd(Ff,t.__scopePopover),i=S.useRef(null),o=Tu(n,i),a=S.useRef(!1);return S.useEffect(()=>{const s=i.current;if(s)return vS(s)},[]),z.jsx(Gv,{as:GIe,allowPinchZoom:!0,children:z.jsx(dF,{...t,ref:o,trapFocus:r.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:Ha(t.onCloseAutoFocus,s=>{var l;s.preventDefault(),a.current||(l=r.triggerRef.current)==null||l.focus()}),onPointerDownOutside:Ha(t.onPointerDownOutside,s=>{const l=s.detail.originalEvent,u=l.button===0&&l.ctrlKey===!0,f=l.button===2||u;a.current=f},{checkForDefaultPrevented:!1}),onFocusOutside:Ha(t.onFocusOutside,s=>s.preventDefault(),{checkForDefaultPrevented:!1})})})},"PopoverContentModal")),YIe=S.forwardRef(Iu(function(t,n){const r=yd(Ff,t.__scopePopover),i=S.useRef(!1),o=S.useRef(!1);return z.jsx(dF,{...t,ref:n,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:a=>{var s,l;(s=t.onCloseAutoFocus)==null||s.call(t,a),a.defaultPrevented||(i.current||(l=r.triggerRef.current)==null||l.focus(),a.preventDefault()),i.current=!1,o.current=!1},onInteractOutside:a=>{var u,f;(u=t.onInteractOutside)==null||u.call(t,a),a.defaultPrevented||(i.current=!0,a.detail.originalEvent.type==="pointerdown"&&(o.current=!0));const s=a.target;((f=r.triggerRef.current)==null?void 0:f.contains(s))&&a.preventDefault(),a.detail.originalEvent.type==="focusin"&&o.current&&a.preventDefault()}})},"PopoverContentNonModal")),dF=S.forwardRef(Iu(function(t,n){const{__scopePopover:r,trapFocus:i,onOpenAutoFocus:o,onCloseAutoFocus:a,disableOutsidePointerEvents:s,onEscapeKeyDown:l,onPointerDownOutside:u,onFocusOutside:f,onInteractOutside:c,...d}=t,p=yd(Ff,r),h=ZIe(r);return D$(),z.jsx(TIe,{asChild:!0,loop:!0,trapped:i,onMountAutoFocus:o,onUnmountAutoFocus:a,children:z.jsx(SIe,{asChild:!0,disableOutsidePointerEvents:s,onInteractOutside:c,onEscapeKeyDown:l,onPointerDownOutside:u,onFocusOutside:f,onDismiss:()=>p.onOpenChange(!1),deferPointerDownOutside:!0,children:z.jsx(MIe,{"data-state":pF(p.open),role:"dialog",id:p.contentId,...h,...d,ref:n,style:{...d.style,"--radix-popover-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-popover-content-available-width":"var(--radix-popper-available-width)","--radix-popover-content-available-height":"var(--radix-popper-available-height)","--radix-popover-trigger-width":"var(--radix-popper-anchor-width)","--radix-popover-trigger-height":"var(--radix-popper-anchor-height)"}})})})},"PopoverContentImpl"));function pF(e){return e?"open":"closed"}Iu(pF,"getState");var vUe=VIe,gUe=qIe,XIe=Object.defineProperty,JIe=(e,t)=>XIe(e,"name",{value:t,configurable:!0}),QIe=S.forwardRef(JIe(function(t,n){return z.jsx(gd.label,{...t,ref:n,onMouseDown:r=>{var o;r.target.closest("button, input, select, textarea")||((o=t.onMouseDown)==null||o.call(t,r),!r.defaultPrevented&&r.detail>1&&r.preventDefault())}})},"Label")),yUe=QIe;const yo=[[{color:"0, 0, 0",class:"ansi-black"},{color:"187, 0, 0",class:"ansi-red"},{color:"0, 187, 0",class:"ansi-green"},{color:"187, 187, 0",class:"ansi-yellow"},{color:"0, 0, 187",class:"ansi-blue"},{color:"187, 0, 187",class:"ansi-magenta"},{color:"0, 187, 187",class:"ansi-cyan"},{color:"255,255,255",class:"ansi-white"}],[{color:"85, 85, 85",class:"ansi-bright-black"},{color:"255, 85, 85",class:"ansi-bright-red"},{color:"0, 255, 0",class:"ansi-bright-green"},{color:"255, 255, 85",class:"ansi-bright-yellow"},{color:"85, 85, 255",class:"ansi-bright-blue"},{color:"255, 85, 255",class:"ansi-bright-magenta"},{color:"85, 255, 255",class:"ansi-bright-cyan"},{color:"255, 255, 255",class:"ansi-bright-white"}]],eAe=/(https?:\/\/(?:[A-Za-z0-9#;/?:@=+$',_.!~*()[\]-]|&|%[A-Fa-f0-9]{2})+)/gm;class Oa{static escapeForHtml(t){return new Oa().escapeForHtml(t)}static linkify(t){return new Oa().linkify(t)}static ansiToHtml(t,n){return new Oa().ansiToHtml(t,n)}static ansiToJson(t,n){return new Oa().ansiToJson(t,n)}static ansiToText(t){return new Oa().ansiToText(t)}constructor(){this.fg=this.bg=this.fg_truecolor=this.bg_truecolor=null,this.bright=0,this.decorations=[]}setupPalette(){this.PALETTE_COLORS=[];for(let i=0;i<2;++i)for(let o=0;o<8;++o)this.PALETTE_COLORS.push(yo[i][o].color);let t=[0,95,135,175,215,255],n=(i,o,a)=>t[i]+", "+t[o]+", "+t[a];for(let i=0;i<6;++i)for(let o=0;o<6;++o)for(let a=0;a<6;++a)this.PALETTE_COLORS.push(n(i,o,a));let r=8;for(let i=0;i<24;++i,r+=10)this.PALETTE_COLORS.push(r+", "+r+", "+r)}escapeForHtml(t){return t.replace(/[&<>\"]/gm,n=>n=="&"?"&":n=='"'?""":n=="<"?"<":n==">"?">":"")}linkify(t){return t.replace(eAe,n=>`${n}`)}ansiToHtml(t,n){return this.process(t,n,!0)}ansiToJson(t,n){return n=n||{},n.json=!0,n.clearLine=!1,this.process(t,n,!0)}ansiToText(t){return this.process(t,{},!1)}process(t,n,r){let i=this,o=t.split(/\033\[/),a=o.shift();n==null&&(n={}),n.clearLine=/\r/.test(t);let s=o.map(l=>this.processChunk(l,n,r));if(n&&n.json){let l=i.processChunkJson("");return l.content=a,l.clearLine=n.clearLine,s.unshift(l),n.remove_empty&&(s=s.filter(u=>!u.isEmpty())),s}else s.unshift(a);return s.join("")}processChunkJson(t,n,r){n=typeof n>"u"?{}:n;let i=n.use_classes=typeof n.use_classes<"u"&&n.use_classes,o=n.key=i?"class":"color",a={content:t,fg:null,bg:null,fg_truecolor:null,bg_truecolor:null,isInverted:!1,clearLine:n.clearLine,decoration:null,decorations:[],was_processed:!1,isEmpty:()=>!a.content},s=t.match(/^([!\x3c-\x3f]*)([\d;]*)([\x20-\x2c]*[\x40-\x7e])([\s\S]*)/m);if(!s)return a;a.content=s[4];let l=s[2].split(";");if(s[1]!==""||s[3]!=="m"||!r)return a;let u=this;for(;l.length>0;){let f=l.shift(),c=parseInt(f);if(isNaN(c)||c===0)u.fg=u.bg=null,u.decorations=[];else if(c===1)u.decorations.push("bold");else if(c===2)u.decorations.push("dim");else if(c===3)u.decorations.push("italic");else if(c===4)u.decorations.push("underline");else if(c===5)u.decorations.push("blink");else if(c===7)u.decorations.push("reverse");else if(c===8)u.decorations.push("hidden");else if(c===9)u.decorations.push("strikethrough");else if(c===21)u.removeDecoration("bold");else if(c===22)u.removeDecoration("bold"),u.removeDecoration("dim");else if(c===23)u.removeDecoration("italic");else if(c===24)u.removeDecoration("underline");else if(c===25)u.removeDecoration("blink");else if(c===27)u.removeDecoration("reverse");else if(c===28)u.removeDecoration("hidden");else if(c===29)u.removeDecoration("strikethrough");else if(c===39)u.fg=null;else if(c===49)u.bg=null;else if(c>=30&&c<38)u.fg=yo[0][c%10][o];else if(c>=90&&c<98)u.fg=yo[1][c%10][o];else if(c>=40&&c<48)u.bg=yo[0][c%10][o];else if(c>=100&&c<108)u.bg=yo[1][c%10][o];else if(c===38||c===48){let d=c===38;if(l.length>=1){let p=l.shift();if(p==="5"&&l.length>=1){let h=parseInt(l.shift());if(h>=0&&h<=255)if(!i)this.PALETTE_COLORS||u.setupPalette(),d?u.fg=this.PALETTE_COLORS[h]:u.bg=this.PALETTE_COLORS[h];else{let m=h>=16?"ansi-palette-"+h:yo[h>7?1:0][h%8].class;d?u.fg=m:u.bg=m}}else if(p==="2"&&l.length>=3){let h=parseInt(l.shift()),m=parseInt(l.shift()),g=parseInt(l.shift());if(h>=0&&h<=255&&m>=0&&m<=255&&g>=0&&g<=255){let v=h+", "+m+", "+g;i?d?(u.fg="ansi-truecolor",u.fg_truecolor=v):(u.bg="ansi-truecolor",u.bg_truecolor=v):d?u.fg=v:u.bg=v}}}}}return u.fg===null&&u.bg===null&&u.decorations.length===0||(a.fg=u.fg,a.bg=u.bg,a.fg_truecolor=u.fg_truecolor,a.bg_truecolor=u.bg_truecolor,a.decorations=u.decorations,a.decoration=u.decorations.slice(-1).pop()||null,a.was_processed=!0),a}processChunk(t,n,r){n=n||{};let i=this.processChunkJson(t,n,r),o=n.use_classes;if(i.decorations=i.decorations.filter(c=>{if(c==="reverse"){i.fg||(i.fg=yo[0][7][o?"class":"color"]),i.bg||(i.bg=yo[0][0][o?"class":"color"]);let d=i.fg;i.fg=i.bg,i.bg=d;let p=i.fg_truecolor;return i.fg_truecolor=i.bg_truecolor,i.bg_truecolor=p,i.isInverted=!0,!1}return!0}),n.json)return i;if(i.isEmpty())return"";if(!i.was_processed)return i.content;let a=[],s=[],l=[],u={},f=c=>{let d=[],p;for(p in c)c.hasOwnProperty(p)&&d.push("data-"+p+'="'+this.escapeForHtml(c[p])+'"');return d.length>0?" "+d.join(" "):""};return i.isInverted&&(u["ansi-is-inverted"]="true"),i.fg&&(o?(a.push(i.fg+"-fg"),i.fg_truecolor!==null&&(u["ansi-truecolor-fg"]=i.fg_truecolor,i.fg_truecolor=null)):a.push("color:rgb("+i.fg+")")),i.bg&&(o?(a.push(i.bg+"-bg"),i.bg_truecolor!==null&&(u["ansi-truecolor-bg"]=i.bg_truecolor,i.bg_truecolor=null)):a.push("background-color:rgb("+i.bg+")")),i.decorations.forEach(c=>{if(o){s.push("ansi-"+c);return}c==="bold"?s.push("font-weight:bold"):c==="dim"?s.push("opacity:0.5"):c==="italic"?s.push("font-style:italic"):c==="hidden"?s.push("visibility:hidden"):c==="strikethrough"?l.push("line-through"):l.push(c)}),l.length&&s.push("text-decoration:"+l.join(" ")),o?'"+i.content+"":'"+i.content+""}removeDecoration(t){const n=this.decorations.indexOf(t);n>=0&&this.decorations.splice(n,1)}}var tAe=Oa;const bUe=Ge(tAe);function q(e,t,n){function r(s,l){var u;Object.defineProperty(s,"_zod",{value:s._zod??{},enumerable:!1}),(u=s._zod).traits??(u.traits=new Set),s._zod.traits.add(e),t(s,l);for(const f in a.prototype)f in s||Object.defineProperty(s,f,{value:a.prototype[f].bind(s)});s._zod.constr=a,s._zod.def=l}const i=(n==null?void 0:n.Parent)??Object;class o extends i{}Object.defineProperty(o,"name",{value:e});function a(s){var l;const u=n!=null&&n.Parent?new o:this;r(u,s),(l=u._zod).deferred??(l.deferred=[]);for(const f of u._zod.deferred)f();return u}return Object.defineProperty(a,"init",{value:r}),Object.defineProperty(a,Symbol.hasInstance,{value:s=>{var l,u;return n!=null&&n.Parent&&s instanceof n.Parent?!0:(u=(l=s==null?void 0:s._zod)==null?void 0:l.traits)==null?void 0:u.has(e)}}),Object.defineProperty(a,"name",{value:e}),a}class Bf extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}const hF={};function Go(e){return hF}function nAe(e){const t=Object.values(e).filter(r=>typeof r=="number");return Object.entries(e).filter(([r,i])=>t.indexOf(+r)===-1).map(([r,i])=>i)}function rAe(e,t){return typeof t=="bigint"?t.toString():t}function L$(e){return{get value(){{const t=e();return Object.defineProperty(this,"value",{value:t}),t}}}}function U$(e){return e==null}function F$(e){const t=e.startsWith("^")?1:0,n=e.endsWith("$")?e.length-1:e.length;return e.slice(t,n)}function iAe(e,t){const n=(e.toString().split(".")[1]||"").length,r=(t.toString().split(".")[1]||"").length,i=n>r?n:r,o=Number.parseInt(e.toFixed(i).replace(".","")),a=Number.parseInt(t.toFixed(i).replace(".",""));return o%a/10**i}function vt(e,t,n){Object.defineProperty(e,t,{get(){{const r=n();return e[t]=r,r}},set(r){Object.defineProperty(e,t,{value:r})},configurable:!0})}function Fg(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}function oc(e){return JSON.stringify(e)}const mF=Error.captureStackTrace?Error.captureStackTrace:(...e)=>{};function iv(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}const oAe=L$(()=>{var e;if(typeof navigator<"u"&&((e=navigator==null?void 0:navigator.userAgent)!=null&&e.includes("Cloudflare")))return!1;try{const t=Function;return new t(""),!0}catch{return!1}});function ov(e){if(iv(e)===!1)return!1;const t=e.constructor;if(t===void 0)return!0;const n=t.prototype;return!(iv(n)===!1||Object.prototype.hasOwnProperty.call(n,"isPrototypeOf")===!1)}const aAe=new Set(["string","number","symbol"]);function bd(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function xs(e,t,n){const r=new e._zod.constr(t??e._zod.def);return(!t||n!=null&&n.parent)&&(r._zod.parent=e),r}function he(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if((t==null?void 0:t.message)!==void 0){if((t==null?void 0:t.error)!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}function sAe(e){return Object.keys(e).filter(t=>e[t]._zod.optin==="optional"&&e[t]._zod.optout==="optional")}const lAe={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function uAe(e,t){const n={},r=e._zod.def;for(const i in t){if(!(i in r.shape))throw new Error(`Unrecognized key: "${i}"`);t[i]&&(n[i]=r.shape[i])}return xs(e,{...e._zod.def,shape:n,checks:[]})}function cAe(e,t){const n={...e._zod.def.shape},r=e._zod.def;for(const i in t){if(!(i in r.shape))throw new Error(`Unrecognized key: "${i}"`);t[i]&&delete n[i]}return xs(e,{...e._zod.def,shape:n,checks:[]})}function fAe(e,t){if(!ov(t))throw new Error("Invalid input to extend: expected a plain object");const n={...e._zod.def,get shape(){const r={...e._zod.def.shape,...t};return Fg(this,"shape",r),r},checks:[]};return xs(e,n)}function dAe(e,t){return xs(e,{...e._zod.def,get shape(){const n={...e._zod.def.shape,...t._zod.def.shape};return Fg(this,"shape",n),n},catchall:t._zod.def.catchall,checks:[]})}function pAe(e,t,n){const r=t._zod.def.shape,i={...r};if(n)for(const o in n){if(!(o in r))throw new Error(`Unrecognized key: "${o}"`);n[o]&&(i[o]=e?new e({type:"optional",innerType:r[o]}):r[o])}else for(const o in r)i[o]=e?new e({type:"optional",innerType:r[o]}):r[o];return xs(t,{...t._zod.def,shape:i,checks:[]})}function hAe(e,t,n){const r=t._zod.def.shape,i={...r};if(n)for(const o in n){if(!(o in i))throw new Error(`Unrecognized key: "${o}"`);n[o]&&(i[o]=new e({type:"nonoptional",innerType:r[o]}))}else for(const o in r)i[o]=new e({type:"nonoptional",innerType:r[o]});return xs(t,{...t._zod.def,shape:i,checks:[]})}function Rc(e,t=0){var n;for(let r=t;r{var r;return(r=n).path??(r.path=[]),n.path.unshift(e),n})}function Hp(e){return typeof e=="string"?e:e==null?void 0:e.message}function Ko(e,t,n){var i,o,a,s,l,u;const r={...e,path:e.path??[]};if(!e.message){const f=Hp((a=(o=(i=e.inst)==null?void 0:i._zod.def)==null?void 0:o.error)==null?void 0:a.call(o,e))??Hp((s=t==null?void 0:t.error)==null?void 0:s.call(t,e))??Hp((l=n.customError)==null?void 0:l.call(n,e))??Hp((u=n.localeError)==null?void 0:u.call(n,e))??"Invalid input";r.message=f}return delete r.inst,delete r.continue,t!=null&&t.reportInput||delete r.input,r}function B$(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function Zf(...e){const[t,n,r]=e;return typeof t=="string"?{message:t,code:"custom",input:n,inst:r}:{...t}}const vF=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),Object.defineProperty(e,"message",{get(){return JSON.stringify(t,rAe,2)},enumerable:!0}),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},gF=q("$ZodError",vF),yF=q("$ZodError",vF,{Parent:Error});function mAe(e,t=n=>n.message){const n={},r=[];for(const i of e.issues)i.path.length>0?(n[i.path[0]]=n[i.path[0]]||[],n[i.path[0]].push(t(i))):r.push(t(i));return{formErrors:r,fieldErrors:n}}function vAe(e,t){const n=t||function(o){return o.message},r={_errors:[]},i=o=>{for(const a of o.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(s=>i({issues:s}));else if(a.code==="invalid_key")i({issues:a.issues});else if(a.code==="invalid_element")i({issues:a.issues});else if(a.path.length===0)r._errors.push(n(a));else{let s=r,l=0;for(;l(t,n,r,i)=>{const o=r?Object.assign(r,{async:!1}):{async:!1},a=t._zod.run({value:n,issues:[]},o);if(a instanceof Promise)throw new Bf;if(a.issues.length){const s=new((i==null?void 0:i.Err)??e)(a.issues.map(l=>Ko(l,o,Go())));throw mF(s,i==null?void 0:i.callee),s}return a.value},yAe=e=>async(t,n,r,i)=>{const o=r?Object.assign(r,{async:!0}):{async:!0};let a=t._zod.run({value:n,issues:[]},o);if(a instanceof Promise&&(a=await a),a.issues.length){const s=new((i==null?void 0:i.Err)??e)(a.issues.map(l=>Ko(l,o,Go())));throw mF(s,i==null?void 0:i.callee),s}return a.value},bF=e=>(t,n,r)=>{const i=r?{...r,async:!1}:{async:!1},o=t._zod.run({value:n,issues:[]},i);if(o instanceof Promise)throw new Bf;return o.issues.length?{success:!1,error:new(e??gF)(o.issues.map(a=>Ko(a,i,Go())))}:{success:!0,data:o.value}},wF=bF(yF),xF=e=>async(t,n,r)=>{const i=r?Object.assign(r,{async:!0}):{async:!0};let o=t._zod.run({value:n,issues:[]},i);return o instanceof Promise&&(o=await o),o.issues.length?{success:!1,error:new e(o.issues.map(a=>Ko(a,i,Go())))}:{success:!0,data:o.value}},bAe=xF(yF),wAe=/^[cC][^\s-]{8,}$/,xAe=/^[0-9a-z]+$/,_Ae=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,SAe=/^[0-9a-vA-V]{20}$/,$Ae=/^[A-Za-z0-9]{27}$/,kAe=/^[a-zA-Z0-9_-]{21}$/,EAe=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,PAe=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,U2=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$/,OAe=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,TAe="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function IAe(){return new RegExp(TAe,"u")}const AAe=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,CAe=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})$/,RAe=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,NAe=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,jAe=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,_F=/^[A-Za-z0-9_-]*$/,DAe=/^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/,MAe=/^\+(?:[0-9]){6,14}[0-9]$/,SF="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",zAe=new RegExp(`^${SF}$`);function $F(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function LAe(e){return new RegExp(`^${$F(e)}$`)}function UAe(e){const t=$F({precision:e.precision}),n=["Z"];e.local&&n.push(""),e.offset&&n.push("([+-]\\d{2}:\\d{2})");const r=`${t}(?:${n.join("|")})`;return new RegExp(`^${SF}T(?:${r})$`)}const FAe=e=>{const t=e?`[\\s\\S]{${(e==null?void 0:e.minimum)??0},${(e==null?void 0:e.maximum)??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},BAe=/^\d+$/,ZAe=/^-?\d+(?:\.\d+)?/i,WAe=/true|false/i,HAe=/null/i,VAe=/^[^A-Z]*$/,qAe=/^[^a-z]*$/,Hn=q("$ZodCheck",(e,t)=>{var n;e._zod??(e._zod={}),e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),kF={number:"number",bigint:"bigint",object:"date"},EF=q("$ZodCheckLessThan",(e,t)=>{Hn.init(e,t);const n=kF[typeof t.value];e._zod.onattach.push(r=>{const i=r._zod.bag,o=(t.inclusive?i.maximum:i.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?r.value<=t.value:r.value{Hn.init(e,t);const n=kF[typeof t.value];e._zod.onattach.push(r=>{const i=r._zod.bag,o=(t.inclusive?i.minimum:i.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>o&&(t.inclusive?i.minimum=t.value:i.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:"too_small",minimum:t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),GAe=q("$ZodCheckMultipleOf",(e,t)=>{Hn.init(e,t),e._zod.onattach.push(n=>{var r;(r=n._zod.bag).multipleOf??(r.multipleOf=t.value)}),e._zod.check=n=>{if(typeof n.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof n.value=="bigint"?n.value%t.value===BigInt(0):iAe(n.value,t.value)===0)||n.issues.push({origin:typeof n.value,code:"not_multiple_of",divisor:t.value,input:n.value,inst:e,continue:!t.abort})}}),KAe=q("$ZodCheckNumberFormat",(e,t)=>{var a;Hn.init(e,t),t.format=t.format||"float64";const n=(a=t.format)==null?void 0:a.includes("int"),r=n?"int":"number",[i,o]=lAe[t.format];e._zod.onattach.push(s=>{const l=s._zod.bag;l.format=t.format,l.minimum=i,l.maximum=o,n&&(l.pattern=BAe)}),e._zod.check=s=>{const l=s.value;if(n){if(!Number.isInteger(l)){s.issues.push({expected:r,format:t.format,code:"invalid_type",input:l,inst:e});return}if(!Number.isSafeInteger(l)){l>0?s.issues.push({input:l,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort}):s.issues.push({input:l,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort});return}}lo&&s.issues.push({origin:"number",input:l,code:"too_big",maximum:o,inst:e})}}),YAe=q("$ZodCheckMaxLength",(e,t)=>{var n;Hn.init(e,t),(n=e._zod.def).when??(n.when=r=>{const i=r.value;return!U$(i)&&i.length!==void 0}),e._zod.onattach.push(r=>{const i=r._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const i=r.value;if(i.length<=t.maximum)return;const a=B$(i);r.issues.push({origin:a,code:"too_big",maximum:t.maximum,inclusive:!0,input:i,inst:e,continue:!t.abort})}}),XAe=q("$ZodCheckMinLength",(e,t)=>{var n;Hn.init(e,t),(n=e._zod.def).when??(n.when=r=>{const i=r.value;return!U$(i)&&i.length!==void 0}),e._zod.onattach.push(r=>{const i=r._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>i&&(r._zod.bag.minimum=t.minimum)}),e._zod.check=r=>{const i=r.value;if(i.length>=t.minimum)return;const a=B$(i);r.issues.push({origin:a,code:"too_small",minimum:t.minimum,inclusive:!0,input:i,inst:e,continue:!t.abort})}}),JAe=q("$ZodCheckLengthEquals",(e,t)=>{var n;Hn.init(e,t),(n=e._zod.def).when??(n.when=r=>{const i=r.value;return!U$(i)&&i.length!==void 0}),e._zod.onattach.push(r=>{const i=r._zod.bag;i.minimum=t.length,i.maximum=t.length,i.length=t.length}),e._zod.check=r=>{const i=r.value,o=i.length;if(o===t.length)return;const a=B$(i),s=o>t.length;r.issues.push({origin:a,...s?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:r.value,inst:e,continue:!t.abort})}}),Bg=q("$ZodCheckStringFormat",(e,t)=>{var n,r;Hn.init(e,t),e._zod.onattach.push(i=>{const o=i._zod.bag;o.format=t.format,t.pattern&&(o.patterns??(o.patterns=new Set),o.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=i=>{t.pattern.lastIndex=0,!t.pattern.test(i.value)&&i.issues.push({origin:"string",code:"invalid_format",format:t.format,input:i.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),QAe=q("$ZodCheckRegex",(e,t)=>{Bg.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:"string",code:"invalid_format",format:"regex",input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),eCe=q("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=VAe),Bg.init(e,t)}),tCe=q("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=qAe),Bg.init(e,t)}),nCe=q("$ZodCheckIncludes",(e,t)=>{Hn.init(e,t);const n=bd(t.includes),r=new RegExp(typeof t.position=="number"?`^.{${t.position}}${n}`:n);t.pattern=r,e._zod.onattach.push(i=>{const o=i._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(r)}),e._zod.check=i=>{i.value.includes(t.includes,t.position)||i.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:i.value,inst:e,continue:!t.abort})}}),rCe=q("$ZodCheckStartsWith",(e,t)=>{Hn.init(e,t);const n=new RegExp(`^${bd(t.prefix)}.*`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const i=r._zod.bag;i.patterns??(i.patterns=new Set),i.patterns.add(n)}),e._zod.check=r=>{r.value.startsWith(t.prefix)||r.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:r.value,inst:e,continue:!t.abort})}}),iCe=q("$ZodCheckEndsWith",(e,t)=>{Hn.init(e,t);const n=new RegExp(`.*${bd(t.suffix)}$`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const i=r._zod.bag;i.patterns??(i.patterns=new Set),i.patterns.add(n)}),e._zod.check=r=>{r.value.endsWith(t.suffix)||r.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:r.value,inst:e,continue:!t.abort})}}),oCe=q("$ZodCheckOverwrite",(e,t)=>{Hn.init(e,t),e._zod.check=n=>{n.value=t.tx(n.value)}});class aCe{constructor(t=[]){this.content=[],this.indent=0,this&&(this.args=t)}indented(t){this.indent+=1,t(this),this.indent-=1}write(t){if(typeof t=="function"){t(this,{execution:"sync"}),t(this,{execution:"async"});return}const r=t.split(` +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function OU(e,t){if(e){if(typeof e=="string")return o1(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return o1(e,t)}}function WTe(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function HTe(e){if(Array.isArray(e))return o1(e)}function o1(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0?a:t&&t.length&&re(i)&&re(o)?t.slice(i,o+1):[]};function AU(e){return e==="number"?[0,"auto"]:void 0}var a1=function(t,n,r,i){var o=t.graphicalItems,a=t.tooltipAxis,s=Ug(n,t);return r<0||!o||!o.length||r>=s.length?null:o.reduce(function(l,u){var f,c=(f=u.props.data)!==null&&f!==void 0?f:n;c&&t.dataStartIndex+t.dataEndIndex!==0&&t.dataEndIndex-t.dataStartIndex>=r&&(c=c.slice(t.dataStartIndex,t.dataEndIndex+1));var d;if(a.dataKey&&!a.allowDuplicatedCategory){var p=c===void 0?s:c;d=tm(p,a.dataKey,i)}else d=c&&c[r]||s[r];return d?[].concat(Yl(l),[S6(u,d)]):l},[])},N2=function(t,n,r,i){var o=i||{x:t.chartX,y:t.chartY},a=KTe(o,r),s=t.orderedTooltipTicks,l=t.tooltipAxis,u=t.tooltipTicks,f=Yxe(a,s,u,l);if(f>=0&&u){var c=u[f]&&u[f].value,d=a1(t,n,f,c),p=YTe(r,s,f,o);return{activeTooltipIndex:f,activeLabel:c,activePayload:d,activeCoordinate:p}}return null},XTe=function(t,n){var r=n.axes,i=n.graphicalItems,o=n.axisType,a=n.axisIdKey,s=n.stackGroups,l=n.dataStartIndex,u=n.dataEndIndex,f=t.layout,c=t.children,d=t.stackOffset,p=w6(f,o);return r.reduce(function(h,m){var g,v=m.type.defaultProps!==void 0?H(H({},m.type.defaultProps),m.props):m.props,y=v.type,b=v.dataKey,_=v.allowDataOverflow,x=v.allowDuplicatedCategory,w=v.scale,$=v.ticks,k=v.includeHidden,T=v[a];if(h[T])return h;var E=Ug(t.data,{graphicalItems:i.filter(function(V){var A,pe=a in V.props?V.props[a]:(A=V.type.defaultProps)===null||A===void 0?void 0:A[a];return pe===T}),dataStartIndex:l,dataEndIndex:u}),I=E.length,O,j,D;STe(v.domain,_,y)&&(O=Sx(v.domain,null,_),p&&(y==="number"||w!=="auto")&&(D=Ic(E,b,"category")));var M=AU(y);if(!O||O.length===0){var L,B=(L=v.domain)!==null&&L!==void 0?L:M;if(b){if(O=Ic(E,b,y),y==="category"&&p){var R=zse(O);x&&R?(j=O,O=Zm(0,I)):x||(O=pC(B,O,m).reduce(function(V,A){return V.indexOf(A)>=0?V:[].concat(Yl(V),[A])},[]))}else if(y==="category")x?O=O.filter(function(V){return V!==""&&!Ce(V)}):O=pC(B,O,m).reduce(function(V,A){return V.indexOf(A)>=0||A===""||Ce(A)?V:[].concat(Yl(V),[A])},[]);else if(y==="number"){var U=t1e(E,i.filter(function(V){var A,pe,me=a in V.props?V.props[a]:(A=V.type.defaultProps)===null||A===void 0?void 0:A[a],ce="hide"in V.props?V.props.hide:(pe=V.type.defaultProps)===null||pe===void 0?void 0:pe.hide;return me===T&&(k||!ce)}),b,o,f);U&&(O=U)}p&&(y==="number"||w!=="auto")&&(D=Ic(E,b,"category"))}else p?O=Zm(0,I):s&&s[T]&&s[T].hasStack&&y==="number"?O=d==="expand"?[0,1]:_6(s[T].stackGroups,l,u):O=b6(E,i.filter(function(V){var A=a in V.props?V.props[a]:V.type.defaultProps[a],pe="hide"in V.props?V.props.hide:V.type.defaultProps.hide;return A===T&&(k||!pe)}),y,f,!0);if(y==="number")O=r1(c,O,T,o,$),B&&(O=Sx(B,O,_));else if(y==="category"&&B){var P=B,G=O.every(function(V){return P.indexOf(V)>=0});G&&(O=P)}}return H(H({},h),{},be({},T,H(H({},v),{},{axisType:o,domain:O,categoricalDomain:D,duplicateDomain:j,originalDomain:(g=v.domain)!==null&&g!==void 0?g:M,isCategorical:p,layout:f})))},{})},JTe=function(t,n){var r=n.graphicalItems,i=n.Axis,o=n.axisType,a=n.axisIdKey,s=n.stackGroups,l=n.dataStartIndex,u=n.dataEndIndex,f=t.layout,c=t.children,d=Ug(t.data,{graphicalItems:r,dataStartIndex:l,dataEndIndex:u}),p=d.length,h=w6(f,o),m=-1;return r.reduce(function(g,v){var y=v.type.defaultProps!==void 0?H(H({},v.type.defaultProps),v.props):v.props,b=y[a],_=AU("number");if(!g[b]){m++;var x;return h?x=Zm(0,p):s&&s[b]&&s[b].hasStack?(x=_6(s[b].stackGroups,l,u),x=r1(c,x,b,o)):(x=Sx(_,b6(d,r.filter(function(w){var $,k,T=a in w.props?w.props[a]:($=w.type.defaultProps)===null||$===void 0?void 0:$[a],E="hide"in w.props?w.props.hide:(k=w.type.defaultProps)===null||k===void 0?void 0:k.hide;return T===b&&!E}),"number",f),i.defaultProps.allowDataOverflow),x=r1(c,x,b,o)),H(H({},g),{},be({},b,H(H({axisType:o},i.defaultProps),{},{hide:!0,orientation:Sr(qTe,"".concat(o,".").concat(m%2),null),domain:x,originalDomain:_,isCategorical:h,layout:f})))}return g},{})},QTe=function(t,n){var r=n.axisType,i=r===void 0?"xAxis":r,o=n.AxisComp,a=n.graphicalItems,s=n.stackGroups,l=n.dataStartIndex,u=n.dataEndIndex,f=t.children,c="".concat(i,"Id"),d=$r(f,o),p={};return d&&d.length?p=XTe(t,{axes:d,graphicalItems:a,axisType:i,axisIdKey:c,stackGroups:s,dataStartIndex:l,dataEndIndex:u}):a&&a.length&&(p=JTe(t,{Axis:o,graphicalItems:a,axisType:i,axisIdKey:c,stackGroups:s,dataStartIndex:l,dataEndIndex:u})),p},eIe=function(t){var n=Oo(t),r=Ui(n,!1,!0);return{tooltipTicks:r,orderedTooltipTicks:VS(r,function(i){return i.coordinate}),tooltipAxis:n,tooltipAxisBandSize:Rm(n,r)}},j2=function(t){var n=t.children,r=t.defaultShowTooltip,i=Xn(n,Ll),o=0,a=0;return t.data&&t.data.length!==0&&(a=t.data.length-1),i&&i.props&&(i.props.startIndex>=0&&(o=i.props.startIndex),i.props.endIndex>=0&&(a=i.props.endIndex)),{chartX:0,chartY:0,dataStartIndex:o,dataEndIndex:a,activeTooltipIndex:-1,isTooltipActive:!!r}},tIe=function(t){return!t||!t.length?!1:t.some(function(n){var r=Zi(n&&n.type);return r&&r.indexOf("Bar")>=0})},D2=function(t){return t==="horizontal"?{numericAxisName:"yAxis",cateAxisName:"xAxis"}:t==="vertical"?{numericAxisName:"xAxis",cateAxisName:"yAxis"}:t==="centric"?{numericAxisName:"radiusAxis",cateAxisName:"angleAxis"}:{numericAxisName:"angleAxis",cateAxisName:"radiusAxis"}},nIe=function(t,n){var r=t.props,i=t.graphicalItems,o=t.xAxisMap,a=o===void 0?{}:o,s=t.yAxisMap,l=s===void 0?{}:s,u=r.width,f=r.height,c=r.children,d=r.margin||{},p=Xn(c,Ll),h=Xn(c,cl),m=Object.keys(l).reduce(function(x,w){var $=l[w],k=$.orientation;return!$.mirror&&!$.hide?H(H({},x),{},be({},k,x[k]+$.width)):x},{left:d.left||0,right:d.right||0}),g=Object.keys(a).reduce(function(x,w){var $=a[w],k=$.orientation;return!$.mirror&&!$.hide?H(H({},x),{},be({},k,Sr(x,"".concat(k))+$.height)):x},{top:d.top||0,bottom:d.bottom||0}),v=H(H({},g),m),y=v.bottom;p&&(v.bottom+=p.props.height||Ll.defaultProps.height),h&&n&&(v=Qxe(v,i,r,n));var b=u-v.left-v.right,_=f-v.top-v.bottom;return H(H({brushBottom:y},v),{},{width:Math.max(b,0),height:Math.max(_,0)})},rIe=function(t,n){if(n==="xAxis")return t[n].width;if(n==="yAxis")return t[n].height},I$=function(t){var n=t.chartName,r=t.GraphicalChild,i=t.defaultTooltipEventType,o=i===void 0?"axis":i,a=t.validateTooltipEventTypes,s=a===void 0?["axis"]:a,l=t.axisComponents,u=t.legendContent,f=t.formatAxisMap,c=t.defaultProps,d=function(v,y){var b=y.graphicalItems,_=y.stackGroups,x=y.offset,w=y.updateId,$=y.dataStartIndex,k=y.dataEndIndex,T=v.barSize,E=v.layout,I=v.barGap,O=v.barCategoryGap,j=v.maxBarSize,D=D2(E),M=D.numericAxisName,L=D.cateAxisName,B=tIe(b),R=[];return b.forEach(function(U,P){var G=Ug(v.data,{graphicalItems:[U],dataStartIndex:$,dataEndIndex:k}),V=U.type.defaultProps!==void 0?H(H({},U.type.defaultProps),U.props):U.props,A=V.dataKey,pe=V.maxBarSize,me=V["".concat(M,"Id")],ce=V["".concat(L,"Id")],ie={},oe=l.reduce(function(jr,Rn){var Nu=y["".concat(Rn.axisType,"Map")],Xd=V["".concat(Rn.axisType,"Id")];Nu&&Nu[Xd]||Rn.axisType==="zAxis"||os();var Jd=Nu[Xd];return H(H({},jr),{},be(be({},Rn.axisType,Jd),"".concat(Rn.axisType,"Ticks"),Ui(Jd)))},ie),Y=oe[L],ne=oe["".concat(L,"Ticks")],Q=_&&_[me]&&_[me].hasStack&&p1e(U,_[me].stackGroups),Z=Zi(U.type).indexOf("Bar")>=0,$e=Rm(Y,ne),ve=[],Me=B&&Xxe({barSize:T,stackGroups:_,totalSize:rIe(oe,L)});if(Z){var Ue,Qe,fn=Ce(pe)?j:pe,Sn=(Ue=(Qe=Rm(Y,ne,!0))!==null&&Qe!==void 0?Qe:fn)!==null&&Ue!==void 0?Ue:0;ve=Jxe({barGap:I,barCategoryGap:O,bandSize:Sn!==$e?Sn:$e,sizeList:Me[ce],maxBarSize:fn}),Sn!==$e&&(ve=ve.map(function(jr){return H(H({},jr),{},{position:H(H({},jr.position),{},{offset:jr.position.offset-Sn/2})})}))}var Ft=U&&U.type&&U.type.getComposedData;Ft&&R.push({props:H(H({},Ft(H(H({},oe),{},{displayedData:G,props:v,dataKey:A,item:U,bandSize:$e,barPosition:ve,offset:x,stackedData:Q,layout:E,dataStartIndex:$,dataEndIndex:k}))),{},be(be(be({key:U.key||"item-".concat(P)},M,oe[M]),L,oe[L]),"animationId",w)),childIndex:Yse(U,v.children),item:U})}),R},p=function(v,y){var b=v.props,_=v.dataStartIndex,x=v.dataEndIndex,w=v.updateId;if(!sI({props:b}))return null;var $=b.children,k=b.layout,T=b.stackOffset,E=b.data,I=b.reverseStackOrder,O=D2(k),j=O.numericAxisName,D=O.cateAxisName,M=$r($,r),L=c1e(E,M,"".concat(j,"Id"),"".concat(D,"Id"),T,I),B=l.reduce(function(V,A){var pe="".concat(A.axisType,"Map");return H(H({},V),{},be({},pe,QTe(b,H(H({},A),{},{graphicalItems:M,stackGroups:A.axisType===j&&L,dataStartIndex:_,dataEndIndex:x}))))},{}),R=nIe(H(H({},B),{},{props:b,graphicalItems:M}),y==null?void 0:y.legendBBox);Object.keys(B).forEach(function(V){B[V]=f(b,B[V],R,V.replace("Map",""),n)});var U=B["".concat(D,"Map")],P=eIe(U),G=d(b,H(H({},B),{},{dataStartIndex:_,dataEndIndex:x,updateId:w,graphicalItems:M,stackGroups:L,offset:R}));return H(H({formattedGraphicalItems:G,graphicalItems:M,offset:R,stackGroups:L},P),B)},h=function(g){function v(y){var b,_,x;return DTe(this,v),x=LTe(this,v,[y]),be(x,"eventEmitterSymbol",Symbol("rechartsEventEmitter")),be(x,"accessibilityManager",new _Te),be(x,"handleLegendBBoxUpdate",function(w){if(w){var $=x.state,k=$.dataStartIndex,T=$.dataEndIndex,E=$.updateId;x.setState(H({legendBBox:w},p({props:x.props,dataStartIndex:k,dataEndIndex:T,updateId:E},H(H({},x.state),{},{legendBBox:w}))))}}),be(x,"handleReceiveSyncEvent",function(w,$,k){if(x.props.syncId===w){if(k===x.eventEmitterSymbol&&typeof x.props.syncMethod!="function")return;x.applySyncEvent($)}}),be(x,"handleBrushChange",function(w){var $=w.startIndex,k=w.endIndex;if($!==x.state.dataStartIndex||k!==x.state.dataEndIndex){var T=x.state.updateId;x.setState(function(){return H({dataStartIndex:$,dataEndIndex:k},p({props:x.props,dataStartIndex:$,dataEndIndex:k,updateId:T},x.state))}),x.triggerSyncEvent({dataStartIndex:$,dataEndIndex:k})}}),be(x,"handleMouseEnter",function(w){var $=x.getMouseInfo(w);if($){var k=H(H({},$),{},{isTooltipActive:!0});x.setState(k),x.triggerSyncEvent(k);var T=x.props.onMouseEnter;Pe(T)&&T(k,w)}}),be(x,"triggeredAfterMouseMove",function(w){var $=x.getMouseInfo(w),k=$?H(H({},$),{},{isTooltipActive:!0}):{isTooltipActive:!1};x.setState(k),x.triggerSyncEvent(k);var T=x.props.onMouseMove;Pe(T)&&T(k,w)}),be(x,"handleItemMouseEnter",function(w){x.setState(function(){return{isTooltipActive:!0,activeItem:w,activePayload:w.tooltipPayload,activeCoordinate:w.tooltipPosition||{x:w.cx,y:w.cy}}})}),be(x,"handleItemMouseLeave",function(){x.setState(function(){return{isTooltipActive:!1}})}),be(x,"handleMouseMove",function(w){w.persist(),x.throttleTriggeredAfterMouseMove(w)}),be(x,"handleMouseLeave",function(w){x.throttleTriggeredAfterMouseMove.cancel();var $={isTooltipActive:!1};x.setState($),x.triggerSyncEvent($);var k=x.props.onMouseLeave;Pe(k)&&k($,w)}),be(x,"handleOuterEvent",function(w){var $=Kse(w),k=Sr(x.props,"".concat($));if($&&Pe(k)){var T,E;/.*touch.*/i.test($)?E=x.getMouseInfo(w.changedTouches[0]):E=x.getMouseInfo(w),k((T=E)!==null&&T!==void 0?T:{},w)}}),be(x,"handleClick",function(w){var $=x.getMouseInfo(w);if($){var k=H(H({},$),{},{isTooltipActive:!0});x.setState(k),x.triggerSyncEvent(k);var T=x.props.onClick;Pe(T)&&T(k,w)}}),be(x,"handleMouseDown",function(w){var $=x.props.onMouseDown;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"handleMouseUp",function(w){var $=x.props.onMouseUp;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"handleTouchMove",function(w){w.changedTouches!=null&&w.changedTouches.length>0&&x.throttleTriggeredAfterMouseMove(w.changedTouches[0])}),be(x,"handleTouchStart",function(w){w.changedTouches!=null&&w.changedTouches.length>0&&x.handleMouseDown(w.changedTouches[0])}),be(x,"handleTouchEnd",function(w){w.changedTouches!=null&&w.changedTouches.length>0&&x.handleMouseUp(w.changedTouches[0])}),be(x,"handleDoubleClick",function(w){var $=x.props.onDoubleClick;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"handleContextMenu",function(w){var $=x.props.onContextMenu;if(Pe($)){var k=x.getMouseInfo(w);$(k,w)}}),be(x,"triggerSyncEvent",function(w){x.props.syncId!==void 0&&c0.emit(f0,x.props.syncId,w,x.eventEmitterSymbol)}),be(x,"applySyncEvent",function(w){var $=x.props,k=$.layout,T=$.syncMethod,E=x.state.updateId,I=w.dataStartIndex,O=w.dataEndIndex;if(w.dataStartIndex!==void 0||w.dataEndIndex!==void 0)x.setState(H({dataStartIndex:I,dataEndIndex:O},p({props:x.props,dataStartIndex:I,dataEndIndex:O,updateId:E},x.state)));else if(w.activeTooltipIndex!==void 0){var j=w.chartX,D=w.chartY,M=w.activeTooltipIndex,L=x.state,B=L.offset,R=L.tooltipTicks;if(!B)return;if(typeof T=="function")M=T(R,w);else if(T==="value"){M=-1;for(var U=0;U=0){var Q,Z;if(j.dataKey&&!j.allowDuplicatedCategory){var $e=typeof j.dataKey=="function"?ne:"payload.".concat(j.dataKey.toString());Q=tm(U,$e,M),Z=P&&G&&tm(G,$e,M)}else Q=U==null?void 0:U[D],Z=P&&G&&G[D];if(ce||me){var ve=w.props.activeIndex!==void 0?w.props.activeIndex:D;return[S.cloneElement(w,H(H(H({},T.props),oe),{},{activeIndex:ve})),null,null]}if(!Ce(Q))return[Y].concat(Yl(x.renderActivePoints({item:T,activePoint:Q,basePoint:Z,childIndex:D,isRange:P})))}else{var Me,Ue=(Me=x.getItemByXY(x.state.activeCoordinate))!==null&&Me!==void 0?Me:{graphicalItem:Y},Qe=Ue.graphicalItem,fn=Qe.item,Sn=fn===void 0?w:fn,Ft=Qe.childIndex,jr=H(H(H({},T.props),oe),{},{activeIndex:Ft});return[S.cloneElement(Sn,jr),null,null]}return P?[Y,null,null]:[Y,null]}),be(x,"renderCustomized",function(w,$,k){return S.cloneElement(w,H(H({key:"recharts-customized-".concat(k)},x.props),x.state))}),be(x,"renderMap",{CartesianGrid:{handler:Bp,once:!0},ReferenceArea:{handler:x.renderReferenceElement},ReferenceLine:{handler:Bp},ReferenceDot:{handler:x.renderReferenceElement},XAxis:{handler:Bp},YAxis:{handler:Bp},Brush:{handler:x.renderBrush,once:!0},Bar:{handler:x.renderGraphicChild},Line:{handler:x.renderGraphicChild},Area:{handler:x.renderGraphicChild},Radar:{handler:x.renderGraphicChild},RadialBar:{handler:x.renderGraphicChild},Scatter:{handler:x.renderGraphicChild},Pie:{handler:x.renderGraphicChild},Funnel:{handler:x.renderGraphicChild},Tooltip:{handler:x.renderCursor,once:!0},PolarGrid:{handler:x.renderPolarGrid,once:!0},PolarAngleAxis:{handler:x.renderPolarAxis},PolarRadiusAxis:{handler:x.renderPolarAxis},Customized:{handler:x.renderCustomized}}),x.clipPathId="".concat((b=y.id)!==null&&b!==void 0?b:bu("recharts"),"-clip"),x.throttleTriggeredAfterMouseMove=g4(x.triggeredAfterMouseMove,(_=y.throttleDelay)!==null&&_!==void 0?_:1e3/60),x.state={},x}return BTe(v,g),zTe(v,[{key:"componentDidMount",value:function(){var b,_;this.addListener(),this.accessibilityManager.setDetails({container:this.container,offset:{left:(b=this.props.margin.left)!==null&&b!==void 0?b:0,top:(_=this.props.margin.top)!==null&&_!==void 0?_:0},coordinateList:this.state.tooltipTicks,mouseHandlerCallback:this.triggeredAfterMouseMove,layout:this.props.layout}),this.displayDefaultTooltip()}},{key:"displayDefaultTooltip",value:function(){var b=this.props,_=b.children,x=b.data,w=b.height,$=b.layout,k=Xn(_,Ri);if(k){var T=k.props.defaultIndex;if(!(typeof T!="number"||T<0||T>this.state.tooltipTicks.length-1)){var E=this.state.tooltipTicks[T]&&this.state.tooltipTicks[T].value,I=a1(this.state,x,T,E),O=this.state.tooltipTicks[T].coordinate,j=(this.state.offset.top+w)/2,D=$==="horizontal",M=D?{x:O,y:j}:{y:O,x:j},L=this.state.formattedGraphicalItems.find(function(R){var U=R.item;return U.type.name==="Scatter"});L&&(M=H(H({},M),L.props.points[T].tooltipPosition),I=L.props.points[T].tooltipPayload);var B={activeTooltipIndex:T,isTooltipActive:!0,activeLabel:E,activePayload:I,activeCoordinate:M};this.setState(B),this.renderCursor(k),this.accessibilityManager.setIndex(T)}}}},{key:"getSnapshotBeforeUpdate",value:function(b,_){if(!this.props.accessibilityLayer)return null;if(this.state.tooltipTicks!==_.tooltipTicks&&this.accessibilityManager.setDetails({coordinateList:this.state.tooltipTicks}),this.props.layout!==b.layout&&this.accessibilityManager.setDetails({layout:this.props.layout}),this.props.margin!==b.margin){var x,w;this.accessibilityManager.setDetails({offset:{left:(x=this.props.margin.left)!==null&&x!==void 0?x:0,top:(w=this.props.margin.top)!==null&&w!==void 0?w:0}})}return null}},{key:"componentDidUpdate",value:function(b){zw([Xn(b.children,Ri)],[Xn(this.props.children,Ri)])||this.displayDefaultTooltip()}},{key:"componentWillUnmount",value:function(){this.removeListener(),this.throttleTriggeredAfterMouseMove.cancel()}},{key:"getTooltipEventType",value:function(){var b=Xn(this.props.children,Ri);if(b&&typeof b.props.shared=="boolean"){var _=b.props.shared?"axis":"item";return s.indexOf(_)>=0?_:o}return o}},{key:"getMouseInfo",value:function(b){if(!this.container)return null;var _=this.container,x=_.getBoundingClientRect(),w=_ye(x),$={chartX:Math.round(b.pageX-w.left),chartY:Math.round(b.pageY-w.top)},k=x.width/_.offsetWidth||1,T=this.inRange($.chartX,$.chartY,k);if(!T)return null;var E=this.state,I=E.xAxisMap,O=E.yAxisMap,j=this.getTooltipEventType(),D=N2(this.state,this.props.data,this.props.layout,T);if(j!=="axis"&&I&&O){var M=Oo(I).scale,L=Oo(O).scale,B=M&&M.invert?M.invert($.chartX):null,R=L&&L.invert?L.invert($.chartY):null;return H(H({},$),{},{xValue:B,yValue:R},D)}return D?H(H({},$),D):null}},{key:"inRange",value:function(b,_){var x=arguments.length>2&&arguments[2]!==void 0?arguments[2]:1,w=this.props.layout,$=b/x,k=_/x;if(w==="horizontal"||w==="vertical"){var T=this.state.offset,E=$>=T.left&&$<=T.left+T.width&&k>=T.top&&k<=T.top+T.height;return E?{x:$,y:k}:null}var I=this.state,O=I.angleAxisMap,j=I.radiusAxisMap;if(O&&j){var D=Oo(O);return vC({x:$,y:k},D)}return null}},{key:"parseEventsOfWrapper",value:function(){var b=this.props.children,_=this.getTooltipEventType(),x=Xn(b,Ri),w={};x&&_==="axis"&&(x.props.trigger==="click"?w={onClick:this.handleClick}:w={onMouseEnter:this.handleMouseEnter,onDoubleClick:this.handleDoubleClick,onMouseMove:this.handleMouseMove,onMouseLeave:this.handleMouseLeave,onTouchMove:this.handleTouchMove,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onContextMenu:this.handleContextMenu});var $=nm(this.props,this.handleOuterEvent);return H(H({},$),w)}},{key:"addListener",value:function(){c0.on(f0,this.handleReceiveSyncEvent)}},{key:"removeListener",value:function(){c0.removeListener(f0,this.handleReceiveSyncEvent)}},{key:"filterFormatItem",value:function(b,_,x){for(var w=this.state.formattedGraphicalItems,$=0,k=w.length;$iIe(e,"name",{value:t,configurable:!0}),CU=!!(typeof window<"u"&&window.document&&window.document.createElement);function Ha(e,t,{checkForDefaultPrevented:n=!0}={}){return Ou(function(i){if(e==null||e(i),n===!1||!i||!i.defaultPrevented)return t==null?void 0:t(i)},"handleEvent")}Ou(Ha,"composeEventHandlers");function oIe(e){var t;if(!CU)throw new Error("Cannot access window outside of the DOM");return((t=e==null?void 0:e.ownerDocument)==null?void 0:t.defaultView)??window}Ou(oIe,"getOwnerWindow");function s1(e){if(!CU)throw new Error("Cannot access document outside of the DOM");return(e==null?void 0:e.ownerDocument)??document}Ou(s1,"getOwnerDocument");function RU(e,t=!1){const{activeElement:n}=s1(e);if(!(n!=null&&n.nodeName))return null;if(NU(n)&&n.contentDocument)return RU(n.contentDocument.body,t);if(t){const r=n.getAttribute("aria-activedescendant");if(r){const i=s1(n).getElementById(r);if(i)return i}}return n}Ou(RU,"getActiveElement");function NU(e){return e.tagName==="IFRAME"}Ou(NU,"isFrame");var aIe=Object.defineProperty,A$=(e,t)=>aIe(e,"name",{value:t,configurable:!0});function l1(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}A$(l1,"setRef");function jU(...e){return t=>{let n=!1;const r=e.map(i=>{const o=l1(i,t);return!n&&typeof o=="function"&&(n=!0),o});if(n)return()=>{for(let i=0;isIe(e,"name",{value:t,configurable:!0});function lIe(e,t){const n=S.createContext(t);n.displayName=e+"Context";const r=yr(o=>{const{children:a,...s}=o,l=S.useMemo(()=>s,Object.values(s));return z.jsx(n.Provider,{value:l,children:a})},"Provider");r.displayName=e+"Provider";function i(o,a={}){const{optional:s=!1}=a,l=S.useContext(n);if(l)return l;if(t!==void 0)return t;if(!s)throw new Error(`\`${o}\` must be used within \`${e}\``)}return yr(i,"useContext"),[r,i]}yr(lIe,"createContext");function C$(e,t=[]){let n=[];function r(o,a){const s=S.createContext(a);s.displayName=o+"Context";const l=n.length;n=[...n,a];const u=yr(c=>{var v;const{scope:d,children:p,...h}=c,m=((v=d==null?void 0:d[e])==null?void 0:v[l])||s,g=S.useMemo(()=>h,Object.values(h));return z.jsx(m.Provider,{value:g,children:p})},"Provider");u.displayName=o+"Provider";function f(c,d,p={}){var v;const{optional:h=!1}=p,m=((v=d==null?void 0:d[e])==null?void 0:v[l])||s,g=S.useContext(m);if(g)return g;if(a!==void 0)return a;if(!h)throw new Error(`\`${c}\` must be used within \`${o}\``)}return yr(f,"useContext"),[u,f]}yr(r,"createContext");const i=yr(()=>{const o=n.map(a=>S.createContext(a));return yr(function(s){const l=(s==null?void 0:s[e])||o;return S.useMemo(()=>({[`__scope${e}`]:{...s,[e]:l}}),[s,l])},"useScope")},"createScope");return i.scopeName=e,[r,DU(i,...t)]}yr(C$,"createContextScope");function DU(...e){const t=e[0];if(e.length===1)return t;const n=yr(()=>{const r=e.map(i=>({useScope:i(),scopeName:i.scopeName}));return yr(function(o){const a=r.reduce((s,{useScope:l,scopeName:u})=>{const c=l(o)[`__scope${u}`];return{...s,...c}},{});return S.useMemo(()=>({[`__scope${t.scopeName}`]:a}),[a])},"useComposedScopes")},"createScope");return n.scopeName=t.scopeName,n}yr(DU,"composeContextScopes");var uIe=Object.defineProperty,oi=(e,t)=>uIe(e,"name",{value:t,configurable:!0});function R$(e){const t=S.forwardRef((n,r)=>{let{children:i,...o}=n,a=null,s=!1;const l=[];u1(i)&&typeof Zp=="function"&&(i=Zp(i._payload)),S.Children.forEach(i,d=>{var p;if(UU(d)){s=!0;const h=d;let m="child"in h.props?h.props.child:h.props.children;u1(m)&&typeof Zp=="function"&&(m=Zp(m._payload)),a=fIe(h,m),l.push((p=a==null?void 0:a.props)==null?void 0:p.children)}else l.push(d)}),a?a=S.cloneElement(a,void 0,l):!s&&S.Children.count(i)===1&&S.isValidElement(i)&&(a=i);const u=a?LU(a):void 0,f=Tu(r,u);if(!a){if(i||i===0)throw new Error(s?hIe(e):pIe(e));return i}const c=zU(o,a.props??{});return a.type!==S.Fragment&&(c.ref=r?f:u),S.cloneElement(a,c)});return t.displayName=`${e}.Slot`,t}oi(R$,"createSlot");var MU=Symbol.for("radix.slottable");function cIe(e){const t=oi(n=>"child"in n?n.children(n.child):n.children,"Slottable");return t.displayName=`${e}.Slottable`,t.__radixId=MU,t}oi(cIe,"createSlottable");var fIe=oi((e,t)=>{if("child"in e.props){const n=e.props.child;return S.isValidElement(n)?S.cloneElement(n,void 0,e.props.children(n.props.children)):null}return S.isValidElement(t)?t:null},"getSlottableElementFromSlottable");function zU(e,t){const n={...t};for(const r in t){const i=e[r],o=t[r];/^on[A-Z]/.test(r)?i&&o?n[r]=(...s)=>{const l=o(...s);return i(...s),l}:i&&(n[r]=i):r==="style"?n[r]={...i,...o}:r==="className"&&(n[r]=[i,o].filter(Boolean).join(" "))}return{...e,...n}}oi(zU,"mergeProps");function LU(e){var r,i;let t=(r=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:r.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=(i=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:i.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}oi(LU,"getElementRef");function UU(e){return S.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===MU}oi(UU,"isSlottable");var dIe=Symbol.for("react.lazy");function u1(e){return e!=null&&typeof e=="object"&&"$$typeof"in e&&e.$$typeof===dIe&&"_payload"in e&&FU(e._payload)}oi(u1,"isLazyComponent");function FU(e){return typeof e=="object"&&e!==null&&"then"in e}oi(FU,"isPromiseLike");var pIe=oi(e=>`${e} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`,"createSlotError"),hIe=oi(e=>`${e} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`,"createSlottableError"),Zp=Xf[" use ".trim().toString()],mIe=Object.defineProperty,vIe=(e,t)=>mIe(e,"name",{value:t,configurable:!0}),gIe=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],gd=gIe.reduce((e,t)=>{const n=R$(`Primitive.${t}`),r=S.forwardRef((i,o)=>{const{asChild:a,...s}=i,l=a?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),z.jsx(l,{...s,ref:o})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function BU(e,t){e&&$i.flushSync(()=>e.dispatchEvent(t))}vIe(BU,"dispatchDiscreteCustomEvent");var yIe=Object.defineProperty,bIe=(e,t)=>yIe(e,"name",{value:t,configurable:!0});function ss(e){const t=S.useRef(e);return S.useEffect(()=>{t.current=e}),S.useMemo(()=>(...n)=>{var r;return(r=t.current)==null?void 0:r.call(t,...n)},[])}bIe(ss,"useCallbackRef");var wIe=Object.defineProperty,Xt=(e,t)=>wIe(e,"name",{value:t,configurable:!0}),c1="dismissableLayer.update",xIe="dismissableLayer.pointerDownOutside",_Ie="dismissableLayer.focusOutside",M2,ZU=S.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set,dismissableSurfaces:new Set}),SIe=S.forwardRef(Xt(function(t,n){const{disableOutsidePointerEvents:r=!1,deferPointerDownOutside:i=!1,onEscapeKeyDown:o,onPointerDownOutside:a,onFocusOutside:s,onInteractOutside:l,onDismiss:u,...f}=t,c=S.useContext(ZU),[d,p]=S.useState(null),h=(d==null?void 0:d.ownerDocument)??(globalThis==null?void 0:globalThis.document),[,m]=S.useState({}),g=Tu(n,p),v=Array.from(c.layers),[y]=[...c.layersWithOutsidePointerEventsDisabled].slice(-1),b=y?v.indexOf(y):-1,_=d?v.indexOf(d):-1,x=c.layersWithOutsidePointerEventsDisabled.size>0,w=_>=b,$=S.useRef(!1),k=WU(O=>{a==null||a(O),l==null||l(O),O.defaultPrevented||u==null||u()},{ownerDocument:h,deferPointerDownOutside:i,isDeferredPointerDownOutsideRef:$,dismissableSurfaces:c.dismissableSurfaces,shouldHandlePointerDownOutside:S.useCallback(O=>{if(!(O instanceof Node))return!1;const j=[...c.branches].some(D=>D.contains(O));return w&&!j},[c.branches,w])}),T=HU(O=>{if(i&&$.current)return;const j=O.target;[...c.branches].some(M=>M.contains(j))||(s==null||s(O),l==null||l(O),O.defaultPrevented||u==null||u())},h),E=d?_===v.length-1:!1,I=ss(O=>{O.key==="Escape"&&(o==null||o(O),!O.defaultPrevented&&u&&(O.preventDefault(),u()))});return S.useEffect(()=>{if(E)return h.addEventListener("keydown",I,{capture:!0}),()=>h.removeEventListener("keydown",I,{capture:!0})},[h,E,I]),S.useEffect(()=>{if(d)return r&&(c.layersWithOutsidePointerEventsDisabled.size===0&&(M2=h.body.style.pointerEvents,h.body.style.pointerEvents="none"),c.layersWithOutsidePointerEventsDisabled.add(d)),c.layers.add(d),f1(),()=>{r&&(c.layersWithOutsidePointerEventsDisabled.delete(d),c.layersWithOutsidePointerEventsDisabled.size===0&&(h.body.style.pointerEvents=M2))}},[d,h,r,c]),S.useEffect(()=>()=>{d&&(c.layers.delete(d),c.layersWithOutsidePointerEventsDisabled.delete(d),f1())},[d,c]),S.useEffect(()=>{const O=Xt(()=>m({}),"handleUpdate");return document.addEventListener(c1,O),()=>document.removeEventListener(c1,O)},[]),z.jsx(gd.div,{...f,ref:g,style:{pointerEvents:x?w?"auto":"none":void 0,...t.style},onFocusCapture:Ha(t.onFocusCapture,T.onFocusCapture),onBlurCapture:Ha(t.onBlurCapture,T.onBlurCapture),onPointerDownCapture:Ha(t.onPointerDownCapture,k.onPointerDownCapture)})},"DismissableLayer"));function $Ie(){const e=S.useContext(ZU),[t,n]=S.useState(null);return S.useEffect(()=>{if(t)return e.dismissableSurfaces.add(t),()=>{e.dismissableSurfaces.delete(t)}},[t,e.dismissableSurfaces]),n}Xt($Ie,"useDismissableLayerSurface");var kIe=Xt(()=>!0,"IS_TRUE");function WU(e,t){const{ownerDocument:n=globalThis==null?void 0:globalThis.document,deferPointerDownOutside:r=!1,isDeferredPointerDownOutsideRef:i,dismissableSurfaces:o,shouldHandlePointerDownOutside:a=kIe}=t,s=ss(e),l=S.useRef(!1),u=S.useRef(!1),f=S.useRef(new Map),c=S.useRef(()=>{});return S.useEffect(()=>{function d(){u.current=!1,i.current=!1,f.current.clear()}Xt(d,"resetOutsideInteraction");function p(){return Array.from(f.current.values()).some(Boolean)}Xt(p,"isOutsideInteractionIntercepted");function h(b){if(!u.current)return;const _=b.target;_ instanceof Node&&[...o].some(w=>w.contains(_))||f.current.set(b.type,!0),b.type==="click"&&window.setTimeout(()=>{u.current&&c.current()},0)}Xt(h,"handleInteractionCapture");function m(b){u.current&&f.current.set(b.type,!1)}Xt(m,"handleInteractionBubble");const g=Xt(b=>{if(b.target&&!l.current){let _=function(){n.removeEventListener("click",c.current);const w=p();d(),w||N$(xIe,s,x,{discrete:!0})};if(Xt(_,"handleAndDispatchPointerDownOutsideEvent"),!a(b.target)){n.removeEventListener("click",c.current),d(),l.current=!1;return}const x={originalEvent:b};u.current=!0,i.current=r&&b.button===0,f.current.clear(),!r||b.button!==0?_():(n.removeEventListener("click",c.current),c.current=_,n.addEventListener("click",c.current,{once:!0}))}else n.removeEventListener("click",c.current),d();l.current=!1},"handlePointerDown"),v=["pointerup","mousedown","mouseup","touchstart","touchend","click"];for(const b of v)n.addEventListener(b,h,!0),n.addEventListener(b,m);const y=window.setTimeout(()=>{n.addEventListener("pointerdown",g)},0);return()=>{window.clearTimeout(y),n.removeEventListener("pointerdown",g),n.removeEventListener("click",c.current);for(const b of v)n.removeEventListener(b,h,!0),n.removeEventListener(b,m)}},[n,s,r,i,o,a]),{onPointerDownCapture:Xt(()=>l.current=!0,"onPointerDownCapture")}}Xt(WU,"usePointerDownOutside");function HU(e,t=globalThis==null?void 0:globalThis.document){const n=ss(e),r=S.useRef(!1);return S.useEffect(()=>{const i=Xt(o=>{o.target&&!r.current&&N$(_Ie,n,{originalEvent:o},{discrete:!1})},"handleFocus");return t.addEventListener("focusin",i),()=>t.removeEventListener("focusin",i)},[t,n]),{onFocusCapture:Xt(()=>r.current=!0,"onFocusCapture"),onBlurCapture:Xt(()=>r.current=!1,"onBlurCapture")}}Xt(HU,"useFocusOutside");function f1(){const e=new CustomEvent(c1);document.dispatchEvent(e)}Xt(f1,"dispatchUpdate");function N$(e,t,n,{discrete:r}){const i=n.originalEvent.target,o=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&i.addEventListener(e,t,{once:!0}),r?BU(i,o):i.dispatchEvent(o)}Xt(N$,"handleAndDispatchCustomEvent");var EIe=Object.defineProperty,j$=(e,t)=>EIe(e,"name",{value:t,configurable:!0}),Wp=0,li=null;function PIe(e){return D$(),e.children}j$(PIe,"FocusGuards");function D$(){S.useEffect(()=>{li||(li={start:d1(),end:d1()});const{start:e,end:t}=li;return document.body.firstElementChild!==e&&document.body.insertAdjacentElement("afterbegin",e),document.body.lastElementChild!==t&&document.body.insertAdjacentElement("beforeend",t),Wp++,()=>{Wp===1&&(li==null||li.start.remove(),li==null||li.end.remove(),li=null),Wp=Math.max(0,Wp-1)}},[])}j$(D$,"useFocusGuards");function d1(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}j$(d1,"createFocusGuard");var OIe=Object.defineProperty,On=(e,t)=>OIe(e,"name",{value:t,configurable:!0}),p0="focusScope.autoFocusOnMount",h0="focusScope.autoFocusOnUnmount",z2={bubbles:!1,cancelable:!0},TIe=S.forwardRef(On(function(t,n){const{loop:r=!1,trapped:i=!1,onMountAutoFocus:o,onUnmountAutoFocus:a,...s}=t,[l,u]=S.useState(null),f=ss(o),c=ss(a),d=S.useRef(null),p=Tu(n,u),h=S.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;S.useEffect(()=>{if(i){let g=function(_){if(h.paused||!l)return;const x=_.target;l.contains(x)?d.current=x:Ni(d.current,{select:!0})},v=function(_){if(h.paused||!l)return;const x=_.relatedTarget;x!==null&&(l.contains(x)||Ni(d.current,{select:!0}))},y=function(_){if(document.activeElement===document.body)for(const w of _)w.removedNodes.length>0&&Ni(l)};On(g,"handleFocusIn"),On(v,"handleFocusOut"),On(y,"handleMutations"),document.addEventListener("focusin",g),document.addEventListener("focusout",v);const b=new MutationObserver(y);return l&&b.observe(l,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",g),document.removeEventListener("focusout",v),b.disconnect()}}},[i,l,h.paused]),S.useEffect(()=>{if(l){L2.add(h);const g=document.activeElement;if(!l.contains(g)){const y=new CustomEvent(p0,z2);l.addEventListener(p0,f),l.dispatchEvent(y),y.defaultPrevented||(VU(XU(M$(l)),{select:!0}),document.activeElement===g&&Ni(l))}return()=>{l.removeEventListener(p0,f),setTimeout(()=>{const y=new CustomEvent(h0,z2);l.addEventListener(h0,c),l.dispatchEvent(y),y.defaultPrevented||Ni(g??document.body,{select:!0}),l.removeEventListener(h0,c),L2.remove(h)},0)}}},[l,f,c,h]);const m=S.useCallback(g=>{if(!r&&!i||h.paused)return;const v=g.key==="Tab"&&!g.altKey&&!g.ctrlKey&&!g.metaKey,y=document.activeElement;if(v&&y){const b=g.currentTarget,[_,x]=qU(b);_&&x?!g.shiftKey&&y===x?(g.preventDefault(),r&&Ni(_,{select:!0})):g.shiftKey&&y===_&&(g.preventDefault(),r&&Ni(x,{select:!0})):y===b&&g.preventDefault()}},[r,i,h.paused]);return z.jsx(gd.div,{tabIndex:-1,...s,ref:p,onKeyDown:m})},"FocusScope"));function VU(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(Ni(r,{select:t}),document.activeElement!==n)return}On(VU,"focusFirst");function qU(e){const t=M$(e),n=p1(t,e),r=p1(t.reverse(),e);return[n,r]}On(qU,"getTabbableEdges");function M$(e){const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:On(r=>{const i=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||i?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP},"acceptNode")});for(;n.nextNode();)t.push(n.currentNode);return t}On(M$,"getTabbableCandidates");function p1(e,t){const n=typeof t.checkVisibility=="function"&&t.checkVisibility({checkVisibilityCSS:!0});for(const r of e)if(!(n?!r.checkVisibility({checkVisibilityCSS:!0}):GU(r,{upTo:t})))return r}On(p1,"findVisible");function GU(e,{upTo:t}){if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1}On(GU,"isHidden");function KU(e){return e instanceof HTMLInputElement&&"select"in e}On(KU,"isSelectableInput");function Ni(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&KU(e)&&t&&e.select()}}On(Ni,"focus");var L2=YU();function YU(){let e=[];return{add(t){const n=e[0];t!==n&&(n==null||n.pause()),e=h1(e,t),e.unshift(t)},remove(t){var n;e=h1(e,t),(n=e[0])==null||n.resume()}}}On(YU,"createFocusScopesStack");function h1(e,t){const n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}On(h1,"arrayRemove");function XU(e){return e.filter(t=>t.tagName!=="A")}On(XU,"removeLinks");var Va=globalThis!=null&&globalThis.document?S.useLayoutEffect:()=>{},IIe=Object.defineProperty,AIe=(e,t)=>IIe(e,"name",{value:t,configurable:!0});function JU(e){const[t,n]=S.useState(void 0);return Va(()=>{if(e){n({width:e.offsetWidth,height:e.offsetHeight});const r=new ResizeObserver(i=>{if(!Array.isArray(i)||!i.length)return;const o=i[0];let a,s;if("borderBoxSize"in o){const l=o.borderBoxSize,u=Array.isArray(l)?l[0]:l;a=u.inlineSize,s=u.blockSize}else a=e.offsetWidth,s=e.offsetHeight;n({width:a,height:s})});return r.observe(e,{box:"border-box"}),()=>r.unobserve(e)}else n(void 0)},[e]),t}AIe(JU,"useSize");var CIe=Object.defineProperty,hl=(e,t)=>CIe(e,"name",{value:t,configurable:!0}),QU="Popper",[eF,tF]=C$(QU),[pUe,RIe]=eF(QU),nF="PopperContent",[NIe,hUe]=eF(nF),jIe=S.forwardRef(hl(function(t,n){var Y,ne,Q,Z,$e,ve,Me;const{__scopePopper:r,side:i="bottom",sideOffset:o=0,align:a="center",alignOffset:s=0,arrowPadding:l=0,avoidCollisions:u=!0,collisionBoundary:f=[],collisionPadding:c=0,sticky:d="partial",hideWhenDetached:p=!1,updatePositionStrategy:h="optimized",onPlaced:m,...g}=t,v=RIe(nF,r),[y,b]=S.useState(null),_=Tu(n,b),[x,w]=S.useState(null),$=JU(x),k=($==null?void 0:$.width)??0,T=($==null?void 0:$.height)??0,E=i+(a!=="center"?"-"+a:""),I=typeof c=="number"?c:{top:0,right:0,bottom:0,left:0,...c},O=Array.isArray(f)?f:[f],j=O.length>0,D={padding:I,boundary:O.filter(rF),altBoundary:j},{refs:M,floatingStyles:L,placement:B,isPositioned:R,middlewareData:U}=X_({strategy:"fixed",placement:E,whileElementsMounted:hl((...Ue)=>Y_(...Ue,{animationFrame:h==="always"}),"whileElementsMounted"),elements:{reference:v.anchor},middleware:[J_({mainAxis:o+T,alignmentAxis:s}),u&&Q_({mainAxis:!0,crossAxis:!1,limiter:d==="partial"?eS():void 0,...D}),u&&tS({...D}),nS({...D,apply:hl(({elements:Ue,rects:Qe,availableWidth:fn,availableHeight:Sn})=>{const{width:Ft,height:jr}=Qe.reference,Rn=Ue.floating.style;Rn.setProperty("--radix-popper-available-width",`${fn}px`),Rn.setProperty("--radix-popper-available-height",`${Sn}px`),Rn.setProperty("--radix-popper-anchor-width",`${Ft}px`),Rn.setProperty("--radix-popper-anchor-height",`${jr}px`)},"apply")}),x&&iS({element:x,padding:l}),DIe({arrowWidth:k,arrowHeight:T}),p&&rS({strategy:"referenceHidden",...D,boundary:j?D.boundary:void 0})]}),P=v.setPlacementState;Va(()=>(P(B),()=>{P(void 0)}),[B,P]);const[G,V]=z$(B),A=ss(m);Va(()=>{R&&(A==null||A())},[R,A]);const pe=(Y=U.arrow)==null?void 0:Y.x,me=(ne=U.arrow)==null?void 0:ne.y,ce=((Q=U.arrow)==null?void 0:Q.centerOffset)!==0,[ie,oe]=S.useState();return Va(()=>{y&&oe(window.getComputedStyle(y).zIndex)},[y]),z.jsx("div",{ref:M.setFloating,"data-radix-popper-content-wrapper":"",style:{...L,transform:R?L.transform:"translate(0, -200%)",minWidth:"max-content",zIndex:ie,"--radix-popper-transform-origin":[(Z=U.transformOrigin)==null?void 0:Z.x,($e=U.transformOrigin)==null?void 0:$e.y].join(" "),...((ve=U.hide)==null?void 0:ve.referenceHidden)&&{visibility:"hidden",pointerEvents:"none"}},dir:t.dir,children:z.jsx(NIe,{scope:r,placedSide:G,placedAlign:V,onArrowChange:w,arrowX:pe,arrowY:me,shouldHideArrow:ce,children:z.jsx(gd.div,{"data-side":G,"data-align":V,...g,ref:_,style:{...g.style,animation:R?(Me=g.style)==null?void 0:Me.animation:"none"}})})})},"PopperContent"));function rF(e){return e!==null}hl(rF,"isNotNull");var DIe=hl(e=>({name:"transformOrigin",options:e,fn(t){var g,v,y;const{placement:n,rects:r,middlewareData:i}=t,a=((g=i.arrow)==null?void 0:g.centerOffset)!==0,s=a?0:e.arrowWidth,l=a?0:e.arrowHeight,[u,f]=z$(n),c={start:"0%",center:"50%",end:"100%"}[f],d=(((v=i.arrow)==null?void 0:v.x)??0)+s/2,p=(((y=i.arrow)==null?void 0:y.y)??0)+l/2;let h="",m="";return u==="bottom"?(h=a?c:`${d}px`,m=`${-l}px`):u==="top"?(h=a?c:`${d}px`,m=`${r.floating.height+l}px`):u==="right"?(h=`${-l}px`,m=a?c:`${p}px`):u==="left"&&(h=`${r.floating.width+l}px`,m=a?c:`${p}px`),{data:{x:h,y:m}}}}),"transformOrigin");function z$(e){const[t,n="center"]=e.split("-");return[t,n]}hl(z$,"getSideAndAlignFromPlacement");var MIe=jIe,zIe=Object.defineProperty,LIe=(e,t)=>zIe(e,"name",{value:t,configurable:!0}),UIe=S.forwardRef(LIe(function(t,n){var l;const{container:r,...i}=t,[o,a]=S.useState(!1);Va(()=>a(!0),[]);const s=r||o&&((l=globalThis==null?void 0:globalThis.document)==null?void 0:l.body);return s?$i.createPortal(z.jsx(gd.div,{...i,ref:n}),s):null},"Portal")),FIe=Object.defineProperty,no=(e,t)=>FIe(e,"name",{value:t,configurable:!0});function iF(e,t){return S.useReducer((n,r)=>t[n][r]??n,e)}no(iF,"useStateMachine");var oF=no(e=>{const{present:t,children:n}=e,r=aF(t),i=typeof n=="function"?n({present:r.isPresent}):S.Children.only(n),o=sF(r.ref,lF(i));return typeof n=="function"||r.isPresent?S.cloneElement(i,{ref:o}):null},"Presence");function aF(e){const[t,n]=S.useState(),r=S.useRef(null),i=S.useRef(e),o=S.useRef("none"),a=S.useRef(void 0),s=e?"mounted":"unmounted",[l,u]=iF(s,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return S.useEffect(()=>{l==="mounted"?(o.current=a.current??Ds(r.current),a.current=void 0):o.current="none"},[l]),Va(()=>{const f=r.current,c=i.current;if(c!==e){const p=o.current,h=Ds(f);e?(a.current=h,u("MOUNT")):h==="none"||(f==null?void 0:f.display)==="none"?u("UNMOUNT"):u(c&&p!==h?"ANIMATION_OUT":"UNMOUNT"),i.current=e}},[e,u]),Va(()=>{if(t){let f;const c=t.ownerDocument.defaultView??window,d=no(h=>{const g=Ds(r.current).includes(CSS.escape(h.animationName));if(h.target===t&&g&&(u("ANIMATION_END"),!i.current)){const v=t.style.animationFillMode;t.style.animationFillMode="forwards",f=c.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=v)})}},"handleAnimationEnd"),p=no(h=>{h.target===t&&(o.current=Ds(r.current))},"handleAnimationStart");return t.addEventListener("animationstart",p),t.addEventListener("animationcancel",d),t.addEventListener("animationend",d),()=>{c.clearTimeout(f),t.removeEventListener("animationstart",p),t.removeEventListener("animationcancel",d),t.removeEventListener("animationend",d)}}else u("ANIMATION_END")},[t,u]),{isPresent:["mounted","unmountSuspended"].includes(l),ref:S.useCallback(f=>{if(f){const c=getComputedStyle(f);r.current=c,a.current=Ds(c)}else r.current=null;n(f)},[])}}no(aF,"usePresence");function m1(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}no(m1,"setRef");function sF(...e){const t=S.useRef(e);return t.current=e,S.useCallback(n=>{const r=t.current;let i=!1;const o=r.map(a=>{const s=m1(a,n);return!i&&typeof s=="function"&&(i=!0),s});if(i)return()=>{for(let a=0;aBIe(e,"name",{value:t,configurable:!0}),uF="Popover",[cF,mUe]=C$(uF,[tF]),ZIe=tF(),[vUe,yd]=cF(uF),fF="PopoverPortal",[WIe,HIe]=cF(fF,{forceMount:void 0}),VIe=Iu(e=>{const{__scopePopover:t,forceMount:n,children:r,container:i}=e,o=yd(fF,t);return z.jsx(WIe,{scope:t,forceMount:n,children:z.jsx(oF,{present:n||o.open,children:z.jsx(UIe,{asChild:!0,container:i,children:r})})})},"PopoverPortal"),Ff="PopoverContent",qIe=S.forwardRef(Iu(function(t,n){const r=HIe(Ff,t.__scopePopover),{forceMount:i=r.forceMount,...o}=t,a=yd(Ff,t.__scopePopover);return z.jsx(oF,{present:i||a.open,children:a.modal?z.jsx(KIe,{...o,ref:n}):z.jsx(YIe,{...o,ref:n})})},"PopoverContent")),GIe=R$("PopoverContent.RemoveScroll"),KIe=S.forwardRef(Iu(function(t,n){const r=yd(Ff,t.__scopePopover),i=S.useRef(null),o=Tu(n,i),a=S.useRef(!1);return S.useEffect(()=>{const s=i.current;if(s)return vS(s)},[]),z.jsx(Gv,{as:GIe,allowPinchZoom:!0,children:z.jsx(dF,{...t,ref:o,trapFocus:r.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:Ha(t.onCloseAutoFocus,s=>{var l;s.preventDefault(),a.current||(l=r.triggerRef.current)==null||l.focus()}),onPointerDownOutside:Ha(t.onPointerDownOutside,s=>{const l=s.detail.originalEvent,u=l.button===0&&l.ctrlKey===!0,f=l.button===2||u;a.current=f},{checkForDefaultPrevented:!1}),onFocusOutside:Ha(t.onFocusOutside,s=>s.preventDefault(),{checkForDefaultPrevented:!1})})})},"PopoverContentModal")),YIe=S.forwardRef(Iu(function(t,n){const r=yd(Ff,t.__scopePopover),i=S.useRef(!1),o=S.useRef(!1);return z.jsx(dF,{...t,ref:n,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:a=>{var s,l;(s=t.onCloseAutoFocus)==null||s.call(t,a),a.defaultPrevented||(i.current||(l=r.triggerRef.current)==null||l.focus(),a.preventDefault()),i.current=!1,o.current=!1},onInteractOutside:a=>{var u,f;(u=t.onInteractOutside)==null||u.call(t,a),a.defaultPrevented||(i.current=!0,a.detail.originalEvent.type==="pointerdown"&&(o.current=!0));const s=a.target;((f=r.triggerRef.current)==null?void 0:f.contains(s))&&a.preventDefault(),a.detail.originalEvent.type==="focusin"&&o.current&&a.preventDefault()}})},"PopoverContentNonModal")),dF=S.forwardRef(Iu(function(t,n){const{__scopePopover:r,trapFocus:i,onOpenAutoFocus:o,onCloseAutoFocus:a,disableOutsidePointerEvents:s,onEscapeKeyDown:l,onPointerDownOutside:u,onFocusOutside:f,onInteractOutside:c,...d}=t,p=yd(Ff,r),h=ZIe(r);return D$(),z.jsx(TIe,{asChild:!0,loop:!0,trapped:i,onMountAutoFocus:o,onUnmountAutoFocus:a,children:z.jsx(SIe,{asChild:!0,disableOutsidePointerEvents:s,onInteractOutside:c,onEscapeKeyDown:l,onPointerDownOutside:u,onFocusOutside:f,onDismiss:()=>p.onOpenChange(!1),deferPointerDownOutside:!0,children:z.jsx(MIe,{"data-state":pF(p.open),role:"dialog",id:p.contentId,...h,...d,ref:n,style:{...d.style,"--radix-popover-content-transform-origin":"var(--radix-popper-transform-origin)","--radix-popover-content-available-width":"var(--radix-popper-available-width)","--radix-popover-content-available-height":"var(--radix-popper-available-height)","--radix-popover-trigger-width":"var(--radix-popper-anchor-width)","--radix-popover-trigger-height":"var(--radix-popper-anchor-height)"}})})})},"PopoverContentImpl"));function pF(e){return e?"open":"closed"}Iu(pF,"getState");var gUe=VIe,yUe=qIe,XIe=Object.defineProperty,JIe=(e,t)=>XIe(e,"name",{value:t,configurable:!0}),QIe=S.forwardRef(JIe(function(t,n){return z.jsx(gd.label,{...t,ref:n,onMouseDown:r=>{var o;r.target.closest("button, input, select, textarea")||((o=t.onMouseDown)==null||o.call(t,r),!r.defaultPrevented&&r.detail>1&&r.preventDefault())}})},"Label")),bUe=QIe;const yo=[[{color:"0, 0, 0",class:"ansi-black"},{color:"187, 0, 0",class:"ansi-red"},{color:"0, 187, 0",class:"ansi-green"},{color:"187, 187, 0",class:"ansi-yellow"},{color:"0, 0, 187",class:"ansi-blue"},{color:"187, 0, 187",class:"ansi-magenta"},{color:"0, 187, 187",class:"ansi-cyan"},{color:"255,255,255",class:"ansi-white"}],[{color:"85, 85, 85",class:"ansi-bright-black"},{color:"255, 85, 85",class:"ansi-bright-red"},{color:"0, 255, 0",class:"ansi-bright-green"},{color:"255, 255, 85",class:"ansi-bright-yellow"},{color:"85, 85, 255",class:"ansi-bright-blue"},{color:"255, 85, 255",class:"ansi-bright-magenta"},{color:"85, 255, 255",class:"ansi-bright-cyan"},{color:"255, 255, 255",class:"ansi-bright-white"}]],eAe=/(https?:\/\/(?:[A-Za-z0-9#;/?:@=+$',_.!~*()[\]-]|&|%[A-Fa-f0-9]{2})+)/gm;class Oa{static escapeForHtml(t){return new Oa().escapeForHtml(t)}static linkify(t){return new Oa().linkify(t)}static ansiToHtml(t,n){return new Oa().ansiToHtml(t,n)}static ansiToJson(t,n){return new Oa().ansiToJson(t,n)}static ansiToText(t){return new Oa().ansiToText(t)}constructor(){this.fg=this.bg=this.fg_truecolor=this.bg_truecolor=null,this.bright=0,this.decorations=[]}setupPalette(){this.PALETTE_COLORS=[];for(let i=0;i<2;++i)for(let o=0;o<8;++o)this.PALETTE_COLORS.push(yo[i][o].color);let t=[0,95,135,175,215,255],n=(i,o,a)=>t[i]+", "+t[o]+", "+t[a];for(let i=0;i<6;++i)for(let o=0;o<6;++o)for(let a=0;a<6;++a)this.PALETTE_COLORS.push(n(i,o,a));let r=8;for(let i=0;i<24;++i,r+=10)this.PALETTE_COLORS.push(r+", "+r+", "+r)}escapeForHtml(t){return t.replace(/[&<>\"]/gm,n=>n=="&"?"&":n=='"'?""":n=="<"?"<":n==">"?">":"")}linkify(t){return t.replace(eAe,n=>`${n}`)}ansiToHtml(t,n){return this.process(t,n,!0)}ansiToJson(t,n){return n=n||{},n.json=!0,n.clearLine=!1,this.process(t,n,!0)}ansiToText(t){return this.process(t,{},!1)}process(t,n,r){let i=this,o=t.split(/\033\[/),a=o.shift();n==null&&(n={}),n.clearLine=/\r/.test(t);let s=o.map(l=>this.processChunk(l,n,r));if(n&&n.json){let l=i.processChunkJson("");return l.content=a,l.clearLine=n.clearLine,s.unshift(l),n.remove_empty&&(s=s.filter(u=>!u.isEmpty())),s}else s.unshift(a);return s.join("")}processChunkJson(t,n,r){n=typeof n>"u"?{}:n;let i=n.use_classes=typeof n.use_classes<"u"&&n.use_classes,o=n.key=i?"class":"color",a={content:t,fg:null,bg:null,fg_truecolor:null,bg_truecolor:null,isInverted:!1,clearLine:n.clearLine,decoration:null,decorations:[],was_processed:!1,isEmpty:()=>!a.content},s=t.match(/^([!\x3c-\x3f]*)([\d;]*)([\x20-\x2c]*[\x40-\x7e])([\s\S]*)/m);if(!s)return a;a.content=s[4];let l=s[2].split(";");if(s[1]!==""||s[3]!=="m"||!r)return a;let u=this;for(;l.length>0;){let f=l.shift(),c=parseInt(f);if(isNaN(c)||c===0)u.fg=u.bg=null,u.decorations=[];else if(c===1)u.decorations.push("bold");else if(c===2)u.decorations.push("dim");else if(c===3)u.decorations.push("italic");else if(c===4)u.decorations.push("underline");else if(c===5)u.decorations.push("blink");else if(c===7)u.decorations.push("reverse");else if(c===8)u.decorations.push("hidden");else if(c===9)u.decorations.push("strikethrough");else if(c===21)u.removeDecoration("bold");else if(c===22)u.removeDecoration("bold"),u.removeDecoration("dim");else if(c===23)u.removeDecoration("italic");else if(c===24)u.removeDecoration("underline");else if(c===25)u.removeDecoration("blink");else if(c===27)u.removeDecoration("reverse");else if(c===28)u.removeDecoration("hidden");else if(c===29)u.removeDecoration("strikethrough");else if(c===39)u.fg=null;else if(c===49)u.bg=null;else if(c>=30&&c<38)u.fg=yo[0][c%10][o];else if(c>=90&&c<98)u.fg=yo[1][c%10][o];else if(c>=40&&c<48)u.bg=yo[0][c%10][o];else if(c>=100&&c<108)u.bg=yo[1][c%10][o];else if(c===38||c===48){let d=c===38;if(l.length>=1){let p=l.shift();if(p==="5"&&l.length>=1){let h=parseInt(l.shift());if(h>=0&&h<=255)if(!i)this.PALETTE_COLORS||u.setupPalette(),d?u.fg=this.PALETTE_COLORS[h]:u.bg=this.PALETTE_COLORS[h];else{let m=h>=16?"ansi-palette-"+h:yo[h>7?1:0][h%8].class;d?u.fg=m:u.bg=m}}else if(p==="2"&&l.length>=3){let h=parseInt(l.shift()),m=parseInt(l.shift()),g=parseInt(l.shift());if(h>=0&&h<=255&&m>=0&&m<=255&&g>=0&&g<=255){let v=h+", "+m+", "+g;i?d?(u.fg="ansi-truecolor",u.fg_truecolor=v):(u.bg="ansi-truecolor",u.bg_truecolor=v):d?u.fg=v:u.bg=v}}}}}return u.fg===null&&u.bg===null&&u.decorations.length===0||(a.fg=u.fg,a.bg=u.bg,a.fg_truecolor=u.fg_truecolor,a.bg_truecolor=u.bg_truecolor,a.decorations=u.decorations,a.decoration=u.decorations.slice(-1).pop()||null,a.was_processed=!0),a}processChunk(t,n,r){n=n||{};let i=this.processChunkJson(t,n,r),o=n.use_classes;if(i.decorations=i.decorations.filter(c=>{if(c==="reverse"){i.fg||(i.fg=yo[0][7][o?"class":"color"]),i.bg||(i.bg=yo[0][0][o?"class":"color"]);let d=i.fg;i.fg=i.bg,i.bg=d;let p=i.fg_truecolor;return i.fg_truecolor=i.bg_truecolor,i.bg_truecolor=p,i.isInverted=!0,!1}return!0}),n.json)return i;if(i.isEmpty())return"";if(!i.was_processed)return i.content;let a=[],s=[],l=[],u={},f=c=>{let d=[],p;for(p in c)c.hasOwnProperty(p)&&d.push("data-"+p+'="'+this.escapeForHtml(c[p])+'"');return d.length>0?" "+d.join(" "):""};return i.isInverted&&(u["ansi-is-inverted"]="true"),i.fg&&(o?(a.push(i.fg+"-fg"),i.fg_truecolor!==null&&(u["ansi-truecolor-fg"]=i.fg_truecolor,i.fg_truecolor=null)):a.push("color:rgb("+i.fg+")")),i.bg&&(o?(a.push(i.bg+"-bg"),i.bg_truecolor!==null&&(u["ansi-truecolor-bg"]=i.bg_truecolor,i.bg_truecolor=null)):a.push("background-color:rgb("+i.bg+")")),i.decorations.forEach(c=>{if(o){s.push("ansi-"+c);return}c==="bold"?s.push("font-weight:bold"):c==="dim"?s.push("opacity:0.5"):c==="italic"?s.push("font-style:italic"):c==="hidden"?s.push("visibility:hidden"):c==="strikethrough"?l.push("line-through"):l.push(c)}),l.length&&s.push("text-decoration:"+l.join(" ")),o?'"+i.content+"":'"+i.content+""}removeDecoration(t){const n=this.decorations.indexOf(t);n>=0&&this.decorations.splice(n,1)}}var tAe=Oa;const wUe=Ge(tAe);function q(e,t,n){function r(s,l){var u;Object.defineProperty(s,"_zod",{value:s._zod??{},enumerable:!1}),(u=s._zod).traits??(u.traits=new Set),s._zod.traits.add(e),t(s,l);for(const f in a.prototype)f in s||Object.defineProperty(s,f,{value:a.prototype[f].bind(s)});s._zod.constr=a,s._zod.def=l}const i=(n==null?void 0:n.Parent)??Object;class o extends i{}Object.defineProperty(o,"name",{value:e});function a(s){var l;const u=n!=null&&n.Parent?new o:this;r(u,s),(l=u._zod).deferred??(l.deferred=[]);for(const f of u._zod.deferred)f();return u}return Object.defineProperty(a,"init",{value:r}),Object.defineProperty(a,Symbol.hasInstance,{value:s=>{var l,u;return n!=null&&n.Parent&&s instanceof n.Parent?!0:(u=(l=s==null?void 0:s._zod)==null?void 0:l.traits)==null?void 0:u.has(e)}}),Object.defineProperty(a,"name",{value:e}),a}class Bf extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}const hF={};function Go(e){return hF}function nAe(e){const t=Object.values(e).filter(r=>typeof r=="number");return Object.entries(e).filter(([r,i])=>t.indexOf(+r)===-1).map(([r,i])=>i)}function rAe(e,t){return typeof t=="bigint"?t.toString():t}function L$(e){return{get value(){{const t=e();return Object.defineProperty(this,"value",{value:t}),t}}}}function U$(e){return e==null}function F$(e){const t=e.startsWith("^")?1:0,n=e.endsWith("$")?e.length-1:e.length;return e.slice(t,n)}function iAe(e,t){const n=(e.toString().split(".")[1]||"").length,r=(t.toString().split(".")[1]||"").length,i=n>r?n:r,o=Number.parseInt(e.toFixed(i).replace(".","")),a=Number.parseInt(t.toFixed(i).replace(".",""));return o%a/10**i}function vt(e,t,n){Object.defineProperty(e,t,{get(){{const r=n();return e[t]=r,r}},set(r){Object.defineProperty(e,t,{value:r})},configurable:!0})}function Fg(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}function oc(e){return JSON.stringify(e)}const mF=Error.captureStackTrace?Error.captureStackTrace:(...e)=>{};function iv(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}const oAe=L$(()=>{var e;if(typeof navigator<"u"&&((e=navigator==null?void 0:navigator.userAgent)!=null&&e.includes("Cloudflare")))return!1;try{const t=Function;return new t(""),!0}catch{return!1}});function ov(e){if(iv(e)===!1)return!1;const t=e.constructor;if(t===void 0)return!0;const n=t.prototype;return!(iv(n)===!1||Object.prototype.hasOwnProperty.call(n,"isPrototypeOf")===!1)}const aAe=new Set(["string","number","symbol"]);function bd(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function xs(e,t,n){const r=new e._zod.constr(t??e._zod.def);return(!t||n!=null&&n.parent)&&(r._zod.parent=e),r}function he(e){const t=e;if(!t)return{};if(typeof t=="string")return{error:()=>t};if((t==null?void 0:t.message)!==void 0){if((t==null?void 0:t.error)!==void 0)throw new Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error=="string"?{...t,error:()=>t.error}:t}function sAe(e){return Object.keys(e).filter(t=>e[t]._zod.optin==="optional"&&e[t]._zod.optout==="optional")}const lAe={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function uAe(e,t){const n={},r=e._zod.def;for(const i in t){if(!(i in r.shape))throw new Error(`Unrecognized key: "${i}"`);t[i]&&(n[i]=r.shape[i])}return xs(e,{...e._zod.def,shape:n,checks:[]})}function cAe(e,t){const n={...e._zod.def.shape},r=e._zod.def;for(const i in t){if(!(i in r.shape))throw new Error(`Unrecognized key: "${i}"`);t[i]&&delete n[i]}return xs(e,{...e._zod.def,shape:n,checks:[]})}function fAe(e,t){if(!ov(t))throw new Error("Invalid input to extend: expected a plain object");const n={...e._zod.def,get shape(){const r={...e._zod.def.shape,...t};return Fg(this,"shape",r),r},checks:[]};return xs(e,n)}function dAe(e,t){return xs(e,{...e._zod.def,get shape(){const n={...e._zod.def.shape,...t._zod.def.shape};return Fg(this,"shape",n),n},catchall:t._zod.def.catchall,checks:[]})}function pAe(e,t,n){const r=t._zod.def.shape,i={...r};if(n)for(const o in n){if(!(o in r))throw new Error(`Unrecognized key: "${o}"`);n[o]&&(i[o]=e?new e({type:"optional",innerType:r[o]}):r[o])}else for(const o in r)i[o]=e?new e({type:"optional",innerType:r[o]}):r[o];return xs(t,{...t._zod.def,shape:i,checks:[]})}function hAe(e,t,n){const r=t._zod.def.shape,i={...r};if(n)for(const o in n){if(!(o in i))throw new Error(`Unrecognized key: "${o}"`);n[o]&&(i[o]=new e({type:"nonoptional",innerType:r[o]}))}else for(const o in r)i[o]=new e({type:"nonoptional",innerType:r[o]});return xs(t,{...t._zod.def,shape:i,checks:[]})}function Rc(e,t=0){var n;for(let r=t;r{var r;return(r=n).path??(r.path=[]),n.path.unshift(e),n})}function Hp(e){return typeof e=="string"?e:e==null?void 0:e.message}function Ko(e,t,n){var i,o,a,s,l,u;const r={...e,path:e.path??[]};if(!e.message){const f=Hp((a=(o=(i=e.inst)==null?void 0:i._zod.def)==null?void 0:o.error)==null?void 0:a.call(o,e))??Hp((s=t==null?void 0:t.error)==null?void 0:s.call(t,e))??Hp((l=n.customError)==null?void 0:l.call(n,e))??Hp((u=n.localeError)==null?void 0:u.call(n,e))??"Invalid input";r.message=f}return delete r.inst,delete r.continue,t!=null&&t.reportInput||delete r.input,r}function B$(e){return Array.isArray(e)?"array":typeof e=="string"?"string":"unknown"}function Zf(...e){const[t,n,r]=e;return typeof t=="string"?{message:t,code:"custom",input:n,inst:r}:{...t}}const vF=(e,t)=>{e.name="$ZodError",Object.defineProperty(e,"_zod",{value:e._zod,enumerable:!1}),Object.defineProperty(e,"issues",{value:t,enumerable:!1}),Object.defineProperty(e,"message",{get(){return JSON.stringify(t,rAe,2)},enumerable:!0}),Object.defineProperty(e,"toString",{value:()=>e.message,enumerable:!1})},gF=q("$ZodError",vF),yF=q("$ZodError",vF,{Parent:Error});function mAe(e,t=n=>n.message){const n={},r=[];for(const i of e.issues)i.path.length>0?(n[i.path[0]]=n[i.path[0]]||[],n[i.path[0]].push(t(i))):r.push(t(i));return{formErrors:r,fieldErrors:n}}function vAe(e,t){const n=t||function(o){return o.message},r={_errors:[]},i=o=>{for(const a of o.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(s=>i({issues:s}));else if(a.code==="invalid_key")i({issues:a.issues});else if(a.code==="invalid_element")i({issues:a.issues});else if(a.path.length===0)r._errors.push(n(a));else{let s=r,l=0;for(;l(t,n,r,i)=>{const o=r?Object.assign(r,{async:!1}):{async:!1},a=t._zod.run({value:n,issues:[]},o);if(a instanceof Promise)throw new Bf;if(a.issues.length){const s=new((i==null?void 0:i.Err)??e)(a.issues.map(l=>Ko(l,o,Go())));throw mF(s,i==null?void 0:i.callee),s}return a.value},yAe=e=>async(t,n,r,i)=>{const o=r?Object.assign(r,{async:!0}):{async:!0};let a=t._zod.run({value:n,issues:[]},o);if(a instanceof Promise&&(a=await a),a.issues.length){const s=new((i==null?void 0:i.Err)??e)(a.issues.map(l=>Ko(l,o,Go())));throw mF(s,i==null?void 0:i.callee),s}return a.value},bF=e=>(t,n,r)=>{const i=r?{...r,async:!1}:{async:!1},o=t._zod.run({value:n,issues:[]},i);if(o instanceof Promise)throw new Bf;return o.issues.length?{success:!1,error:new(e??gF)(o.issues.map(a=>Ko(a,i,Go())))}:{success:!0,data:o.value}},wF=bF(yF),xF=e=>async(t,n,r)=>{const i=r?Object.assign(r,{async:!0}):{async:!0};let o=t._zod.run({value:n,issues:[]},i);return o instanceof Promise&&(o=await o),o.issues.length?{success:!1,error:new e(o.issues.map(a=>Ko(a,i,Go())))}:{success:!0,data:o.value}},bAe=xF(yF),wAe=/^[cC][^\s-]{8,}$/,xAe=/^[0-9a-z]+$/,_Ae=/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,SAe=/^[0-9a-vA-V]{20}$/,$Ae=/^[A-Za-z0-9]{27}$/,kAe=/^[a-zA-Z0-9_-]{21}$/,EAe=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,PAe=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,U2=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$/,OAe=/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,TAe="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";function IAe(){return new RegExp(TAe,"u")}const AAe=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,CAe=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})$/,RAe=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,NAe=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,jAe=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,_F=/^[A-Za-z0-9_-]*$/,DAe=/^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/,MAe=/^\+(?:[0-9]){6,14}[0-9]$/,SF="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))",zAe=new RegExp(`^${SF}$`);function $F(e){const t="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${t}`:e.precision===0?`${t}:[0-5]\\d`:`${t}:[0-5]\\d\\.\\d{${e.precision}}`:`${t}(?::[0-5]\\d(?:\\.\\d+)?)?`}function LAe(e){return new RegExp(`^${$F(e)}$`)}function UAe(e){const t=$F({precision:e.precision}),n=["Z"];e.local&&n.push(""),e.offset&&n.push("([+-]\\d{2}:\\d{2})");const r=`${t}(?:${n.join("|")})`;return new RegExp(`^${SF}T(?:${r})$`)}const FAe=e=>{const t=e?`[\\s\\S]{${(e==null?void 0:e.minimum)??0},${(e==null?void 0:e.maximum)??""}}`:"[\\s\\S]*";return new RegExp(`^${t}$`)},BAe=/^\d+$/,ZAe=/^-?\d+(?:\.\d+)?/i,WAe=/true|false/i,HAe=/null/i,VAe=/^[^A-Z]*$/,qAe=/^[^a-z]*$/,Hn=q("$ZodCheck",(e,t)=>{var n;e._zod??(e._zod={}),e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),kF={number:"number",bigint:"bigint",object:"date"},EF=q("$ZodCheckLessThan",(e,t)=>{Hn.init(e,t);const n=kF[typeof t.value];e._zod.onattach.push(r=>{const i=r._zod.bag,o=(t.inclusive?i.maximum:i.exclusiveMaximum)??Number.POSITIVE_INFINITY;t.value{(t.inclusive?r.value<=t.value:r.value{Hn.init(e,t);const n=kF[typeof t.value];e._zod.onattach.push(r=>{const i=r._zod.bag,o=(t.inclusive?i.minimum:i.exclusiveMinimum)??Number.NEGATIVE_INFINITY;t.value>o&&(t.inclusive?i.minimum=t.value:i.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:"too_small",minimum:t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),GAe=q("$ZodCheckMultipleOf",(e,t)=>{Hn.init(e,t),e._zod.onattach.push(n=>{var r;(r=n._zod.bag).multipleOf??(r.multipleOf=t.value)}),e._zod.check=n=>{if(typeof n.value!=typeof t.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof n.value=="bigint"?n.value%t.value===BigInt(0):iAe(n.value,t.value)===0)||n.issues.push({origin:typeof n.value,code:"not_multiple_of",divisor:t.value,input:n.value,inst:e,continue:!t.abort})}}),KAe=q("$ZodCheckNumberFormat",(e,t)=>{var a;Hn.init(e,t),t.format=t.format||"float64";const n=(a=t.format)==null?void 0:a.includes("int"),r=n?"int":"number",[i,o]=lAe[t.format];e._zod.onattach.push(s=>{const l=s._zod.bag;l.format=t.format,l.minimum=i,l.maximum=o,n&&(l.pattern=BAe)}),e._zod.check=s=>{const l=s.value;if(n){if(!Number.isInteger(l)){s.issues.push({expected:r,format:t.format,code:"invalid_type",input:l,inst:e});return}if(!Number.isSafeInteger(l)){l>0?s.issues.push({input:l,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort}):s.issues.push({input:l,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:r,continue:!t.abort});return}}lo&&s.issues.push({origin:"number",input:l,code:"too_big",maximum:o,inst:e})}}),YAe=q("$ZodCheckMaxLength",(e,t)=>{var n;Hn.init(e,t),(n=e._zod.def).when??(n.when=r=>{const i=r.value;return!U$(i)&&i.length!==void 0}),e._zod.onattach.push(r=>{const i=r._zod.bag.maximum??Number.POSITIVE_INFINITY;t.maximum{const i=r.value;if(i.length<=t.maximum)return;const a=B$(i);r.issues.push({origin:a,code:"too_big",maximum:t.maximum,inclusive:!0,input:i,inst:e,continue:!t.abort})}}),XAe=q("$ZodCheckMinLength",(e,t)=>{var n;Hn.init(e,t),(n=e._zod.def).when??(n.when=r=>{const i=r.value;return!U$(i)&&i.length!==void 0}),e._zod.onattach.push(r=>{const i=r._zod.bag.minimum??Number.NEGATIVE_INFINITY;t.minimum>i&&(r._zod.bag.minimum=t.minimum)}),e._zod.check=r=>{const i=r.value;if(i.length>=t.minimum)return;const a=B$(i);r.issues.push({origin:a,code:"too_small",minimum:t.minimum,inclusive:!0,input:i,inst:e,continue:!t.abort})}}),JAe=q("$ZodCheckLengthEquals",(e,t)=>{var n;Hn.init(e,t),(n=e._zod.def).when??(n.when=r=>{const i=r.value;return!U$(i)&&i.length!==void 0}),e._zod.onattach.push(r=>{const i=r._zod.bag;i.minimum=t.length,i.maximum=t.length,i.length=t.length}),e._zod.check=r=>{const i=r.value,o=i.length;if(o===t.length)return;const a=B$(i),s=o>t.length;r.issues.push({origin:a,...s?{code:"too_big",maximum:t.length}:{code:"too_small",minimum:t.length},inclusive:!0,exact:!0,input:r.value,inst:e,continue:!t.abort})}}),Bg=q("$ZodCheckStringFormat",(e,t)=>{var n,r;Hn.init(e,t),e._zod.onattach.push(i=>{const o=i._zod.bag;o.format=t.format,t.pattern&&(o.patterns??(o.patterns=new Set),o.patterns.add(t.pattern))}),t.pattern?(n=e._zod).check??(n.check=i=>{t.pattern.lastIndex=0,!t.pattern.test(i.value)&&i.issues.push({origin:"string",code:"invalid_format",format:t.format,input:i.value,...t.pattern?{pattern:t.pattern.toString()}:{},inst:e,continue:!t.abort})}):(r=e._zod).check??(r.check=()=>{})}),QAe=q("$ZodCheckRegex",(e,t)=>{Bg.init(e,t),e._zod.check=n=>{t.pattern.lastIndex=0,!t.pattern.test(n.value)&&n.issues.push({origin:"string",code:"invalid_format",format:"regex",input:n.value,pattern:t.pattern.toString(),inst:e,continue:!t.abort})}}),eCe=q("$ZodCheckLowerCase",(e,t)=>{t.pattern??(t.pattern=VAe),Bg.init(e,t)}),tCe=q("$ZodCheckUpperCase",(e,t)=>{t.pattern??(t.pattern=qAe),Bg.init(e,t)}),nCe=q("$ZodCheckIncludes",(e,t)=>{Hn.init(e,t);const n=bd(t.includes),r=new RegExp(typeof t.position=="number"?`^.{${t.position}}${n}`:n);t.pattern=r,e._zod.onattach.push(i=>{const o=i._zod.bag;o.patterns??(o.patterns=new Set),o.patterns.add(r)}),e._zod.check=i=>{i.value.includes(t.includes,t.position)||i.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:t.includes,input:i.value,inst:e,continue:!t.abort})}}),rCe=q("$ZodCheckStartsWith",(e,t)=>{Hn.init(e,t);const n=new RegExp(`^${bd(t.prefix)}.*`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const i=r._zod.bag;i.patterns??(i.patterns=new Set),i.patterns.add(n)}),e._zod.check=r=>{r.value.startsWith(t.prefix)||r.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:t.prefix,input:r.value,inst:e,continue:!t.abort})}}),iCe=q("$ZodCheckEndsWith",(e,t)=>{Hn.init(e,t);const n=new RegExp(`.*${bd(t.suffix)}$`);t.pattern??(t.pattern=n),e._zod.onattach.push(r=>{const i=r._zod.bag;i.patterns??(i.patterns=new Set),i.patterns.add(n)}),e._zod.check=r=>{r.value.endsWith(t.suffix)||r.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:t.suffix,input:r.value,inst:e,continue:!t.abort})}}),oCe=q("$ZodCheckOverwrite",(e,t)=>{Hn.init(e,t),e._zod.check=n=>{n.value=t.tx(n.value)}});class aCe{constructor(t=[]){this.content=[],this.indent=0,this&&(this.args=t)}indented(t){this.indent+=1,t(this),this.indent-=1}write(t){if(typeof t=="function"){t(this,{execution:"sync"}),t(this,{execution:"async"});return}const r=t.split(` `).filter(a=>a),i=Math.min(...r.map(a=>a.length-a.trimStart().length)),o=r.map(a=>a.slice(i)).map(a=>" ".repeat(this.indent*2)+a);for(const a of o)this.content.push(a)}compile(){const t=Function,n=this==null?void 0:this.args,i=[...((this==null?void 0:this.content)??[""]).map(o=>` ${o}`)];return new t(...n,i.join(` `))}}const sCe={major:4,minor:0,patch:0},yt=q("$ZodType",(e,t)=>{var i;var n;e??(e={}),e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=sCe;const r=[...e._zod.def.checks??[]];e._zod.traits.has("$ZodCheck")&&r.unshift(e);for(const o of r)for(const a of o._zod.onattach)a(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),(i=e._zod.deferred)==null||i.push(()=>{e._zod.run=e._zod.parse});else{const o=(a,s,l)=>{let u=Rc(a),f;for(const c of s){if(c._zod.def.when){if(!c._zod.def.when(a))continue}else if(u)continue;const d=a.issues.length,p=c._zod.check(a);if(p instanceof Promise&&(l==null?void 0:l.async)===!1)throw new Bf;if(f||p instanceof Promise)f=(f??Promise.resolve()).then(async()=>{await p,a.issues.length!==d&&(u||(u=Rc(a,d)))});else{if(a.issues.length===d)continue;u||(u=Rc(a,d))}}return f?f.then(()=>a):a};e._zod.run=(a,s)=>{const l=e._zod.parse(a,s);if(l instanceof Promise){if(s.async===!1)throw new Bf;return l.then(u=>o(u,r,s))}return o(l,r,s)}}e["~standard"]={validate:o=>{var a;try{const s=wF(e,o);return s.success?{value:s.data}:{issues:(a=s.error)==null?void 0:a.issues}}catch{return bAe(e,o).then(l=>{var u;return l.success?{value:l.data}:{issues:(u=l.error)==null?void 0:u.issues}})}},vendor:"zod",version:1}}),Z$=q("$ZodString",(e,t)=>{var n;yt.init(e,t),e._zod.pattern=[...((n=e==null?void 0:e._zod.bag)==null?void 0:n.patterns)??[]].pop()??FAe(e._zod.bag),e._zod.parse=(r,i)=>{if(t.coerce)try{r.value=String(r.value)}catch{}return typeof r.value=="string"||r.issues.push({expected:"string",code:"invalid_type",input:r.value,inst:e}),r}}),_t=q("$ZodStringFormat",(e,t)=>{Bg.init(e,t),Z$.init(e,t)}),lCe=q("$ZodGUID",(e,t)=>{t.pattern??(t.pattern=PAe),_t.init(e,t)}),uCe=q("$ZodUUID",(e,t)=>{if(t.version){const r={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[t.version];if(r===void 0)throw new Error(`Invalid UUID version: "${t.version}"`);t.pattern??(t.pattern=U2(r))}else t.pattern??(t.pattern=U2());_t.init(e,t)}),cCe=q("$ZodEmail",(e,t)=>{t.pattern??(t.pattern=OAe),_t.init(e,t)}),fCe=q("$ZodURL",(e,t)=>{_t.init(e,t),e._zod.check=n=>{try{const r=n.value,i=new URL(r),o=i.href;t.hostname&&(t.hostname.lastIndex=0,t.hostname.test(i.hostname)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:DAe.source,input:n.value,inst:e,continue:!t.abort})),t.protocol&&(t.protocol.lastIndex=0,t.protocol.test(i.protocol.endsWith(":")?i.protocol.slice(0,-1):i.protocol)||n.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:t.protocol.source,input:n.value,inst:e,continue:!t.abort})),!r.endsWith("/")&&o.endsWith("/")?n.value=o.slice(0,-1):n.value=o;return}catch{n.issues.push({code:"invalid_format",format:"url",input:n.value,inst:e,continue:!t.abort})}}}),dCe=q("$ZodEmoji",(e,t)=>{t.pattern??(t.pattern=IAe()),_t.init(e,t)}),pCe=q("$ZodNanoID",(e,t)=>{t.pattern??(t.pattern=kAe),_t.init(e,t)}),hCe=q("$ZodCUID",(e,t)=>{t.pattern??(t.pattern=wAe),_t.init(e,t)}),mCe=q("$ZodCUID2",(e,t)=>{t.pattern??(t.pattern=xAe),_t.init(e,t)}),vCe=q("$ZodULID",(e,t)=>{t.pattern??(t.pattern=_Ae),_t.init(e,t)}),gCe=q("$ZodXID",(e,t)=>{t.pattern??(t.pattern=SAe),_t.init(e,t)}),yCe=q("$ZodKSUID",(e,t)=>{t.pattern??(t.pattern=$Ae),_t.init(e,t)}),bCe=q("$ZodISODateTime",(e,t)=>{t.pattern??(t.pattern=UAe(t)),_t.init(e,t)}),wCe=q("$ZodISODate",(e,t)=>{t.pattern??(t.pattern=zAe),_t.init(e,t)}),xCe=q("$ZodISOTime",(e,t)=>{t.pattern??(t.pattern=LAe(t)),_t.init(e,t)}),_Ce=q("$ZodISODuration",(e,t)=>{t.pattern??(t.pattern=EAe),_t.init(e,t)}),SCe=q("$ZodIPv4",(e,t)=>{t.pattern??(t.pattern=AAe),_t.init(e,t),e._zod.onattach.push(n=>{const r=n._zod.bag;r.format="ipv4"})}),$Ce=q("$ZodIPv6",(e,t)=>{t.pattern??(t.pattern=CAe),_t.init(e,t),e._zod.onattach.push(n=>{const r=n._zod.bag;r.format="ipv6"}),e._zod.check=n=>{try{new URL(`http://[${n.value}]`)}catch{n.issues.push({code:"invalid_format",format:"ipv6",input:n.value,inst:e,continue:!t.abort})}}}),kCe=q("$ZodCIDRv4",(e,t)=>{t.pattern??(t.pattern=RAe),_t.init(e,t)}),ECe=q("$ZodCIDRv6",(e,t)=>{t.pattern??(t.pattern=NAe),_t.init(e,t),e._zod.check=n=>{const[r,i]=n.value.split("/");try{if(!i)throw new Error;const o=Number(i);if(`${o}`!==i)throw new Error;if(o<0||o>128)throw new Error;new URL(`http://[${r}]`)}catch{n.issues.push({code:"invalid_format",format:"cidrv6",input:n.value,inst:e,continue:!t.abort})}}});function OF(e){if(e==="")return!0;if(e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const PCe=q("$ZodBase64",(e,t)=>{t.pattern??(t.pattern=jAe),_t.init(e,t),e._zod.onattach.push(n=>{n._zod.bag.contentEncoding="base64"}),e._zod.check=n=>{OF(n.value)||n.issues.push({code:"invalid_format",format:"base64",input:n.value,inst:e,continue:!t.abort})}});function OCe(e){if(!_F.test(e))return!1;const t=e.replace(/[-_]/g,r=>r==="-"?"+":"/"),n=t.padEnd(Math.ceil(t.length/4)*4,"=");return OF(n)}const TCe=q("$ZodBase64URL",(e,t)=>{t.pattern??(t.pattern=_F),_t.init(e,t),e._zod.onattach.push(n=>{n._zod.bag.contentEncoding="base64url"}),e._zod.check=n=>{OCe(n.value)||n.issues.push({code:"invalid_format",format:"base64url",input:n.value,inst:e,continue:!t.abort})}}),ICe=q("$ZodE164",(e,t)=>{t.pattern??(t.pattern=MAe),_t.init(e,t)});function ACe(e,t=null){try{const n=e.split(".");if(n.length!==3)return!1;const[r]=n;if(!r)return!1;const i=JSON.parse(atob(r));return!("typ"in i&&(i==null?void 0:i.typ)!=="JWT"||!i.alg||t&&(!("alg"in i)||i.alg!==t))}catch{return!1}}const CCe=q("$ZodJWT",(e,t)=>{_t.init(e,t),e._zod.check=n=>{ACe(n.value,t.alg)||n.issues.push({code:"invalid_format",format:"jwt",input:n.value,inst:e,continue:!t.abort})}}),TF=q("$ZodNumber",(e,t)=>{yt.init(e,t),e._zod.pattern=e._zod.bag.pattern??ZAe,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}const i=n.value;if(typeof i=="number"&&!Number.isNaN(i)&&Number.isFinite(i))return n;const o=typeof i=="number"?Number.isNaN(i)?"NaN":Number.isFinite(i)?void 0:"Infinity":void 0;return n.issues.push({expected:"number",code:"invalid_type",input:i,inst:e,...o?{received:o}:{}}),n}}),RCe=q("$ZodNumber",(e,t)=>{KAe.init(e,t),TF.init(e,t)}),NCe=q("$ZodBoolean",(e,t)=>{yt.init(e,t),e._zod.pattern=WAe,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=!!n.value}catch{}const i=n.value;return typeof i=="boolean"||n.issues.push({expected:"boolean",code:"invalid_type",input:i,inst:e}),n}}),jCe=q("$ZodNull",(e,t)=>{yt.init(e,t),e._zod.pattern=HAe,e._zod.values=new Set([null]),e._zod.parse=(n,r)=>{const i=n.value;return i===null||n.issues.push({expected:"null",code:"invalid_type",input:i,inst:e}),n}}),DCe=q("$ZodUnknown",(e,t)=>{yt.init(e,t),e._zod.parse=n=>n}),MCe=q("$ZodNever",(e,t)=>{yt.init(e,t),e._zod.parse=(n,r)=>(n.issues.push({expected:"never",code:"invalid_type",input:n.value,inst:e}),n)});function F2(e,t,n){e.issues.length&&t.issues.push(...Ma(n,e.issues)),t.value[n]=e.value}const zCe=q("$ZodArray",(e,t)=>{yt.init(e,t),e._zod.parse=(n,r)=>{const i=n.value;if(!Array.isArray(i))return n.issues.push({expected:"array",code:"invalid_type",input:i,inst:e}),n;n.value=Array(i.length);const o=[];for(let a=0;aF2(u,n,a))):F2(l,n,a)}return o.length?Promise.all(o).then(()=>n):n}});function Vp(e,t,n){e.issues.length&&t.issues.push(...Ma(n,e.issues)),t.value[n]=e.value}function B2(e,t,n,r){e.issues.length?r[n]===void 0?n in r?t.value[n]=void 0:t.value[n]=e.value:t.issues.push(...Ma(n,e.issues)):e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}const LCe=q("$ZodObject",(e,t)=>{yt.init(e,t);const n=L$(()=>{const c=Object.keys(t.shape);for(const p of c)if(!(t.shape[p]instanceof yt))throw new Error(`Invalid element at key "${p}": expected a Zod schema`);const d=sAe(t.shape);return{shape:t.shape,keys:c,keySet:new Set(c),numKeys:c.length,optionalKeys:new Set(d)}});vt(e._zod,"propValues",()=>{const c=t.shape,d={};for(const p in c){const h=c[p]._zod;if(h.values){d[p]??(d[p]=new Set);for(const m of h.values)d[p].add(m)}}return d});const r=c=>{const d=new aCe(["shape","payload","ctx"]),p=n.value,h=y=>{const b=oc(y);return`shape[${b}]._zod.run({ value: input[${b}], issues: [] }, ctx)`};d.write("const input = payload.value;");const m=Object.create(null);let g=0;for(const y of p.keys)m[y]=`key_${g++}`;d.write("const newResult = {}");for(const y of p.keys)if(p.optionalKeys.has(y)){const b=m[y];d.write(`const ${b} = ${h(y)};`);const _=oc(y);d.write(` if (${b}.issues.length) { @@ -1959,4 +1964,4 @@ Note: This type uses \`Record\` rather than \`Partial{var i,o;if(n&&r.source!==this.eventSource){console.error("Ignoring message from unknown source",r);return}let a=rNe.safeParse(r.data);a.success?(console.debug("Parsed message",a.data),(i=this.onmessage)==null||i.call(this,a.data)):(console.error("Failed to parse message",a.error.message,r),(o=this.onerror)==null||o.call(this,Error("Invalid JSON-RPC message received: "+a.error.message)))}}async start(){window.addEventListener("message",this.messageListener)}async send(t,n){console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){var t;window.removeEventListener("message",this.messageListener),(t=this.onclose)==null||t.call(this)}}var g4e="ui/resourceUri",_W="text/html;profile=mcp-app";function y4e(e){var t,n,r;let i=(n=(t=e._meta)==null?void 0:t.ui)==null?void 0:n.resourceUri;if(i===void 0&&(i=(r=e._meta)==null?void 0:r[g4e]),typeof i=="string"&&i.startsWith("ui://"))return i;if(i!==void 0)throw Error(`Invalid UI resource URI: ${JSON.stringify(i)}`)}var b4e=[R3];class w4e extends jje{constructor(t,n,r,i){super(i),Nt(this,"_client"),Nt(this,"_hostInfo"),Nt(this,"_capabilities"),Nt(this,"_appCapabilities"),Nt(this,"_hostContext",{}),Nt(this,"_appInfo"),Nt(this,"onping"),Nt(this,"sendResourceTeardown",this.teardownResource),this._client=t,this._hostInfo=n,this._capabilities=r,this._hostContext=(i==null?void 0:i.hostContext)||{},this.setRequestHandler(m4e,o=>this._oninitialize(o)),this.setRequestHandler(Vg,(o,a)=>{var s;return(s=this.onping)==null||s.call(this,o.params,a),{}}),this.setRequestHandler(DR,o=>({mode:this._hostContext.displayMode??"inline"}))}getAppCapabilities(){return this._appCapabilities}getAppVersion(){return this._appInfo}set onsizechange(t){this.setNotificationHandler(o4e,n=>t(n.params))}set onsandboxready(t){this.setNotificationHandler(i4e,n=>t(n.params))}set oninitialized(t){this.setNotificationHandler(f4e,n=>t(n.params))}set onmessage(t){this.setRequestHandler(h4e,async(n,r)=>t(n.params,r))}set onopenlink(t){this.setRequestHandler(r4e,async(n,r)=>t(n.params,r))}set onrequestdisplaymode(t){this.setRequestHandler(DR,async(n,r)=>t(n.params,r))}set onloggingmessage(t){this.setNotificationHandler(d3,async n=>{t(n.params)})}set oncalltool(t){this.setRequestHandler(u3,async(n,r)=>t(n.params,r))}sendToolListChanged(t={}){return this.notification({method:"notifications/tools/list_changed",params:t})}set onlistresources(t){this.setRequestHandler(QF,async(n,r)=>t(n.params,r))}set onlistresourcetemplates(t){this.setRequestHandler(t3,async(n,r)=>t(n.params,r))}set onreadresource(t){this.setRequestHandler(r3,async(n,r)=>t(n.params,r))}sendResourceListChanged(t={}){return this.notification({method:"notifications/resources/list_changed",params:t})}set onlistprompts(t){this.setRequestHandler(a3,async(n,r)=>t(n.params,r))}sendPromptListChanged(t={}){return this.notification({method:"notifications/prompts/list_changed",params:t})}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){}assertNotificationCapability(t){}assertTaskCapability(t){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(t){throw Error("Task handlers are not supported in MCP Apps")}getCapabilities(){return this._capabilities}async _oninitialize(t){let n=t.params.protocolVersion;return this._appCapabilities=t.params.appCapabilities,this._appInfo=t.params.appInfo,{protocolVersion:b4e.includes(n)?n:R3,hostCapabilities:this.getCapabilities(),hostInfo:this._hostInfo,hostContext:this._hostContext}}setHostContext(t){let n={},r=!1;for(let i of Object.keys(t)){let o=this._hostContext[i],a=t[i];x4e(o,a)||(n[i]=a,r=!0)}r&&(this._hostContext=t,this.sendHostContextChange(n))}sendHostContextChange(t){return this.notification({method:"ui/notifications/host-context-changed",params:t})}sendToolInput(t){return this.notification({method:"ui/notifications/tool-input",params:t})}sendToolInputPartial(t){return this.notification({method:"ui/notifications/tool-input-partial",params:t})}sendToolResult(t){return this.notification({method:"ui/notifications/tool-result",params:t})}sendToolCancelled(t){return this.notification({method:"ui/notifications/tool-cancelled",params:t})}sendSandboxResourceReady(t){return this.notification({method:"ui/notifications/sandbox-resource-ready",params:t})}teardownResource(t,n){return this.request({method:"ui/resource-teardown",params:t},l4e,n)}async connect(t){if(this._client){let n=this._client.getServerCapabilities();if(!n)throw Error("Client server capabilities not available");n.tools&&(this.oncalltool=async(r,i)=>this._client.request({method:"tools/call",params:r},Gg,{signal:i.signal}),n.tools.listChanged&&this._client.setNotificationHandler(c3,r=>this.sendToolListChanged(r.params))),n.resources&&(this.onlistresources=async(r,i)=>this._client.request({method:"resources/list",params:r},e3,{signal:i.signal}),this.onlistresourcetemplates=async(r,i)=>this._client.request({method:"resources/templates/list",params:r},n3,{signal:i.signal}),this.onreadresource=async(r,i)=>this._client.request({method:"resources/read",params:r},i3,{signal:i.signal}),n.resources.listChanged&&this._client.setNotificationHandler(o3,r=>this.sendResourceListChanged(r.params))),n.prompts&&(this.onlistprompts=async(r,i)=>this._client.request({method:"prompts/list",params:r},s3,{signal:i.signal}),n.prompts.listChanged&&this._client.setNotificationHandler(l3,r=>this.sendPromptListChanged(r.params)))}return super.connect(t)}}function x4e(e,t){return JSON.stringify(e)===JSON.stringify(t)}const _4e=1e4;async function S4e(e){const t=document.createElement("iframe");t.style.width="100%",t.style.height="600px",t.style.border="none",t.style.backgroundColor="transparent",t.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms");const n=new Promise((r,i)=>{let o=!1;const a=()=>{window.removeEventListener("message",l),t.removeEventListener("error",u)},s=setTimeout(()=>{o||(o=!0,a(),i(new Error("Timed out waiting for sandbox proxy iframe to be ready")))},_4e),l=f=>{f.source===t.contentWindow&&f.data&&f.data.method===_De&&(o||(o=!0,clearTimeout(s),a(),r()))},u=()=>{o||(o=!0,clearTimeout(s),a(),i(new Error("Failed to load sandbox proxy iframe")))};window.addEventListener("message",l),t.addEventListener("error",u)});return t.src=e.href,{iframe:t,onReady:n}}async function $4e(e,t){let n,r;do{const o=await e.listTools({cursor:r});n=o.tools.find(a=>a.name===t),r=o.nextCursor}while(!n&&r);if(!n)throw new Error(`tool ${t} not found`);if(!n._meta)return null;const i=y4e(n);if(!i)return null;if(!i.startsWith("ui://"))throw new Error(`tool ${t} has unsupported output template URI: ${i}`);return{uri:i}}async function k4e(e,t){const n=await e.readResource({uri:t.uri});if(!n)throw new Error("UI resource not found: "+t.uri);if(n.contents.length!==1)throw new Error("Unsupported UI resource content length: "+n.contents.length);const r=n.contents[0];let i;const o=a=>a===_W;if("text"in r&&typeof r.text=="string"&&o(r.mimeType))i=r.text;else if("blob"in r&&typeof r.blob=="string"&&o(r.mimeType))i=atob(r.blob);else throw new Error("Unsupported UI resource content format: "+JSON.stringify(r));return i}function E4e(e,t){const n=new URL(e.href);return t&&Object.keys(t).length>0&&n.searchParams.set("csp",JSON.stringify(t)),n}const P4e=e=>{const{html:t,sandbox:n,appBridge:r,onSizeChanged:i,onInitialized:o,toolInput:a,toolResult:s,onError:l}=e,[u,f]=S.useState(!1),[c,d]=S.useState(!1),[p,h]=S.useState(null),m=S.useRef(null),g=S.useRef(null),v=S.useRef(null),y=S.useRef(null),b=S.useRef(i),_=S.useRef(o),x=S.useRef(l);return S.useEffect(()=>{b.current=i,_.current=o,x.current=l}),S.useEffect(()=>{const w=E4e(n.url,n.csp),$=w.href;if(v.current===$&&y.current===r&&g.current)return;f(!1),d(!1),h(null);let k=!0;return(async()=>{var T,E;try{g.current&&(T=m.current)!=null&&T.contains(g.current)&&(m.current.removeChild(g.current),g.current=null,v.current=null,y.current=null);const{iframe:I,onReady:O}=await S4e(w);if(!k||(g.current=I,v.current=$,y.current=r,m.current&&m.current.appendChild(I),await O,!k)||(r.onsizechange=async j=>{var D;(D=b.current)==null||D.call(b,j),g.current&&(j.width!==void 0&&(g.current.style.width=`${j.width}px`),j.height!==void 0&&(g.current.style.height=`${j.height}px`))},r.oninitialized=()=>{var j;k&&(console.log("[AppFrame] App initialized"),f(!0),(j=_.current)==null||j.call(_,{appVersion:r.getAppVersion(),appCapabilities:r.getAppCapabilities()}))},await r.connect(new v4e(I.contentWindow,I.contentWindow)),!k))return;d(!0)}catch(I){if(console.error("[AppFrame] Error:",I),!k)return;const O=I instanceof Error?I:new Error(String(I));h(O),(E=x.current)==null||E.call(x,O)}})(),()=>{k=!1}},[n.url,n.csp,r]),S.useEffect(()=>{!c||!t||y.current!==r||(async()=>{var w;try{console.log("[AppFrame] Sending HTML to sandbox"),await r.sendSandboxResourceReady({html:t,csp:n.csp})}catch($){const k=$ instanceof Error?$:new Error(String($));h(k),(w=x.current)==null||w.call(x,k)}})()},[c,t,r,n.csp]),S.useEffect(()=>{c&&u&&a&&y.current===r&&(console.log("[AppFrame] Sending tool input:",a),r.sendToolInput({arguments:a}))},[r,c,u,a]),S.useEffect(()=>{c&&u&&s&&y.current===r&&(console.log("[AppFrame] Sending tool result:",s),r.sendToolResult(s))},[r,c,u,s]),z.jsx("div",{ref:m,style:{width:"100%",height:"100%",display:"flex",flexDirection:"column"},children:p&&z.jsxs("div",{style:{color:"red",padding:"1rem"},children:["Error: ",p.message]})})},O4e=S.forwardRef((e,t)=>{const{client:n,toolName:r,sandbox:i,toolResourceUri:o,html:a,toolInput:s,toolResult:l,toolInputPartial:u,toolCancelled:f,hostContext:c,onMessage:d,onOpenLink:p,onLoggingMessage:h,onSizeChanged:m,onError:g,onCallTool:v,onListResources:y,onListResourceTemplates:b,onReadResource:_,onListPrompts:x}=e,[w,$]=S.useState(null),[k,T]=S.useState(a??null),[E,I]=S.useState(null),O=S.useRef(d),j=S.useRef(p),D=S.useRef(h),M=S.useRef(m),L=S.useRef(g),B=S.useRef(v),R=S.useRef(y),U=S.useRef(b),P=S.useRef(_),G=S.useRef(x);S.useEffect(()=>{O.current=d,j.current=p,D.current=h,M.current=m,L.current=g,B.current=v,R.current=y,U.current=b,P.current=_,G.current=x}),S.useImperativeHandle(t,()=>({sendToolListChanged:()=>w==null?void 0:w.sendToolListChanged(),sendResourceListChanged:()=>w==null?void 0:w.sendResourceListChanged(),sendPromptListChanged:()=>w==null?void 0:w.sendPromptListChanged(),teardownResource:()=>w==null?void 0:w.teardownResource({})}),[w]),S.useEffect(()=>{let A=!0;return(()=>{var pe;try{const me=n==null?void 0:n.getServerCapabilities(),ce=new w4e(n??null,{name:"MCP-UI Host",version:"1.0.0"},{openLinks:{},serverTools:me==null?void 0:me.tools,serverResources:me==null?void 0:me.resources});if(ce.onmessage=async(ie,oe)=>{if(O.current)return O.current(ie,oe);throw new Le(Ve.MethodNotFound,"Method not found")},ce.onopenlink=async(ie,oe)=>{if(j.current)return j.current(ie,oe);throw new Le(Ve.MethodNotFound,"Method not found")},ce.onloggingmessage=ie=>{D.current&&D.current(ie)},B.current&&(ce.oncalltool=(ie,oe)=>B.current(ie,oe)),R.current&&(ce.onlistresources=(ie,oe)=>R.current(ie,oe)),U.current&&(ce.onlistresourcetemplates=(ie,oe)=>U.current(ie,oe)),P.current&&(ce.onreadresource=(ie,oe)=>P.current(ie,oe)),G.current&&(ce.onlistprompts=(ie,oe)=>G.current(ie,oe)),!A)return;$(ce)}catch(me){if(console.error("[AppRenderer] Error creating bridge:",me),!A)return;const ce=me instanceof Error?me:new Error(String(me));I(ce),(pe=L.current)==null||pe.call(L,ce)}})(),()=>{A=!1}},[n]),S.useEffect(()=>{if(a){T(a);return}const A=!!n,pe=!!o&&!!P.current;if(!A&&!pe){I(new Error("Either 'html' prop, 'client', or ('toolResourceUri' + 'onReadResource') must be provided to fetch UI resource"));return}let me=!0;return(async()=>{var ce,ie;try{let oe;if(o)oe=o,console.log(`[AppRenderer] Using provided resource URI: ${oe}`);else if(n){console.log(`[AppRenderer] Fetching resource URI for tool: ${r}`);const ne=await $4e(n,r);if(!ne)throw new Error(`Tool ${r} has no UI resource (no ui/resourceUri in tool._meta)`);oe=ne.uri,console.log(`[AppRenderer] Got resource URI: ${oe}`)}else throw new Error("Cannot determine resource URI without client or toolResourceUri");if(!me)return;console.log(`[AppRenderer] Reading resource HTML from: ${oe}`);let Y;if(n)Y=await k4e(n,{uri:oe});else if(P.current){const ne=await P.current({uri:oe},{});if(!ne.contents||ne.contents.length!==1)throw new Error("Unsupported UI resource content length: "+((ce=ne.contents)==null?void 0:ce.length));const Q=ne.contents[0],Z=$e=>$e===_W;if("text"in Q&&typeof Q.text=="string"&&Z(Q.mimeType))Y=Q.text;else if("blob"in Q&&typeof Q.blob=="string"&&Z(Q.mimeType))Y=atob(Q.blob);else throw new Error("Unsupported UI resource content format: "+JSON.stringify(Q))}else throw new Error("No way to read resource HTML");if(!me)return;T(Y)}catch(oe){if(!me)return;const Y=oe instanceof Error?oe:new Error(String(oe));I(Y),(ie=L.current)==null||ie.call(L,Y)}})(),()=>{me=!1}},[n,r,o,a]),S.useEffect(()=>{w&&c&&w.setHostContext(c)},[w,c]),S.useEffect(()=>{w&&u&&w.sendToolInputPartial(u)},[w,u]),S.useEffect(()=>{w&&f&&w.sendToolCancelled({})},[w,f]);const V=M.current;return E?z.jsxs("div",{style:{color:"red",padding:"1rem"},children:["Error: ",E.message]}):!w||!k?null:z.jsx(P4e,{html:k,sandbox:i,appBridge:w,toolInput:s,toolResult:l,onSizeChanged:V,onError:g})});O4e.displayName="AppRenderer";export{SOe as $,W4e as A,K4e as B,J4e as C,f6e as D,d6e as E,b6e as F,w6e as G,_6e as H,j6e as I,Ri as J,P6e as K,T6e as L,z6e as M,L6e as N,Q4e as O,cUe as P,Eu as Q,lUe as R,G6e as S,Q6e as T,nUe as U,fUe as V,aUe as W,sUe as X,ws as Y,uUe as Z,Lg as _,Y6e as a,Wre as a$,md as a0,vd as a1,F6e as a2,U6e as a3,a6e as a4,q6e as a5,D6e as a6,H4e as a7,S6e as a8,o6e as a9,T4e as aA,De as aB,R4e as aC,z4e as aD,L4e as aE,j4e as aF,D4e as aG,M4e as aH,F4e as aI,B4e as aJ,Z4e as aK,U4e as aL,Yne as aM,Kne as aN,Qne as aO,ire as aP,nre as aQ,rre as aR,Vne as aS,qne as aT,Are as aU,Nre as aV,Qre as aW,e6e as aX,eie as aY,Mre as aZ,zre as a_,i6e as aa,p6e as ab,N4e as ac,bUe as ad,eUe as ae,n6e as af,u6e as ag,s6e as ah,B6e as ai,m6e as aj,x6e as ak,y6e as al,h6e as am,E6e as an,t6e as ao,r6e as ap,Z6e as aq,A6e as ar,l6e as as,V6e as at,A4e as au,C4e as av,wDe as aw,KV as ax,v6e as ay,G4e as az,$6e as b,Gre as b0,Yre as b1,Jre as b2,Xre as b3,nie as b4,Ire as b5,Cre as b6,vUe as b7,gUe as b8,yUe as b9,R6e as c,K6e as d,W6e as e,k6e as f,rUe as g,g6e as h,Y4e as i,z as j,O6e as k,X6e as l,N6e as m,V4e as n,I6e as o,J6e as p,H6e as q,S as r,X4e as s,c6e as t,tUe as u,M6e as v,q4e as w,iUe as x,C6e as y,oUe as z}; +container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:C.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:C.string().optional().describe("User's timezone in IANA format."),userAgent:C.string().optional().describe("Host application identifier."),platform:C.union([C.literal("web"),C.literal("desktop"),C.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:C.object({touch:C.boolean().optional().describe("Whether the device supports touch input."),hover:C.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:C.object({top:C.number().describe("Top safe area inset in pixels."),right:C.number().describe("Right safe area inset in pixels."),bottom:C.number().describe("Bottom safe area inset in pixels."),left:C.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough();C.object({method:C.literal("ui/notifications/host-context-changed"),params:xW.describe("Partial context update containing only changed fields.")});var m4e=C.object({method:C.literal("ui/initialize"),params:C.object({appInfo:Hg.describe("App identification (name and version)."),appCapabilities:c4e.describe("Features and capabilities this app provides."),protocolVersion:C.string().describe("Protocol version this app supports.")})});C.object({protocolVersion:C.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Hg.describe("Host application identification and version."),hostCapabilities:u4e.describe("Features and capabilities provided by the host."),hostContext:xW.describe("Rich context about the host environment.")}).passthrough();class v4e{constructor(t=window.parent,n){Nt(this,"eventTarget"),Nt(this,"eventSource"),Nt(this,"messageListener"),Nt(this,"onclose"),Nt(this,"onerror"),Nt(this,"onmessage"),Nt(this,"sessionId"),Nt(this,"setProtocolVersion"),this.eventTarget=t,this.eventSource=n,this.messageListener=r=>{var i,o;if(n&&r.source!==this.eventSource){console.error("Ignoring message from unknown source",r);return}let a=rNe.safeParse(r.data);a.success?(console.debug("Parsed message",a.data),(i=this.onmessage)==null||i.call(this,a.data)):(console.error("Failed to parse message",a.error.message,r),(o=this.onerror)==null||o.call(this,Error("Invalid JSON-RPC message received: "+a.error.message)))}}async start(){window.addEventListener("message",this.messageListener)}async send(t,n){console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){var t;window.removeEventListener("message",this.messageListener),(t=this.onclose)==null||t.call(this)}}var g4e="ui/resourceUri",_W="text/html;profile=mcp-app";function y4e(e){var t,n,r;let i=(n=(t=e._meta)==null?void 0:t.ui)==null?void 0:n.resourceUri;if(i===void 0&&(i=(r=e._meta)==null?void 0:r[g4e]),typeof i=="string"&&i.startsWith("ui://"))return i;if(i!==void 0)throw Error(`Invalid UI resource URI: ${JSON.stringify(i)}`)}var b4e=[R3];class w4e extends jje{constructor(t,n,r,i){super(i),Nt(this,"_client"),Nt(this,"_hostInfo"),Nt(this,"_capabilities"),Nt(this,"_appCapabilities"),Nt(this,"_hostContext",{}),Nt(this,"_appInfo"),Nt(this,"onping"),Nt(this,"sendResourceTeardown",this.teardownResource),this._client=t,this._hostInfo=n,this._capabilities=r,this._hostContext=(i==null?void 0:i.hostContext)||{},this.setRequestHandler(m4e,o=>this._oninitialize(o)),this.setRequestHandler(Vg,(o,a)=>{var s;return(s=this.onping)==null||s.call(this,o.params,a),{}}),this.setRequestHandler(DR,o=>({mode:this._hostContext.displayMode??"inline"}))}getAppCapabilities(){return this._appCapabilities}getAppVersion(){return this._appInfo}set onsizechange(t){this.setNotificationHandler(o4e,n=>t(n.params))}set onsandboxready(t){this.setNotificationHandler(i4e,n=>t(n.params))}set oninitialized(t){this.setNotificationHandler(f4e,n=>t(n.params))}set onmessage(t){this.setRequestHandler(h4e,async(n,r)=>t(n.params,r))}set onopenlink(t){this.setRequestHandler(r4e,async(n,r)=>t(n.params,r))}set onrequestdisplaymode(t){this.setRequestHandler(DR,async(n,r)=>t(n.params,r))}set onloggingmessage(t){this.setNotificationHandler(d3,async n=>{t(n.params)})}set oncalltool(t){this.setRequestHandler(u3,async(n,r)=>t(n.params,r))}sendToolListChanged(t={}){return this.notification({method:"notifications/tools/list_changed",params:t})}set onlistresources(t){this.setRequestHandler(QF,async(n,r)=>t(n.params,r))}set onlistresourcetemplates(t){this.setRequestHandler(t3,async(n,r)=>t(n.params,r))}set onreadresource(t){this.setRequestHandler(r3,async(n,r)=>t(n.params,r))}sendResourceListChanged(t={}){return this.notification({method:"notifications/resources/list_changed",params:t})}set onlistprompts(t){this.setRequestHandler(a3,async(n,r)=>t(n.params,r))}sendPromptListChanged(t={}){return this.notification({method:"notifications/prompts/list_changed",params:t})}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){}assertNotificationCapability(t){}assertTaskCapability(t){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(t){throw Error("Task handlers are not supported in MCP Apps")}getCapabilities(){return this._capabilities}async _oninitialize(t){let n=t.params.protocolVersion;return this._appCapabilities=t.params.appCapabilities,this._appInfo=t.params.appInfo,{protocolVersion:b4e.includes(n)?n:R3,hostCapabilities:this.getCapabilities(),hostInfo:this._hostInfo,hostContext:this._hostContext}}setHostContext(t){let n={},r=!1;for(let i of Object.keys(t)){let o=this._hostContext[i],a=t[i];x4e(o,a)||(n[i]=a,r=!0)}r&&(this._hostContext=t,this.sendHostContextChange(n))}sendHostContextChange(t){return this.notification({method:"ui/notifications/host-context-changed",params:t})}sendToolInput(t){return this.notification({method:"ui/notifications/tool-input",params:t})}sendToolInputPartial(t){return this.notification({method:"ui/notifications/tool-input-partial",params:t})}sendToolResult(t){return this.notification({method:"ui/notifications/tool-result",params:t})}sendToolCancelled(t){return this.notification({method:"ui/notifications/tool-cancelled",params:t})}sendSandboxResourceReady(t){return this.notification({method:"ui/notifications/sandbox-resource-ready",params:t})}teardownResource(t,n){return this.request({method:"ui/resource-teardown",params:t},l4e,n)}async connect(t){if(this._client){let n=this._client.getServerCapabilities();if(!n)throw Error("Client server capabilities not available");n.tools&&(this.oncalltool=async(r,i)=>this._client.request({method:"tools/call",params:r},Gg,{signal:i.signal}),n.tools.listChanged&&this._client.setNotificationHandler(c3,r=>this.sendToolListChanged(r.params))),n.resources&&(this.onlistresources=async(r,i)=>this._client.request({method:"resources/list",params:r},e3,{signal:i.signal}),this.onlistresourcetemplates=async(r,i)=>this._client.request({method:"resources/templates/list",params:r},n3,{signal:i.signal}),this.onreadresource=async(r,i)=>this._client.request({method:"resources/read",params:r},i3,{signal:i.signal}),n.resources.listChanged&&this._client.setNotificationHandler(o3,r=>this.sendResourceListChanged(r.params))),n.prompts&&(this.onlistprompts=async(r,i)=>this._client.request({method:"prompts/list",params:r},s3,{signal:i.signal}),n.prompts.listChanged&&this._client.setNotificationHandler(l3,r=>this.sendPromptListChanged(r.params)))}return super.connect(t)}}function x4e(e,t){return JSON.stringify(e)===JSON.stringify(t)}const _4e=1e4;async function S4e(e){const t=document.createElement("iframe");t.style.width="100%",t.style.height="600px",t.style.border="none",t.style.backgroundColor="transparent",t.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms");const n=new Promise((r,i)=>{let o=!1;const a=()=>{window.removeEventListener("message",l),t.removeEventListener("error",u)},s=setTimeout(()=>{o||(o=!0,a(),i(new Error("Timed out waiting for sandbox proxy iframe to be ready")))},_4e),l=f=>{f.source===t.contentWindow&&f.data&&f.data.method===_De&&(o||(o=!0,clearTimeout(s),a(),r()))},u=()=>{o||(o=!0,clearTimeout(s),a(),i(new Error("Failed to load sandbox proxy iframe")))};window.addEventListener("message",l),t.addEventListener("error",u)});return t.src=e.href,{iframe:t,onReady:n}}async function $4e(e,t){let n,r;do{const o=await e.listTools({cursor:r});n=o.tools.find(a=>a.name===t),r=o.nextCursor}while(!n&&r);if(!n)throw new Error(`tool ${t} not found`);if(!n._meta)return null;const i=y4e(n);if(!i)return null;if(!i.startsWith("ui://"))throw new Error(`tool ${t} has unsupported output template URI: ${i}`);return{uri:i}}async function k4e(e,t){const n=await e.readResource({uri:t.uri});if(!n)throw new Error("UI resource not found: "+t.uri);if(n.contents.length!==1)throw new Error("Unsupported UI resource content length: "+n.contents.length);const r=n.contents[0];let i;const o=a=>a===_W;if("text"in r&&typeof r.text=="string"&&o(r.mimeType))i=r.text;else if("blob"in r&&typeof r.blob=="string"&&o(r.mimeType))i=atob(r.blob);else throw new Error("Unsupported UI resource content format: "+JSON.stringify(r));return i}function E4e(e,t){const n=new URL(e.href);return t&&Object.keys(t).length>0&&n.searchParams.set("csp",JSON.stringify(t)),n}const P4e=e=>{const{html:t,sandbox:n,appBridge:r,onSizeChanged:i,onInitialized:o,toolInput:a,toolResult:s,onError:l}=e,[u,f]=S.useState(!1),[c,d]=S.useState(!1),[p,h]=S.useState(null),m=S.useRef(null),g=S.useRef(null),v=S.useRef(null),y=S.useRef(null),b=S.useRef(i),_=S.useRef(o),x=S.useRef(l);return S.useEffect(()=>{b.current=i,_.current=o,x.current=l}),S.useEffect(()=>{const w=E4e(n.url,n.csp),$=w.href;if(v.current===$&&y.current===r&&g.current)return;f(!1),d(!1),h(null);let k=!0;return(async()=>{var T,E;try{g.current&&(T=m.current)!=null&&T.contains(g.current)&&(m.current.removeChild(g.current),g.current=null,v.current=null,y.current=null);const{iframe:I,onReady:O}=await S4e(w);if(!k||(g.current=I,v.current=$,y.current=r,m.current&&m.current.appendChild(I),await O,!k)||(r.onsizechange=async j=>{var D;(D=b.current)==null||D.call(b,j),g.current&&(j.width!==void 0&&(g.current.style.width=`${j.width}px`),j.height!==void 0&&(g.current.style.height=`${j.height}px`))},r.oninitialized=()=>{var j;k&&(console.log("[AppFrame] App initialized"),f(!0),(j=_.current)==null||j.call(_,{appVersion:r.getAppVersion(),appCapabilities:r.getAppCapabilities()}))},await r.connect(new v4e(I.contentWindow,I.contentWindow)),!k))return;d(!0)}catch(I){if(console.error("[AppFrame] Error:",I),!k)return;const O=I instanceof Error?I:new Error(String(I));h(O),(E=x.current)==null||E.call(x,O)}})(),()=>{k=!1}},[n.url,n.csp,r]),S.useEffect(()=>{!c||!t||y.current!==r||(async()=>{var w;try{console.log("[AppFrame] Sending HTML to sandbox"),await r.sendSandboxResourceReady({html:t,csp:n.csp})}catch($){const k=$ instanceof Error?$:new Error(String($));h(k),(w=x.current)==null||w.call(x,k)}})()},[c,t,r,n.csp]),S.useEffect(()=>{c&&u&&a&&y.current===r&&(console.log("[AppFrame] Sending tool input:",a),r.sendToolInput({arguments:a}))},[r,c,u,a]),S.useEffect(()=>{c&&u&&s&&y.current===r&&(console.log("[AppFrame] Sending tool result:",s),r.sendToolResult(s))},[r,c,u,s]),z.jsx("div",{ref:m,style:{width:"100%",height:"100%",display:"flex",flexDirection:"column"},children:p&&z.jsxs("div",{style:{color:"red",padding:"1rem"},children:["Error: ",p.message]})})},O4e=S.forwardRef((e,t)=>{const{client:n,toolName:r,sandbox:i,toolResourceUri:o,html:a,toolInput:s,toolResult:l,toolInputPartial:u,toolCancelled:f,hostContext:c,onMessage:d,onOpenLink:p,onLoggingMessage:h,onSizeChanged:m,onError:g,onCallTool:v,onListResources:y,onListResourceTemplates:b,onReadResource:_,onListPrompts:x}=e,[w,$]=S.useState(null),[k,T]=S.useState(a??null),[E,I]=S.useState(null),O=S.useRef(d),j=S.useRef(p),D=S.useRef(h),M=S.useRef(m),L=S.useRef(g),B=S.useRef(v),R=S.useRef(y),U=S.useRef(b),P=S.useRef(_),G=S.useRef(x);S.useEffect(()=>{O.current=d,j.current=p,D.current=h,M.current=m,L.current=g,B.current=v,R.current=y,U.current=b,P.current=_,G.current=x}),S.useImperativeHandle(t,()=>({sendToolListChanged:()=>w==null?void 0:w.sendToolListChanged(),sendResourceListChanged:()=>w==null?void 0:w.sendResourceListChanged(),sendPromptListChanged:()=>w==null?void 0:w.sendPromptListChanged(),teardownResource:()=>w==null?void 0:w.teardownResource({})}),[w]),S.useEffect(()=>{let A=!0;return(()=>{var pe;try{const me=n==null?void 0:n.getServerCapabilities(),ce=new w4e(n??null,{name:"MCP-UI Host",version:"1.0.0"},{openLinks:{},serverTools:me==null?void 0:me.tools,serverResources:me==null?void 0:me.resources});if(ce.onmessage=async(ie,oe)=>{if(O.current)return O.current(ie,oe);throw new Le(Ve.MethodNotFound,"Method not found")},ce.onopenlink=async(ie,oe)=>{if(j.current)return j.current(ie,oe);throw new Le(Ve.MethodNotFound,"Method not found")},ce.onloggingmessage=ie=>{D.current&&D.current(ie)},B.current&&(ce.oncalltool=(ie,oe)=>B.current(ie,oe)),R.current&&(ce.onlistresources=(ie,oe)=>R.current(ie,oe)),U.current&&(ce.onlistresourcetemplates=(ie,oe)=>U.current(ie,oe)),P.current&&(ce.onreadresource=(ie,oe)=>P.current(ie,oe)),G.current&&(ce.onlistprompts=(ie,oe)=>G.current(ie,oe)),!A)return;$(ce)}catch(me){if(console.error("[AppRenderer] Error creating bridge:",me),!A)return;const ce=me instanceof Error?me:new Error(String(me));I(ce),(pe=L.current)==null||pe.call(L,ce)}})(),()=>{A=!1}},[n]),S.useEffect(()=>{if(a){T(a);return}const A=!!n,pe=!!o&&!!P.current;if(!A&&!pe){I(new Error("Either 'html' prop, 'client', or ('toolResourceUri' + 'onReadResource') must be provided to fetch UI resource"));return}let me=!0;return(async()=>{var ce,ie;try{let oe;if(o)oe=o,console.log(`[AppRenderer] Using provided resource URI: ${oe}`);else if(n){console.log(`[AppRenderer] Fetching resource URI for tool: ${r}`);const ne=await $4e(n,r);if(!ne)throw new Error(`Tool ${r} has no UI resource (no ui/resourceUri in tool._meta)`);oe=ne.uri,console.log(`[AppRenderer] Got resource URI: ${oe}`)}else throw new Error("Cannot determine resource URI without client or toolResourceUri");if(!me)return;console.log(`[AppRenderer] Reading resource HTML from: ${oe}`);let Y;if(n)Y=await k4e(n,{uri:oe});else if(P.current){const ne=await P.current({uri:oe},{});if(!ne.contents||ne.contents.length!==1)throw new Error("Unsupported UI resource content length: "+((ce=ne.contents)==null?void 0:ce.length));const Q=ne.contents[0],Z=$e=>$e===_W;if("text"in Q&&typeof Q.text=="string"&&Z(Q.mimeType))Y=Q.text;else if("blob"in Q&&typeof Q.blob=="string"&&Z(Q.mimeType))Y=atob(Q.blob);else throw new Error("Unsupported UI resource content format: "+JSON.stringify(Q))}else throw new Error("No way to read resource HTML");if(!me)return;T(Y)}catch(oe){if(!me)return;const Y=oe instanceof Error?oe:new Error(String(oe));I(Y),(ie=L.current)==null||ie.call(L,Y)}})(),()=>{me=!1}},[n,r,o,a]),S.useEffect(()=>{w&&c&&w.setHostContext(c)},[w,c]),S.useEffect(()=>{w&&u&&w.sendToolInputPartial(u)},[w,u]),S.useEffect(()=>{w&&f&&w.sendToolCancelled({})},[w,f]);const V=M.current;return E?z.jsxs("div",{style:{color:"red",padding:"1rem"},children:["Error: ",E.message]}):!w||!k?null:z.jsx(P4e,{html:k,sandbox:i,appBridge:w,toolInput:s,toolResult:l,onSizeChanged:V,onError:g})});O4e.displayName="AppRenderer";export{Lg as $,W4e as A,K4e as B,J4e as C,f6e as D,aUe as E,b6e as F,w6e as G,_6e as H,d6e as I,D6e as J,P6e as K,I6e as L,L6e as M,U6e as N,Ri as O,Q4e as P,fUe as Q,uUe as R,K6e as S,eUe as T,rUe as U,Eu as V,sUe as W,lUe as X,dUe as Y,ws as Z,cUe as _,X6e as a,zre as a$,SOe as a0,md as a1,vd as a2,B6e as a3,F6e as a4,a6e as a5,G6e as a6,M6e as a7,H4e as a8,S6e as a9,G4e as aA,T4e as aB,De as aC,R4e as aD,z4e as aE,L4e as aF,j4e as aG,D4e as aH,M4e as aI,F4e as aJ,B4e as aK,Z4e as aL,U4e as aM,Yne as aN,Kne as aO,Qne as aP,ire as aQ,nre as aR,rre as aS,Vne as aT,qne as aU,Are as aV,Nre as aW,Qre as aX,e6e as aY,eie as aZ,Mre as a_,o6e as aa,i6e as ab,p6e as ac,N4e as ad,wUe as ae,tUe as af,n6e as ag,u6e as ah,s6e as ai,Z6e as aj,m6e as ak,x6e as al,y6e as am,h6e as an,E6e as ao,t6e as ap,r6e as aq,W6e as ar,C6e as as,l6e as at,q6e as au,A4e as av,C4e as aw,wDe as ax,KV as ay,v6e as az,$6e as b,Wre as b0,Gre as b1,Yre as b2,Jre as b3,Xre as b4,nie as b5,Ire as b6,Cre as b7,gUe as b8,yUe as b9,bUe as ba,N6e as c,O6e as d,Y6e as e,H6e as f,k6e as g,iUe as h,g6e as i,z as j,Y4e as k,T6e as l,J6e as m,j6e as n,V4e as o,A6e as p,Q6e as q,S as r,V6e as s,X4e as t,c6e as u,nUe as v,z6e as w,q4e as x,oUe as y,R6e as z}; diff --git a/site/content/docs/cli-reference.mdx b/site/content/docs/cli-reference.mdx index 842c58f5..b9565bc7 100644 --- a/site/content/docs/cli-reference.mdx +++ b/site/content/docs/cli-reference.mdx @@ -41,6 +41,7 @@ from the CLI, so it matches the binary you have. Pick a family for its subcomman | [`oap sandbox`](/docs/oap-sandbox) | Inspect SpiceboxSession resources (per-bundle running sandbox pods) | | [`oap session`](/docs/oap-session) | Inspect AgentSession resources | | [`oap settings`](/docs/oap-settings) | Manage cluster-wide agent settings (security defaults wizard + apply). | +| [`oap setup-mcp`](/docs/oap-setup-mcp) | Emit MCP client config pointing a local tool at this cluster's MCP server | | [`oap skill`](/docs/oap-skill) | List, view, create, and delete agent Skills and SkillSources | | [`oap spicedb`](/docs/oap-spicedb) | Apply schema and check permissions against the system SpiceDB. | | [`oap tools`](/docs/oap-tools) | Manage tools used by agents (kind-agnostic) | diff --git a/site/content/docs/crd-accesstoken.mdx b/site/content/docs/crd-accesstoken.mdx new file mode 100644 index 00000000..5b411ec7 --- /dev/null +++ b/site/content/docs/crd-accesstoken.mdx @@ -0,0 +1,49 @@ +export const meta = { + title: 'AccessToken', + section: 'Reference', + group: 'CRD reference', + order: 3010, + description: 'AccessToken is the AUTHENTICATION record for one inbound delegated', +} + +# AccessToken + +**Group** `agentprimitives.authzed.com` · **Scope** Namespaced · **Short names** `atok` + +AccessToken is the AUTHENTICATION record for one inbound delegated +credential minted at OAuth consent: hash → token identity, plus display +metadata and expiry. It deliberately carries NO role and NO scope — the +authorization truth lives entirely in SpiceDB on accesstoken:<name> +(see pkg/authz/spicedb/schema/schema.zed). Deleting this object is +revocation: the finalizer removes the SpiceDB tuples. + +## Spec + +| Field | Type | Description | +| --- | --- | --- | +| `spec.clientID` | `string` | ClientID is the OAuth client the token was issued to. | +| `spec.clientName` | `string` | ClientName is the DCR-supplied display name, sanitized at mint. | +| `spec.expiresAt` \* | `string (date-time)` | ExpiresAt is when the token stops authenticating. The SpiceDB tuples carry the same instant via the expiration trait; this copy gates the authentication (hash-lookup) step without a SpiceDB round trip. | +| `spec.owner` \* | `string` | Owner is the canonical user id the token acts as. | +| `spec.tokenHash` \* | `string` | TokenHash is hex(sha256(bearer value)). The value itself is never stored server-side. | + +\* required + +## Status + +Status is controller-owned (observed state). + +| Field | Type | Description | +| --- | --- | --- | +| `status.conditions` | `[]object` | | +| `status.conditions[].lastTransitionTime` \* | `string (date-time)` | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. | +| `status.conditions[].message` \* | `string` | message is a human readable message indicating details about the transition. This may be an empty string. | +| `status.conditions[].observedGeneration` | `integer (int64)` | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. _(min 0)_ | +| `status.conditions[].reason` \* | `string` | reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. | +| `status.conditions[].status` \* | `string` | status of the condition, one of True, False, Unknown. _(enum: True \| False \| Unknown)_ | +| `status.conditions[].type` \* | `string` | type of condition in CamelCase or in foo.example.com/CamelCase. | +| `status.lastUsedAt` | `string (date-time)` | LastUsedAt is observation, controller-owned, updated on meaningful change only (not every request). | +| `status.observedGeneration` | `integer (int64)` | | + +\* required + diff --git a/site/content/docs/crd-agentclass.mdx b/site/content/docs/crd-agentclass.mdx index 6d1ff47f..a680a769 100644 --- a/site/content/docs/crd-agentclass.mdx +++ b/site/content/docs/crd-agentclass.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'AgentClass', section: 'Reference', group: 'CRD reference', - order: 3010, + order: 3020, description: 'AgentClass is the reviewable template an agent is defined by: model, system', } diff --git a/site/content/docs/crd-agentidentity.mdx b/site/content/docs/crd-agentidentity.mdx index 5ed61ce9..c7786129 100644 --- a/site/content/docs/crd-agentidentity.mdx +++ b/site/content/docs/crd-agentidentity.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'AgentIdentity', section: 'Reference', group: 'CRD reference', - order: 3020, + order: 3030, description: 'AgentIdentity is the named credential set an agent acts as: reusable static', } diff --git a/site/content/docs/crd-agentsession.mdx b/site/content/docs/crd-agentsession.mdx index a15a5b51..9961a2dd 100644 --- a/site/content/docs/crd-agentsession.mdx +++ b/site/content/docs/crd-agentsession.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'AgentSession', section: 'Reference', group: 'CRD reference', - order: 3030, + order: 3040, description: 'AgentSession is one running instance of an AgentClass: a conversation with', } diff --git a/site/content/docs/crd-agentsessiongrants.mdx b/site/content/docs/crd-agentsessiongrants.mdx index f2749d1a..9d0ecc4b 100644 --- a/site/content/docs/crd-agentsessiongrants.mdx +++ b/site/content/docs/crd-agentsessiongrants.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'AgentSessionGrants', section: 'Reference', group: 'CRD reference', - order: 3040, + order: 3050, description: 'AgentSessionGrants is the per-AgentClass declaration of the (resourceType,', } diff --git a/site/content/docs/crd-agentsettings.mdx b/site/content/docs/crd-agentsettings.mdx index 9d2157b4..d82d6503 100644 --- a/site/content/docs/crd-agentsettings.mdx +++ b/site/content/docs/crd-agentsettings.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'AgentSettings', section: 'Reference', group: 'CRD reference', - order: 3050, + order: 3060, description: 'AgentSettings is the namespace tier of agent governance settings: limits', } diff --git a/site/content/docs/crd-agentui.mdx b/site/content/docs/crd-agentui.mdx index 996744d9..eeab4971 100644 --- a/site/content/docs/crd-agentui.mdx +++ b/site/content/docs/crd-agentui.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'AgentUI', section: 'Reference', group: 'CRD reference', - order: 3060, + order: 3070, description: 'AgentUI is a bundle-authored DECLARATION of an agent-defined view: a page', } diff --git a/site/content/docs/crd-artifactrender.mdx b/site/content/docs/crd-artifactrender.mdx index 250e09c8..44ee7c71 100644 --- a/site/content/docs/crd-artifactrender.mdx +++ b/site/content/docs/crd-artifactrender.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'ArtifactRender', section: 'Reference', group: 'CRD reference', - order: 3070, + order: 3080, description: 'ArtifactRender is one request to turn agent-supplied bytes into a', } diff --git a/site/content/docs/crd-channel.mdx b/site/content/docs/crd-channel.mdx index 4f420401..0817fe49 100644 --- a/site/content/docs/crd-channel.mdx +++ b/site/content/docs/crd-channel.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'Channel', section: 'Reference', group: 'CRD reference', - order: 3080, + order: 3090, description: 'Channel binds one transport conversation -- a Slack channel, a scheduled', } diff --git a/site/content/docs/crd-clusteragentsettings.mdx b/site/content/docs/crd-clusteragentsettings.mdx index 022a414d..7288fe92 100644 --- a/site/content/docs/crd-clusteragentsettings.mdx +++ b/site/content/docs/crd-clusteragentsettings.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'ClusterAgentSettings', section: 'Reference', group: 'CRD reference', - order: 3090, + order: 3100, description: 'ClusterAgentSettings is the cluster-wide top tier of agent governance', } diff --git a/site/content/docs/crd-clusteridentityprovider.mdx b/site/content/docs/crd-clusteridentityprovider.mdx index 4dc9e458..3346a35c 100644 --- a/site/content/docs/crd-clusteridentityprovider.mdx +++ b/site/content/docs/crd-clusteridentityprovider.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'ClusterIdentityProvider', section: 'Reference', group: 'CRD reference', - order: 3100, + order: 3110, description: 'ClusterIdentityProvider configures cluster-wide human login: which', } diff --git a/site/content/docs/crd-clusterskill.mdx b/site/content/docs/crd-clusterskill.mdx index 6eb8b263..967d0332 100644 --- a/site/content/docs/crd-clusterskill.mdx +++ b/site/content/docs/crd-clusterskill.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'ClusterSkill', section: 'Reference', group: 'CRD reference', - order: 3110, + order: 3120, description: 'ClusterSkill is a cluster-scoped Skill, visible to every namespace. A', } diff --git a/site/content/docs/crd-clusterskillsource.mdx b/site/content/docs/crd-clusterskillsource.mdx index 296681df..ba45ffab 100644 --- a/site/content/docs/crd-clusterskillsource.mdx +++ b/site/content/docs/crd-clusterskillsource.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'ClusterSkillSource', section: 'Reference', group: 'CRD reference', - order: 3120, + order: 3130, description: 'ClusterSkillSource is a git repo that materializes cluster-scoped', } diff --git a/site/content/docs/crd-credentialupdaterequest.mdx b/site/content/docs/crd-credentialupdaterequest.mdx index 874e6ff0..607e4c92 100644 --- a/site/content/docs/crd-credentialupdaterequest.mdx +++ b/site/content/docs/crd-credentialupdaterequest.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'CredentialUpdateRequest', section: 'Reference', group: 'CRD reference', - order: 3130, + order: 3140, description: 'CredentialUpdateRequest is an agent\'s REQUEST that a human replace a', } diff --git a/site/content/docs/crd-mcpserver.mdx b/site/content/docs/crd-mcpserver.mdx index 5c547a18..333ff81a 100644 --- a/site/content/docs/crd-mcpserver.mdx +++ b/site/content/docs/crd-mcpserver.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'MCPServer', section: 'Reference', group: 'CRD reference', - order: 3140, + order: 3150, description: 'MCPServer declares one MCP endpoint an agent may call: where it lives', } diff --git a/site/content/docs/crd-publicendpoint.mdx b/site/content/docs/crd-publicendpoint.mdx index b1ffa3b2..92131303 100644 --- a/site/content/docs/crd-publicendpoint.mdx +++ b/site/content/docs/crd-publicendpoint.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'PublicEndpoint', section: 'Reference', group: 'CRD reference', - order: 3150, + order: 3160, description: 'PublicEndpoint is how this cluster is reached from the public Internet.', } diff --git a/site/content/docs/crd-reference.mdx b/site/content/docs/crd-reference.mdx index ca79f241..8371a973 100644 --- a/site/content/docs/crd-reference.mdx +++ b/site/content/docs/crd-reference.mdx @@ -14,6 +14,7 @@ full spec. | Kind | Scope | Summary | | --- | --- | --- | +| [AccessToken](/docs/crd-accesstoken) | Namespaced | AccessToken is the AUTHENTICATION record for one inbound delegated | | [AgentClass](/docs/crd-agentclass) | Namespaced | AgentClass is the reviewable template an agent is defined by: model, system | | [AgentIdentity](/docs/crd-agentidentity) | Namespaced | AgentIdentity is the named credential set an agent acts as: reusable static | | [AgentSession](/docs/crd-agentsession) | Namespaced | AgentSession is one running instance of an AgentClass: a conversation with | diff --git a/site/content/docs/crd-relationshipsource.mdx b/site/content/docs/crd-relationshipsource.mdx index e8bdb05e..ac1915e3 100644 --- a/site/content/docs/crd-relationshipsource.mdx +++ b/site/content/docs/crd-relationshipsource.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'RelationshipSource', section: 'Reference', group: 'CRD reference', - order: 3160, + order: 3170, description: 'RelationshipSource declares one upstream directory (Slack first) to poll', } diff --git a/site/content/docs/crd-sessionhold.mdx b/site/content/docs/crd-sessionhold.mdx index f8c7b9c9..2c0a7ff8 100644 --- a/site/content/docs/crd-sessionhold.mdx +++ b/site/content/docs/crd-sessionhold.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SessionHold', section: 'Reference', group: 'CRD reference', - order: 3170, + order: 3180, description: 'SessionHold parks an AgentSession for forensic review and gates its return to', } diff --git a/site/content/docs/crd-sessionuseridentity.mdx b/site/content/docs/crd-sessionuseridentity.mdx index 40cc63b3..4b245337 100644 --- a/site/content/docs/crd-sessionuseridentity.mdx +++ b/site/content/docs/crd-sessionuseridentity.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SessionUserIdentity', section: 'Reference', group: 'CRD reference', - order: 3180, + order: 3190, description: 'SessionUserIdentity is one session\'s narrowing of a user\'s UserIdentity', } diff --git a/site/content/docs/crd-sidecartoolbox.mdx b/site/content/docs/crd-sidecartoolbox.mdx index e0c6189c..1c2f6af6 100644 --- a/site/content/docs/crd-sidecartoolbox.mdx +++ b/site/content/docs/crd-sidecartoolbox.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SidecarToolbox', section: 'Reference', group: 'CRD reference', - order: 3190, + order: 3200, description: 'SidecarToolbox declares a user-supplied MCP server that runs as a sidecar', } diff --git a/site/content/docs/crd-skill.mdx b/site/content/docs/crd-skill.mdx index 896e1e4a..7a909ece 100644 --- a/site/content/docs/crd-skill.mdx +++ b/site/content/docs/crd-skill.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'Skill', section: 'Reference', group: 'CRD reference', - order: 3200, + order: 3210, description: 'Skill is one agentskills.io skill: a SKILL.md (frontmatter + body) plus', } diff --git a/site/content/docs/crd-skillsource.mdx b/site/content/docs/crd-skillsource.mdx index 9facfa39..090dcfef 100644 --- a/site/content/docs/crd-skillsource.mdx +++ b/site/content/docs/crd-skillsource.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SkillSource', section: 'Reference', group: 'CRD reference', - order: 3210, + order: 3220, description: 'SkillSource is a git repo that yields one or more Skills.', } diff --git a/site/content/docs/crd-spiceboxclass.mdx b/site/content/docs/crd-spiceboxclass.mdx index 299fdf7e..b3452c7b 100644 --- a/site/content/docs/crd-spiceboxclass.mdx +++ b/site/content/docs/crd-spiceboxclass.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SpiceboxClass', section: 'Reference', group: 'CRD reference', - order: 3230, + order: 3240, description: 'SpiceboxClass is the template a tool sandbox is cut from: image, resources,', } diff --git a/site/content/docs/crd-spiceboxsession.mdx b/site/content/docs/crd-spiceboxsession.mdx index aa7da5bc..7bbb5eab 100644 --- a/site/content/docs/crd-spiceboxsession.mdx +++ b/site/content/docs/crd-spiceboxsession.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SpiceboxSession', section: 'Reference', group: 'CRD reference', - order: 3240, + order: 3250, description: 'SpiceboxSession is one live sandbox instantiated from a SpiceboxClass: the', } diff --git a/site/content/docs/crd-spiceboxtoolchain.mdx b/site/content/docs/crd-spiceboxtoolchain.mdx index a9a329c7..005ffbb1 100644 --- a/site/content/docs/crd-spiceboxtoolchain.mdx +++ b/site/content/docs/crd-spiceboxtoolchain.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SpiceboxToolchain', section: 'Reference', group: 'CRD reference', - order: 3250, + order: 3260, description: 'SpiceboxToolchain is a language or tooling overlay a sandbox can mount -- a', } diff --git a/site/content/docs/crd-spiceboxtoolkit.mdx b/site/content/docs/crd-spiceboxtoolkit.mdx index 74b7d15a..5dd44509 100644 --- a/site/content/docs/crd-spiceboxtoolkit.mdx +++ b/site/content/docs/crd-spiceboxtoolkit.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SpiceboxToolkit', section: 'Reference', group: 'CRD reference', - order: 3260, + order: 3270, description: 'SpiceboxToolkit is the machine-readable description of one CLI a sandbox', } diff --git a/site/content/docs/crd-spiceboxtoolspec.mdx b/site/content/docs/crd-spiceboxtoolspec.mdx index 81325042..5b60e8e5 100644 --- a/site/content/docs/crd-spiceboxtoolspec.mdx +++ b/site/content/docs/crd-spiceboxtoolspec.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SpiceboxToolspec', section: 'Reference', group: 'CRD reference', - order: 3270, + order: 3280, description: 'SpiceboxToolspec is one sandbox tool as the agent sees it: a SpiceboxToolkit', } diff --git a/site/content/docs/crd-spicedbbootstrap.mdx b/site/content/docs/crd-spicedbbootstrap.mdx index 19760ee4..bf8a0c5e 100644 --- a/site/content/docs/crd-spicedbbootstrap.mdx +++ b/site/content/docs/crd-spicedbbootstrap.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SpiceDBBootstrap', section: 'Reference', group: 'CRD reference', - order: 3220, + order: 3230, description: 'SpiceDBBootstrap declaratively seeds SpiceDB state: an optional schema', } diff --git a/site/content/docs/crd-subagentrequest.mdx b/site/content/docs/crd-subagentrequest.mdx index f970f19a..182b902b 100644 --- a/site/content/docs/crd-subagentrequest.mdx +++ b/site/content/docs/crd-subagentrequest.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'SubagentRequest', section: 'Reference', group: 'CRD reference', - order: 3280, + order: 3290, description: 'SubagentRequest is a runner\'s request to delegate a task to a child session.', } diff --git a/site/content/docs/crd-toolcall.mdx b/site/content/docs/crd-toolcall.mdx index 8e410e82..46fe611d 100644 --- a/site/content/docs/crd-toolcall.mdx +++ b/site/content/docs/crd-toolcall.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'ToolCall', section: 'Reference', group: 'CRD reference', - order: 3290, + order: 3300, description: 'ToolCall is one execution of one sandbox tool inside a SpiceboxSession: the', } diff --git a/site/content/docs/crd-useridentity.mdx b/site/content/docs/crd-useridentity.mdx index dac65b2d..65620037 100644 --- a/site/content/docs/crd-useridentity.mdx +++ b/site/content/docs/crd-useridentity.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'UserIdentity', section: 'Reference', group: 'CRD reference', - order: 3300, + order: 3310, description: 'UserIdentity is a human\'s credential catalog: the credentials one person has', } diff --git a/site/content/docs/crd-workshop.mdx b/site/content/docs/crd-workshop.mdx index 1ac0806a..e004ded6 100644 --- a/site/content/docs/crd-workshop.mdx +++ b/site/content/docs/crd-workshop.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'Workshop', section: 'Reference', group: 'CRD reference', - order: 3310, + order: 3320, description: 'Workshop is one builder session\'s isolated build space: the durable record', } diff --git a/site/content/docs/crd-workshopprobe.mdx b/site/content/docs/crd-workshopprobe.mdx index 16958ade..f5177f4f 100644 --- a/site/content/docs/crd-workshopprobe.mdx +++ b/site/content/docs/crd-workshopprobe.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'WorkshopProbe', section: 'Reference', group: 'CRD reference', - order: 3320, + order: 3330, description: 'WorkshopProbe is the tool-discovery probe object for an agent-builder', } diff --git a/site/content/docs/crd-workspacesource.mdx b/site/content/docs/crd-workspacesource.mdx index 98fd9261..000eef61 100644 --- a/site/content/docs/crd-workspacesource.mdx +++ b/site/content/docs/crd-workspacesource.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'WorkspaceSource', section: 'Reference', group: 'CRD reference', - order: 3330, + order: 3340, description: 'WorkspaceSource declares a named, pluggable origin that per-session', } diff --git a/site/content/docs/oap-setup-mcp.mdx b/site/content/docs/oap-setup-mcp.mdx new file mode 100644 index 00000000..0d349071 --- /dev/null +++ b/site/content/docs/oap-setup-mcp.mdx @@ -0,0 +1,21 @@ +export const meta = { + title: 'oap setup-mcp', + section: 'Reference', + group: 'CLI', + order: 2970, + description: 'Emit MCP client config pointing a local tool at this cluster\'s MCP server', +} + +# oap setup-mcp + +Resolve this cluster's MCP server URL -- from --server, or by discovering the +webd external-URL ConfigMap via the current kube context -- and print a config +snippet for a local MCP-capable tool (claude-code, cursor, or generic). + +By default (no --mint), no credential is embedded: OAuth-native MCP clients +run their own discovery/consent flow against the server directly, and consent +there defaults to read-only. Pass --mint to run that flow here instead -- +dogfooding this cluster's own OAuth authorization server -- and embed the +resulting bearer token in the emitted config, for tools that do not speak MCP +OAuth themselves. + diff --git a/site/content/docs/oap-skill.mdx b/site/content/docs/oap-skill.mdx index 6030c452..bec8e16c 100644 --- a/site/content/docs/oap-skill.mdx +++ b/site/content/docs/oap-skill.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'oap skill', section: 'Reference', group: 'CLI', - order: 2970, + order: 2980, description: 'List, view, create, and delete agent Skills and SkillSources', } diff --git a/site/content/docs/oap-spicedb.mdx b/site/content/docs/oap-spicedb.mdx index 51c82228..2e804555 100644 --- a/site/content/docs/oap-spicedb.mdx +++ b/site/content/docs/oap-spicedb.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'oap spicedb', section: 'Reference', group: 'CLI', - order: 2980, + order: 2990, description: 'Apply schema and check permissions against the system SpiceDB.', } diff --git a/site/content/docs/oap-tools.mdx b/site/content/docs/oap-tools.mdx index a334d5ef..2da81d14 100644 --- a/site/content/docs/oap-tools.mdx +++ b/site/content/docs/oap-tools.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'oap tools', section: 'Reference', group: 'CLI', - order: 2990, + order: 3000, description: 'Manage tools used by agents (kind-agnostic)', } diff --git a/site/content/docs/oap-user-identity.mdx b/site/content/docs/oap-user-identity.mdx index 16c58a05..f1cc4f12 100644 --- a/site/content/docs/oap-user-identity.mdx +++ b/site/content/docs/oap-user-identity.mdx @@ -2,7 +2,7 @@ export const meta = { title: 'oap user-identity', section: 'Reference', group: 'CLI', - order: 3000, + order: 3010, description: 'Manage UserIdentity resources (per-user credential catalogs)', }