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.
- package/CHANGELOG.md +107 -0
- package/LICENSE +7 -0
- package/README.md +235 -3
- 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 +353 -0
- package/dist/agent-chat/bridge.js.map +1 -0
- package/dist/agent-chat/session.d.ts +79 -0
- package/dist/agent-chat/session.js +391 -0
- package/dist/agent-chat/session.js.map +1 -0
- package/dist/agent-chat/step-label.d.ts +99 -0
- package/dist/agent-chat/step-label.js +116 -0
- package/dist/agent-chat/step-label.js.map +1 -0
- package/dist/agent-chat/store.d.ts +102 -0
- package/dist/agent-chat/store.js +876 -0
- package/dist/agent-chat/store.js.map +1 -0
- package/dist/agent-chat/types.d.ts +277 -0
- package/dist/agent-chat/types.js +17 -0
- package/dist/agent-chat/types.js.map +1 -0
- package/dist/agent-chat.d.ts +11 -0
- package/dist/agent-chat.js +11 -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 +163 -0
- package/dist/components/agent-chat.js +673 -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 +1 -1
- 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 +18 -5
- package/src/agent-chat/a2ui-block.ts +118 -0
- package/src/agent-chat/agui-client.ts +405 -0
- package/src/agent-chat/bridge.ts +445 -0
- package/src/agent-chat/session.ts +549 -0
- package/src/agent-chat/step-label.ts +177 -0
- package/src/agent-chat/store.ts +1234 -0
- package/src/agent-chat/types.ts +308 -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 +1006 -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 +137 -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,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport contract `AgentChat` is written against.
|
|
3
|
+
*
|
|
4
|
+
* Everything the chat surface needs from a server is behind `AgentChatClient`,
|
|
5
|
+
* and nothing in the interface names AG-UI, HTTP or React. That is what makes
|
|
6
|
+
* the surface testable — `test/agent-chat.test.tsx` drives it from a fake that
|
|
7
|
+
* replays scripted event sequences — and it is what keeps `@ag-ui/client` out
|
|
8
|
+
* of every module except `./agui-client.ts`.
|
|
9
|
+
*
|
|
10
|
+
* The three sinks are subscriptions rather than constructor callbacks so one
|
|
11
|
+
* client can outlive several mounts of the component, which is what a pop-up
|
|
12
|
+
* that collapses and reopens mid-run does.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { A2UIChatBlock } from "./a2ui-block";
|
|
16
|
+
|
|
17
|
+
/* ── what a session is ───────────────────────────────────────────────────── */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A row of `sessions.list`.
|
|
21
|
+
*
|
|
22
|
+
* Titles fall back in a fixed order (§17.2 of the extension how-to):
|
|
23
|
+
* `label`, `autoTitle`, `derivedTitle`, `displayName`, `lastMessagePreview`,
|
|
24
|
+
* then "New conversation". `sessionTitle` below is that order, in one place.
|
|
25
|
+
*/
|
|
26
|
+
export interface AgentSession {
|
|
27
|
+
/** The session key, verbatim — this is also the AG-UI `threadId`. */
|
|
28
|
+
key: string;
|
|
29
|
+
label?: string | null;
|
|
30
|
+
autoTitle?: string | null;
|
|
31
|
+
derivedTitle?: string | null;
|
|
32
|
+
displayName?: string | null;
|
|
33
|
+
lastMessagePreview?: string | null;
|
|
34
|
+
updatedAt?: number | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The title to show for a session row. */
|
|
38
|
+
export function sessionTitle(session: AgentSession): string {
|
|
39
|
+
for (const candidate of [
|
|
40
|
+
session.label,
|
|
41
|
+
session.autoTitle,
|
|
42
|
+
session.derivedTitle,
|
|
43
|
+
session.displayName,
|
|
44
|
+
session.lastMessagePreview,
|
|
45
|
+
]) {
|
|
46
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
47
|
+
return candidate.trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return "New conversation";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ── what a message is ───────────────────────────────────────────────────── */
|
|
54
|
+
|
|
55
|
+
export interface AgentChatMessage {
|
|
56
|
+
id: string;
|
|
57
|
+
role: "user" | "assistant";
|
|
58
|
+
text: string;
|
|
59
|
+
timestamp: number;
|
|
60
|
+
attachments?: string[];
|
|
61
|
+
/** The chain of thought behind this turn, collapsed by default. */
|
|
62
|
+
thinkingText?: string;
|
|
63
|
+
isError?: boolean;
|
|
64
|
+
/** True when the wire record was a tool call or a tool result; hidden. */
|
|
65
|
+
isToolMessage?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The run that produced this message. Absent on anything loaded from
|
|
68
|
+
* history. A `final` folds into the previous bubble only when the two share
|
|
69
|
+
* this value, so one run's answer cannot be rewritten by the next one's.
|
|
70
|
+
*/
|
|
71
|
+
runId?: string;
|
|
72
|
+
/** UI the agent drew, carried outside the prose. */
|
|
73
|
+
a2ui?: A2UIChatBlock[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ── what a step is ──────────────────────────────────────────────────────── */
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One tool call of the turn, in plain words (SKILLS-9).
|
|
80
|
+
*
|
|
81
|
+
* A step and an `AgentToolEntry` are the same call seen from two distances.
|
|
82
|
+
* The entry is the record — name, arguments, output, the `ui://` resource an
|
|
83
|
+
* MCP App is mounted from — and it is what `renderToolCall` is handed. The step
|
|
84
|
+
* is the sentence: what the agent is doing, now, for someone who is waiting and
|
|
85
|
+
* is not going to open a JSON blob to find out.
|
|
86
|
+
*
|
|
87
|
+
* `label` is stored rather than derived at render time because it is derived
|
|
88
|
+
* from the arguments, and the arguments arrive AFTER the start event. Keeping
|
|
89
|
+
* the computed sentence on the step means the strip is a pure function of state
|
|
90
|
+
* and the recompute happens once, where the arguments land.
|
|
91
|
+
*/
|
|
92
|
+
export interface AgentChatStep {
|
|
93
|
+
/** The AG-UI `toolCallId`; the same id the tool strip's entry carries. */
|
|
94
|
+
id: string;
|
|
95
|
+
toolName: string;
|
|
96
|
+
/**
|
|
97
|
+
* The call's input, when it parsed as an object. Absent until it arrives.
|
|
98
|
+
*
|
|
99
|
+
* In memory only. It is NOT persisted — see `storedStep` in `session.ts` —
|
|
100
|
+
* because a tool call's arguments are the user's data: a search intent, a
|
|
101
|
+
* customer id, the body of a record being written.
|
|
102
|
+
*/
|
|
103
|
+
args?: Record<string, unknown>;
|
|
104
|
+
/** The plain sentence. See `step-label.ts` for the map that produces it. */
|
|
105
|
+
label: string;
|
|
106
|
+
startedAt: number;
|
|
107
|
+
/**
|
|
108
|
+
* `stopped` is the user's Stop button, and is deliberately not `error`:
|
|
109
|
+
* nothing failed, the answer was simply no longer wanted, and a red cross
|
|
110
|
+
* against a call the user cancelled reads as a fault they caused.
|
|
111
|
+
*
|
|
112
|
+
* `paused` is the run ending on an interrupt with the call still OPEN — the
|
|
113
|
+
* producer is waiting for an approval or for a frontend tool to run — and it
|
|
114
|
+
* is deliberately not `done`: nothing has happened yet, and a tick against a
|
|
115
|
+
* write the user has not agreed to is a lie about their data. It stops the
|
|
116
|
+
* spinner and the elapsed counter, resolves to `done` or `error` when the
|
|
117
|
+
* run resumes and the result arrives, and becomes `stopped` on a deny.
|
|
118
|
+
*/
|
|
119
|
+
status: "running" | "done" | "error" | "stopped" | "paused";
|
|
120
|
+
endedAt?: number;
|
|
121
|
+
/** The failure to show, on a step that failed. Bounded and sanitised. */
|
|
122
|
+
errorMessage?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The result detail cortenacore puts on `TOOL_CALL_RESULT.metadata.meta`. */
|
|
126
|
+
export interface AgentToolResultMeta {
|
|
127
|
+
status?: number;
|
|
128
|
+
code?: string;
|
|
129
|
+
message?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `TOOL_CALL_RESULT.metadata`, normalised.
|
|
134
|
+
*
|
|
135
|
+
* This is the ONLY thing that says whether a tool call failed. `content` is
|
|
136
|
+
* the result as the model sees it — for a Cortena tool, a JSON string of
|
|
137
|
+
* `{ content: [{ type: "text", text }], details }` — and reading an envelope
|
|
138
|
+
* out of it means guessing at a shape the producer never promised. The client
|
|
139
|
+
* did guess, for `ok: false` at the top level, and the guess was simply wrong:
|
|
140
|
+
* the top level is the tool-result wrapper, so every failed call was drawn as
|
|
141
|
+
* a success.
|
|
142
|
+
*
|
|
143
|
+
* `meta` is optional, and where it is absent only `isError` can be trusted.
|
|
144
|
+
*/
|
|
145
|
+
export interface AgentToolResultMetadata {
|
|
146
|
+
toolName?: string;
|
|
147
|
+
isError: boolean;
|
|
148
|
+
meta?: AgentToolResultMeta;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ── the three sinks ─────────────────────────────────────────────────────── */
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* One chat event. The same `{ state: "delta" | "final" | "error" }` shape
|
|
155
|
+
* cortenacore broadcasts, so a client for a different transport has a
|
|
156
|
+
* documented target to hit.
|
|
157
|
+
*
|
|
158
|
+
* `delta` carries a SNAPSHOT, not an increment: every delta repeats all the
|
|
159
|
+
* text, all the thinking and every A2UI block seen so far. Two transports that
|
|
160
|
+
* disagree about this leave the store in different states for the same run.
|
|
161
|
+
*/
|
|
162
|
+
export interface AgentChatEvent {
|
|
163
|
+
runId: string;
|
|
164
|
+
sessionKey: string;
|
|
165
|
+
seq: number;
|
|
166
|
+
state: "delta" | "final" | "error";
|
|
167
|
+
/**
|
|
168
|
+
* Set on a `final` that came from a `RUN_FINISHED` carrying
|
|
169
|
+
* `outcome.type === "interrupt"`.
|
|
170
|
+
*
|
|
171
|
+
* The run has stopped, but it has not FINISHED: cortenacore pauses it for an
|
|
172
|
+
* approval or for a frontend tool and leaves the tool call open, and the
|
|
173
|
+
* continuation is a second run that ends the same call. Without this the
|
|
174
|
+
* store read every interrupt as a success and ticked a write nobody had
|
|
175
|
+
* approved yet.
|
|
176
|
+
*/
|
|
177
|
+
interrupted?: boolean;
|
|
178
|
+
message?: {
|
|
179
|
+
role?: string;
|
|
180
|
+
content?: Array<{ type: string; text?: string; thinking?: string; a2ui?: unknown }>;
|
|
181
|
+
timestamp?: number;
|
|
182
|
+
};
|
|
183
|
+
errorMessage?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** One step of the tool strip. */
|
|
187
|
+
export interface AgentToolEvent {
|
|
188
|
+
event: "tool.start" | "tool.args" | "tool.progress" | "tool.complete" | "tool.error";
|
|
189
|
+
data: {
|
|
190
|
+
id?: string;
|
|
191
|
+
name?: string;
|
|
192
|
+
/** `tool.args`: the call's input, parsed when it was valid JSON. */
|
|
193
|
+
input?: unknown;
|
|
194
|
+
output?: unknown;
|
|
195
|
+
/**
|
|
196
|
+
* `TOOL_CALL_RESULT.metadata`, forwarded verbatim on an end event.
|
|
197
|
+
*
|
|
198
|
+
* Untyped on purpose: it crosses a transport, so it is whatever arrived.
|
|
199
|
+
* `readResultMetadata` in `store.ts` is what turns it into an
|
|
200
|
+
* `AgentToolResultMetadata`, and it is the one place that decides whether
|
|
201
|
+
* a call failed.
|
|
202
|
+
*/
|
|
203
|
+
metadata?: Record<string, unknown>;
|
|
204
|
+
startedAt?: number;
|
|
205
|
+
completedAt?: number;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Decisions cortenacore accepts on `POST /agui/approval`. */
|
|
210
|
+
export type AgentApprovalDecision = "allow-once" | "allow-always" | "deny";
|
|
211
|
+
|
|
212
|
+
export interface AgentApprovalRequest {
|
|
213
|
+
id: string;
|
|
214
|
+
/** The command, or the tool, the approval is about. */
|
|
215
|
+
toolName: string;
|
|
216
|
+
args: Record<string, unknown>;
|
|
217
|
+
timestamp: number;
|
|
218
|
+
/**
|
|
219
|
+
* The run and the session this request arrived on, captured when it arrived.
|
|
220
|
+
*
|
|
221
|
+
* A decision is bound to them rather than resolved against whatever run
|
|
222
|
+
* happens to be in flight when the user presses a button. The two come
|
|
223
|
+
* apart routinely: an approval pauses a run, the user opens another session
|
|
224
|
+
* in the drawer or starts a second turn, and the client's idea of "the
|
|
225
|
+
* current thread" has moved on. Answering "allow" against the wrong thread
|
|
226
|
+
* is a command approved in a conversation nobody was asked about.
|
|
227
|
+
*/
|
|
228
|
+
runId: string;
|
|
229
|
+
sessionKey: string;
|
|
230
|
+
/** Set when a decision was made and the server would not take it. */
|
|
231
|
+
error?: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Where an approval came from, and therefore where the decision must go. */
|
|
235
|
+
export interface AgentApprovalContext {
|
|
236
|
+
runId: string;
|
|
237
|
+
sessionKey: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export type AgentApprovalEvent =
|
|
241
|
+
| { type: "request"; request: AgentApprovalRequest }
|
|
242
|
+
/** `decision: null` is an expiry. It is treated as a denial. */
|
|
243
|
+
| { type: "resolved"; id: string; decision: AgentApprovalDecision | null };
|
|
244
|
+
|
|
245
|
+
/* ── the client ──────────────────────────────────────────────────────────── */
|
|
246
|
+
|
|
247
|
+
export interface AgentChatSendInput {
|
|
248
|
+
/** The session key to run in. `threadId` is this value verbatim. */
|
|
249
|
+
sessionKey: string;
|
|
250
|
+
/** What the user typed. Empty for an action sent by rendered UI. */
|
|
251
|
+
message: string;
|
|
252
|
+
/** The run id; also the idempotency key. */
|
|
253
|
+
runId: string;
|
|
254
|
+
attachments?: unknown[];
|
|
255
|
+
thinking?: "low" | "medium" | "high";
|
|
256
|
+
model?: { provider: string; model: string };
|
|
257
|
+
/** A click inside an A2UI surface. Never a sentence the user appears to type. */
|
|
258
|
+
a2uiAction?: { surfaceId: string; componentId: string; name: string; payload: Record<string, unknown> };
|
|
259
|
+
/** The same, from inside a rendered MCP App. */
|
|
260
|
+
mcpAppAction?: Record<string, unknown>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Everything `AgentChat` needs from a server.
|
|
265
|
+
*
|
|
266
|
+
* `request` is deliberately a method name plus params rather than a URL: the
|
|
267
|
+
* two calls the chat makes — `sessions.list` and `chat.history` — are control
|
|
268
|
+
* plane methods, and a host that proxies them through its own gateway chooses
|
|
269
|
+
* its own paths.
|
|
270
|
+
*/
|
|
271
|
+
export interface AgentChatClient {
|
|
272
|
+
/** The agent this client talks to. Session keys are `agent:<agentId>:…`. */
|
|
273
|
+
readonly agentId: string;
|
|
274
|
+
/** Start a run. Resolves when the run has been accepted, not when it ends. */
|
|
275
|
+
send(input: AgentChatSendInput): Promise<void>;
|
|
276
|
+
/** Stop the run in flight. Best-effort; local state resets either way. */
|
|
277
|
+
abort(params?: { runId?: string; sessionKey?: string }): Promise<void>;
|
|
278
|
+
/**
|
|
279
|
+
* Answer an approval. `context` is the `{ runId, sessionKey }` the request
|
|
280
|
+
* carried, not the run in flight — see `AgentApprovalRequest`. Rejects when
|
|
281
|
+
* the server refuses the decision, so the caller can put the card back.
|
|
282
|
+
*/
|
|
283
|
+
resolveApproval(
|
|
284
|
+
id: string,
|
|
285
|
+
decision: AgentApprovalDecision,
|
|
286
|
+
context: AgentApprovalContext,
|
|
287
|
+
): Promise<void>;
|
|
288
|
+
/** `sessions.list` and `chat.history`. */
|
|
289
|
+
request<T = unknown>(method: string, params: Record<string, unknown>): Promise<T>;
|
|
290
|
+
/** Chat deltas, finals and errors. Returns an unsubscribe. */
|
|
291
|
+
onChat(sink: (event: AgentChatEvent) => void): () => void;
|
|
292
|
+
/** Tool starts, arguments, progress and results. Returns an unsubscribe. */
|
|
293
|
+
onTool(sink: (event: AgentToolEvent) => void): () => void;
|
|
294
|
+
/** Approval requests and resolutions. Returns an unsubscribe. */
|
|
295
|
+
onApproval(sink: (event: AgentApprovalEvent) => void): () => void;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** What `sessions.list` answers. */
|
|
299
|
+
export interface AgentSessionsListResponse {
|
|
300
|
+
sessions?: AgentSession[];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** What `chat.history` answers. `messages` are raw transcript records. */
|
|
304
|
+
export interface AgentChatHistoryResponse {
|
|
305
|
+
messages?: unknown[];
|
|
306
|
+
hasMore?: boolean;
|
|
307
|
+
total?: number;
|
|
308
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { PageHeader } from "@/components/page-header";
|
|
5
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/tabs";
|
|
6
|
+
import { cn } from "@/lib/cn";
|
|
7
|
+
import { AdminPermissionsProvider } from "./context";
|
|
8
|
+
import { AdminLicenceScreen } from "./licence";
|
|
9
|
+
import { AdminMatrix } from "./matrix";
|
|
10
|
+
import { AdminMembers } from "./members";
|
|
11
|
+
import { AdminRoles } from "./roles";
|
|
12
|
+
import {
|
|
13
|
+
ADMIN_ROUTES,
|
|
14
|
+
ADMIN_ROUTE_LABELS,
|
|
15
|
+
type AdminPermissionsApi,
|
|
16
|
+
type AdminRoute,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
export interface AdminPermissionsProps
|
|
20
|
+
extends Omit<React.ComponentProps<"div">, "children" | "title"> {
|
|
21
|
+
/** Everything the screen reads and writes. The extension's, entirely. */
|
|
22
|
+
api: AdminPermissionsApi;
|
|
23
|
+
/**
|
|
24
|
+
* The screen to show. Pass it and the host's router owns the URL; omit it
|
|
25
|
+
* and the composition keeps the current screen in its own state.
|
|
26
|
+
*/
|
|
27
|
+
route?: AdminRoute;
|
|
28
|
+
/** The screen to open with when `route` is not passed. @default "members" */
|
|
29
|
+
defaultRoute?: AdminRoute;
|
|
30
|
+
/**
|
|
31
|
+
* Fired when the administrator picks another screen. Always fired, whether
|
|
32
|
+
* or not `route` is controlled, so a host can push the URL without also
|
|
33
|
+
* having to hold the state.
|
|
34
|
+
*/
|
|
35
|
+
onNavigate?: (route: AdminRoute) => void;
|
|
36
|
+
/** `false` hides the title block, for a host that renders its own. */
|
|
37
|
+
header?: boolean;
|
|
38
|
+
title?: React.ReactNode;
|
|
39
|
+
description?: React.ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* AdminPermissions — the consistent admin screen for every Cortena extension
|
|
44
|
+
* (§13, DESIGN-D11 as amended by DESIGN-D22; audit rules P-14, P-42, P-43).
|
|
45
|
+
*
|
|
46
|
+
* Four screens behind `admin/*`: `/admin/members`, `/admin/roles`,
|
|
47
|
+
* `/admin/licence`, `/admin/permissions`. It renders whatever roles and
|
|
48
|
+
* actions the extension declares and **defines none of its own** — there is
|
|
49
|
+
* not one role name, action name or grant in this folder. That is exactly what
|
|
50
|
+
* lets a verification workflow and a work tracker have completely different
|
|
51
|
+
* role models and still look like one product.
|
|
52
|
+
*
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <Route path="admin/*" element={
|
|
55
|
+
* <AdminPermissions
|
|
56
|
+
* api={adminApi}
|
|
57
|
+
* route={screenFromPath(location.pathname)}
|
|
58
|
+
* onNavigate={(next) => navigate(`/admin/${next}`)}
|
|
59
|
+
* />
|
|
60
|
+
* } />
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* **Router-agnostic, by a `route` prop plus `onNavigate`.** The component
|
|
64
|
+
* imports no router and never touches `history` or `location`. Those four
|
|
65
|
+
* paths are real URLs — deep-linkable, bookmarkable, and answerable to the
|
|
66
|
+
* back button — and only the host's router can own a URL; a component that
|
|
67
|
+
* pushed history itself would fight whichever router the extension chose.
|
|
68
|
+
* `onNavigate` reports the intent, the host turns it into a navigation, and
|
|
69
|
+
* `route` comes back as the answer.
|
|
70
|
+
*
|
|
71
|
+
* An internal fallback exists for the case with no router in it — the guide,
|
|
72
|
+
* a test, a small app: omit `route` and the composition keeps the screen in
|
|
73
|
+
* its own state. `onNavigate` still fires, so a host can start uncontrolled
|
|
74
|
+
* and add a router later without changing anything else.
|
|
75
|
+
*
|
|
76
|
+
* The navigation is a real tablist rather than a row of links, because the
|
|
77
|
+
* choice is between panels of one screen; the host is free to hide it with
|
|
78
|
+
* `header={false}` and drive `route` from its own chrome.
|
|
79
|
+
*/
|
|
80
|
+
export function AdminPermissions({
|
|
81
|
+
api,
|
|
82
|
+
route,
|
|
83
|
+
defaultRoute = "members",
|
|
84
|
+
onNavigate,
|
|
85
|
+
header = true,
|
|
86
|
+
title = "Administration",
|
|
87
|
+
description = "Who is in this organisation, what your roles let them do, and the licence behind it.",
|
|
88
|
+
className,
|
|
89
|
+
...props
|
|
90
|
+
}: AdminPermissionsProps) {
|
|
91
|
+
const [internal, setInternal] = React.useState<AdminRoute>(defaultRoute);
|
|
92
|
+
const active = route ?? internal;
|
|
93
|
+
|
|
94
|
+
const go = (next: AdminRoute) => {
|
|
95
|
+
if (route === undefined) setInternal(next);
|
|
96
|
+
onNavigate?.(next);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<AdminPermissionsProvider api={api}>
|
|
101
|
+
<div
|
|
102
|
+
data-slot="admin-permissions"
|
|
103
|
+
data-route={active}
|
|
104
|
+
className={cn("flex flex-col gap-5", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
>
|
|
107
|
+
{header ? <PageHeader title={title} description={description} /> : null}
|
|
108
|
+
<Tabs
|
|
109
|
+
value={active}
|
|
110
|
+
onValueChange={(value) => go(value as AdminRoute)}
|
|
111
|
+
data-slot="admin-permissions-nav"
|
|
112
|
+
>
|
|
113
|
+
<TabsList aria-label="Administration screens">
|
|
114
|
+
{ADMIN_ROUTES.map((item) => (
|
|
115
|
+
<TabsTrigger key={item} value={item} data-route={item}>
|
|
116
|
+
{ADMIN_ROUTE_LABELS[item]}
|
|
117
|
+
</TabsTrigger>
|
|
118
|
+
))}
|
|
119
|
+
</TabsList>
|
|
120
|
+
</Tabs>
|
|
121
|
+
<div data-slot="admin-permissions-screen">
|
|
122
|
+
{active === "members" ? <AdminMembers /> : null}
|
|
123
|
+
{active === "roles" ? <AdminRoles /> : null}
|
|
124
|
+
{active === "licence" ? <AdminLicenceScreen /> : null}
|
|
125
|
+
{active === "permissions" ? <AdminMatrix /> : null}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</AdminPermissionsProvider>
|
|
129
|
+
);
|
|
130
|
+
}
|