chatablex-web-sdk 1.0.32 → 1.0.36
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/dist/index.d.mts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +252 -6
- package/dist/index.mjs +252 -6
- package/package.json +3 -3
- package/src/assets/bee.png +0 -0
- package/src/assets.d.ts +4 -0
- package/src/index.ts +5 -1
- package/src/modules/agentLock.ts +313 -0
- package/src/modules/tool.ts +12 -1
- package/src/types.ts +58 -0
package/dist/index.d.mts
CHANGED
|
@@ -127,6 +127,8 @@ interface ChatableXInitConfig {
|
|
|
127
127
|
* calls reject with a clear error.
|
|
128
128
|
*/
|
|
129
129
|
apiBaseUrl?: string;
|
|
130
|
+
/** Agent lock configuration — blocks user input during tool execution. */
|
|
131
|
+
agentLock?: AgentLockConfig;
|
|
130
132
|
}
|
|
131
133
|
interface ChatableXAI {
|
|
132
134
|
chat(message: string, options?: ChatOptions): Promise<ChatResponse>;
|
|
@@ -261,6 +263,55 @@ interface ChatableXCloud {
|
|
|
261
263
|
/** Read the account's storage usage / quota. */
|
|
262
264
|
usage(): Promise<CloudUsage>;
|
|
263
265
|
}
|
|
266
|
+
interface AgentLockConfig {
|
|
267
|
+
/** Enable the agent lock feature (default: true). */
|
|
268
|
+
enabled?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* `"overlay"` — SDK renders a built-in transparent overlay (default).
|
|
271
|
+
* `"events-only"` — SDK only emits lock/unlock events; no overlay injected.
|
|
272
|
+
*/
|
|
273
|
+
mode?: 'overlay' | 'events-only';
|
|
274
|
+
/** URL of the logo displayed in the overlay centre. Defaults to built-in Chatablex SVG. */
|
|
275
|
+
logoUrl?: string;
|
|
276
|
+
/** Message shown below the logo (default: "Agent 正在操作,请稍候…"). */
|
|
277
|
+
message?: string;
|
|
278
|
+
/** Show a cancel button on the overlay (default: true). */
|
|
279
|
+
allowCancel?: boolean;
|
|
280
|
+
/** Overlay background opacity, 0–1 (default: 0.3). */
|
|
281
|
+
opacity?: number;
|
|
282
|
+
/** Auto-unlock timeout for a single tool execution, in ms (default: 30000). 0 disables. */
|
|
283
|
+
timeout?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Safety auto-unlock timeout for a whole agent turn, in ms (default: 0 =
|
|
286
|
+
* disabled, rely on the host's turn-end signal). When the host drives a
|
|
287
|
+
* turn-level lock (the lock spans the entire agent response, not just one
|
|
288
|
+
* tool), this acts purely as a fallback in case the turn-end signal is lost.
|
|
289
|
+
*/
|
|
290
|
+
turnTimeout?: number;
|
|
291
|
+
/** Delay before actually removing the overlay after unlock, to avoid flicker between consecutive tools (default: 200ms). */
|
|
292
|
+
debounceUnlock?: number;
|
|
293
|
+
}
|
|
294
|
+
type AgentLockEventType = 'lock' | 'unlock' | 'cancel' | 'timeout';
|
|
295
|
+
interface AgentLockEventData {
|
|
296
|
+
requestId?: string;
|
|
297
|
+
timestamp: number;
|
|
298
|
+
}
|
|
299
|
+
type AgentLockEventHandler = (data: AgentLockEventData) => void;
|
|
300
|
+
interface ChatableXAgentLock {
|
|
301
|
+
/** Manually lock user interaction with an optional custom message / timeout. */
|
|
302
|
+
lock(opts?: {
|
|
303
|
+
message?: string;
|
|
304
|
+
timeout?: number;
|
|
305
|
+
}): void;
|
|
306
|
+
/** Manually unlock. Safe to call when already unlocked. */
|
|
307
|
+
unlock(): void;
|
|
308
|
+
/** Whether the overlay is currently active. */
|
|
309
|
+
isLocked(): boolean;
|
|
310
|
+
/** Subscribe to lock lifecycle events. Returns an unsubscribe function. */
|
|
311
|
+
on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void;
|
|
312
|
+
/** Remove a previously registered handler. */
|
|
313
|
+
off(event: AgentLockEventType, handler: AgentLockEventHandler): void;
|
|
314
|
+
}
|
|
264
315
|
interface ChatableXSDK {
|
|
265
316
|
ai: ChatableXAI;
|
|
266
317
|
tools: ChatableXTools;
|
|
@@ -271,6 +322,7 @@ interface ChatableXSDK {
|
|
|
271
322
|
platform: ChatableXPlatform;
|
|
272
323
|
auth: ChatableXAuth;
|
|
273
324
|
cloud: ChatableXCloud;
|
|
325
|
+
agentLock: ChatableXAgentLock;
|
|
274
326
|
}
|
|
275
327
|
declare global {
|
|
276
328
|
interface Window {
|
|
@@ -386,4 +438,4 @@ declare const ChatableX: {
|
|
|
386
438
|
version: string;
|
|
387
439
|
};
|
|
388
440
|
|
|
389
|
-
export { type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
|
441
|
+
export { type AgentLockConfig, type AgentLockEventData, type AgentLockEventHandler, type AgentLockEventType, type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAgentLock, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,6 +127,8 @@ interface ChatableXInitConfig {
|
|
|
127
127
|
* calls reject with a clear error.
|
|
128
128
|
*/
|
|
129
129
|
apiBaseUrl?: string;
|
|
130
|
+
/** Agent lock configuration — blocks user input during tool execution. */
|
|
131
|
+
agentLock?: AgentLockConfig;
|
|
130
132
|
}
|
|
131
133
|
interface ChatableXAI {
|
|
132
134
|
chat(message: string, options?: ChatOptions): Promise<ChatResponse>;
|
|
@@ -261,6 +263,55 @@ interface ChatableXCloud {
|
|
|
261
263
|
/** Read the account's storage usage / quota. */
|
|
262
264
|
usage(): Promise<CloudUsage>;
|
|
263
265
|
}
|
|
266
|
+
interface AgentLockConfig {
|
|
267
|
+
/** Enable the agent lock feature (default: true). */
|
|
268
|
+
enabled?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* `"overlay"` — SDK renders a built-in transparent overlay (default).
|
|
271
|
+
* `"events-only"` — SDK only emits lock/unlock events; no overlay injected.
|
|
272
|
+
*/
|
|
273
|
+
mode?: 'overlay' | 'events-only';
|
|
274
|
+
/** URL of the logo displayed in the overlay centre. Defaults to built-in Chatablex SVG. */
|
|
275
|
+
logoUrl?: string;
|
|
276
|
+
/** Message shown below the logo (default: "Agent 正在操作,请稍候…"). */
|
|
277
|
+
message?: string;
|
|
278
|
+
/** Show a cancel button on the overlay (default: true). */
|
|
279
|
+
allowCancel?: boolean;
|
|
280
|
+
/** Overlay background opacity, 0–1 (default: 0.3). */
|
|
281
|
+
opacity?: number;
|
|
282
|
+
/** Auto-unlock timeout for a single tool execution, in ms (default: 30000). 0 disables. */
|
|
283
|
+
timeout?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Safety auto-unlock timeout for a whole agent turn, in ms (default: 0 =
|
|
286
|
+
* disabled, rely on the host's turn-end signal). When the host drives a
|
|
287
|
+
* turn-level lock (the lock spans the entire agent response, not just one
|
|
288
|
+
* tool), this acts purely as a fallback in case the turn-end signal is lost.
|
|
289
|
+
*/
|
|
290
|
+
turnTimeout?: number;
|
|
291
|
+
/** Delay before actually removing the overlay after unlock, to avoid flicker between consecutive tools (default: 200ms). */
|
|
292
|
+
debounceUnlock?: number;
|
|
293
|
+
}
|
|
294
|
+
type AgentLockEventType = 'lock' | 'unlock' | 'cancel' | 'timeout';
|
|
295
|
+
interface AgentLockEventData {
|
|
296
|
+
requestId?: string;
|
|
297
|
+
timestamp: number;
|
|
298
|
+
}
|
|
299
|
+
type AgentLockEventHandler = (data: AgentLockEventData) => void;
|
|
300
|
+
interface ChatableXAgentLock {
|
|
301
|
+
/** Manually lock user interaction with an optional custom message / timeout. */
|
|
302
|
+
lock(opts?: {
|
|
303
|
+
message?: string;
|
|
304
|
+
timeout?: number;
|
|
305
|
+
}): void;
|
|
306
|
+
/** Manually unlock. Safe to call when already unlocked. */
|
|
307
|
+
unlock(): void;
|
|
308
|
+
/** Whether the overlay is currently active. */
|
|
309
|
+
isLocked(): boolean;
|
|
310
|
+
/** Subscribe to lock lifecycle events. Returns an unsubscribe function. */
|
|
311
|
+
on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void;
|
|
312
|
+
/** Remove a previously registered handler. */
|
|
313
|
+
off(event: AgentLockEventType, handler: AgentLockEventHandler): void;
|
|
314
|
+
}
|
|
264
315
|
interface ChatableXSDK {
|
|
265
316
|
ai: ChatableXAI;
|
|
266
317
|
tools: ChatableXTools;
|
|
@@ -271,6 +322,7 @@ interface ChatableXSDK {
|
|
|
271
322
|
platform: ChatableXPlatform;
|
|
272
323
|
auth: ChatableXAuth;
|
|
273
324
|
cloud: ChatableXCloud;
|
|
325
|
+
agentLock: ChatableXAgentLock;
|
|
274
326
|
}
|
|
275
327
|
declare global {
|
|
276
328
|
interface Window {
|
|
@@ -386,4 +438,4 @@ declare const ChatableX: {
|
|
|
386
438
|
version: string;
|
|
387
439
|
};
|
|
388
440
|
|
|
389
|
-
export { type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
|
441
|
+
export { type AgentLockConfig, type AgentLockEventData, type AgentLockEventHandler, type AgentLockEventType, type AiResponseEventData, type AuthTokenData, Bridge, type ChatOptions, type ChatResponse, ChatableX, type ChatableXAI, type ChatableXAgentLock, type ChatableXAuth, type ChatableXCloud, type ChatableXEvents, type ChatableXInitConfig, type ChatableXPlatform, type ChatableXSDK, type ChatableXStorage, type ChatableXToolModule, type ChatableXTools, type ChatableXUI, type CloseEventData, CloudAuthRequiredError, CloudError, type CloudFileInfo, type CloudListOptions, CloudQuotaExceededError, CloudSubscriptionRequiredError, type CloudUploadData, type CloudUploadOptions, type CloudUploadResult, type CloudUsage, type EventCallbackMap, type EventType, type FilePickerOptions, type Message, type NotificationType, SDK_VERSION, type SessionContext, type StateUpdate, type StreamingContentEventData, type TabConfig, type ToolCall, type ToolExecuteHandler, type ToolExecutionEventData, type ToolInfo, type ToolParameter, type ToolResult, type Unsubscribe, type UserMessageEventData };
|
package/dist/index.js
CHANGED
|
@@ -164,7 +164,7 @@ var Bridge = class {
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
// src/modules/tool.ts
|
|
167
|
-
function createToolModule(bridge, appId) {
|
|
167
|
+
function createToolModule(bridge, appId, agentLock) {
|
|
168
168
|
let _info = { id: appId, name: appId, version: "1.0.0", description: "" };
|
|
169
169
|
let _handler = null;
|
|
170
170
|
const dispatch = async (params) => {
|
|
@@ -182,6 +182,7 @@ function createToolModule(bridge, appId) {
|
|
|
182
182
|
bridge.addEventListener("toolExecution", async (data) => {
|
|
183
183
|
const params = data;
|
|
184
184
|
const requestId = params._requestId;
|
|
185
|
+
if (requestId && agentLock) agentLock._autoLock(requestId);
|
|
185
186
|
const result = await dispatch(params);
|
|
186
187
|
if (requestId && window.ChatableXBridge) {
|
|
187
188
|
window.ChatableXBridge.postMessage(JSON.stringify({
|
|
@@ -189,6 +190,7 @@ function createToolModule(bridge, appId) {
|
|
|
189
190
|
params: { _requestId: requestId, ...result }
|
|
190
191
|
}));
|
|
191
192
|
}
|
|
193
|
+
if (requestId && agentLock) agentLock._autoUnlock(requestId);
|
|
192
194
|
});
|
|
193
195
|
return {
|
|
194
196
|
getInfo() {
|
|
@@ -539,10 +541,252 @@ function createCloudModule(bridge, deps) {
|
|
|
539
541
|
};
|
|
540
542
|
}
|
|
541
543
|
|
|
544
|
+
// src/assets/bee.png
|
|
545
|
+
var bee_default = "data:image/png;base64,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";
|
|
546
|
+
|
|
547
|
+
// src/modules/agentLock.ts
|
|
548
|
+
var DEFAULT_CONFIG = {
|
|
549
|
+
enabled: true,
|
|
550
|
+
mode: "overlay",
|
|
551
|
+
logoUrl: "",
|
|
552
|
+
message: "Agent \u6B63\u5728\u64CD\u4F5C\uFF0C\u8BF7\u7A0D\u5019\u2026",
|
|
553
|
+
allowCancel: true,
|
|
554
|
+
opacity: 0.3,
|
|
555
|
+
timeout: 3e4,
|
|
556
|
+
turnTimeout: 0,
|
|
557
|
+
debounceUnlock: 200
|
|
558
|
+
};
|
|
559
|
+
var TURN_EVENT = "agentLock";
|
|
560
|
+
var OVERLAY_ID = "__chatablex_agent_lock_overlay__";
|
|
561
|
+
var BLOCKED_EVENTS = [
|
|
562
|
+
"mousedown",
|
|
563
|
+
"mouseup",
|
|
564
|
+
"click",
|
|
565
|
+
"dblclick",
|
|
566
|
+
"contextmenu",
|
|
567
|
+
"keydown",
|
|
568
|
+
"keyup",
|
|
569
|
+
"keypress",
|
|
570
|
+
"touchstart",
|
|
571
|
+
"touchmove",
|
|
572
|
+
"touchend",
|
|
573
|
+
"wheel",
|
|
574
|
+
"scroll",
|
|
575
|
+
"pointerdown",
|
|
576
|
+
"pointerup"
|
|
577
|
+
];
|
|
578
|
+
var CANCEL_BTN_ID = "__ctx_agent_lock_cancel__";
|
|
579
|
+
function blockEvent(e) {
|
|
580
|
+
const target = e.target;
|
|
581
|
+
if (target?.id === CANCEL_BTN_ID) return;
|
|
582
|
+
e.stopPropagation();
|
|
583
|
+
e.preventDefault();
|
|
584
|
+
}
|
|
585
|
+
function createAgentLockModule(bridge, userConfig = {}) {
|
|
586
|
+
const cfg = { ...DEFAULT_CONFIG, ...userConfig };
|
|
587
|
+
const logoSrc = cfg.logoUrl || bee_default;
|
|
588
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
589
|
+
let overlayEl = null;
|
|
590
|
+
let locked = false;
|
|
591
|
+
let lockCount = 0;
|
|
592
|
+
let turnActive = false;
|
|
593
|
+
let timeoutTimer = null;
|
|
594
|
+
let debounceTimer = null;
|
|
595
|
+
let currentMessage = cfg.message;
|
|
596
|
+
function emit(event, data = {}) {
|
|
597
|
+
const payload = { timestamp: Date.now(), ...data };
|
|
598
|
+
const handlers = listeners.get(event);
|
|
599
|
+
if (handlers) {
|
|
600
|
+
for (const fn of handlers) {
|
|
601
|
+
try {
|
|
602
|
+
fn(payload);
|
|
603
|
+
} catch (e) {
|
|
604
|
+
console.error("[ChatableX AgentLock] event handler error:", e);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function injectOverlay(message) {
|
|
610
|
+
if (typeof document === "undefined") return;
|
|
611
|
+
if (document.getElementById(OVERLAY_ID)) return;
|
|
612
|
+
const el = document.createElement("div");
|
|
613
|
+
el.id = OVERLAY_ID;
|
|
614
|
+
el.setAttribute("aria-hidden", "true");
|
|
615
|
+
el.style.cssText = [
|
|
616
|
+
"position:fixed",
|
|
617
|
+
"inset:0",
|
|
618
|
+
`z-index:2147483646`,
|
|
619
|
+
`background:rgba(255,255,255,${cfg.opacity})`,
|
|
620
|
+
"display:flex",
|
|
621
|
+
"flex-direction:column",
|
|
622
|
+
"align-items:center",
|
|
623
|
+
"justify-content:center",
|
|
624
|
+
"pointer-events:all",
|
|
625
|
+
"user-select:none",
|
|
626
|
+
"backdrop-filter:blur(1px)",
|
|
627
|
+
"-webkit-backdrop-filter:blur(1px)"
|
|
628
|
+
].join(";");
|
|
629
|
+
el.innerHTML = `
|
|
630
|
+
<img src="${logoSrc}" alt="" style="width:48px;height:48px;animation:__ctx_spin 1.5s linear infinite;" />
|
|
631
|
+
<p style="margin:12px 0 0;font:14px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#666;">${message}</p>
|
|
632
|
+
${cfg.allowCancel ? '<button id="__ctx_agent_lock_cancel__" style="margin-top:16px;background:none;border:none;color:#6366f1;font:13px -apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;text-decoration:underline;padding:4px 8px;">\u53D6\u6D88</button>' : ""}
|
|
633
|
+
`;
|
|
634
|
+
if (!document.getElementById("__ctx_agent_lock_style__")) {
|
|
635
|
+
const style = document.createElement("style");
|
|
636
|
+
style.id = "__ctx_agent_lock_style__";
|
|
637
|
+
style.textContent = "@keyframes __ctx_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}";
|
|
638
|
+
document.head.appendChild(style);
|
|
639
|
+
}
|
|
640
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
641
|
+
el.addEventListener(evt, blockEvent, { capture: true, passive: false });
|
|
642
|
+
}
|
|
643
|
+
if (cfg.allowCancel) {
|
|
644
|
+
queueMicrotask(() => {
|
|
645
|
+
const btn = el.querySelector("#__ctx_agent_lock_cancel__");
|
|
646
|
+
if (btn) {
|
|
647
|
+
btn.addEventListener("click", (e) => {
|
|
648
|
+
e.stopPropagation();
|
|
649
|
+
handleCancel();
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
document.body.appendChild(el);
|
|
655
|
+
overlayEl = el;
|
|
656
|
+
}
|
|
657
|
+
function removeOverlay() {
|
|
658
|
+
if (overlayEl) {
|
|
659
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
660
|
+
overlayEl.removeEventListener(evt, blockEvent, { capture: true });
|
|
661
|
+
}
|
|
662
|
+
overlayEl.remove();
|
|
663
|
+
overlayEl = null;
|
|
664
|
+
}
|
|
665
|
+
const style = typeof document !== "undefined" ? document.getElementById("__ctx_agent_lock_style__") : null;
|
|
666
|
+
if (style) style.remove();
|
|
667
|
+
}
|
|
668
|
+
function startTimeout(ms, requestId) {
|
|
669
|
+
clearTimeoutTimer();
|
|
670
|
+
if (ms <= 0) return;
|
|
671
|
+
timeoutTimer = setTimeout(() => {
|
|
672
|
+
console.warn("[ChatableX] Agent lock timeout \u2014 auto-unlocking");
|
|
673
|
+
forceUnlock();
|
|
674
|
+
emit("timeout", { requestId });
|
|
675
|
+
}, ms);
|
|
676
|
+
}
|
|
677
|
+
function clearTimeoutTimer() {
|
|
678
|
+
if (timeoutTimer !== null) {
|
|
679
|
+
clearTimeout(timeoutTimer);
|
|
680
|
+
timeoutTimer = null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function clearDebounceTimer() {
|
|
684
|
+
if (debounceTimer !== null) {
|
|
685
|
+
clearTimeout(debounceTimer);
|
|
686
|
+
debounceTimer = null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function doLock(message, timeout, requestId) {
|
|
690
|
+
if (locked) return;
|
|
691
|
+
locked = true;
|
|
692
|
+
currentMessage = message;
|
|
693
|
+
if (cfg.mode === "overlay") {
|
|
694
|
+
injectOverlay(currentMessage);
|
|
695
|
+
}
|
|
696
|
+
startTimeout(timeout, requestId);
|
|
697
|
+
emit("lock", { requestId });
|
|
698
|
+
}
|
|
699
|
+
function forceUnlock(requestId) {
|
|
700
|
+
if (!locked) return;
|
|
701
|
+
locked = false;
|
|
702
|
+
lockCount = 0;
|
|
703
|
+
turnActive = false;
|
|
704
|
+
clearTimeoutTimer();
|
|
705
|
+
clearDebounceTimer();
|
|
706
|
+
if (cfg.mode === "overlay") {
|
|
707
|
+
removeOverlay();
|
|
708
|
+
}
|
|
709
|
+
emit("unlock", { requestId });
|
|
710
|
+
}
|
|
711
|
+
function maybeUnlock(requestId) {
|
|
712
|
+
if (turnActive || lockCount > 0) return;
|
|
713
|
+
forceUnlock(requestId);
|
|
714
|
+
}
|
|
715
|
+
function scheduleDebouncedUnlock(requestId) {
|
|
716
|
+
clearDebounceTimer();
|
|
717
|
+
debounceTimer = setTimeout(() => maybeUnlock(requestId), cfg.debounceUnlock);
|
|
718
|
+
}
|
|
719
|
+
function handleCancel() {
|
|
720
|
+
const rid = void 0;
|
|
721
|
+
forceUnlock(rid);
|
|
722
|
+
emit("cancel", { requestId: rid });
|
|
723
|
+
}
|
|
724
|
+
function setTurn(active) {
|
|
725
|
+
if (!cfg.enabled) return;
|
|
726
|
+
if (active) {
|
|
727
|
+
turnActive = true;
|
|
728
|
+
clearDebounceTimer();
|
|
729
|
+
if (!locked) {
|
|
730
|
+
doLock(cfg.message, cfg.turnTimeout);
|
|
731
|
+
} else {
|
|
732
|
+
startTimeout(cfg.turnTimeout);
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
turnActive = false;
|
|
736
|
+
if (lockCount === 0) scheduleDebouncedUnlock();
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function lock(opts) {
|
|
740
|
+
if (!cfg.enabled) return;
|
|
741
|
+
const msg = opts?.message ?? cfg.message;
|
|
742
|
+
const timeout = opts?.timeout ?? cfg.timeout;
|
|
743
|
+
doLock(msg, timeout);
|
|
744
|
+
}
|
|
745
|
+
function unlock() {
|
|
746
|
+
forceUnlock();
|
|
747
|
+
}
|
|
748
|
+
function isLocked() {
|
|
749
|
+
return locked;
|
|
750
|
+
}
|
|
751
|
+
function on(event, handler) {
|
|
752
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
753
|
+
listeners.get(event).add(handler);
|
|
754
|
+
return () => off(event, handler);
|
|
755
|
+
}
|
|
756
|
+
function off(event, handler) {
|
|
757
|
+
listeners.get(event)?.delete(handler);
|
|
758
|
+
}
|
|
759
|
+
function _autoLock(requestId) {
|
|
760
|
+
if (!cfg.enabled) return;
|
|
761
|
+
clearDebounceTimer();
|
|
762
|
+
lockCount++;
|
|
763
|
+
if (!locked) {
|
|
764
|
+
doLock(cfg.message, turnActive ? cfg.turnTimeout : cfg.timeout, requestId);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function _autoUnlock(requestId) {
|
|
768
|
+
if (!cfg.enabled) return;
|
|
769
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
770
|
+
if (lockCount === 0) {
|
|
771
|
+
scheduleDebouncedUnlock(requestId);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function _destroy() {
|
|
775
|
+
if (removeTurnListener) removeTurnListener();
|
|
776
|
+
forceUnlock();
|
|
777
|
+
listeners.clear();
|
|
778
|
+
}
|
|
779
|
+
const removeTurnListener = bridge.addEventListener(TURN_EVENT, (data) => {
|
|
780
|
+
const active = !!data?.active;
|
|
781
|
+
setTurn(active);
|
|
782
|
+
});
|
|
783
|
+
return { lock, unlock, isLocked, on, off, _autoLock, _autoUnlock, _destroy };
|
|
784
|
+
}
|
|
785
|
+
|
|
542
786
|
// package.json
|
|
543
787
|
var package_default = {
|
|
544
788
|
name: "chatablex-web-sdk",
|
|
545
|
-
version: "1.0.
|
|
789
|
+
version: "1.0.36",
|
|
546
790
|
description: "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
547
791
|
main: "dist/index.js",
|
|
548
792
|
module: "dist/index.mjs",
|
|
@@ -561,8 +805,8 @@ var package_default = {
|
|
|
561
805
|
"README.zh-CN.md"
|
|
562
806
|
],
|
|
563
807
|
scripts: {
|
|
564
|
-
build: "tsup
|
|
565
|
-
dev: "tsup
|
|
808
|
+
build: "tsup",
|
|
809
|
+
dev: "tsup --watch",
|
|
566
810
|
typecheck: "tsc --noEmit",
|
|
567
811
|
test: "vitest run",
|
|
568
812
|
"test:watch": "vitest",
|
|
@@ -632,7 +876,8 @@ var ChatableX = {
|
|
|
632
876
|
} catch {
|
|
633
877
|
if (debug) console.warn("[ChatableX] sdk_init handshake failed, continuing with defaults");
|
|
634
878
|
}
|
|
635
|
-
const
|
|
879
|
+
const agentLockModule = createAgentLockModule(bridge, config.agentLock);
|
|
880
|
+
const toolModule = createToolModule(bridge, config.appId, agentLockModule);
|
|
636
881
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
637
882
|
const authModule = createAuthModule(bridge);
|
|
638
883
|
const sdk = {
|
|
@@ -648,7 +893,8 @@ var ChatableX = {
|
|
|
648
893
|
appId: config.appId,
|
|
649
894
|
auth: authModule,
|
|
650
895
|
apiBaseUrl: config.apiBaseUrl
|
|
651
|
-
})
|
|
896
|
+
}),
|
|
897
|
+
agentLock: agentLockModule
|
|
652
898
|
};
|
|
653
899
|
window.ChatableX = sdk;
|
|
654
900
|
_instance = sdk;
|
package/dist/index.mjs
CHANGED
|
@@ -132,7 +132,7 @@ var Bridge = class {
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
// src/modules/tool.ts
|
|
135
|
-
function createToolModule(bridge, appId) {
|
|
135
|
+
function createToolModule(bridge, appId, agentLock) {
|
|
136
136
|
let _info = { id: appId, name: appId, version: "1.0.0", description: "" };
|
|
137
137
|
let _handler = null;
|
|
138
138
|
const dispatch = async (params) => {
|
|
@@ -150,6 +150,7 @@ function createToolModule(bridge, appId) {
|
|
|
150
150
|
bridge.addEventListener("toolExecution", async (data) => {
|
|
151
151
|
const params = data;
|
|
152
152
|
const requestId = params._requestId;
|
|
153
|
+
if (requestId && agentLock) agentLock._autoLock(requestId);
|
|
153
154
|
const result = await dispatch(params);
|
|
154
155
|
if (requestId && window.ChatableXBridge) {
|
|
155
156
|
window.ChatableXBridge.postMessage(JSON.stringify({
|
|
@@ -157,6 +158,7 @@ function createToolModule(bridge, appId) {
|
|
|
157
158
|
params: { _requestId: requestId, ...result }
|
|
158
159
|
}));
|
|
159
160
|
}
|
|
161
|
+
if (requestId && agentLock) agentLock._autoUnlock(requestId);
|
|
160
162
|
});
|
|
161
163
|
return {
|
|
162
164
|
getInfo() {
|
|
@@ -507,10 +509,252 @@ function createCloudModule(bridge, deps) {
|
|
|
507
509
|
};
|
|
508
510
|
}
|
|
509
511
|
|
|
512
|
+
// src/assets/bee.png
|
|
513
|
+
var bee_default = "data:image/png;base64,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";
|
|
514
|
+
|
|
515
|
+
// src/modules/agentLock.ts
|
|
516
|
+
var DEFAULT_CONFIG = {
|
|
517
|
+
enabled: true,
|
|
518
|
+
mode: "overlay",
|
|
519
|
+
logoUrl: "",
|
|
520
|
+
message: "Agent \u6B63\u5728\u64CD\u4F5C\uFF0C\u8BF7\u7A0D\u5019\u2026",
|
|
521
|
+
allowCancel: true,
|
|
522
|
+
opacity: 0.3,
|
|
523
|
+
timeout: 3e4,
|
|
524
|
+
turnTimeout: 0,
|
|
525
|
+
debounceUnlock: 200
|
|
526
|
+
};
|
|
527
|
+
var TURN_EVENT = "agentLock";
|
|
528
|
+
var OVERLAY_ID = "__chatablex_agent_lock_overlay__";
|
|
529
|
+
var BLOCKED_EVENTS = [
|
|
530
|
+
"mousedown",
|
|
531
|
+
"mouseup",
|
|
532
|
+
"click",
|
|
533
|
+
"dblclick",
|
|
534
|
+
"contextmenu",
|
|
535
|
+
"keydown",
|
|
536
|
+
"keyup",
|
|
537
|
+
"keypress",
|
|
538
|
+
"touchstart",
|
|
539
|
+
"touchmove",
|
|
540
|
+
"touchend",
|
|
541
|
+
"wheel",
|
|
542
|
+
"scroll",
|
|
543
|
+
"pointerdown",
|
|
544
|
+
"pointerup"
|
|
545
|
+
];
|
|
546
|
+
var CANCEL_BTN_ID = "__ctx_agent_lock_cancel__";
|
|
547
|
+
function blockEvent(e) {
|
|
548
|
+
const target = e.target;
|
|
549
|
+
if (target?.id === CANCEL_BTN_ID) return;
|
|
550
|
+
e.stopPropagation();
|
|
551
|
+
e.preventDefault();
|
|
552
|
+
}
|
|
553
|
+
function createAgentLockModule(bridge, userConfig = {}) {
|
|
554
|
+
const cfg = { ...DEFAULT_CONFIG, ...userConfig };
|
|
555
|
+
const logoSrc = cfg.logoUrl || bee_default;
|
|
556
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
557
|
+
let overlayEl = null;
|
|
558
|
+
let locked = false;
|
|
559
|
+
let lockCount = 0;
|
|
560
|
+
let turnActive = false;
|
|
561
|
+
let timeoutTimer = null;
|
|
562
|
+
let debounceTimer = null;
|
|
563
|
+
let currentMessage = cfg.message;
|
|
564
|
+
function emit(event, data = {}) {
|
|
565
|
+
const payload = { timestamp: Date.now(), ...data };
|
|
566
|
+
const handlers = listeners.get(event);
|
|
567
|
+
if (handlers) {
|
|
568
|
+
for (const fn of handlers) {
|
|
569
|
+
try {
|
|
570
|
+
fn(payload);
|
|
571
|
+
} catch (e) {
|
|
572
|
+
console.error("[ChatableX AgentLock] event handler error:", e);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function injectOverlay(message) {
|
|
578
|
+
if (typeof document === "undefined") return;
|
|
579
|
+
if (document.getElementById(OVERLAY_ID)) return;
|
|
580
|
+
const el = document.createElement("div");
|
|
581
|
+
el.id = OVERLAY_ID;
|
|
582
|
+
el.setAttribute("aria-hidden", "true");
|
|
583
|
+
el.style.cssText = [
|
|
584
|
+
"position:fixed",
|
|
585
|
+
"inset:0",
|
|
586
|
+
`z-index:2147483646`,
|
|
587
|
+
`background:rgba(255,255,255,${cfg.opacity})`,
|
|
588
|
+
"display:flex",
|
|
589
|
+
"flex-direction:column",
|
|
590
|
+
"align-items:center",
|
|
591
|
+
"justify-content:center",
|
|
592
|
+
"pointer-events:all",
|
|
593
|
+
"user-select:none",
|
|
594
|
+
"backdrop-filter:blur(1px)",
|
|
595
|
+
"-webkit-backdrop-filter:blur(1px)"
|
|
596
|
+
].join(";");
|
|
597
|
+
el.innerHTML = `
|
|
598
|
+
<img src="${logoSrc}" alt="" style="width:48px;height:48px;animation:__ctx_spin 1.5s linear infinite;" />
|
|
599
|
+
<p style="margin:12px 0 0;font:14px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#666;">${message}</p>
|
|
600
|
+
${cfg.allowCancel ? '<button id="__ctx_agent_lock_cancel__" style="margin-top:16px;background:none;border:none;color:#6366f1;font:13px -apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;text-decoration:underline;padding:4px 8px;">\u53D6\u6D88</button>' : ""}
|
|
601
|
+
`;
|
|
602
|
+
if (!document.getElementById("__ctx_agent_lock_style__")) {
|
|
603
|
+
const style = document.createElement("style");
|
|
604
|
+
style.id = "__ctx_agent_lock_style__";
|
|
605
|
+
style.textContent = "@keyframes __ctx_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}";
|
|
606
|
+
document.head.appendChild(style);
|
|
607
|
+
}
|
|
608
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
609
|
+
el.addEventListener(evt, blockEvent, { capture: true, passive: false });
|
|
610
|
+
}
|
|
611
|
+
if (cfg.allowCancel) {
|
|
612
|
+
queueMicrotask(() => {
|
|
613
|
+
const btn = el.querySelector("#__ctx_agent_lock_cancel__");
|
|
614
|
+
if (btn) {
|
|
615
|
+
btn.addEventListener("click", (e) => {
|
|
616
|
+
e.stopPropagation();
|
|
617
|
+
handleCancel();
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
document.body.appendChild(el);
|
|
623
|
+
overlayEl = el;
|
|
624
|
+
}
|
|
625
|
+
function removeOverlay() {
|
|
626
|
+
if (overlayEl) {
|
|
627
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
628
|
+
overlayEl.removeEventListener(evt, blockEvent, { capture: true });
|
|
629
|
+
}
|
|
630
|
+
overlayEl.remove();
|
|
631
|
+
overlayEl = null;
|
|
632
|
+
}
|
|
633
|
+
const style = typeof document !== "undefined" ? document.getElementById("__ctx_agent_lock_style__") : null;
|
|
634
|
+
if (style) style.remove();
|
|
635
|
+
}
|
|
636
|
+
function startTimeout(ms, requestId) {
|
|
637
|
+
clearTimeoutTimer();
|
|
638
|
+
if (ms <= 0) return;
|
|
639
|
+
timeoutTimer = setTimeout(() => {
|
|
640
|
+
console.warn("[ChatableX] Agent lock timeout \u2014 auto-unlocking");
|
|
641
|
+
forceUnlock();
|
|
642
|
+
emit("timeout", { requestId });
|
|
643
|
+
}, ms);
|
|
644
|
+
}
|
|
645
|
+
function clearTimeoutTimer() {
|
|
646
|
+
if (timeoutTimer !== null) {
|
|
647
|
+
clearTimeout(timeoutTimer);
|
|
648
|
+
timeoutTimer = null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function clearDebounceTimer() {
|
|
652
|
+
if (debounceTimer !== null) {
|
|
653
|
+
clearTimeout(debounceTimer);
|
|
654
|
+
debounceTimer = null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function doLock(message, timeout, requestId) {
|
|
658
|
+
if (locked) return;
|
|
659
|
+
locked = true;
|
|
660
|
+
currentMessage = message;
|
|
661
|
+
if (cfg.mode === "overlay") {
|
|
662
|
+
injectOverlay(currentMessage);
|
|
663
|
+
}
|
|
664
|
+
startTimeout(timeout, requestId);
|
|
665
|
+
emit("lock", { requestId });
|
|
666
|
+
}
|
|
667
|
+
function forceUnlock(requestId) {
|
|
668
|
+
if (!locked) return;
|
|
669
|
+
locked = false;
|
|
670
|
+
lockCount = 0;
|
|
671
|
+
turnActive = false;
|
|
672
|
+
clearTimeoutTimer();
|
|
673
|
+
clearDebounceTimer();
|
|
674
|
+
if (cfg.mode === "overlay") {
|
|
675
|
+
removeOverlay();
|
|
676
|
+
}
|
|
677
|
+
emit("unlock", { requestId });
|
|
678
|
+
}
|
|
679
|
+
function maybeUnlock(requestId) {
|
|
680
|
+
if (turnActive || lockCount > 0) return;
|
|
681
|
+
forceUnlock(requestId);
|
|
682
|
+
}
|
|
683
|
+
function scheduleDebouncedUnlock(requestId) {
|
|
684
|
+
clearDebounceTimer();
|
|
685
|
+
debounceTimer = setTimeout(() => maybeUnlock(requestId), cfg.debounceUnlock);
|
|
686
|
+
}
|
|
687
|
+
function handleCancel() {
|
|
688
|
+
const rid = void 0;
|
|
689
|
+
forceUnlock(rid);
|
|
690
|
+
emit("cancel", { requestId: rid });
|
|
691
|
+
}
|
|
692
|
+
function setTurn(active) {
|
|
693
|
+
if (!cfg.enabled) return;
|
|
694
|
+
if (active) {
|
|
695
|
+
turnActive = true;
|
|
696
|
+
clearDebounceTimer();
|
|
697
|
+
if (!locked) {
|
|
698
|
+
doLock(cfg.message, cfg.turnTimeout);
|
|
699
|
+
} else {
|
|
700
|
+
startTimeout(cfg.turnTimeout);
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
turnActive = false;
|
|
704
|
+
if (lockCount === 0) scheduleDebouncedUnlock();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function lock(opts) {
|
|
708
|
+
if (!cfg.enabled) return;
|
|
709
|
+
const msg = opts?.message ?? cfg.message;
|
|
710
|
+
const timeout = opts?.timeout ?? cfg.timeout;
|
|
711
|
+
doLock(msg, timeout);
|
|
712
|
+
}
|
|
713
|
+
function unlock() {
|
|
714
|
+
forceUnlock();
|
|
715
|
+
}
|
|
716
|
+
function isLocked() {
|
|
717
|
+
return locked;
|
|
718
|
+
}
|
|
719
|
+
function on(event, handler) {
|
|
720
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
721
|
+
listeners.get(event).add(handler);
|
|
722
|
+
return () => off(event, handler);
|
|
723
|
+
}
|
|
724
|
+
function off(event, handler) {
|
|
725
|
+
listeners.get(event)?.delete(handler);
|
|
726
|
+
}
|
|
727
|
+
function _autoLock(requestId) {
|
|
728
|
+
if (!cfg.enabled) return;
|
|
729
|
+
clearDebounceTimer();
|
|
730
|
+
lockCount++;
|
|
731
|
+
if (!locked) {
|
|
732
|
+
doLock(cfg.message, turnActive ? cfg.turnTimeout : cfg.timeout, requestId);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function _autoUnlock(requestId) {
|
|
736
|
+
if (!cfg.enabled) return;
|
|
737
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
738
|
+
if (lockCount === 0) {
|
|
739
|
+
scheduleDebouncedUnlock(requestId);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function _destroy() {
|
|
743
|
+
if (removeTurnListener) removeTurnListener();
|
|
744
|
+
forceUnlock();
|
|
745
|
+
listeners.clear();
|
|
746
|
+
}
|
|
747
|
+
const removeTurnListener = bridge.addEventListener(TURN_EVENT, (data) => {
|
|
748
|
+
const active = !!data?.active;
|
|
749
|
+
setTurn(active);
|
|
750
|
+
});
|
|
751
|
+
return { lock, unlock, isLocked, on, off, _autoLock, _autoUnlock, _destroy };
|
|
752
|
+
}
|
|
753
|
+
|
|
510
754
|
// package.json
|
|
511
755
|
var package_default = {
|
|
512
756
|
name: "chatablex-web-sdk",
|
|
513
|
-
version: "1.0.
|
|
757
|
+
version: "1.0.36",
|
|
514
758
|
description: "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
515
759
|
main: "dist/index.js",
|
|
516
760
|
module: "dist/index.mjs",
|
|
@@ -529,8 +773,8 @@ var package_default = {
|
|
|
529
773
|
"README.zh-CN.md"
|
|
530
774
|
],
|
|
531
775
|
scripts: {
|
|
532
|
-
build: "tsup
|
|
533
|
-
dev: "tsup
|
|
776
|
+
build: "tsup",
|
|
777
|
+
dev: "tsup --watch",
|
|
534
778
|
typecheck: "tsc --noEmit",
|
|
535
779
|
test: "vitest run",
|
|
536
780
|
"test:watch": "vitest",
|
|
@@ -600,7 +844,8 @@ var ChatableX = {
|
|
|
600
844
|
} catch {
|
|
601
845
|
if (debug) console.warn("[ChatableX] sdk_init handshake failed, continuing with defaults");
|
|
602
846
|
}
|
|
603
|
-
const
|
|
847
|
+
const agentLockModule = createAgentLockModule(bridge, config.agentLock);
|
|
848
|
+
const toolModule = createToolModule(bridge, config.appId, agentLockModule);
|
|
604
849
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
605
850
|
const authModule = createAuthModule(bridge);
|
|
606
851
|
const sdk = {
|
|
@@ -616,7 +861,8 @@ var ChatableX = {
|
|
|
616
861
|
appId: config.appId,
|
|
617
862
|
auth: authModule,
|
|
618
863
|
apiBaseUrl: config.apiBaseUrl
|
|
619
|
-
})
|
|
864
|
+
}),
|
|
865
|
+
agentLock: agentLockModule
|
|
620
866
|
};
|
|
621
867
|
window.ChatableX = sdk;
|
|
622
868
|
_instance = sdk;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatablex-web-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"README.zh-CN.md"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "tsup
|
|
23
|
-
"dev": "tsup
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:watch": "vitest",
|
|
Binary file
|
package/src/assets.d.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { createToolsModule } from './modules/tools';
|
|
|
28
28
|
import { createPlatformModule } from './modules/platform';
|
|
29
29
|
import { createAuthModule } from './modules/auth';
|
|
30
30
|
import { createCloudModule } from './modules/cloud';
|
|
31
|
+
import { createAgentLockModule } from './modules/agentLock';
|
|
31
32
|
import type { ChatableXSDK, ChatableXInitConfig, ToolInfo } from './types';
|
|
32
33
|
import pkg from '../package.json';
|
|
33
34
|
|
|
@@ -77,7 +78,9 @@ export const ChatableX = {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// 4. Create modules
|
|
80
|
-
const
|
|
81
|
+
const agentLockModule = createAgentLockModule(bridge, config.agentLock);
|
|
82
|
+
|
|
83
|
+
const toolModule = createToolModule(bridge, config.appId, agentLockModule);
|
|
81
84
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
82
85
|
|
|
83
86
|
const authModule = createAuthModule(bridge);
|
|
@@ -96,6 +99,7 @@ export const ChatableX = {
|
|
|
96
99
|
auth: authModule,
|
|
97
100
|
apiBaseUrl: config.apiBaseUrl,
|
|
98
101
|
}),
|
|
102
|
+
agentLock: agentLockModule,
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
// Expose on window for debugging / Flutter interop
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import type { Bridge } from '../bridge';
|
|
2
|
+
import type {
|
|
3
|
+
AgentLockConfig,
|
|
4
|
+
AgentLockEventType,
|
|
5
|
+
AgentLockEventData,
|
|
6
|
+
AgentLockEventHandler,
|
|
7
|
+
ChatableXAgentLock,
|
|
8
|
+
} from '../types';
|
|
9
|
+
import beeLogo from '../assets/bee.png';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CONFIG: Required<AgentLockConfig> = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
mode: 'overlay',
|
|
14
|
+
logoUrl: '',
|
|
15
|
+
message: 'Agent 正在操作,请稍候…',
|
|
16
|
+
allowCancel: true,
|
|
17
|
+
opacity: 0.3,
|
|
18
|
+
timeout: 30_000,
|
|
19
|
+
turnTimeout: 0,
|
|
20
|
+
debounceUnlock: 200,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Bridge event pushed by the Flutter host to lock the UI for the *entire* agent
|
|
25
|
+
* turn (from the moment the agent starts responding until the turn ends),
|
|
26
|
+
* rather than only during each individual tool execution. Payload: `{ active }`.
|
|
27
|
+
*/
|
|
28
|
+
const TURN_EVENT = 'agentLock';
|
|
29
|
+
|
|
30
|
+
const OVERLAY_ID = '__chatablex_agent_lock_overlay__';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Blocked event types — we intercept these on the overlay to prevent user
|
|
34
|
+
* interaction from reaching the app underneath.
|
|
35
|
+
*/
|
|
36
|
+
const BLOCKED_EVENTS: string[] = [
|
|
37
|
+
'mousedown', 'mouseup', 'click', 'dblclick', 'contextmenu',
|
|
38
|
+
'keydown', 'keyup', 'keypress',
|
|
39
|
+
'touchstart', 'touchmove', 'touchend',
|
|
40
|
+
'wheel', 'scroll',
|
|
41
|
+
'pointerdown', 'pointerup',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const CANCEL_BTN_ID = '__ctx_agent_lock_cancel__';
|
|
45
|
+
|
|
46
|
+
function blockEvent(e: Event): void {
|
|
47
|
+
const target = e.target as HTMLElement | null;
|
|
48
|
+
if (target?.id === CANCEL_BTN_ID) return;
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AgentLockModule extends ChatableXAgentLock {
|
|
54
|
+
/**
|
|
55
|
+
* @internal — called by tool module to lock before dispatch and schedule
|
|
56
|
+
* unlock after result. Uses ref-counting to support consecutive tools.
|
|
57
|
+
*/
|
|
58
|
+
_autoLock(requestId: string): void;
|
|
59
|
+
/** @internal */
|
|
60
|
+
_autoUnlock(requestId: string): void;
|
|
61
|
+
/** @internal */
|
|
62
|
+
_destroy(): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createAgentLockModule(
|
|
66
|
+
bridge: Bridge,
|
|
67
|
+
userConfig: AgentLockConfig = {},
|
|
68
|
+
): AgentLockModule {
|
|
69
|
+
const cfg: Required<AgentLockConfig> = { ...DEFAULT_CONFIG, ...userConfig };
|
|
70
|
+
const logoSrc = cfg.logoUrl || beeLogo;
|
|
71
|
+
|
|
72
|
+
const listeners = new Map<AgentLockEventType, Set<AgentLockEventHandler>>();
|
|
73
|
+
let overlayEl: HTMLDivElement | null = null;
|
|
74
|
+
let locked = false;
|
|
75
|
+
let lockCount = 0;
|
|
76
|
+
/**
|
|
77
|
+
* True while an agent *turn* is in progress (driven by the host's
|
|
78
|
+
* {@link TURN_EVENT}). Keeps the overlay up across the whole response — and
|
|
79
|
+
* across the gaps between individual tool calls — until the turn ends.
|
|
80
|
+
*/
|
|
81
|
+
let turnActive = false;
|
|
82
|
+
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
83
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
84
|
+
let currentMessage = cfg.message;
|
|
85
|
+
|
|
86
|
+
function emit(event: AgentLockEventType, data: Partial<AgentLockEventData> = {}): void {
|
|
87
|
+
const payload: AgentLockEventData = { timestamp: Date.now(), ...data };
|
|
88
|
+
const handlers = listeners.get(event);
|
|
89
|
+
if (handlers) {
|
|
90
|
+
for (const fn of handlers) {
|
|
91
|
+
try { fn(payload); } catch (e) { console.error('[ChatableX AgentLock] event handler error:', e); }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function injectOverlay(message: string): void {
|
|
97
|
+
if (typeof document === 'undefined') return;
|
|
98
|
+
if (document.getElementById(OVERLAY_ID)) return;
|
|
99
|
+
|
|
100
|
+
const el = document.createElement('div');
|
|
101
|
+
el.id = OVERLAY_ID;
|
|
102
|
+
el.setAttribute('aria-hidden', 'true');
|
|
103
|
+
el.style.cssText = [
|
|
104
|
+
'position:fixed', 'inset:0', `z-index:2147483646`,
|
|
105
|
+
`background:rgba(255,255,255,${cfg.opacity})`,
|
|
106
|
+
'display:flex', 'flex-direction:column',
|
|
107
|
+
'align-items:center', 'justify-content:center',
|
|
108
|
+
'pointer-events:all', 'user-select:none',
|
|
109
|
+
'backdrop-filter:blur(1px)', '-webkit-backdrop-filter:blur(1px)',
|
|
110
|
+
].join(';');
|
|
111
|
+
|
|
112
|
+
el.innerHTML = `
|
|
113
|
+
<img src="${logoSrc}" alt="" style="width:48px;height:48px;animation:__ctx_spin 1.5s linear infinite;" />
|
|
114
|
+
<p style="margin:12px 0 0;font:14px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#666;">${message}</p>
|
|
115
|
+
${cfg.allowCancel ? '<button id="__ctx_agent_lock_cancel__" style="margin-top:16px;background:none;border:none;color:#6366f1;font:13px -apple-system,BlinkMacSystemFont,sans-serif;cursor:pointer;text-decoration:underline;padding:4px 8px;">取消</button>' : ''}
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
if (!document.getElementById('__ctx_agent_lock_style__')) {
|
|
119
|
+
const style = document.createElement('style');
|
|
120
|
+
style.id = '__ctx_agent_lock_style__';
|
|
121
|
+
style.textContent = '@keyframes __ctx_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}';
|
|
122
|
+
document.head.appendChild(style);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
126
|
+
el.addEventListener(evt, blockEvent, { capture: true, passive: false });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (cfg.allowCancel) {
|
|
130
|
+
// Defer binding so the DOM is ready
|
|
131
|
+
queueMicrotask(() => {
|
|
132
|
+
const btn = el.querySelector('#__ctx_agent_lock_cancel__');
|
|
133
|
+
if (btn) {
|
|
134
|
+
btn.addEventListener('click', (e) => {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
handleCancel();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
document.body.appendChild(el);
|
|
143
|
+
overlayEl = el;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function removeOverlay(): void {
|
|
147
|
+
if (overlayEl) {
|
|
148
|
+
for (const evt of BLOCKED_EVENTS) {
|
|
149
|
+
overlayEl.removeEventListener(evt, blockEvent, { capture: true } as EventListenerOptions);
|
|
150
|
+
}
|
|
151
|
+
overlayEl.remove();
|
|
152
|
+
overlayEl = null;
|
|
153
|
+
}
|
|
154
|
+
const style = typeof document !== 'undefined' ? document.getElementById('__ctx_agent_lock_style__') : null;
|
|
155
|
+
if (style) style.remove();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function startTimeout(ms: number, requestId?: string): void {
|
|
159
|
+
clearTimeoutTimer();
|
|
160
|
+
if (ms <= 0) return;
|
|
161
|
+
timeoutTimer = setTimeout(() => {
|
|
162
|
+
console.warn('[ChatableX] Agent lock timeout — auto-unlocking');
|
|
163
|
+
forceUnlock();
|
|
164
|
+
emit('timeout', { requestId });
|
|
165
|
+
}, ms);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function clearTimeoutTimer(): void {
|
|
169
|
+
if (timeoutTimer !== null) {
|
|
170
|
+
clearTimeout(timeoutTimer);
|
|
171
|
+
timeoutTimer = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function clearDebounceTimer(): void {
|
|
176
|
+
if (debounceTimer !== null) {
|
|
177
|
+
clearTimeout(debounceTimer);
|
|
178
|
+
debounceTimer = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function doLock(message: string, timeout: number, requestId?: string): void {
|
|
183
|
+
if (locked) return;
|
|
184
|
+
locked = true;
|
|
185
|
+
currentMessage = message;
|
|
186
|
+
|
|
187
|
+
if (cfg.mode === 'overlay') {
|
|
188
|
+
injectOverlay(currentMessage);
|
|
189
|
+
}
|
|
190
|
+
startTimeout(timeout, requestId);
|
|
191
|
+
emit('lock', { requestId });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function forceUnlock(requestId?: string): void {
|
|
195
|
+
if (!locked) return;
|
|
196
|
+
locked = false;
|
|
197
|
+
lockCount = 0;
|
|
198
|
+
turnActive = false;
|
|
199
|
+
clearTimeoutTimer();
|
|
200
|
+
clearDebounceTimer();
|
|
201
|
+
if (cfg.mode === 'overlay') {
|
|
202
|
+
removeOverlay();
|
|
203
|
+
}
|
|
204
|
+
emit('unlock', { requestId });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Release the lock only when nothing is still holding it — i.e. no agent turn
|
|
209
|
+
* is in progress and no tool execution is outstanding. This is what keeps the
|
|
210
|
+
* overlay from flickering off between tools or mid-turn.
|
|
211
|
+
*/
|
|
212
|
+
function maybeUnlock(requestId?: string): void {
|
|
213
|
+
if (turnActive || lockCount > 0) return;
|
|
214
|
+
forceUnlock(requestId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function scheduleDebouncedUnlock(requestId?: string): void {
|
|
218
|
+
clearDebounceTimer();
|
|
219
|
+
debounceTimer = setTimeout(() => maybeUnlock(requestId), cfg.debounceUnlock);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function handleCancel(): void {
|
|
223
|
+
const rid = undefined; // auto mode tracks this externally
|
|
224
|
+
// Explicit user cancel wins over any in-progress turn/tool tracking.
|
|
225
|
+
forceUnlock(rid);
|
|
226
|
+
emit('cancel', { requestId: rid });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Turn-level lock driven by the host (see {@link TURN_EVENT}). Holds the
|
|
231
|
+
* overlay for the entire agent response; tool-level auto lock/unlock nests
|
|
232
|
+
* inside without tearing the overlay down.
|
|
233
|
+
*/
|
|
234
|
+
function setTurn(active: boolean): void {
|
|
235
|
+
if (!cfg.enabled) return;
|
|
236
|
+
if (active) {
|
|
237
|
+
turnActive = true;
|
|
238
|
+
clearDebounceTimer();
|
|
239
|
+
if (!locked) {
|
|
240
|
+
doLock(cfg.message, cfg.turnTimeout);
|
|
241
|
+
} else {
|
|
242
|
+
// Already locked by a tool — extend its timeout to the (longer) turn
|
|
243
|
+
// budget so a 30s tool timeout can't drop the overlay mid-turn.
|
|
244
|
+
startTimeout(cfg.turnTimeout);
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
turnActive = false;
|
|
248
|
+
if (lockCount === 0) scheduleDebouncedUnlock();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Public API -----------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
function lock(opts?: { message?: string; timeout?: number }): void {
|
|
255
|
+
if (!cfg.enabled) return;
|
|
256
|
+
const msg = opts?.message ?? cfg.message;
|
|
257
|
+
const timeout = opts?.timeout ?? cfg.timeout;
|
|
258
|
+
doLock(msg, timeout);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function unlock(): void {
|
|
262
|
+
forceUnlock();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function isLocked(): boolean {
|
|
266
|
+
return locked;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void {
|
|
270
|
+
if (!listeners.has(event)) listeners.set(event, new Set());
|
|
271
|
+
listeners.get(event)!.add(handler);
|
|
272
|
+
return () => off(event, handler);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function off(event: AgentLockEventType, handler: AgentLockEventHandler): void {
|
|
276
|
+
listeners.get(event)?.delete(handler);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Internal auto-mode API (called by tool module) ----------------------------
|
|
280
|
+
|
|
281
|
+
function _autoLock(requestId: string): void {
|
|
282
|
+
if (!cfg.enabled) return;
|
|
283
|
+
clearDebounceTimer();
|
|
284
|
+
lockCount++;
|
|
285
|
+
if (!locked) {
|
|
286
|
+
// Inside a turn the (longer) turn budget governs; standalone tool calls
|
|
287
|
+
// keep the per-tool timeout.
|
|
288
|
+
doLock(cfg.message, turnActive ? cfg.turnTimeout : cfg.timeout, requestId);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function _autoUnlock(requestId: string): void {
|
|
293
|
+
if (!cfg.enabled) return;
|
|
294
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
295
|
+
if (lockCount === 0) {
|
|
296
|
+
scheduleDebouncedUnlock(requestId);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function _destroy(): void {
|
|
301
|
+
if (removeTurnListener) removeTurnListener();
|
|
302
|
+
forceUnlock();
|
|
303
|
+
listeners.clear();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Listen for host-driven turn lock/unlock. Payload shape: `{ active: boolean }`.
|
|
307
|
+
const removeTurnListener = bridge.addEventListener(TURN_EVENT, (data) => {
|
|
308
|
+
const active = !!(data as { active?: unknown } | undefined)?.active;
|
|
309
|
+
setTurn(active);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return { lock, unlock, isLocked, on, off, _autoLock, _autoUnlock, _destroy };
|
|
313
|
+
}
|
package/src/modules/tool.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { Bridge } from '../bridge';
|
|
2
2
|
import type { ToolInfo, ToolExecuteHandler, ChatableXToolModule } from '../types';
|
|
3
|
+
import type { AgentLockModule } from './agentLock';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Creates the `sdk.tool` module — the primary interface for LLM-driven
|
|
6
7
|
* tool execution in a ChatableX WebUI app.
|
|
7
8
|
*/
|
|
8
|
-
export function createToolModule(
|
|
9
|
+
export function createToolModule(
|
|
10
|
+
bridge: Bridge,
|
|
11
|
+
appId: string,
|
|
12
|
+
agentLock?: AgentLockModule,
|
|
13
|
+
): ChatableXToolModule & { _setInfo(info: Partial<ToolInfo>): void } {
|
|
9
14
|
let _info: ToolInfo = { id: appId, name: appId, version: '1.0.0', description: '' };
|
|
10
15
|
let _handler: ToolExecuteHandler | null = null;
|
|
11
16
|
|
|
@@ -30,13 +35,19 @@ export function createToolModule(bridge: Bridge, appId: string): ChatableXToolMo
|
|
|
30
35
|
bridge.addEventListener('toolExecution', async (data) => {
|
|
31
36
|
const params = data as Record<string, unknown>;
|
|
32
37
|
const requestId = params._requestId as string | undefined;
|
|
38
|
+
|
|
39
|
+
if (requestId && agentLock) agentLock._autoLock(requestId);
|
|
40
|
+
|
|
33
41
|
const result = await dispatch(params);
|
|
42
|
+
|
|
34
43
|
if (requestId && window.ChatableXBridge) {
|
|
35
44
|
window.ChatableXBridge.postMessage(JSON.stringify({
|
|
36
45
|
method: 'tool.executeResult',
|
|
37
46
|
params: { _requestId: requestId, ...result },
|
|
38
47
|
}));
|
|
39
48
|
}
|
|
49
|
+
|
|
50
|
+
if (requestId && agentLock) agentLock._autoUnlock(requestId);
|
|
40
51
|
});
|
|
41
52
|
|
|
42
53
|
return {
|
package/src/types.ts
CHANGED
|
@@ -169,6 +169,8 @@ export interface ChatableXInitConfig {
|
|
|
169
169
|
* calls reject with a clear error.
|
|
170
170
|
*/
|
|
171
171
|
apiBaseUrl?: string;
|
|
172
|
+
/** Agent lock configuration — blocks user input during tool execution. */
|
|
173
|
+
agentLock?: AgentLockConfig;
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
// ---------------------------------------------------------------------------
|
|
@@ -332,6 +334,61 @@ export interface ChatableXCloud {
|
|
|
332
334
|
usage(): Promise<CloudUsage>;
|
|
333
335
|
}
|
|
334
336
|
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Agent Lock
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
export interface AgentLockConfig {
|
|
342
|
+
/** Enable the agent lock feature (default: true). */
|
|
343
|
+
enabled?: boolean;
|
|
344
|
+
/**
|
|
345
|
+
* `"overlay"` — SDK renders a built-in transparent overlay (default).
|
|
346
|
+
* `"events-only"` — SDK only emits lock/unlock events; no overlay injected.
|
|
347
|
+
*/
|
|
348
|
+
mode?: 'overlay' | 'events-only';
|
|
349
|
+
/** URL of the logo displayed in the overlay centre. Defaults to built-in Chatablex SVG. */
|
|
350
|
+
logoUrl?: string;
|
|
351
|
+
/** Message shown below the logo (default: "Agent 正在操作,请稍候…"). */
|
|
352
|
+
message?: string;
|
|
353
|
+
/** Show a cancel button on the overlay (default: true). */
|
|
354
|
+
allowCancel?: boolean;
|
|
355
|
+
/** Overlay background opacity, 0–1 (default: 0.3). */
|
|
356
|
+
opacity?: number;
|
|
357
|
+
/** Auto-unlock timeout for a single tool execution, in ms (default: 30000). 0 disables. */
|
|
358
|
+
timeout?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Safety auto-unlock timeout for a whole agent turn, in ms (default: 0 =
|
|
361
|
+
* disabled, rely on the host's turn-end signal). When the host drives a
|
|
362
|
+
* turn-level lock (the lock spans the entire agent response, not just one
|
|
363
|
+
* tool), this acts purely as a fallback in case the turn-end signal is lost.
|
|
364
|
+
*/
|
|
365
|
+
turnTimeout?: number;
|
|
366
|
+
/** Delay before actually removing the overlay after unlock, to avoid flicker between consecutive tools (default: 200ms). */
|
|
367
|
+
debounceUnlock?: number;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export type AgentLockEventType = 'lock' | 'unlock' | 'cancel' | 'timeout';
|
|
371
|
+
|
|
372
|
+
export interface AgentLockEventData {
|
|
373
|
+
requestId?: string;
|
|
374
|
+
timestamp: number;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export type AgentLockEventHandler = (data: AgentLockEventData) => void;
|
|
378
|
+
|
|
379
|
+
export interface ChatableXAgentLock {
|
|
380
|
+
/** Manually lock user interaction with an optional custom message / timeout. */
|
|
381
|
+
lock(opts?: { message?: string; timeout?: number }): void;
|
|
382
|
+
/** Manually unlock. Safe to call when already unlocked. */
|
|
383
|
+
unlock(): void;
|
|
384
|
+
/** Whether the overlay is currently active. */
|
|
385
|
+
isLocked(): boolean;
|
|
386
|
+
/** Subscribe to lock lifecycle events. Returns an unsubscribe function. */
|
|
387
|
+
on(event: AgentLockEventType, handler: AgentLockEventHandler): () => void;
|
|
388
|
+
/** Remove a previously registered handler. */
|
|
389
|
+
off(event: AgentLockEventType, handler: AgentLockEventHandler): void;
|
|
390
|
+
}
|
|
391
|
+
|
|
335
392
|
export interface ChatableXSDK {
|
|
336
393
|
ai: ChatableXAI;
|
|
337
394
|
tools: ChatableXTools;
|
|
@@ -342,6 +399,7 @@ export interface ChatableXSDK {
|
|
|
342
399
|
platform: ChatableXPlatform;
|
|
343
400
|
auth: ChatableXAuth;
|
|
344
401
|
cloud: ChatableXCloud;
|
|
402
|
+
agentLock: ChatableXAgentLock;
|
|
345
403
|
}
|
|
346
404
|
|
|
347
405
|
// ---------------------------------------------------------------------------
|