@qilin-agent/client-ui-approval 0.0.0-stage → 3.1.4

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-approval/README.md
5
+ /:
6
+ en: a16f3a25592b9a2d
7
+ zh: 6be5e99282ce23d9
8
+ /qilin-agent-client-ui-approval:
9
+ en: 602a418241ca2403
10
+ zh: 145eb4cad4329ccc
11
+ /qilin-agent-client-ui-approval/summary:
12
+ en: 8d6c7c6e63f849f6
13
+ zh: d89fa273e30b99d3
14
+ /qilin-agent-client-ui-approval/table-of-contents:
15
+ en: b4683d2a53256fda
16
+ zh: 4e65ff63dc8c48fe
17
+ /qilin-agent-client-ui-approval/model-experience:
18
+ en: 8161dce49e9f899c
19
+ zh: a17401e1e78ba6be
20
+ /qilin-agent-client-ui-approval/model-experience/kv-cache-effect:
21
+ en: adf7112e0691b89e
22
+ zh: 14eeddcff985119f
23
+ /qilin-agent-client-ui-approval/known-limitations-and-deferred-work:
24
+ en: 158f435625e41ca9
25
+ zh: 9d58169aaf6c7ed7
26
+ /qilin-agent-client-ui-approval/known-limitations-and-deferred-work/dev-note:
27
+ en: e67f633dc6fe7773
28
+ zh: e88aeb617ffb74ea
package/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Browser approval UI that answers Host permission requests through the scoped interaction path."
3
+ kind: "package-reference"
4
+ ---
5
+ # @qilin-agent/client-ui-approval
2
6
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
7
+ English | [中文](README.zh.md)
8
+
9
+ ## Summary
10
+
11
+ Browser approval presentation over the Agent-scoped Remote Event waterfall. The plugin publishes each pending request through `ctx.uiSession`, takes over the Conversation composer, optionally renders correlated Tool detail, and returns the user's decision to the waiting Host request. Use it when a browser must collect approval for a waiting Host operation.
12
+
13
+ ## Table of Contents
14
+
15
+ - [Model Experience](#model-experience)
16
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
17
+ - [Dev Note](#dev-note)
18
+
19
+ -----
20
+
21
+ <a id="model-experience"></a>
22
+ ## Model Experience
23
+
24
+ None, as this package presents approval requests in the browser and registers nothing model-facing.
25
+
26
+ #### KV Cache effect
27
+
28
+ None; approval request and response rendering does not alter a model request.
29
+
30
+ ## Known Limitations and Deferred Work
31
+
32
+ <a id="known-limitations-and-deferred-work"></a>
33
+
34
+ - **The panel exposes transient decisions only** — it supports allow-once and reject; persistent permission policy remains owned by Host-side approval packages.
35
+
36
+
37
+ <a id="dev-note"></a>
38
+ ### Dev Note
39
+
40
+ <details>
41
+ <summary>Working context for maintainers — click to expand</summary>
42
+
43
+ None.
44
+
45
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: "通过作用域交互路径响应 Host 权限请求的浏览器批准界面。"
3
+ kind: "package-reference"
4
+ ---
5
+ # @qilin-agent/client-ui-approval
6
+
7
+ [English](README.md) | 中文
8
+
9
+ ## 概述
10
+
11
+ 基于 Agent-scoped Remote Event waterfall 的浏览器审批界面。插件通过 `ctx.uiSession` 发布每个待处理请求、接管 Conversation composer、按需渲染关联的 Tool 详情,并将用户决定返回给等待中的 Host 请求。当浏览器必须为等待中的 Host 操作收集批准时,请使用它。
12
+
13
+ ## 目录
14
+
15
+ - [模型体验](#model-experience)
16
+ - [已知限制与暂缓事项](#known-limitations-and-deferred-work)
17
+ - [开发备注](#dev-note)
18
+
19
+ -----
20
+
21
+ <a id="model-experience"></a>
22
+ ## 模型体验
23
+
24
+ 无,因为本包只在浏览器中呈现审批请求,不注册任何面向模型的内容。
25
+
26
+ #### KV Cache 影响
27
+
28
+ 无;审批请求和响应的呈现不会改变模型请求。
29
+
30
+ ## 已知限制与暂缓事项
31
+
32
+ <a id="known-limitations-and-deferred-work"></a>
33
+
34
+ - **面板只提供临时决定**——它支持仅本次允许和拒绝;持久权限策略仍由 Host 侧审批包拥有。
35
+
36
+
37
+ <a id="dev-note"></a>
38
+ ### 开发备注
39
+
40
+ <details>
41
+ <summary>维护者工作上下文——点击展开</summary>
42
+
43
+ 无。
44
+
45
+ </details>
package/lib/client.js ADDED
@@ -0,0 +1,353 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@qilin-agent/client-ui-approval",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react = require("react");
9
+ let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
10
+ //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-approval/src/client/ApprovalPanel.module.css.mjs
11
+ const css = ".Dg9Iaq_root{padding:8px calc(var(--qilin-composer-side-clearance) + 16px) 12px;flex-direction:column;align-items:center;display:flex}.Dg9Iaq_card{width:100%;max-width:var(--qilin-chat-content-width);border:1px solid var(--qilin-alias-state-warn-secondary);background:var(--qilin-specific-input-major);box-shadow:var(--qilin-shadow-lv2);--qilin-scrollbar-thumb:var(--qilin-alias-scrollbar-bg-l2);--qilin-scrollbar-thumb-hover:var(--qilin-alias-scrollbar-hover-l2);border-radius:20px;overflow:hidden}.Dg9Iaq_strip{background:var(--qilin-alias-state-warn-tertiary);color:var(--qilin-alias-state-warn-primary);align-items:center;gap:8px;padding:10px 16px;font-size:13px;line-height:18px;display:flex}.Dg9Iaq_dot{corner-shape:round;background:var(--qilin-alias-state-warn-primary);border-radius:50%;width:8px;height:8px}.Dg9Iaq_body{box-sizing:border-box;max-height:var(--qilin-composer-text-max-height);flex-direction:column;gap:6px;padding:12px 16px 0;display:flex;overflow-y:auto}.Dg9Iaq_headline{color:var(--qilin-alias-label-primary);font-size:15px;font-weight:500;line-height:24px}.Dg9Iaq_command{color:var(--qilin-alias-label-tertiary);font-family:var(--ds-font-family-code);word-break:break-all;font-size:13px;line-height:20px}.Dg9Iaq_actionRow{justify-content:flex-end;gap:8px;padding:14px 16px;display:flex}.Dg9Iaq_reject:hover:not(:disabled){background:var(--qilin-alias-interactive-bg-hover-danger);color:var(--qilin-alias-state-error-primary);border-color:#0000}";
12
+ const tagId = "@qilin-agent/client-ui-approval/ApprovalPanel.module.css";
13
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
14
+ const tag = document.createElement("style");
15
+ tag.dataset.plugin = "@qilin-agent/client-ui-approval";
16
+ tag.dataset.pluginCss = tagId;
17
+ tag.textContent = css;
18
+ document.head.appendChild(tag);
19
+ }
20
+ var ApprovalPanel_module_css_default = {
21
+ "actionRow": "Dg9Iaq_actionRow",
22
+ "body": "Dg9Iaq_body",
23
+ "card": "Dg9Iaq_card",
24
+ "command": "Dg9Iaq_command",
25
+ "dot": "Dg9Iaq_dot",
26
+ "headline": "Dg9Iaq_headline",
27
+ "reject": "Dg9Iaq_reject",
28
+ "root": "Dg9Iaq_root",
29
+ "strip": "Dg9Iaq_strip"
30
+ };
31
+ //#endregion
32
+ //#region lib/types/client/ApprovalPanel.js
33
+ /** Composer takeover for one pending approval waterfall. */
34
+ /**
35
+ * Render one pending approval and its optional Tool-owned detail.
36
+ * @param props - selector-matched request and standard Slot props.
37
+ * @returns The approval composer takeover.
38
+ */
39
+ function ApprovalPanel(props) {
40
+ const approval = props.matched;
41
+ const detail = approval.callId === void 0 ? null : props.renderSlot("conversation.approval.detail", { callId: approval.callId });
42
+ return (0, react_jsx_runtime.jsx)(ApprovalFlow, {
43
+ pending: approval,
44
+ reason: approval.displayReason === void 0 ? approval.reason : props.resolveReason(approval.displayReason),
45
+ detail,
46
+ t: props.t
47
+ }, approval.key);
48
+ }
49
+ function ApprovalFlow({ pending, reason, detail, t }) {
50
+ const [answered, setAnswered] = (0, react.useState)(false);
51
+ const active = (0, react.useRef)(false);
52
+ const waiting = (0, react.useRef)(false);
53
+ const composing = (0, react.useRef)(false);
54
+ const compositionEnded = (0, react.useRef)(false);
55
+ (0, react.useEffect)(() => {
56
+ active.current = true;
57
+ return () => {
58
+ active.current = false;
59
+ };
60
+ }, []);
61
+ const answer = (outcome) => {
62
+ if (waiting.current) return;
63
+ waiting.current = true;
64
+ setAnswered(true);
65
+ pending.answer(outcome).catch(() => {
66
+ if (!active.current) return;
67
+ waiting.current = false;
68
+ setAnswered(false);
69
+ });
70
+ };
71
+ const keydown = (event) => {
72
+ const element = event.target;
73
+ if (event.defaultPrevented || !event.currentTarget.contains(document.activeElement) || element.closest("input, textarea, select, [contenteditable=\"true\"], [contenteditable=\"\"]") !== null) return;
74
+ if (event.key !== "Enter" && event.key !== "Escape") return;
75
+ if (event.key === "Enter" && element.closest("button, a[href], [role=\"button\"]") !== null) return;
76
+ if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return;
77
+ event.preventDefault();
78
+ event.stopPropagation();
79
+ if (event.repeat || composing.current || compositionEnded.current || event.nativeEvent.isComposing || event.keyCode === 229) return;
80
+ answer(event.key === "Enter" ? "allowed-once" : "rejected");
81
+ };
82
+ return (0, react_jsx_runtime.jsx)("div", {
83
+ className: ApprovalPanel_module_css_default.root,
84
+ "data-approval-key": pending.key,
85
+ "aria-busy": answered,
86
+ onKeyDown: keydown,
87
+ onKeyUpCapture: () => {
88
+ compositionEnded.current = false;
89
+ },
90
+ children: (0, react_jsx_runtime.jsxs)("div", {
91
+ className: ApprovalPanel_module_css_default.card,
92
+ children: [
93
+ (0, react_jsx_runtime.jsxs)("div", {
94
+ className: ApprovalPanel_module_css_default.strip,
95
+ children: [(0, react_jsx_runtime.jsx)("span", { className: ApprovalPanel_module_css_default.dot }), t("waiting")]
96
+ }),
97
+ (0, react_jsx_runtime.jsxs)("div", {
98
+ className: ApprovalPanel_module_css_default.body,
99
+ "data-approval-scroll": "",
100
+ tabIndex: 0,
101
+ role: "group",
102
+ "aria-label": t("detail.aria"),
103
+ children: [(0, react_jsx_runtime.jsx)("div", {
104
+ className: ApprovalPanel_module_css_default.headline,
105
+ children: reason ?? t("escalation", { toolName: pending.toolName })
106
+ }), detail !== null && (0, react_jsx_runtime.jsx)("div", {
107
+ className: ApprovalPanel_module_css_default.command,
108
+ children: detail
109
+ })]
110
+ }),
111
+ (0, react_jsx_runtime.jsxs)("div", {
112
+ className: ApprovalPanel_module_css_default.actionRow,
113
+ children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
114
+ variant: "outline",
115
+ className: ApprovalPanel_module_css_default.reject,
116
+ disabled: answered,
117
+ onClick: () => {
118
+ answer("rejected");
119
+ },
120
+ children: t("reject")
121
+ }), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Button, {
122
+ variant: "primary",
123
+ disabled: answered,
124
+ onClick: () => {
125
+ answer("allowed-once");
126
+ },
127
+ children: t("allowOnce")
128
+ })]
129
+ })
130
+ ]
131
+ })
132
+ });
133
+ }
134
+ //#endregion
135
+ //#region lib/types/client/contract/slots.js
136
+ function settlePendingComposer(settle, failureMessage) {
137
+ try {
138
+ settle();
139
+ return Promise.resolve();
140
+ } catch (error) {
141
+ return Promise.reject(error instanceof Error ? error : new Error(failureMessage, { cause: error }));
142
+ }
143
+ }
144
+ let nextApprovalKey = 0;
145
+ /** One answerable Client presentation of a pending Host waterfall. */
146
+ var PendingApproval = class {
147
+ sessionId;
148
+ /** Domain discriminator used by Session pending-interaction consumers. */
149
+ kind;
150
+ /** Opaque render identity and one-shot remount axis. */
151
+ key;
152
+ /** Tool requesting the decision. */
153
+ toolName;
154
+ /** Correlated Tool call, when supplied by the asker. */
155
+ callId;
156
+ /** Human-readable reason supplied by the asker. */
157
+ reason;
158
+ /** Localized presentation copy, when supplied by the asker. */
159
+ displayReason;
160
+ /** Result returned by the Remote Event listener to the Host waterfall. */
161
+ result;
162
+ #resolve;
163
+ #reject;
164
+ #signal;
165
+ #onAbort;
166
+ #delegated = Symbol("pending approval delegated");
167
+ #settled = false;
168
+ /**
169
+ * @param sessionId - Agent/Session identity owning the scoped request.
170
+ * @param request - Host approval request projected through the Remote Event.
171
+ */
172
+ constructor(sessionId, request) {
173
+ this.sessionId = sessionId;
174
+ this.kind = "approval";
175
+ nextApprovalKey += 1;
176
+ this.key = `approval:${String(nextApprovalKey)}`;
177
+ this.toolName = request.toolName;
178
+ this.callId = request.callId;
179
+ this.reason = request.reason;
180
+ this.displayReason = request.displayReason;
181
+ const completion = Promise.withResolvers();
182
+ this.result = completion.promise;
183
+ this.#resolve = completion.resolve;
184
+ this.#reject = completion.reject;
185
+ this.#signal = request.signal;
186
+ if (request.signal === void 0) {
187
+ this.#onAbort = void 0;
188
+ return;
189
+ }
190
+ const onAbort = () => {
191
+ this.abort(request.signal?.reason ?? /* @__PURE__ */ new Error("approval request was aborted"));
192
+ };
193
+ this.#onAbort = onAbort;
194
+ request.signal.addEventListener("abort", onAbort, { once: true });
195
+ if (request.signal.aborted) onAbort();
196
+ }
197
+ /**
198
+ * Resolve the Host waterfall with the user's decision.
199
+ * @param outcome - supported interactive decision.
200
+ */
201
+ answer(outcome) {
202
+ return settlePendingComposer(() => {
203
+ this.finish(() => {
204
+ this.#resolve(outcome);
205
+ });
206
+ }, "pending approval settlement failed");
207
+ }
208
+ /** Delegate an unanswered request to the next waterfall listener. */
209
+ delegate() {
210
+ if (this.#settled) return;
211
+ this.finish(() => {
212
+ this.#reject(this.#delegated);
213
+ });
214
+ }
215
+ /**
216
+ * Test whether a rejection requests waterfall delegation.
217
+ * @param reason - rejection received from {@link PendingApproval.result}.
218
+ * @returns whether {@link PendingApproval.delegate} produced it.
219
+ */
220
+ isDelegation(reason) {
221
+ return reason === this.#delegated;
222
+ }
223
+ /**
224
+ * End an unanswered presentation when its transport, scope, or plugin lifetime ends.
225
+ * @param reason - rejection exposed to the waiting Remote Event listener.
226
+ */
227
+ abort(reason) {
228
+ if (this.#settled) return;
229
+ this.finish(() => {
230
+ this.#reject(reason);
231
+ });
232
+ }
233
+ finish(settle) {
234
+ if (this.#settled) throw new Error(`pending approval ${this.key} is already settled`);
235
+ this.#settled = true;
236
+ if (this.#signal !== void 0 && this.#onAbort !== void 0) this.#signal.removeEventListener("abort", this.#onAbort);
237
+ settle();
238
+ }
239
+ };
240
+ //#endregion
241
+ //#region lib/types/client/locales.js
242
+ /** `approval` namespace dictionaries. */
243
+ /** Simplified Chinese dictionary and key-set source of truth. */
244
+ const zh = {
245
+ waiting: "等待审批",
246
+ "detail.aria": "审批详情",
247
+ escalation: "工具 {toolName} 请求越权执行",
248
+ reject: "拒绝",
249
+ allowOnce: "允许一次"
250
+ };
251
+ /** English dictionary, checked against the Chinese key set. */
252
+ const en = {
253
+ waiting: "Waiting for approval",
254
+ "detail.aria": "Approval details",
255
+ escalation: "Tool {toolName} requests privileged execution",
256
+ reject: "Reject",
257
+ allowOnce: "Allow once"
258
+ };
259
+ //#endregion
260
+ //#region lib/types/client/index.js
261
+ /** Required services: Agent scopes, Remote Events, Session UI, Slot registry, and copy. */
262
+ const inject = [
263
+ "sessions",
264
+ "remote",
265
+ "uiSession",
266
+ "slots",
267
+ "locale"
268
+ ];
269
+ const NS = "approval";
270
+ /** Present one request until the user answers or its lifetime ends. */
271
+ async function answerApproval(ctx, owner, request, next, registerPendingInteraction) {
272
+ const sessionId = ctx.sessions.scopeOf(owner);
273
+ if (sessionId === void 0) return next();
274
+ const pending = new PendingApproval(sessionId, {
275
+ toolName: request.toolName,
276
+ ...request.callId === void 0 ? {} : { callId: request.callId },
277
+ ...request.reason === void 0 ? {} : { reason: request.reason },
278
+ ...request.displayReason === void 0 ? {} : { displayReason: request.displayReason },
279
+ ...request.signal === void 0 ? {} : { signal: request.signal }
280
+ });
281
+ const completed = Promise.withResolvers();
282
+ const remove = registerPendingInteraction(pending, async () => {
283
+ pending.delegate();
284
+ await completed.promise;
285
+ });
286
+ try {
287
+ try {
288
+ return await pending.result;
289
+ } catch (error) {
290
+ if (pending.isDelegation(error)) return await next();
291
+ throw error;
292
+ }
293
+ } finally {
294
+ remove();
295
+ completed.resolve();
296
+ }
297
+ }
298
+ /**
299
+ * Install approval copy and the scoped waterfall consumer.
300
+ * @param ctx - Client root context.
301
+ */
302
+ function apply(ctx) {
303
+ ctx.effect(() => ctx.locale.register(NS, {
304
+ zh,
305
+ en
306
+ }), "ui-approval: dictionaries");
307
+ ctx.inject(["shortcuts"], (scope) => {
308
+ const t = ctx.locale.bind(NS);
309
+ scope.effect(() => scope.shortcuts.registerFixed({
310
+ id: "approval.allow",
311
+ label: () => t("allowOnce"),
312
+ keys: ["Enter"],
313
+ bindings: [{
314
+ code: "Enter",
315
+ modifiers: []
316
+ }],
317
+ group: "approval"
318
+ }), "ui-approval: fixed allow reference");
319
+ scope.effect(() => scope.shortcuts.registerFixed({
320
+ id: "approval.reject",
321
+ label: () => t("reject"),
322
+ keys: ["Esc"],
323
+ bindings: [{
324
+ code: "Escape",
325
+ modifiers: []
326
+ }],
327
+ group: "approval"
328
+ }), "ui-approval: fixed reject reference");
329
+ });
330
+ const registerPendingInteraction = ctx.uiSession.registerPendingInteraction(() => 0);
331
+ ctx.slots.inject("conversation.composer", () => ctx.slots.register({
332
+ name: "conversation.composer",
333
+ priority: 1,
334
+ select: ({ pendingInteraction }) => pendingInteraction instanceof PendingApproval ? pendingInteraction : null,
335
+ locale: NS,
336
+ inject: () => ({ resolveReason: (reason) => ctx.locale.resolveText(reason) }),
337
+ children: { "conversation.approval.detail": {
338
+ kind: "single",
339
+ scope: "session"
340
+ } }
341
+ }, ApprovalPanel));
342
+ ctx.remote.$on("approval/request", function(request, next) {
343
+ return answerApproval(ctx, this, request, next, registerPendingInteraction);
344
+ });
345
+ }
346
+ //#endregion
347
+ exports.apply = apply;
348
+ exports.inject = inject;
349
+ return module.exports;
350
+ }
351
+ });
352
+
353
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //#region lib/types/index.js
2
+ /** Browser-only approval presentation plugin; the Host capability is composed independently. */
3
+ /** Node plugin body. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,8 @@
1
+ import type { ApprovalComposerProps } from './contract/slots.ts';
2
+ /**
3
+ * Render one pending approval and its optional Tool-owned detail.
4
+ * @param props - selector-matched request and standard Slot props.
5
+ * @returns The approval composer takeover.
6
+ */
7
+ export declare function ApprovalPanel(props: ApprovalComposerProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=ApprovalPanel.d.ts.map
@@ -0,0 +1,105 @@
1
+ /** Approval composer and optional correlated-detail contracts. */
2
+ import type { ToolCallId } from '@qilin-agent/llm';
3
+ import type { SessionId } from '@qilin-agent/session/types';
4
+ import type { PropsLocale, PropsRenderSlots, PropsRuntime } from '@qilin-agent/client-ui-slots';
5
+ import type { ApprovalKey } from '../locales.ts';
6
+ declare module '@qilin-agent/client-ui-session/client' {
7
+ interface SessionPendingInteractionMap {
8
+ /** Pending approval request. */
9
+ approval: PendingApproval;
10
+ }
11
+ }
12
+ declare module '@qilin-agent/client-ui-slots' {
13
+ interface LocaleNamespaceMap {
14
+ /** Approval prompt copy. */
15
+ approval: ApprovalKey;
16
+ }
17
+ interface SlotMap {
18
+ /** Optional detail for the Tool call correlated with an approval request. */
19
+ 'conversation.approval.detail': {
20
+ kind: 'single';
21
+ scope: 'session';
22
+ owner: ApprovalDetailOwnerProps;
23
+ };
24
+ }
25
+ }
26
+ /** Stable identity handed to an optional approval-detail renderer. */
27
+ export interface ApprovalDetailOwnerProps {
28
+ /** Tool call correlated with the request. */
29
+ callId: ToolCallId;
30
+ }
31
+ /** Client-visible fields of an approval request projected through Remote Events. */
32
+ export interface ApprovalPresentationRequest {
33
+ /** Tool requesting the decision. */
34
+ readonly toolName: string;
35
+ /** Tool call correlated with the request. */
36
+ readonly callId?: ToolCallId;
37
+ /** Human-readable reason supplied by the requester. */
38
+ readonly reason?: string;
39
+ /** Localized presentation copy; the audit reason remains unchanged. */
40
+ readonly displayReason?: {
41
+ readonly en: string;
42
+ readonly [locale: string]: string;
43
+ };
44
+ /** Cancellation projected from the Host waterfall. */
45
+ readonly signal?: AbortSignal;
46
+ }
47
+ /** Decisions this interactive Client presentation can return. */
48
+ export type ApprovalDecision = 'allowed-once' | 'rejected';
49
+ /** One answerable Client presentation of a pending Host waterfall. */
50
+ export declare class PendingApproval {
51
+ #private;
52
+ readonly sessionId: SessionId;
53
+ /** Domain discriminator used by Session pending-interaction consumers. */
54
+ readonly kind: 'approval';
55
+ /** Opaque render identity and one-shot remount axis. */
56
+ readonly key: string;
57
+ /** Tool requesting the decision. */
58
+ readonly toolName: string;
59
+ /** Correlated Tool call, when supplied by the asker. */
60
+ readonly callId: ToolCallId | undefined;
61
+ /** Human-readable reason supplied by the asker. */
62
+ readonly reason: string | undefined;
63
+ /** Localized presentation copy, when supplied by the asker. */
64
+ readonly displayReason: ApprovalPresentationRequest['displayReason'];
65
+ /** Result returned by the Remote Event listener to the Host waterfall. */
66
+ readonly result: Promise<ApprovalDecision>;
67
+ /**
68
+ * @param sessionId - Agent/Session identity owning the scoped request.
69
+ * @param request - Host approval request projected through the Remote Event.
70
+ */
71
+ constructor(sessionId: SessionId, request: ApprovalPresentationRequest);
72
+ /**
73
+ * Resolve the Host waterfall with the user's decision.
74
+ * @param outcome - supported interactive decision.
75
+ */
76
+ answer(outcome: ApprovalDecision): Promise<void>;
77
+ /** Delegate an unanswered request to the next waterfall listener. */
78
+ delegate(): void;
79
+ /**
80
+ * Test whether a rejection requests waterfall delegation.
81
+ * @param reason - rejection received from {@link PendingApproval.result}.
82
+ * @returns whether {@link PendingApproval.delegate} produced it.
83
+ */
84
+ isDelegation(reason: unknown): boolean;
85
+ /**
86
+ * End an unanswered presentation when its transport, scope, or plugin lifetime ends.
87
+ * @param reason - rejection exposed to the waiting Remote Event listener.
88
+ */
89
+ abort(reason: unknown): void;
90
+ private finish;
91
+ }
92
+ /** Locale lookup supplied by the approval registration. */
93
+ export interface ApprovalInjected {
94
+ /**
95
+ * Resolve requester-owned copy in the current UI language.
96
+ * @param reason - localized presentation text with an English fallback.
97
+ * @returns text for the active UI language.
98
+ */
99
+ resolveReason(reason: NonNullable<ApprovalPresentationRequest['displayReason']>): string;
100
+ }
101
+ /** Full props of the approval composer takeover. */
102
+ export type ApprovalComposerProps = PropsRuntime<'conversation.composer'> & PropsRenderSlots<'conversation.approval.detail'> & {
103
+ matched: PendingApproval;
104
+ } & PropsLocale<'approval'> & ApprovalInjected;
105
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1,12 @@
1
+ /** Browser approval consumer over the existing scoped Remote Event waterfall. */
2
+ import type { Context as ClientContext } from '@qilin-agent/kylin';
3
+ export type { ApprovalComposerProps, ApprovalDecision, ApprovalDetailOwnerProps, ApprovalPresentationRequest, PendingApproval, } from './contract/slots.ts';
4
+ export type { ApprovalKey } from './locales.ts';
5
+ /** Required services: Agent scopes, Remote Events, Session UI, Slot registry, and copy. */
6
+ export declare const inject: string[];
7
+ /**
8
+ * Install approval copy and the scoped waterfall consumer.
9
+ * @param ctx - Client root context.
10
+ */
11
+ export declare function apply(ctx: ClientContext): void;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,20 @@
1
+ /** `approval` namespace dictionaries. */
2
+ /** Simplified Chinese dictionary and key-set source of truth. */
3
+ export declare const zh: {
4
+ waiting: string;
5
+ 'detail.aria': string;
6
+ escalation: string;
7
+ reject: string;
8
+ allowOnce: string;
9
+ };
10
+ /** Approval dictionary key union. */
11
+ export type ApprovalKey = keyof typeof zh;
12
+ /** English dictionary, checked against the Chinese key set. */
13
+ export declare const en: {
14
+ waiting: string;
15
+ 'detail.aria': string;
16
+ escalation: string;
17
+ reject: string;
18
+ allowOnce: string;
19
+ };
20
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Browser-only approval presentation plugin; the Host capability is composed independently. */
2
+ /** Node plugin body. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "@qilin-agent/client-ui-approval",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Approval composer takeover over the scoped Remote Event waterfall",
4
+ "version": "3.1.4",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/client/ui-approval"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./client": {
25
+ "types": "./lib/types/client/index.d.ts",
26
+ "default": "./lib/client.js"
27
+ },
28
+ "./src/*": "./src/*",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "qilin": {
32
+ "client": {
33
+ "inject": [
34
+ "@qilin-agent/api-remotes",
35
+ "@qilin-agent/api-session-controller",
36
+ "@qilin-agent/client-locale",
37
+ "@qilin-agent/client-shortcuts",
38
+ "@qilin-agent/client-ui-conversation",
39
+ "@qilin-agent/client-ui-renderer",
40
+ "@qilin-agent/client-ui-session"
41
+ ],
42
+ "platform": "web"
43
+ }
44
+ },
45
+ "license": "MIT",
46
+ "peerDependencies": {
47
+ "@qilin-agent/kylin": "~4.0.3"
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "~18.3.1",
51
+ "react": "^18.2.0",
52
+ "@qilin-agent/kylin": "~4.0.3",
53
+ "@qilin-agent/client-locale": "3.1.4",
54
+ "@qilin-agent/api-remotes": "3.1.4",
55
+ "@qilin-agent/client-ui-conversation": "3.1.4",
56
+ "@qilin-agent/api-session-controller": "3.1.4",
57
+ "@qilin-agent/client-ui-primitives": "3.1.4",
58
+ "@qilin-agent/client-ui-session": "3.1.4",
59
+ "@qilin-agent/client-shortcuts": "3.1.4",
60
+ "@qilin-agent/client-ui-renderer": "3.1.4",
61
+ "@qilin-agent/llm": "3.1.4",
62
+ "@qilin-agent/client-ui-slots": "3.1.4",
63
+ "@qilin-agent/typert-protocol": "3.1.4",
64
+ "@qilin-agent/session": "3.1.4"
65
+ },
66
+ "files": [
67
+ "lib/index.js",
68
+ "lib/client.js",
69
+ "lib/types/**/*.d.ts"
70
+ ],
71
+ "scripts": {
72
+ "bundle": "tsdown",
73
+ "watch": "tsdown --watch"
74
+ }
6
75
  }