cortena-ui 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +149 -1
- package/dist/a2ui/views.js +2 -2
- package/dist/agent-chat/a2ui-block.d.ts +60 -0
- package/dist/agent-chat/a2ui-block.js +69 -0
- package/dist/agent-chat/a2ui-block.js.map +1 -0
- package/dist/agent-chat/agui-client.d.ts +40 -0
- package/dist/agent-chat/agui-client.js +251 -0
- package/dist/agent-chat/agui-client.js.map +1 -0
- package/dist/agent-chat/bridge.d.ts +109 -0
- package/dist/agent-chat/bridge.js +349 -0
- package/dist/agent-chat/bridge.js.map +1 -0
- package/dist/agent-chat/session.d.ts +58 -0
- package/dist/agent-chat/session.js +249 -0
- package/dist/agent-chat/session.js.map +1 -0
- package/dist/agent-chat/store.d.ts +67 -0
- package/dist/agent-chat/store.js +548 -0
- package/dist/agent-chat/store.js.map +1 -0
- package/dist/agent-chat/types.d.ts +187 -0
- package/dist/agent-chat/types.js +17 -0
- package/dist/agent-chat/types.js.map +1 -0
- package/dist/agent-chat.d.ts +10 -0
- package/dist/agent-chat.js +10 -0
- package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
- package/dist/components/admin-permissions/admin-permissions.js +101 -0
- package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
- package/dist/components/admin-permissions/context.d.ts +70 -0
- package/dist/components/admin-permissions/context.js +258 -0
- package/dist/components/admin-permissions/context.js.map +1 -0
- package/dist/components/admin-permissions/index.d.ts +10 -0
- package/dist/components/admin-permissions/licence.d.ts +15 -0
- package/dist/components/admin-permissions/licence.js +78 -0
- package/dist/components/admin-permissions/licence.js.map +1 -0
- package/dist/components/admin-permissions/matrix.d.ts +20 -0
- package/dist/components/admin-permissions/matrix.js +191 -0
- package/dist/components/admin-permissions/matrix.js.map +1 -0
- package/dist/components/admin-permissions/members.d.ts +18 -0
- package/dist/components/admin-permissions/members.js +185 -0
- package/dist/components/admin-permissions/members.js.map +1 -0
- package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
- package/dist/components/admin-permissions/role-assignment.js +174 -0
- package/dist/components/admin-permissions/role-assignment.js.map +1 -0
- package/dist/components/admin-permissions/roles.d.ts +25 -0
- package/dist/components/admin-permissions/roles.js +168 -0
- package/dist/components/admin-permissions/roles.js.map +1 -0
- package/dist/components/admin-permissions/types.d.ts +152 -0
- package/dist/components/admin-permissions/types.js +63 -0
- package/dist/components/admin-permissions/types.js.map +1 -0
- package/dist/components/agent-chat-popup.d.ts +29 -0
- package/dist/components/agent-chat-popup.js +188 -0
- package/dist/components/agent-chat-popup.js.map +1 -0
- package/dist/components/agent-chat.d.ts +143 -0
- package/dist/components/agent-chat.js +578 -0
- package/dist/components/agent-chat.js.map +1 -0
- package/dist/components/app-shell.d.ts +126 -0
- package/dist/components/app-shell.js +297 -0
- package/dist/components/app-shell.js.map +1 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button-link.js +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/consent-screen.d.ts +65 -0
- package/dist/components/consent-screen.js +123 -0
- package/dist/components/consent-screen.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +15 -1
- package/dist/components/data-table/data-table.js +18 -4
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/data-table/index.d.ts +4 -4
- package/dist/components/data-table/parts.d.ts +27 -3
- package/dist/components/data-table/parts.js +175 -55
- package/dist/components/data-table/parts.js.map +1 -1
- package/dist/components/data-table/types.d.ts +61 -0
- package/dist/components/data-table/use-data-table.js +91 -6
- package/dist/components/data-table/use-data-table.js.map +1 -1
- package/dist/components/data-table/use-server-source.js +119 -28
- package/dist/components/data-table/use-server-source.js.map +1 -1
- package/dist/components/help-panel.d.ts +131 -0
- package/dist/components/help-panel.js +545 -0
- package/dist/components/help-panel.js.map +1 -0
- package/dist/components/login-screen.d.ts +127 -0
- package/dist/components/login-screen.js +339 -0
- package/dist/components/login-screen.js.map +1 -0
- package/dist/components/session-guard.d.ts +268 -0
- package/dist/components/session-guard.js +632 -0
- package/dist/components/session-guard.js.map +1 -0
- package/dist/components/toast.d.ts +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +11 -7
- package/dist/data-table.d.ts +13 -4
- package/dist/data-table.js +10 -2
- package/dist/hooks/use-cortena-theme.js +49 -3
- package/dist/hooks/use-cortena-theme.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +21 -8
- package/dist/markdown.d.ts +2 -1
- package/dist/markdown.js +2 -1
- package/package.json +16 -4
- package/src/agent-chat/a2ui-block.ts +118 -0
- package/src/agent-chat/agui-client.ts +405 -0
- package/src/agent-chat/bridge.ts +433 -0
- package/src/agent-chat/session.ts +392 -0
- package/src/agent-chat/store.ts +738 -0
- package/src/agent-chat/types.ts +213 -0
- package/src/components/admin-permissions/admin-permissions.tsx +130 -0
- package/src/components/admin-permissions/context.tsx +376 -0
- package/src/components/admin-permissions/index.tsx +32 -0
- package/src/components/admin-permissions/licence.tsx +84 -0
- package/src/components/admin-permissions/matrix.tsx +257 -0
- package/src/components/admin-permissions/members.tsx +204 -0
- package/src/components/admin-permissions/role-assignment.tsx +239 -0
- package/src/components/admin-permissions/roles.tsx +169 -0
- package/src/components/admin-permissions/types.ts +231 -0
- package/src/components/agent-chat-popup.tsx +289 -0
- package/src/components/agent-chat.tsx +843 -0
- package/src/components/app-shell.tsx +502 -0
- package/src/components/consent-screen.tsx +239 -0
- package/src/components/data-table/data-table.tsx +36 -0
- package/src/components/data-table/index.tsx +6 -1
- package/src/components/data-table/parts.tsx +223 -47
- package/src/components/data-table/types.ts +68 -0
- package/src/components/data-table/use-data-table.ts +152 -4
- package/src/components/data-table/use-server-source.ts +150 -12
- package/src/components/help-panel.tsx +765 -0
- package/src/components/login-screen.tsx +479 -0
- package/src/components/session-guard.tsx +1071 -0
- package/src/entries/agent-chat.ts +113 -0
- package/src/entries/core.ts +8 -0
- package/src/entries/data-table.ts +41 -0
- package/src/entries/markdown.ts +25 -0
- package/src/hooks/use-cortena-theme.ts +63 -4
- package/src/index.ts +6 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Button } from "./button.js";
|
|
5
|
+
import { Spinner } from "./spinner.js";
|
|
6
|
+
import { Badge } from "./badge.js";
|
|
7
|
+
import { ErrorBanner } from "./error-banner.js";
|
|
8
|
+
import { Textarea } from "./textarea.js";
|
|
9
|
+
import { Markdown } from "./markdown.js";
|
|
10
|
+
import { A2UIErrorCard } from "../a2ui/views.js";
|
|
11
|
+
import { A2UIRenderer } from "../a2ui/renderer.js";
|
|
12
|
+
import { isHiddenMessage } from "../agent-chat/session.js";
|
|
13
|
+
import { useAgentChat } from "../agent-chat/store.js";
|
|
14
|
+
import { sessionTitle } from "../agent-chat/types.js";
|
|
15
|
+
import { ChevronDown, ChevronRight, MessageSquarePlus, Send, ShieldCheck, ShieldX, Sparkles, Square } from "lucide-react";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
//#region src/components/agent-chat.tsx
|
|
19
|
+
/**
|
|
20
|
+
* AgentChat — the chat surface an extension's own agent runs in.
|
|
21
|
+
*
|
|
22
|
+
* §17 of how-to-create-a-cortena-extension, audit rule P-25. Moved here from
|
|
23
|
+
* `cortenaweb/components/chat/**` so there is one chat and not two: a cut-down
|
|
24
|
+
* widget diverges on approvals, A2UI and MCP Apps within one release, and every
|
|
25
|
+
* fix then has to be made twice. cortenaweb adopts this component as a
|
|
26
|
+
* follow-up; until then the two are kept in step by hand.
|
|
27
|
+
*
|
|
28
|
+
* Everything comes from the `AgentChatClient` prop — nothing here knows about
|
|
29
|
+
* HTTP, AG-UI or a store — which is what lets `test/agent-chat.test.tsx` drive
|
|
30
|
+
* the whole surface from a fake that replays scripted event sequences.
|
|
31
|
+
*
|
|
32
|
+
* Not in this package, on purpose: the MCP App frame. Rendering an extension's
|
|
33
|
+
* own screen means a sandboxed iframe, a `srcdoc` CSP, a JSON-RPC postMessage
|
|
34
|
+
* bridge and a `tools/invoke` proxy holding a credential — a host concern, not
|
|
35
|
+
* a component one. `renderToolCall` is the seam: return a node for a tool call
|
|
36
|
+
* and it replaces the card, return nothing and the default card is drawn.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Should the list jump back to the bottom after its content grew?
|
|
40
|
+
*
|
|
41
|
+
* Judged against the height BEFORE the growth. Measuring afterwards asks "is
|
|
42
|
+
* the newest content already visible", which is false by construction for any
|
|
43
|
+
* append taller than the threshold — a paragraph, a code block, an image — so
|
|
44
|
+
* the re-pin skipped exactly when it was needed.
|
|
45
|
+
*/
|
|
46
|
+
function shouldStickToBottom(params) {
|
|
47
|
+
const { prevScrollHeight, scrollTop, clientHeight, threshold = 150 } = params;
|
|
48
|
+
if (prevScrollHeight <= 0) return true;
|
|
49
|
+
return prevScrollHeight - scrollTop - clientHeight < threshold;
|
|
50
|
+
}
|
|
51
|
+
function AgentThinking({ text, defaultOpen, streaming }) {
|
|
52
|
+
const [open, setOpen] = React.useState(defaultOpen ?? false);
|
|
53
|
+
if (!text) return null;
|
|
54
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
55
|
+
"data-slot": "agent-chat-thinking",
|
|
56
|
+
className: "mt-1",
|
|
57
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
58
|
+
type: "button",
|
|
59
|
+
"aria-expanded": open,
|
|
60
|
+
onClick: () => setOpen((v) => !v),
|
|
61
|
+
className: cn("inline-flex items-center gap-1 rounded-[var(--ds-radius-sm)] px-1 py-0.5", "text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]", "transition-colors duration-[var(--ds-duration-fast)] hover:text-[color:var(--ds-foreground)]"),
|
|
62
|
+
children: [open ? /* @__PURE__ */ jsx(ChevronDown, {
|
|
63
|
+
className: "size-3",
|
|
64
|
+
"aria-hidden": true
|
|
65
|
+
}) : /* @__PURE__ */ jsx(ChevronRight, {
|
|
66
|
+
className: "size-3",
|
|
67
|
+
"aria-hidden": true
|
|
68
|
+
}), streaming ? "Thinking…" : "Chain of thought"]
|
|
69
|
+
}), open ? /* @__PURE__ */ jsx("div", {
|
|
70
|
+
"data-slot": "agent-chat-thinking-body",
|
|
71
|
+
className: cn("mt-1 max-h-64 overflow-auto rounded-[var(--ds-radius-sm)] border border-[var(--ds-border-subtle)]", "bg-[var(--ds-muted)] px-3 py-2 whitespace-pre-wrap", "text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]"),
|
|
72
|
+
children: text
|
|
73
|
+
}) : null]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Every block of one message, in arrival order.
|
|
78
|
+
*
|
|
79
|
+
* A block carrying a transport `error` — an unclosed fence, a body that did not
|
|
80
|
+
* parse — gets an error card rather than being dropped. A block that is merely
|
|
81
|
+
* malformed inside is the renderer's error to draw, and it draws the parts that
|
|
82
|
+
* were fine.
|
|
83
|
+
*/
|
|
84
|
+
function AgentA2UIBlocks({ blocks, streaming, onAction }) {
|
|
85
|
+
if (!blocks || blocks.length === 0) return null;
|
|
86
|
+
return /* @__PURE__ */ jsx("div", {
|
|
87
|
+
className: "mt-2 flex flex-col gap-3",
|
|
88
|
+
"data-slot": "agent-chat-a2ui",
|
|
89
|
+
children: blocks.map((block, index) => /* @__PURE__ */ jsxs("div", { children: [block.error ? /* @__PURE__ */ jsx(A2UIErrorCard, {
|
|
90
|
+
title: "The agent's UI did not arrive complete",
|
|
91
|
+
detail: block.error
|
|
92
|
+
}) : null, block.messages.length > 0 ? /* @__PURE__ */ jsx(A2UIRenderer, {
|
|
93
|
+
message: block.messages,
|
|
94
|
+
...streaming || !onAction ? {} : { onAction }
|
|
95
|
+
}) : null] }, `${block.surfaceId}:${index}`))
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function AssistantAvatar() {
|
|
99
|
+
return /* @__PURE__ */ jsx("span", {
|
|
100
|
+
"data-slot": "agent-chat-avatar",
|
|
101
|
+
"aria-hidden": true,
|
|
102
|
+
className: cn("grid size-7 shrink-0 place-items-center rounded-[var(--ds-radius-sm)]", "border border-[var(--ds-ring)] bg-[var(--ds-primary-soft)] text-[color:var(--ds-primary)]"),
|
|
103
|
+
children: /* @__PURE__ */ jsx(Sparkles, {
|
|
104
|
+
className: "size-3.5",
|
|
105
|
+
"aria-hidden": true
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function AgentMessageBubble({ message, agentName, streaming, onAction }) {
|
|
110
|
+
if (isHiddenMessage(message)) return null;
|
|
111
|
+
if (message.isError) return /* @__PURE__ */ jsx("div", {
|
|
112
|
+
"data-slot": "agent-chat-message",
|
|
113
|
+
"data-role": "error",
|
|
114
|
+
children: /* @__PURE__ */ jsx(ErrorBanner, { message: message.text })
|
|
115
|
+
});
|
|
116
|
+
if (message.role === "user") return /* @__PURE__ */ jsx("div", {
|
|
117
|
+
"data-slot": "agent-chat-message",
|
|
118
|
+
"data-role": "user",
|
|
119
|
+
className: "flex w-full justify-end",
|
|
120
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
121
|
+
className: cn("max-w-[85%] rounded-[var(--ds-radius-lg)] border border-[var(--ds-border-subtle)]", "bg-[var(--ds-popover)] px-3.5 py-2 text-[color:var(--ds-foreground)]"),
|
|
122
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
123
|
+
compact: true,
|
|
124
|
+
children: message.text
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
129
|
+
"data-slot": "agent-chat-message",
|
|
130
|
+
"data-role": "assistant",
|
|
131
|
+
className: "flex w-full gap-3",
|
|
132
|
+
children: [/* @__PURE__ */ jsx(AssistantAvatar, {}), /* @__PURE__ */ jsxs("div", {
|
|
133
|
+
className: "min-w-0 flex-1",
|
|
134
|
+
children: [
|
|
135
|
+
/* @__PURE__ */ jsx("span", {
|
|
136
|
+
className: cn("text-[length:var(--ds-text-caption)] font-semibold", "text-[color:var(--ds-foreground)]"),
|
|
137
|
+
children: agentName
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ jsx(AgentThinking, { text: message.thinkingText ?? "" }),
|
|
140
|
+
message.text ? /* @__PURE__ */ jsx("div", {
|
|
141
|
+
className: "mt-1",
|
|
142
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
143
|
+
compact: true,
|
|
144
|
+
children: message.text
|
|
145
|
+
})
|
|
146
|
+
}) : null,
|
|
147
|
+
/* @__PURE__ */ jsx(AgentA2UIBlocks, {
|
|
148
|
+
blocks: message.a2ui,
|
|
149
|
+
...streaming ? { streaming } : {},
|
|
150
|
+
...onAction ? { onAction } : {}
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
})]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The assistant bubble while a turn is live.
|
|
158
|
+
*
|
|
159
|
+
* The filter chain is deliberately not applied: streaming text is
|
|
160
|
+
* agent-authored prose in progress, and the filters are about finished
|
|
161
|
+
* messages. Markdown is not processed either — partially arrived source
|
|
162
|
+
* (`**bo`) looks worse than its raw form — so the streaming view is plain text
|
|
163
|
+
* under `whitespace-pre-wrap` and `Markdown` takes over on `final`.
|
|
164
|
+
*/
|
|
165
|
+
function AgentStreamingBubble({ agentName, text, thinkingText, a2ui }) {
|
|
166
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
167
|
+
"data-slot": "agent-chat-streaming",
|
|
168
|
+
className: "flex w-full gap-3",
|
|
169
|
+
children: [/* @__PURE__ */ jsx(AssistantAvatar, {}), /* @__PURE__ */ jsxs("div", {
|
|
170
|
+
className: "min-w-0 flex-1",
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ jsx("span", {
|
|
173
|
+
className: cn("text-[length:var(--ds-text-caption)] font-semibold", "text-[color:var(--ds-foreground)]"),
|
|
174
|
+
children: agentName
|
|
175
|
+
}),
|
|
176
|
+
thinkingText ? /* @__PURE__ */ jsx(AgentThinking, {
|
|
177
|
+
text: thinkingText,
|
|
178
|
+
defaultOpen: true,
|
|
179
|
+
streaming: true
|
|
180
|
+
}) : null,
|
|
181
|
+
text ? /* @__PURE__ */ jsx("div", {
|
|
182
|
+
"data-slot": "agent-chat-streaming-text",
|
|
183
|
+
className: "mt-1 break-words whitespace-pre-wrap text-[color:var(--ds-foreground)]",
|
|
184
|
+
children: text
|
|
185
|
+
}) : null,
|
|
186
|
+
/* @__PURE__ */ jsx(AgentA2UIBlocks, {
|
|
187
|
+
blocks: a2ui,
|
|
188
|
+
streaming: true
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
})]
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** A `ui://` resource in a tool result is an MCP App: the extension's own screen. */
|
|
195
|
+
function readMcpAppUri(output) {
|
|
196
|
+
return (typeof output === "string" ? output : output === void 0 ? "" : JSON.stringify(output)).match(/"uri"\s*:\s*"(ui:\/\/[^"]+)"/)?.[1] ?? null;
|
|
197
|
+
}
|
|
198
|
+
function summarise(input) {
|
|
199
|
+
if (input === void 0 || input === null) return "";
|
|
200
|
+
if (typeof input === "string") return input;
|
|
201
|
+
const record = input;
|
|
202
|
+
for (const key of [
|
|
203
|
+
"command",
|
|
204
|
+
"cmd",
|
|
205
|
+
"query",
|
|
206
|
+
"path",
|
|
207
|
+
"url",
|
|
208
|
+
"name"
|
|
209
|
+
]) {
|
|
210
|
+
const value = record[key];
|
|
211
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
212
|
+
}
|
|
213
|
+
const json = JSON.stringify(input);
|
|
214
|
+
return json.length > 120 ? `${json.slice(0, 117)}…` : json;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The default card. It shows the tool's name, its input and its output — the
|
|
218
|
+
* input being the part that was missing for the whole of the AG-UI path,
|
|
219
|
+
* because the bridge had no `TOOL_CALL_ARGS` case.
|
|
220
|
+
*/
|
|
221
|
+
function AgentToolCard({ entry }) {
|
|
222
|
+
const [open, setOpen] = React.useState(false);
|
|
223
|
+
const summary = summarise(entry.input);
|
|
224
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
225
|
+
"data-slot": "agent-tool-card",
|
|
226
|
+
"data-tool": entry.name,
|
|
227
|
+
"data-status": entry.status,
|
|
228
|
+
className: cn("overflow-hidden rounded-[var(--ds-radius-sm)] border border-[var(--ds-border-subtle)]", "bg-[var(--ds-muted)]"),
|
|
229
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
230
|
+
type: "button",
|
|
231
|
+
"aria-expanded": open,
|
|
232
|
+
onClick: () => setOpen((v) => !v),
|
|
233
|
+
className: cn("flex w-full items-center gap-2.5 px-3 py-2 text-left", "transition-colors duration-[var(--ds-duration-fast)] hover:bg-[var(--ds-hover)]"),
|
|
234
|
+
children: [
|
|
235
|
+
/* @__PURE__ */ jsx("span", {
|
|
236
|
+
className: "inline-flex size-3.5 shrink-0 items-center justify-center",
|
|
237
|
+
children: entry.status === "running" ? /* @__PURE__ */ jsx(Spinner, { size: 14 }) : /* @__PURE__ */ jsx("span", {
|
|
238
|
+
"aria-hidden": true,
|
|
239
|
+
className: cn("size-2 rounded-[var(--ds-radius-full)]", entry.status === "error" ? "bg-[var(--ds-destructive)]" : "bg-[var(--ds-success)]")
|
|
240
|
+
})
|
|
241
|
+
}),
|
|
242
|
+
/* @__PURE__ */ jsx("span", {
|
|
243
|
+
className: "font-mono text-[length:var(--ds-text-caption)] text-[color:var(--ds-primary)]",
|
|
244
|
+
children: entry.name
|
|
245
|
+
}),
|
|
246
|
+
summary ? /* @__PURE__ */ jsx("span", {
|
|
247
|
+
"data-slot": "agent-tool-card-input",
|
|
248
|
+
className: "min-w-0 truncate text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
249
|
+
children: summary
|
|
250
|
+
}) : null,
|
|
251
|
+
/* @__PURE__ */ jsx("span", {
|
|
252
|
+
className: "ml-auto shrink-0 text-[color:var(--ds-text-tertiary)]",
|
|
253
|
+
children: open ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-3" })
|
|
254
|
+
})
|
|
255
|
+
]
|
|
256
|
+
}), open ? /* @__PURE__ */ jsxs("div", {
|
|
257
|
+
className: "border-t border-[var(--ds-border-subtle)] bg-[var(--ds-background)] px-3 py-2",
|
|
258
|
+
children: [entry.input !== void 0 ? /* @__PURE__ */ jsx("pre", {
|
|
259
|
+
"data-slot": "agent-tool-card-args",
|
|
260
|
+
className: "max-h-40 overflow-auto font-mono text-[length:var(--ds-text-caption-sm)] whitespace-pre-wrap text-[color:var(--ds-muted-foreground)]",
|
|
261
|
+
children: typeof entry.input === "string" ? entry.input : JSON.stringify(entry.input, null, 2)
|
|
262
|
+
}) : null, entry.output !== void 0 && entry.output !== null ? /* @__PURE__ */ jsx("pre", {
|
|
263
|
+
className: "mt-2 max-h-40 overflow-auto font-mono text-[length:var(--ds-text-caption-sm)] whitespace-pre-wrap text-[color:var(--ds-muted-foreground)]",
|
|
264
|
+
children: typeof entry.output === "string" ? entry.output : JSON.stringify(entry.output, null, 2)
|
|
265
|
+
}) : null]
|
|
266
|
+
}) : null]
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* `exec` as a render-and-wait card: the run is paused on this decision.
|
|
271
|
+
*
|
|
272
|
+
* The three answers are the three cortenacore accepts. "Always allow" is not a
|
|
273
|
+
* nicety — without it the same command is asked about on every turn, and a user
|
|
274
|
+
* who has to answer the same question repeatedly stops reading the question.
|
|
275
|
+
*/
|
|
276
|
+
function AgentApprovalCard({ request, onResolve }) {
|
|
277
|
+
const command = typeof request.args.command === "string" ? request.args.command : request.toolName;
|
|
278
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
279
|
+
"data-slot": "agent-approval-card",
|
|
280
|
+
role: "group",
|
|
281
|
+
"aria-label": "Command approval required",
|
|
282
|
+
className: cn("flex flex-col gap-3 rounded-[var(--ds-radius-md)] border border-[var(--ds-border)]", "bg-[var(--ds-card)] p-3"),
|
|
283
|
+
children: [
|
|
284
|
+
/* @__PURE__ */ jsxs("div", {
|
|
285
|
+
className: "flex items-center gap-2",
|
|
286
|
+
children: [/* @__PURE__ */ jsx(ShieldCheck, {
|
|
287
|
+
className: "size-4 text-[color:var(--ds-primary)]",
|
|
288
|
+
"aria-hidden": true
|
|
289
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
290
|
+
className: "text-[length:var(--ds-text-body-sm)] font-medium text-[color:var(--ds-foreground)]",
|
|
291
|
+
children: "Approval required"
|
|
292
|
+
})]
|
|
293
|
+
}),
|
|
294
|
+
/* @__PURE__ */ jsx("pre", {
|
|
295
|
+
className: "max-h-32 overflow-auto rounded-[var(--ds-radius-sm)] bg-[var(--ds-muted)] p-2 font-mono text-[length:var(--ds-text-caption)] whitespace-pre-wrap text-[color:var(--ds-foreground)]",
|
|
296
|
+
children: command
|
|
297
|
+
}),
|
|
298
|
+
request.error ? /* @__PURE__ */ jsx("p", {
|
|
299
|
+
"data-slot": "agent-approval-error",
|
|
300
|
+
role: "alert",
|
|
301
|
+
className: "text-[length:var(--ds-text-caption)] text-[color:var(--ds-destructive)]",
|
|
302
|
+
children: request.error
|
|
303
|
+
}) : null,
|
|
304
|
+
/* @__PURE__ */ jsxs("div", {
|
|
305
|
+
className: "flex flex-wrap items-center gap-2",
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
308
|
+
size: "sm",
|
|
309
|
+
onClick: () => onResolve(request.id, "allow-once"),
|
|
310
|
+
children: [/* @__PURE__ */ jsx(ShieldCheck, { "aria-hidden": true }), "Approve"]
|
|
311
|
+
}),
|
|
312
|
+
/* @__PURE__ */ jsx(Button, {
|
|
313
|
+
size: "sm",
|
|
314
|
+
variant: "outline",
|
|
315
|
+
onClick: () => onResolve(request.id, "allow-always"),
|
|
316
|
+
children: "Always allow"
|
|
317
|
+
}),
|
|
318
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
319
|
+
size: "sm",
|
|
320
|
+
variant: "destructive",
|
|
321
|
+
onClick: () => onResolve(request.id, "deny"),
|
|
322
|
+
children: [/* @__PURE__ */ jsx(ShieldX, { "aria-hidden": true }), "Deny"]
|
|
323
|
+
})
|
|
324
|
+
]
|
|
325
|
+
})
|
|
326
|
+
]
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attachmentsSlot, renderToolCall, headerSlot, className, ...props }) {
|
|
330
|
+
const chat = useAgentChat({
|
|
331
|
+
client,
|
|
332
|
+
storageScope,
|
|
333
|
+
...ephemeral === void 0 ? {} : { ephemeral },
|
|
334
|
+
...thinking === void 0 ? {} : { thinking }
|
|
335
|
+
});
|
|
336
|
+
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
337
|
+
const [value, setValue] = React.useState("");
|
|
338
|
+
const scrollRef = React.useRef(null);
|
|
339
|
+
const contentRef = React.useRef(null);
|
|
340
|
+
const prevHeightRef = React.useRef(0);
|
|
341
|
+
/**
|
|
342
|
+
* Re-pin to the bottom whenever the content grows or the viewport shrinks.
|
|
343
|
+
* A message keeps growing after it mounts — markdown settles, the tool strip
|
|
344
|
+
* appears between the list and the composer — and every one of those leaves
|
|
345
|
+
* the newest content under the composer until the reader scrolls by hand.
|
|
346
|
+
*
|
|
347
|
+
* Jumping rather than smooth-scrolling: this fires many times a second during
|
|
348
|
+
* streaming, and queued smooth animations fight each other and lose.
|
|
349
|
+
*/
|
|
350
|
+
React.useEffect(() => {
|
|
351
|
+
const scrollEl = scrollRef.current;
|
|
352
|
+
const contentEl = contentRef.current;
|
|
353
|
+
if (!scrollEl || !contentEl || typeof ResizeObserver === "undefined") return;
|
|
354
|
+
const observer = new ResizeObserver(() => {
|
|
355
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollEl;
|
|
356
|
+
const stick = shouldStickToBottom({
|
|
357
|
+
prevScrollHeight: prevHeightRef.current,
|
|
358
|
+
scrollTop,
|
|
359
|
+
clientHeight
|
|
360
|
+
});
|
|
361
|
+
prevHeightRef.current = scrollHeight;
|
|
362
|
+
if (stick) scrollEl.scrollTop = scrollEl.scrollHeight;
|
|
363
|
+
});
|
|
364
|
+
observer.observe(contentEl);
|
|
365
|
+
observer.observe(scrollEl);
|
|
366
|
+
return () => observer.disconnect();
|
|
367
|
+
}, []);
|
|
368
|
+
const submit = React.useCallback(() => {
|
|
369
|
+
const trimmed = value.trim();
|
|
370
|
+
if (!trimmed || chat.streaming) return;
|
|
371
|
+
chat.send(trimmed);
|
|
372
|
+
setValue("");
|
|
373
|
+
}, [chat, value]);
|
|
374
|
+
const onKeyDown = React.useCallback((event) => {
|
|
375
|
+
if (event.key === "Escape" && chat.streaming) {
|
|
376
|
+
event.preventDefault();
|
|
377
|
+
event.stopPropagation();
|
|
378
|
+
chat.abort();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
submit();
|
|
384
|
+
}
|
|
385
|
+
}, [chat, submit]);
|
|
386
|
+
const toolEntries = chat.toolOrder.map((id) => chat.tools[id]).filter((entry) => entry !== void 0);
|
|
387
|
+
const showTools = toolEntries.length > 0 || chat.approvals.length > 0;
|
|
388
|
+
const visible = chat.messages.filter((message) => !isHiddenMessage(message));
|
|
389
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
390
|
+
"data-slot": "agent-chat",
|
|
391
|
+
className: cn("flex h-full min-h-0 w-full flex-col bg-[var(--ds-background)]", className),
|
|
392
|
+
...props,
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsxs("div", {
|
|
395
|
+
"data-slot": "agent-chat-header",
|
|
396
|
+
className: cn("flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2"),
|
|
397
|
+
children: [
|
|
398
|
+
/* @__PURE__ */ jsx(Button, {
|
|
399
|
+
size: "sm",
|
|
400
|
+
variant: "ghost",
|
|
401
|
+
"data-slot": "agent-chat-sessions-trigger",
|
|
402
|
+
"aria-expanded": drawerOpen,
|
|
403
|
+
onClick: () => {
|
|
404
|
+
setDrawerOpen((open) => !open);
|
|
405
|
+
chat.refreshSessions();
|
|
406
|
+
},
|
|
407
|
+
children: chat.sessions.length === 1 ? "1 session" : `${chat.sessions.length} sessions`
|
|
408
|
+
}),
|
|
409
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
410
|
+
size: "sm",
|
|
411
|
+
variant: "ghost",
|
|
412
|
+
"data-slot": "agent-chat-new-session",
|
|
413
|
+
"aria-label": "New session",
|
|
414
|
+
onClick: () => {
|
|
415
|
+
chat.newSession();
|
|
416
|
+
setDrawerOpen(false);
|
|
417
|
+
},
|
|
418
|
+
children: [/* @__PURE__ */ jsx(MessageSquarePlus, { "aria-hidden": true }), "New"]
|
|
419
|
+
}),
|
|
420
|
+
/* @__PURE__ */ jsx("div", {
|
|
421
|
+
className: "ml-auto flex items-center gap-1",
|
|
422
|
+
children: headerSlot
|
|
423
|
+
})
|
|
424
|
+
]
|
|
425
|
+
}),
|
|
426
|
+
drawerOpen ? /* @__PURE__ */ jsx(AgentSessionDrawer, {
|
|
427
|
+
sessions: chat.sessions,
|
|
428
|
+
current: chat.sessionKey,
|
|
429
|
+
loading: chat.loadingSessions,
|
|
430
|
+
onResume: (key) => {
|
|
431
|
+
chat.resumeSession(key);
|
|
432
|
+
setDrawerOpen(false);
|
|
433
|
+
}
|
|
434
|
+
}) : null,
|
|
435
|
+
/* @__PURE__ */ jsx("div", {
|
|
436
|
+
ref: scrollRef,
|
|
437
|
+
"data-slot": "agent-chat-scroll",
|
|
438
|
+
className: "min-h-0 flex-1 overflow-x-hidden overflow-y-auto",
|
|
439
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
440
|
+
ref: contentRef,
|
|
441
|
+
role: "log",
|
|
442
|
+
"aria-live": "polite",
|
|
443
|
+
"aria-label": `${agentName} chat`,
|
|
444
|
+
className: "flex min-w-0 flex-col gap-5 px-4 py-4",
|
|
445
|
+
children: [
|
|
446
|
+
chat.error ? /* @__PURE__ */ jsx(ErrorBanner, { message: chat.error }) : null,
|
|
447
|
+
chat.hasMoreHistory ? /* @__PURE__ */ jsx("div", {
|
|
448
|
+
className: "flex justify-center",
|
|
449
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
450
|
+
size: "sm",
|
|
451
|
+
variant: "ghost",
|
|
452
|
+
"data-slot": "agent-chat-load-older",
|
|
453
|
+
disabled: chat.loadingHistory,
|
|
454
|
+
onClick: chat.loadOlder,
|
|
455
|
+
children: chat.loadingHistory ? "Loading…" : "Load older messages"
|
|
456
|
+
})
|
|
457
|
+
}) : null,
|
|
458
|
+
visible.length === 0 && !chat.streaming ? /* @__PURE__ */ jsxs("p", {
|
|
459
|
+
"data-slot": "agent-chat-empty",
|
|
460
|
+
className: "text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]",
|
|
461
|
+
children: [
|
|
462
|
+
"Ask ",
|
|
463
|
+
agentName,
|
|
464
|
+
" anything about this screen."
|
|
465
|
+
]
|
|
466
|
+
}) : null,
|
|
467
|
+
visible.map((message) => /* @__PURE__ */ jsx(AgentMessageBubble, {
|
|
468
|
+
message,
|
|
469
|
+
agentName,
|
|
470
|
+
streaming: chat.streaming,
|
|
471
|
+
onAction: chat.sendAction
|
|
472
|
+
}, message.id)),
|
|
473
|
+
chat.streaming ? /* @__PURE__ */ jsx(AgentStreamingBubble, {
|
|
474
|
+
agentName,
|
|
475
|
+
text: chat.streamText,
|
|
476
|
+
thinkingText: chat.streamThinkingText,
|
|
477
|
+
a2ui: chat.streamA2ui
|
|
478
|
+
}) : null
|
|
479
|
+
]
|
|
480
|
+
})
|
|
481
|
+
}),
|
|
482
|
+
showTools ? /* @__PURE__ */ jsxs("div", {
|
|
483
|
+
"data-slot": "agent-chat-tools",
|
|
484
|
+
className: "flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2",
|
|
485
|
+
children: [
|
|
486
|
+
toolEntries.length > 0 ? /* @__PURE__ */ jsx(Badge, {
|
|
487
|
+
variant: "neutral",
|
|
488
|
+
className: "w-fit",
|
|
489
|
+
children: toolEntries.length === 1 ? "1 step" : `${toolEntries.length} steps`
|
|
490
|
+
}) : null,
|
|
491
|
+
toolEntries.map((entry) => {
|
|
492
|
+
const custom = renderToolCall?.({
|
|
493
|
+
entry,
|
|
494
|
+
mcpAppUri: readMcpAppUri(entry.output)
|
|
495
|
+
});
|
|
496
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: custom ?? /* @__PURE__ */ jsx(AgentToolCard, { entry }) }, entry.id);
|
|
497
|
+
}),
|
|
498
|
+
chat.approvals.map((request) => /* @__PURE__ */ jsx(AgentApprovalCard, {
|
|
499
|
+
request,
|
|
500
|
+
onResolve: chat.resolveApproval
|
|
501
|
+
}, request.id))
|
|
502
|
+
]
|
|
503
|
+
}) : null,
|
|
504
|
+
attachmentsSlot ? /* @__PURE__ */ jsx("div", {
|
|
505
|
+
"data-slot": "agent-chat-attachments",
|
|
506
|
+
className: "shrink-0 px-3",
|
|
507
|
+
children: attachmentsSlot
|
|
508
|
+
}) : null,
|
|
509
|
+
/* @__PURE__ */ jsxs("div", {
|
|
510
|
+
"data-slot": "agent-chat-composer",
|
|
511
|
+
className: "flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3",
|
|
512
|
+
children: [/* @__PURE__ */ jsx(Textarea, {
|
|
513
|
+
"data-slot": "agent-chat-input",
|
|
514
|
+
"aria-label": `Message ${agentName}`,
|
|
515
|
+
placeholder: `Message ${agentName}…`,
|
|
516
|
+
rows: 1,
|
|
517
|
+
className: "max-h-40 min-h-9 resize-none",
|
|
518
|
+
value,
|
|
519
|
+
onChange: (event) => setValue(event.target.value),
|
|
520
|
+
onKeyDown
|
|
521
|
+
}), chat.streaming ? /* @__PURE__ */ jsx(Button, {
|
|
522
|
+
size: "icon",
|
|
523
|
+
variant: "outline",
|
|
524
|
+
"data-slot": "agent-chat-stop",
|
|
525
|
+
"aria-label": "Stop the run",
|
|
526
|
+
onClick: chat.abort,
|
|
527
|
+
children: /* @__PURE__ */ jsx(Square, { "aria-hidden": true })
|
|
528
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
529
|
+
size: "icon",
|
|
530
|
+
"data-slot": "agent-chat-send",
|
|
531
|
+
"aria-label": "Send",
|
|
532
|
+
disabled: !value.trim(),
|
|
533
|
+
onClick: submit,
|
|
534
|
+
children: /* @__PURE__ */ jsx(Send, { "aria-hidden": true })
|
|
535
|
+
})]
|
|
536
|
+
})
|
|
537
|
+
]
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Every session of this agent, resumable from any window.
|
|
542
|
+
*
|
|
543
|
+
* The list is not this window's history: a session started in another window is
|
|
544
|
+
* here and can be resumed, which is the difference between "sessions are per
|
|
545
|
+
* window" (which key a window starts on) and "sessions are private to a window"
|
|
546
|
+
* (which they are not).
|
|
547
|
+
*/
|
|
548
|
+
function AgentSessionDrawer({ sessions, current, loading, onResume }) {
|
|
549
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
550
|
+
"data-slot": "agent-chat-sessions",
|
|
551
|
+
className: cn("max-h-56 shrink-0 overflow-y-auto border-b border-[var(--ds-border-subtle)]", "bg-[var(--ds-card)] px-2 py-1.5"),
|
|
552
|
+
children: [
|
|
553
|
+
loading && sessions.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
554
|
+
className: "px-2 py-1 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
555
|
+
children: "Loading sessions…"
|
|
556
|
+
}) : null,
|
|
557
|
+
!loading && sessions.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
558
|
+
className: "px-2 py-1 text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
559
|
+
children: "No earlier sessions."
|
|
560
|
+
}) : null,
|
|
561
|
+
sessions.map((session) => /* @__PURE__ */ jsx("button", {
|
|
562
|
+
type: "button",
|
|
563
|
+
"data-slot": "agent-chat-session",
|
|
564
|
+
"data-current": session.key === current ? "" : void 0,
|
|
565
|
+
onClick: () => onResume(session.key),
|
|
566
|
+
className: cn("flex w-full items-center gap-2 rounded-[var(--ds-radius-sm)] px-2 py-1.5 text-left", "text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-foreground)]", "transition-colors duration-[var(--ds-duration-fast)] hover:bg-[var(--ds-hover)]", session.key === current && "bg-[var(--ds-primary-soft)]"),
|
|
567
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
568
|
+
className: "min-w-0 truncate",
|
|
569
|
+
children: sessionTitle(session)
|
|
570
|
+
})
|
|
571
|
+
}, session.key))
|
|
572
|
+
]
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
//#endregion
|
|
576
|
+
export { AgentA2UIBlocks, AgentApprovalCard, AgentChat, AgentMessageBubble, AgentSessionDrawer, AgentStreamingBubble, AgentThinking, AgentToolCard, readMcpAppUri, shouldStickToBottom };
|
|
577
|
+
|
|
578
|
+
//# sourceMappingURL=agent-chat.js.map
|