cortena-ui 1.4.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/LICENSE +7 -0
  3. package/README.md +235 -3
  4. package/dist/a2ui/views.js +2 -2
  5. package/dist/agent-chat/a2ui-block.d.ts +60 -0
  6. package/dist/agent-chat/a2ui-block.js +69 -0
  7. package/dist/agent-chat/a2ui-block.js.map +1 -0
  8. package/dist/agent-chat/agui-client.d.ts +40 -0
  9. package/dist/agent-chat/agui-client.js +251 -0
  10. package/dist/agent-chat/agui-client.js.map +1 -0
  11. package/dist/agent-chat/bridge.d.ts +109 -0
  12. package/dist/agent-chat/bridge.js +353 -0
  13. package/dist/agent-chat/bridge.js.map +1 -0
  14. package/dist/agent-chat/session.d.ts +79 -0
  15. package/dist/agent-chat/session.js +391 -0
  16. package/dist/agent-chat/session.js.map +1 -0
  17. package/dist/agent-chat/step-label.d.ts +99 -0
  18. package/dist/agent-chat/step-label.js +116 -0
  19. package/dist/agent-chat/step-label.js.map +1 -0
  20. package/dist/agent-chat/store.d.ts +102 -0
  21. package/dist/agent-chat/store.js +876 -0
  22. package/dist/agent-chat/store.js.map +1 -0
  23. package/dist/agent-chat/types.d.ts +277 -0
  24. package/dist/agent-chat/types.js +17 -0
  25. package/dist/agent-chat/types.js.map +1 -0
  26. package/dist/agent-chat.d.ts +11 -0
  27. package/dist/agent-chat.js +11 -0
  28. package/dist/components/admin-permissions/admin-permissions.d.ts +66 -0
  29. package/dist/components/admin-permissions/admin-permissions.js +101 -0
  30. package/dist/components/admin-permissions/admin-permissions.js.map +1 -0
  31. package/dist/components/admin-permissions/context.d.ts +70 -0
  32. package/dist/components/admin-permissions/context.js +258 -0
  33. package/dist/components/admin-permissions/context.js.map +1 -0
  34. package/dist/components/admin-permissions/index.d.ts +10 -0
  35. package/dist/components/admin-permissions/licence.d.ts +15 -0
  36. package/dist/components/admin-permissions/licence.js +78 -0
  37. package/dist/components/admin-permissions/licence.js.map +1 -0
  38. package/dist/components/admin-permissions/matrix.d.ts +20 -0
  39. package/dist/components/admin-permissions/matrix.js +191 -0
  40. package/dist/components/admin-permissions/matrix.js.map +1 -0
  41. package/dist/components/admin-permissions/members.d.ts +18 -0
  42. package/dist/components/admin-permissions/members.js +185 -0
  43. package/dist/components/admin-permissions/members.js.map +1 -0
  44. package/dist/components/admin-permissions/role-assignment.d.ts +35 -0
  45. package/dist/components/admin-permissions/role-assignment.js +174 -0
  46. package/dist/components/admin-permissions/role-assignment.js.map +1 -0
  47. package/dist/components/admin-permissions/roles.d.ts +25 -0
  48. package/dist/components/admin-permissions/roles.js +168 -0
  49. package/dist/components/admin-permissions/roles.js.map +1 -0
  50. package/dist/components/admin-permissions/types.d.ts +152 -0
  51. package/dist/components/admin-permissions/types.js +63 -0
  52. package/dist/components/admin-permissions/types.js.map +1 -0
  53. package/dist/components/agent-chat-popup.d.ts +29 -0
  54. package/dist/components/agent-chat-popup.js +188 -0
  55. package/dist/components/agent-chat-popup.js.map +1 -0
  56. package/dist/components/agent-chat.d.ts +163 -0
  57. package/dist/components/agent-chat.js +673 -0
  58. package/dist/components/agent-chat.js.map +1 -0
  59. package/dist/components/app-shell.d.ts +126 -0
  60. package/dist/components/app-shell.js +297 -0
  61. package/dist/components/app-shell.js.map +1 -0
  62. package/dist/components/badge.d.ts +1 -1
  63. package/dist/components/button-link.js +1 -1
  64. package/dist/components/button.d.ts +1 -1
  65. package/dist/components/checkbox.d.ts +1 -1
  66. package/dist/components/combobox.d.ts +1 -1
  67. package/dist/components/combobox.js +1 -1
  68. package/dist/components/consent-screen.d.ts +65 -0
  69. package/dist/components/consent-screen.js +123 -0
  70. package/dist/components/consent-screen.js.map +1 -0
  71. package/dist/components/data-table/data-table.d.ts +15 -1
  72. package/dist/components/data-table/data-table.js +18 -4
  73. package/dist/components/data-table/data-table.js.map +1 -1
  74. package/dist/components/data-table/index.d.ts +4 -4
  75. package/dist/components/data-table/parts.d.ts +27 -3
  76. package/dist/components/data-table/parts.js +175 -55
  77. package/dist/components/data-table/parts.js.map +1 -1
  78. package/dist/components/data-table/types.d.ts +61 -0
  79. package/dist/components/data-table/use-data-table.js +91 -6
  80. package/dist/components/data-table/use-data-table.js.map +1 -1
  81. package/dist/components/data-table/use-server-source.js +119 -28
  82. package/dist/components/data-table/use-server-source.js.map +1 -1
  83. package/dist/components/help-panel.d.ts +131 -0
  84. package/dist/components/help-panel.js +545 -0
  85. package/dist/components/help-panel.js.map +1 -0
  86. package/dist/components/login-screen.d.ts +127 -0
  87. package/dist/components/login-screen.js +339 -0
  88. package/dist/components/login-screen.js.map +1 -0
  89. package/dist/components/session-guard.d.ts +268 -0
  90. package/dist/components/session-guard.js +632 -0
  91. package/dist/components/session-guard.js.map +1 -0
  92. package/dist/components/toast.d.ts +1 -1
  93. package/dist/core.d.ts +5 -1
  94. package/dist/core.js +11 -7
  95. package/dist/data-table.d.ts +13 -4
  96. package/dist/data-table.js +10 -2
  97. package/dist/hooks/use-cortena-theme.js +49 -3
  98. package/dist/hooks/use-cortena-theme.js.map +1 -1
  99. package/dist/index.d.ts +17 -4
  100. package/dist/index.js +21 -8
  101. package/dist/markdown.d.ts +2 -1
  102. package/dist/markdown.js +2 -1
  103. package/package.json +18 -5
  104. package/src/agent-chat/a2ui-block.ts +118 -0
  105. package/src/agent-chat/agui-client.ts +405 -0
  106. package/src/agent-chat/bridge.ts +445 -0
  107. package/src/agent-chat/session.ts +549 -0
  108. package/src/agent-chat/step-label.ts +177 -0
  109. package/src/agent-chat/store.ts +1234 -0
  110. package/src/agent-chat/types.ts +308 -0
  111. package/src/components/admin-permissions/admin-permissions.tsx +130 -0
  112. package/src/components/admin-permissions/context.tsx +376 -0
  113. package/src/components/admin-permissions/index.tsx +32 -0
  114. package/src/components/admin-permissions/licence.tsx +84 -0
  115. package/src/components/admin-permissions/matrix.tsx +257 -0
  116. package/src/components/admin-permissions/members.tsx +204 -0
  117. package/src/components/admin-permissions/role-assignment.tsx +239 -0
  118. package/src/components/admin-permissions/roles.tsx +169 -0
  119. package/src/components/admin-permissions/types.ts +231 -0
  120. package/src/components/agent-chat-popup.tsx +289 -0
  121. package/src/components/agent-chat.tsx +1006 -0
  122. package/src/components/app-shell.tsx +502 -0
  123. package/src/components/consent-screen.tsx +239 -0
  124. package/src/components/data-table/data-table.tsx +36 -0
  125. package/src/components/data-table/index.tsx +6 -1
  126. package/src/components/data-table/parts.tsx +223 -47
  127. package/src/components/data-table/types.ts +68 -0
  128. package/src/components/data-table/use-data-table.ts +152 -4
  129. package/src/components/data-table/use-server-source.ts +150 -12
  130. package/src/components/help-panel.tsx +765 -0
  131. package/src/components/login-screen.tsx +479 -0
  132. package/src/components/session-guard.tsx +1071 -0
  133. package/src/entries/agent-chat.ts +137 -0
  134. package/src/entries/core.ts +8 -0
  135. package/src/entries/data-table.ts +41 -0
  136. package/src/entries/markdown.ts +25 -0
  137. package/src/hooks/use-cortena-theme.ts +63 -4
  138. package/src/index.ts +6 -0
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { AgentChatClient } from "../agent-chat/types.js";
3
+ import { AgentChatProps } from "./agent-chat.js";
4
+ import * as React from "react";
5
+ //#region src/components/agent-chat-popup.d.ts
6
+ export type AgentChatPopupState = "pill" | "panel" | "full";
7
+ export interface AgentChatPopupExtension {
8
+ /** Registered mark id — the same `x-cortena.icon` the shell draws. */
9
+ id: string;
10
+ /** Display name. Also the agent template slug, unless `agentId` overrides it. */
11
+ name: string;
12
+ }
13
+ export interface AgentChatPopupProps extends Omit<AgentChatProps, "agentName" | "storageScope" | "className" | "headerSlot" | "client"> {
14
+ extension: AgentChatPopupExtension;
15
+ /** The transport. Its `agentId` is the template slug the sessions belong to. */
16
+ client: AgentChatClient;
17
+ /**
18
+ * Names the `sessionStorage` slots. Defaults to the extension id, which is
19
+ * what keeps two extensions on one origin out of each other's sessions.
20
+ */
21
+ storageScope?: string;
22
+ /** Start here instead of collapsed; the stored state still wins on reload. */
23
+ defaultState?: AgentChatPopupState;
24
+ className?: string;
25
+ }
26
+ declare function AgentChatPopup({ extension, client, storageScope, defaultState, className, ...chatProps }: AgentChatPopupProps): React.JSX.Element;
27
+ //#endregion
28
+ export { AgentChatPopup };
29
+ //# sourceMappingURL=agent-chat-popup.d.ts.map
@@ -0,0 +1,188 @@
1
+ "use client";
2
+ "use client";
3
+ import { cn } from "../lib/cn.js";
4
+ import { BrandMark } from "./app-shell.js";
5
+ import { Button } from "./button.js";
6
+ import { viewStorageKey, windowSessionStorage } from "../agent-chat/session.js";
7
+ import { AgentChat } from "./agent-chat.js";
8
+ import { Maximize2, Minimize2, X } from "lucide-react";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import * as React from "react";
11
+ //#region src/components/agent-chat-popup.tsx
12
+ /**
13
+ * AgentChatPopup — the extension's own agent, in the corner of every screen.
14
+ *
15
+ * §17 of how-to-create-a-cortena-extension, audit rule P-25. Mounted in
16
+ * `AppShell`'s `agentSlot`, so it stacks above the theme/help cluster rather
17
+ * than beside it and no extension positions a floating control itself.
18
+ *
19
+ * ```tsx
20
+ * <AppShell
21
+ * extension={{ id: "tasks", name: "Tasks" }}
22
+ * user={user}
23
+ * agentSlot={
24
+ * <AgentChatPopup
25
+ * extension={{ id: "tasks", name: "Tasks" }}
26
+ * client={client} // createAguiAgentChatClient({ …, agentId: "tasks" })
27
+ * />
28
+ * }
29
+ * />
30
+ * ```
31
+ *
32
+ * Three states, and the transitions between them are fixed: a collapsed pill,
33
+ * a panel, and full screen. **Cmd/Ctrl+J** toggles between the pill and the
34
+ * panel; **Escape** steps full screen -> panel -> pill, except while a run is
35
+ * streaming, where the composer takes Escape to mean "stop" — the first Escape
36
+ * during a run is nearly always about the run, not about the window.
37
+ *
38
+ * The chat stays mounted in every state, hidden rather than unmounted while
39
+ * collapsed. That is not an optimisation: a run started before the user
40
+ * collapsed the pop-up keeps streaming into it, and the unread dot on the pill
41
+ * is what says so. Unmounting would throw the reply away halfway through.
42
+ *
43
+ * State is remembered per window, in `sessionStorage`, alongside the session
44
+ * key — a second window of the same extension opens its own chat on its own
45
+ * session rather than inheriting this one's.
46
+ */
47
+ /** Cmd on a Mac, Ctrl elsewhere. Documented in README.md. */
48
+ function isToggleShortcut(event) {
49
+ return (event.metaKey || event.ctrlKey) && !event.altKey && event.key.toLowerCase() === "j";
50
+ }
51
+ function AgentChatPopup({ extension, client, storageScope, defaultState = "pill", className, ...chatProps }) {
52
+ const scope = storageScope ?? extension.id;
53
+ const [state, setState] = React.useState(defaultState);
54
+ const [unread, setUnread] = React.useState(false);
55
+ const stateRef = React.useRef(state);
56
+ stateRef.current = state;
57
+ const pillRef = React.useRef(null);
58
+ const panelRef = React.useRef(null);
59
+ /** Whether the user has opened or closed it, as against it starting open. */
60
+ const moved = React.useRef(false);
61
+ React.useEffect(() => {
62
+ try {
63
+ const stored = windowSessionStorage()?.getItem(viewStorageKey(scope));
64
+ if (stored === "pill" || stored === "panel" || stored === "full") setState(stored);
65
+ } catch {}
66
+ }, [scope]);
67
+ const go = React.useCallback((next) => {
68
+ moved.current = true;
69
+ setState(next);
70
+ if (next !== "pill") setUnread(false);
71
+ try {
72
+ windowSessionStorage()?.setItem(viewStorageKey(scope), next);
73
+ } catch {}
74
+ }, [scope]);
75
+ React.useEffect(() => {
76
+ return client.onChat((event) => {
77
+ if (stateRef.current === "pill" && (event.state === "delta" || event.state === "final")) setUnread(true);
78
+ });
79
+ }, [client]);
80
+ React.useEffect(() => {
81
+ const onKeyDown = (event) => {
82
+ if (!isToggleShortcut(event)) return;
83
+ event.preventDefault();
84
+ go(stateRef.current === "pill" ? "panel" : "pill");
85
+ };
86
+ window.addEventListener("keydown", onKeyDown);
87
+ return () => window.removeEventListener("keydown", onKeyDown);
88
+ }, [go]);
89
+ /**
90
+ * Escape steps down one level. It is handled on the panel rather than on the
91
+ * window so it only fires when focus is inside the pop-up, and it arrives
92
+ * here only when the composer did not claim it — the composer stops
93
+ * propagation while a run is streaming, where Escape means "stop the run".
94
+ */
95
+ const onKeyDown = React.useCallback((event) => {
96
+ if (event.key !== "Escape") return;
97
+ event.preventDefault();
98
+ go(stateRef.current === "full" ? "panel" : "pill");
99
+ }, [go]);
100
+ const open = state !== "pill";
101
+ /**
102
+ * Focus follows the pop-up.
103
+ *
104
+ * The panel is `hidden`, not unmounted, so nothing moves focus by itself.
105
+ * Opening left focus on the pill — which is now `hidden`, so the browser
106
+ * resets it to <body> and the next Tab starts at the top of the page rather
107
+ * than in the chat that was just opened. Closing had the mirror problem:
108
+ * focus was inside a subtree that had just become hidden.
109
+ *
110
+ * Only after the user moves it. A pop-up that starts on `panel`, or one
111
+ * restored from `sessionStorage` on load, must not steal focus from the page.
112
+ */
113
+ React.useEffect(() => {
114
+ if (!moved.current) return;
115
+ const frame = requestAnimationFrame(() => {
116
+ if (open) panelRef.current?.querySelector("[data-slot=\"agent-chat-input\"]")?.focus({ preventScroll: true });
117
+ else pillRef.current?.focus({ preventScroll: true });
118
+ });
119
+ return () => cancelAnimationFrame(frame);
120
+ }, [open]);
121
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
122
+ ref: pillRef,
123
+ type: "button",
124
+ "data-slot": "agent-chat-pill",
125
+ "data-state": state,
126
+ "data-unread": unread ? "" : void 0,
127
+ "aria-expanded": open,
128
+ "aria-label": `Ask ${extension.name}`,
129
+ hidden: open,
130
+ onClick: () => go("panel"),
131
+ className: cn("relative inline-flex h-10 items-center gap-2 rounded-[var(--ds-radius-full)] px-3.5", "border border-[var(--ds-border)] bg-[var(--ds-card)] text-[color:var(--ds-foreground)]", "shadow-[var(--ds-shadow-md)] outline-none", "transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "hover:bg-[var(--ds-hover)]", "focus-visible:ring-2 focus-visible:ring-[var(--ds-ring)] focus-visible:ring-offset-2", "focus-visible:ring-offset-[var(--ds-background)]", className),
132
+ children: [
133
+ /* @__PURE__ */ jsx("span", {
134
+ className: "flex size-5 items-center justify-center text-[color:var(--ds-primary)]",
135
+ children: /* @__PURE__ */ jsx(BrandMark, {
136
+ id: extension.id,
137
+ name: extension.name,
138
+ size: 16,
139
+ decorative: true
140
+ })
141
+ }),
142
+ /* @__PURE__ */ jsxs("span", {
143
+ className: "text-[length:var(--ds-text-caption-lg)] font-medium",
144
+ children: ["Ask ", extension.name]
145
+ }),
146
+ unread ? /* @__PURE__ */ jsx("span", {
147
+ "data-slot": "agent-chat-pill-unread",
148
+ "aria-label": "The agent is replying",
149
+ className: "size-2 rounded-[var(--ds-radius-full)] bg-[var(--ds-primary)]"
150
+ }) : null
151
+ ]
152
+ }), /* @__PURE__ */ jsx("div", {
153
+ ref: panelRef,
154
+ "data-slot": "agent-chat-panel",
155
+ "data-state": state,
156
+ role: "dialog",
157
+ "aria-label": `${extension.name} agent`,
158
+ hidden: !open,
159
+ onKeyDown,
160
+ className: cn("fixed z-50 flex flex-col overflow-hidden border border-[var(--ds-border)]", "bg-[var(--ds-card)] shadow-[var(--ds-shadow-lg)]", state === "full" ? "inset-0 rounded-none" : cn("right-6 bottom-6 rounded-[var(--ds-radius-lg)]", "h-[min(40rem,calc(100vh-3rem))] w-[min(26.25rem,calc(100vw-3rem))]")),
161
+ children: /* @__PURE__ */ jsx(AgentChat, {
162
+ client,
163
+ agentName: extension.name,
164
+ storageScope: scope,
165
+ className: "min-h-0 flex-1",
166
+ headerSlot: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
167
+ size: "icon-sm",
168
+ variant: "ghost",
169
+ "data-slot": "agent-chat-expand",
170
+ "aria-label": state === "full" ? "Restore the panel" : "Expand to full screen",
171
+ onClick: () => go(state === "full" ? "panel" : "full"),
172
+ children: state === "full" ? /* @__PURE__ */ jsx(Minimize2, { "aria-hidden": true }) : /* @__PURE__ */ jsx(Maximize2, { "aria-hidden": true })
173
+ }), /* @__PURE__ */ jsx(Button, {
174
+ size: "icon-sm",
175
+ variant: "ghost",
176
+ "data-slot": "agent-chat-collapse",
177
+ "aria-label": "Collapse the agent",
178
+ onClick: () => go("pill"),
179
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
180
+ })] }),
181
+ ...chatProps
182
+ })
183
+ })] });
184
+ }
185
+ //#endregion
186
+ export { AgentChatPopup };
187
+
188
+ //# sourceMappingURL=agent-chat-popup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-chat-popup.js","names":[],"sources":["../../src/components/agent-chat-popup.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * AgentChatPopup — the extension's own agent, in the corner of every screen.\n *\n * §17 of how-to-create-a-cortena-extension, audit rule P-25. Mounted in\n * `AppShell`'s `agentSlot`, so it stacks above the theme/help cluster rather\n * than beside it and no extension positions a floating control itself.\n *\n * ```tsx\n * <AppShell\n * extension={{ id: \"tasks\", name: \"Tasks\" }}\n * user={user}\n * agentSlot={\n * <AgentChatPopup\n * extension={{ id: \"tasks\", name: \"Tasks\" }}\n * client={client} // createAguiAgentChatClient({ …, agentId: \"tasks\" })\n * />\n * }\n * />\n * ```\n *\n * Three states, and the transitions between them are fixed: a collapsed pill,\n * a panel, and full screen. **Cmd/Ctrl+J** toggles between the pill and the\n * panel; **Escape** steps full screen -> panel -> pill, except while a run is\n * streaming, where the composer takes Escape to mean \"stop\" — the first Escape\n * during a run is nearly always about the run, not about the window.\n *\n * The chat stays mounted in every state, hidden rather than unmounted while\n * collapsed. That is not an optimisation: a run started before the user\n * collapsed the pop-up keeps streaming into it, and the unread dot on the pill\n * is what says so. Unmounting would throw the reply away halfway through.\n *\n * State is remembered per window, in `sessionStorage`, alongside the session\n * key — a second window of the same extension opens its own chat on its own\n * session rather than inheriting this one's.\n */\n\nimport { Maximize2, Minimize2, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { BrandMark } from \"@/components/app-shell\";\nimport { Button } from \"@/components/button\";\nimport { AgentChat, type AgentChatProps } from \"@/components/agent-chat\";\nimport { viewStorageKey, windowSessionStorage } from \"@/agent-chat/session\";\nimport type { AgentChatClient } from \"@/agent-chat/types\";\nimport { cn } from \"@/lib/cn\";\n\nexport type AgentChatPopupState = \"pill\" | \"panel\" | \"full\";\n\nexport interface AgentChatPopupExtension {\n /** Registered mark id — the same `x-cortena.icon` the shell draws. */\n id: string;\n /** Display name. Also the agent template slug, unless `agentId` overrides it. */\n name: string;\n}\n\nexport interface AgentChatPopupProps\n extends Omit<\n AgentChatProps,\n \"agentName\" | \"storageScope\" | \"className\" | \"headerSlot\" | \"client\"\n > {\n extension: AgentChatPopupExtension;\n /** The transport. Its `agentId` is the template slug the sessions belong to. */\n client: AgentChatClient;\n /**\n * Names the `sessionStorage` slots. Defaults to the extension id, which is\n * what keeps two extensions on one origin out of each other's sessions.\n */\n storageScope?: string;\n /** Start here instead of collapsed; the stored state still wins on reload. */\n defaultState?: AgentChatPopupState;\n className?: string;\n}\n\n/** Cmd on a Mac, Ctrl elsewhere. Documented in README.md. */\nfunction isToggleShortcut(event: KeyboardEvent): boolean {\n return (event.metaKey || event.ctrlKey) && !event.altKey && event.key.toLowerCase() === \"j\";\n}\n\nfunction AgentChatPopup({\n extension,\n client,\n storageScope,\n defaultState = \"pill\",\n className,\n ...chatProps\n}: AgentChatPopupProps) {\n const scope = storageScope ?? extension.id;\n const [state, setState] = React.useState<AgentChatPopupState>(defaultState);\n const [unread, setUnread] = React.useState(false);\n const stateRef = React.useRef(state);\n stateRef.current = state;\n const pillRef = React.useRef<HTMLButtonElement>(null);\n const panelRef = React.useRef<HTMLDivElement>(null);\n /** Whether the user has opened or closed it, as against it starting open. */\n const moved = React.useRef(false);\n\n // Restore this window's open/collapsed choice. In an effect rather than a\n // lazy initialiser so a server render and the first client render agree.\n React.useEffect(() => {\n try {\n const stored = windowSessionStorage()?.getItem(viewStorageKey(scope));\n if (stored === \"pill\" || stored === \"panel\" || stored === \"full\") {\n setState(stored);\n }\n } catch {\n // A blocked storage just means the pop-up opens collapsed.\n }\n }, [scope]);\n\n const go = React.useCallback(\n (next: AgentChatPopupState) => {\n moved.current = true;\n setState(next);\n if (next !== \"pill\") {\n setUnread(false);\n }\n try {\n windowSessionStorage()?.setItem(viewStorageKey(scope), next);\n } catch {\n // Not remembering the choice is not a reason to refuse the change.\n }\n },\n [scope],\n );\n\n // The unread dot. Subscribed here rather than read out of the chat, because\n // the point is what happened while the surface was not being looked at.\n React.useEffect(() => {\n return client.onChat((event) => {\n if (stateRef.current === \"pill\" && (event.state === \"delta\" || event.state === \"final\")) {\n setUnread(true);\n }\n });\n }, [client]);\n\n React.useEffect(() => {\n const onKeyDown = (event: KeyboardEvent) => {\n if (!isToggleShortcut(event)) {\n return;\n }\n event.preventDefault();\n go(stateRef.current === \"pill\" ? \"panel\" : \"pill\");\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, [go]);\n\n /**\n * Escape steps down one level. It is handled on the panel rather than on the\n * window so it only fires when focus is inside the pop-up, and it arrives\n * here only when the composer did not claim it — the composer stops\n * propagation while a run is streaming, where Escape means \"stop the run\".\n */\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== \"Escape\") {\n return;\n }\n event.preventDefault();\n go(stateRef.current === \"full\" ? \"panel\" : \"pill\");\n },\n [go],\n );\n\n const open = state !== \"pill\";\n\n /**\n * Focus follows the pop-up.\n *\n * The panel is `hidden`, not unmounted, so nothing moves focus by itself.\n * Opening left focus on the pill — which is now `hidden`, so the browser\n * resets it to <body> and the next Tab starts at the top of the page rather\n * than in the chat that was just opened. Closing had the mirror problem:\n * focus was inside a subtree that had just become hidden.\n *\n * Only after the user moves it. A pop-up that starts on `panel`, or one\n * restored from `sessionStorage` on load, must not steal focus from the page.\n */\n React.useEffect(() => {\n if (!moved.current) {\n return;\n }\n const frame = requestAnimationFrame(() => {\n if (open) {\n panelRef.current\n ?.querySelector<HTMLElement>('[data-slot=\"agent-chat-input\"]')\n ?.focus({ preventScroll: true });\n } else {\n pillRef.current?.focus({ preventScroll: true });\n }\n });\n return () => cancelAnimationFrame(frame);\n }, [open]);\n\n return (\n <>\n <button\n ref={pillRef}\n type=\"button\"\n data-slot=\"agent-chat-pill\"\n data-state={state}\n data-unread={unread ? \"\" : undefined}\n aria-expanded={open}\n aria-label={`Ask ${extension.name}`}\n hidden={open}\n onClick={() => go(\"panel\")}\n className={cn(\n \"relative inline-flex h-10 items-center gap-2 rounded-[var(--ds-radius-full)] px-3.5\",\n \"border border-[var(--ds-border)] bg-[var(--ds-card)] text-[color:var(--ds-foreground)]\",\n \"shadow-[var(--ds-shadow-md)] outline-none\",\n \"transition-colors duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"hover:bg-[var(--ds-hover)]\",\n \"focus-visible:ring-2 focus-visible:ring-[var(--ds-ring)] focus-visible:ring-offset-2\",\n \"focus-visible:ring-offset-[var(--ds-background)]\",\n className,\n )}\n >\n <span className=\"flex size-5 items-center justify-center text-[color:var(--ds-primary)]\">\n <BrandMark id={extension.id} name={extension.name} size={16} decorative />\n </span>\n <span className=\"text-[length:var(--ds-text-caption-lg)] font-medium\">\n Ask {extension.name}\n </span>\n {unread ? (\n <span\n data-slot=\"agent-chat-pill-unread\"\n aria-label=\"The agent is replying\"\n className=\"size-2 rounded-[var(--ds-radius-full)] bg-[var(--ds-primary)]\"\n />\n ) : null}\n </button>\n\n {/* Hidden, not unmounted: a run started before the pop-up was collapsed\n keeps streaming, and the pill's dot is what reports it. */}\n <div\n ref={panelRef}\n data-slot=\"agent-chat-panel\"\n data-state={state}\n role=\"dialog\"\n aria-label={`${extension.name} agent`}\n hidden={!open}\n onKeyDown={onKeyDown}\n className={cn(\n \"fixed z-50 flex flex-col overflow-hidden border border-[var(--ds-border)]\",\n \"bg-[var(--ds-card)] shadow-[var(--ds-shadow-lg)]\",\n state === \"full\"\n ? \"inset-0 rounded-none\"\n : cn(\n \"right-6 bottom-6 rounded-[var(--ds-radius-lg)]\",\n \"h-[min(40rem,calc(100vh-3rem))] w-[min(26.25rem,calc(100vw-3rem))]\",\n ),\n )}\n >\n <AgentChat\n client={client}\n agentName={extension.name}\n storageScope={scope}\n className=\"min-h-0 flex-1\"\n headerSlot={\n <>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-expand\"\n aria-label={state === \"full\" ? \"Restore the panel\" : \"Expand to full screen\"}\n onClick={() => go(state === \"full\" ? \"panel\" : \"full\")}\n >\n {state === \"full\" ? <Minimize2 aria-hidden /> : <Maximize2 aria-hidden />}\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n data-slot=\"agent-chat-collapse\"\n aria-label=\"Collapse the agent\"\n onClick={() => go(\"pill\")}\n >\n <X aria-hidden />\n </Button>\n </>\n }\n {...chatProps}\n />\n </div>\n </>\n );\n}\n\nexport { AgentChatPopup };\n"],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ "use client";
2
+ import { A2UIAction } from "../a2ui/message.js";
3
+ import { A2UIChatBlock } from "../agent-chat/a2ui-block.js";
4
+ import { AgentApprovalRequest, AgentChatClient, AgentChatMessage, AgentChatStep, AgentSession } from "../agent-chat/types.js";
5
+ import { AgentToolEntry } from "../agent-chat/store.js";
6
+ import * as React from "react";
7
+ //#region src/components/agent-chat.d.ts
8
+ /**
9
+ * Should the list jump back to the bottom after its content grew?
10
+ *
11
+ * Judged against the height BEFORE the growth. Measuring afterwards asks "is
12
+ * the newest content already visible", which is false by construction for any
13
+ * append taller than the threshold — a paragraph, a code block, an image — so
14
+ * the re-pin skipped exactly when it was needed.
15
+ */
16
+ export declare function shouldStickToBottom(params: {
17
+ prevScrollHeight: number;
18
+ scrollTop: number;
19
+ clientHeight: number;
20
+ threshold?: number;
21
+ }): boolean;
22
+ export interface AgentThinkingProps {
23
+ text: string;
24
+ /** Open by default while the turn is live, collapsed once it is finished. */
25
+ defaultOpen?: boolean;
26
+ streaming?: boolean;
27
+ }
28
+ declare function AgentThinking({ text, defaultOpen, streaming }: AgentThinkingProps): React.JSX.Element | null;
29
+ export interface AgentStepsProps {
30
+ steps: readonly AgentChatStep[];
31
+ }
32
+ /**
33
+ * What the agent is doing, one plain sentence per tool call (SKILLS-9).
34
+ *
35
+ * This is the surface a person watches while they wait, and it is deliberately
36
+ * not the tool strip: the strip is the record — names, arguments, output, the
37
+ * MCP App seam — and reading it requires knowing what `broker_invoke` is. The
38
+ * strip stays at the foot for anyone who wants the detail.
39
+ *
40
+ * NOT a live region of its own. The strip is drawn inside the transcript, and
41
+ * the transcript is already `role="log" aria-live="polite"` — two nested live
42
+ * regions is one announcement per change from the inner one and another from
43
+ * the outer, which is how a screen reader ends up saying every step twice.
44
+ * `aria-label` stays: it names the group for anyone who navigates to it.
45
+ */
46
+ declare function AgentSteps({ steps }: AgentStepsProps): React.JSX.Element | null;
47
+ export interface AgentA2UIBlocksProps {
48
+ blocks: readonly A2UIChatBlock[] | undefined;
49
+ /** Set on the streaming bubble: actions are disabled while the turn is live. */
50
+ streaming?: boolean;
51
+ onAction?: (action: A2UIAction) => void;
52
+ }
53
+ /**
54
+ * Every block of one message, in arrival order.
55
+ *
56
+ * A block carrying a transport `error` — an unclosed fence, a body that did not
57
+ * parse — gets an error card rather than being dropped. A block that is merely
58
+ * malformed inside is the renderer's error to draw, and it draws the parts that
59
+ * were fine.
60
+ */
61
+ declare function AgentA2UIBlocks({ blocks, streaming, onAction }: AgentA2UIBlocksProps): React.JSX.Element | null;
62
+ export interface AgentMessageBubbleProps {
63
+ message: AgentChatMessage;
64
+ agentName: string;
65
+ /**
66
+ * True while a run is live. Every A2UI surface in the transcript goes
67
+ * read-only, not just the one being drawn: an earlier surface is still a
68
+ * form wired to `sendAction`, and pressing it mid-run starts a second
69
+ * concurrent turn on the same session.
70
+ */
71
+ streaming?: boolean;
72
+ onAction?: (action: A2UIAction) => void;
73
+ }
74
+ declare function AgentMessageBubble({ message, agentName, streaming, onAction }: AgentMessageBubbleProps): React.JSX.Element | null;
75
+ export interface AgentStreamingBubbleProps {
76
+ agentName: string;
77
+ text: string;
78
+ thinkingText: string;
79
+ a2ui: readonly A2UIChatBlock[];
80
+ /** The turn's tool calls in plain words, drawn under the agent's name. */
81
+ steps?: readonly AgentChatStep[];
82
+ }
83
+ /**
84
+ * The assistant bubble while a turn is live.
85
+ *
86
+ * The filter chain is deliberately not applied: streaming text is
87
+ * agent-authored prose in progress, and the filters are about finished
88
+ * messages. Markdown is not processed either — partially arrived source
89
+ * (`**bo`) looks worse than its raw form — so the streaming view is plain text
90
+ * under `whitespace-pre-wrap` and `Markdown` takes over on `final`.
91
+ */
92
+ declare function AgentStreamingBubble({ agentName, text, thinkingText, a2ui, steps }: AgentStreamingBubbleProps): React.JSX.Element;
93
+ /** A `ui://` resource in a tool result is an MCP App: the extension's own screen. */
94
+ export declare function readMcpAppUri(output: unknown): string | null;
95
+ export interface AgentToolCardProps {
96
+ entry: AgentToolEntry;
97
+ }
98
+ /**
99
+ * The default card. It shows the tool's name, its input and its output — the
100
+ * input being the part that was missing for the whole of the AG-UI path,
101
+ * because the bridge had no `TOOL_CALL_ARGS` case.
102
+ */
103
+ declare function AgentToolCard({ entry }: AgentToolCardProps): React.JSX.Element;
104
+ export interface AgentApprovalCardProps {
105
+ request: AgentApprovalRequest;
106
+ onResolve: (id: string, decision: "allow-once" | "allow-always" | "deny") => void;
107
+ }
108
+ /**
109
+ * `exec` as a render-and-wait card: the run is paused on this decision.
110
+ *
111
+ * The three answers are the three cortenacore accepts. "Always allow" is not a
112
+ * nicety — without it the same command is asked about on every turn, and a user
113
+ * who has to answer the same question repeatedly stops reading the question.
114
+ */
115
+ declare function AgentApprovalCard({ request, onResolve }: AgentApprovalCardProps): React.JSX.Element;
116
+ export interface AgentToolRenderContext {
117
+ entry: AgentToolEntry;
118
+ /** The `ui://` resource this result carries, when it carries one. */
119
+ mcpAppUri: string | null;
120
+ }
121
+ export interface AgentChatProps extends Omit<React.ComponentProps<"div">, "children" | "onError"> {
122
+ /** The transport. `createAguiAgentChatClient` is the AG-UI implementation. */
123
+ client: AgentChatClient;
124
+ /** The extension name, shown on each assistant turn. */
125
+ agentName: string;
126
+ /**
127
+ * Names the `sessionStorage` slot this window's session key lives in. Scope
128
+ * it to the extension id; two chats with different scopes never share a key.
129
+ */
130
+ storageScope: string;
131
+ /** Do not read or write `sessionStorage`; for a guide or a test. */
132
+ ephemeral?: boolean;
133
+ thinking?: "low" | "medium" | "high";
134
+ /** Rendered between the tool strip and the composer — an attachment picker. */
135
+ attachmentsSlot?: React.ReactNode;
136
+ /**
137
+ * Host wiring for a tool call. Return a node to replace the default card —
138
+ * this is the seam an MCP App frame mounts through, since the sandboxed
139
+ * iframe, its CSP and the `tools/invoke` proxy are the host's to own.
140
+ */
141
+ renderToolCall?: (context: AgentToolRenderContext) => React.ReactNode | undefined;
142
+ /** Header content, e.g. the pop-up's own window controls. */
143
+ headerSlot?: React.ReactNode;
144
+ }
145
+ declare function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attachmentsSlot, renderToolCall, headerSlot, className, ...props }: AgentChatProps): React.JSX.Element;
146
+ export interface AgentSessionDrawerProps {
147
+ sessions: readonly AgentSession[];
148
+ current: string | null;
149
+ loading?: boolean;
150
+ onResume: (key: string) => void;
151
+ }
152
+ /**
153
+ * Every session of this agent, resumable from any window.
154
+ *
155
+ * The list is not this window's history: a session started in another window is
156
+ * here and can be resumed, which is the difference between "sessions are per
157
+ * window" (which key a window starts on) and "sessions are private to a window"
158
+ * (which they are not).
159
+ */
160
+ declare function AgentSessionDrawer({ sessions, current, loading, onResume }: AgentSessionDrawerProps): React.JSX.Element;
161
+ //#endregion
162
+ export { AgentA2UIBlocks, AgentApprovalCard, AgentChat, AgentMessageBubble, AgentSessionDrawer, AgentSteps, AgentStreamingBubble, AgentThinking, AgentToolCard };
163
+ //# sourceMappingURL=agent-chat.d.ts.map