dewee-webchat 0.4.2 → 0.5.0-beta.2

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.
@@ -1,4 +1,4 @@
1
- import { A as AgentInfo } from './protocol-CLbGE9Ns.js';
1
+ import { A as AgentInfo } from './protocol-BbjGSfZa.js';
2
2
 
3
3
  interface VisitorClaims {
4
4
  /** Visitor id (random, 22 chars; derived from the principal for a signed-in user). */
@@ -89,6 +89,42 @@ declare class CcpError extends Error {
89
89
  } | undefined);
90
90
  }
91
91
 
92
+ /** A cached identity session. The sessionToken is a bearer credential and must be protected. */
93
+ interface StoredIdentitySession {
94
+ sessionToken: string;
95
+ visitorId: string;
96
+ deadline: number;
97
+ margin: number;
98
+ origin?: string;
99
+ }
100
+ /**
101
+ * Storage shared by handler instances. Subject keys are HMAC digests scoped to the integration.
102
+ * Implementations must protect session tokens and expire entries by deadline. Revisions must be
103
+ * unique and never reused after expiry or eviction. Every method must be atomic across instances:
104
+ * read creates a revision even for an absent subject; commit compares and advances it; takeSubject
105
+ * advances it even when empty; deleteToken compares tokens within the supplied namespace and advances
106
+ * each affected revision. This fences an in-flight exchange against logout and concurrent exchanges.
107
+ */
108
+ interface IdentitySessionStore {
109
+ read(subjectKey: string, agent: string): Promise<{
110
+ revision: string;
111
+ session: StoredIdentitySession | null;
112
+ }>;
113
+ /** Atomically verify that no subject mutation has occurred since read. */
114
+ check(subjectKey: string, revision: string): Promise<boolean>;
115
+ /** Atomically compare and advance the subject revision when committing a session. */
116
+ commit(subjectKey: string, agent: string, revision: string, session: StoredIdentitySession): Promise<boolean>;
117
+ /** Remove only entries whose current token equals this token, not a newer replacement. */
118
+ deleteToken(namespace: string, token: string): Promise<void>;
119
+ /** Atomically advance the revision and remove and return all entries, even when none exist. */
120
+ takeSubject(subjectKey: string): Promise<Array<{
121
+ agent: string;
122
+ session: StoredIdentitySession;
123
+ }>>;
124
+ }
125
+ /** Bounded per-handler default; a shared adapter can be provided for multi-instance hosts. */
126
+ declare function memoryIdentitySessionStore(limit?: number): IdentitySessionStore;
127
+
92
128
  /**
93
129
  * A signed-in user of the host app, as `identity.authenticate` reports it. Scopes are not part of
94
130
  * the session exchange: report scope changes with `lifecycle({ event: "scopes_changed" })` from
@@ -170,6 +206,8 @@ interface DeweeIdentitySdk {
170
206
  * to one agent, and the gateway derives the user from it.
171
207
  */
172
208
  interface DeweeIdentityOptions {
209
+ /** Optional protected shared store for identity-session reuse across handler instances. */
210
+ sessionStore?: IdentitySessionStore;
173
211
  /** `deweeIdentitySdk` from `dewee-webchat/server/identity`. */
174
212
  sdk: DeweeIdentitySdk;
175
213
  /**
@@ -237,8 +275,10 @@ interface DeweeHandlerOptions {
237
275
  token?: string;
238
276
  /** Legacy gateway transport. */
239
277
  tenantId?: string;
240
- /** HMAC secret for visitor tokens (>= 32 random chars). */
278
+ /** HMAC secret for visitor tokens (at least 32 random UTF-8 bytes). */
241
279
  secret?: string;
280
+ /** Legacy gateway only: permit a process-local secret for development. Sessions reset on restart. */
281
+ allowEphemeralSecret?: boolean;
242
282
  /** Legacy gateway transport: agents visitors may talk to. The first is the default. `"*"` = every active agent. */
243
283
  agents?: Array<string | AgentInfo> | "*";
244
284
  defaultAgent?: string;
@@ -246,6 +286,17 @@ interface DeweeHandlerOptions {
246
286
  allowAgentSwitch?: boolean;
247
287
  /** Cross-origin sites allowed to call this backend. Same-origin is always allowed. */
248
288
  allowedOrigins?: string[] | "*";
289
+ /** Additional header names allowed in cross-origin requests. */
290
+ allowedHeaders?: string[];
291
+ /** HTTPS directory prefixes from which the backend may proxy images; disabled when unset. */
292
+ imageSources?: string[];
293
+ /** Explicitly public directories in this Worker's Assets binding. Bypasses site middleware. */
294
+ cloudflarePublicImageAssets?: {
295
+ binding: {
296
+ fetch(request: Request): Promise<Response>;
297
+ };
298
+ prefixes: string[];
299
+ };
249
300
  /**
250
301
  * Legacy gateway transport: let visitors bring their own gateway URL + token (sent per request, never stored).
251
302
  * Meant for playgrounds. `allowedHosts` restricts which gateways may be targeted.
@@ -295,8 +346,9 @@ interface DeweeHandler {
295
346
  * and IP (without it, the origin of the request the session was issued for is used). `token` is the
296
347
  * widget's reuse hint (`reuseHint()`) or visitor token, sent along by your logout page: its identity
297
348
  * session is revoked too when it belongs to `subject`, so a host that keeps no memory between
298
- * requests still ends the exact session. Best effort: never throws. Other sessions held only by
299
- * other instances end at their expiry (`sessionTtlSeconds`). Without `identity` it does nothing.
349
+ * requests still ends the exact session. Best effort: never throws; a store failure is logged and
350
+ * the exact supplied token is still attempted. Without a shared store, other instances' sessions
351
+ * end at expiry (`sessionTtlSeconds`). Without `identity` it does nothing.
300
352
  */
301
353
  revoke(subject: string, request?: Request, token?: string): Promise<void>;
302
354
  }
@@ -304,4 +356,4 @@ declare function createDeweeHandler(options: DeweeHandlerOptions): DeweeHandler;
304
356
  /** Reads options from environment variables (process.env, Workers env, import.meta.env…). */
305
357
  declare function optionsFromEnv(env: Record<string, string | undefined>, overrides?: Partial<DeweeHandlerOptions>): DeweeHandlerOptions;
306
358
 
307
- export { type AuthenticatedUser as A, CcpError as C, type DeweeIdentitySdk as D, type IdentityPrivateJwk as I, type MemoryRateLimits as M, type RateLimitBucket as R, type VisitorClaims as V, type CcpOptions as a, type DeweeHandler as b, type DeweeHandlerOptions as c, type DeweeIdentityOptions as d, type DeweeTransport as e, type IdentitySigner as f, type RateLimiter as g, createDeweeHandler as h, memoryRateLimiter as m, optionsFromEnv as o, signVisitorToken as s, verifyVisitorToken as v };
359
+ export { type AuthenticatedUser as A, CcpError as C, type DeweeIdentitySdk as D, type IdentityPrivateJwk as I, type MemoryRateLimits as M, type RateLimitBucket as R, type StoredIdentitySession as S, type VisitorClaims as V, type CcpOptions as a, type DeweeHandler as b, type DeweeHandlerOptions as c, type DeweeIdentityOptions as d, type DeweeTransport as e, type IdentitySessionStore as f, type IdentitySigner as g, type RateLimiter as h, createDeweeHandler as i, memoryRateLimiter as j, memoryIdentitySessionStore as m, optionsFromEnv as o, signVisitorToken as s, verifyVisitorToken as v };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as ActionSensitivity, P as PublicConfig, S as SessionResponse, L as LinkResponse, H as HistoryMessage, e as ChatRequestBody, C as ChatStreamEvent, R as Remediation } from './protocol-CLbGE9Ns.js';
2
- export { a as ActionManifestEntry, b as AdmissionErrorCode, A as AgentInfo, I as IdentityErrorCode, f as LinkTarget, T as ToolCallView } from './protocol-CLbGE9Ns.js';
1
+ import { d as ActionSensitivity, P as PublicConfig, S as SessionResponse, L as LinkResponse, H as HistoryMessage, e as ChatRequestBody, C as ChatStreamEvent, R as Remediation, g as PageContextAttachment } from './protocol-BbjGSfZa.js';
2
+ export { a as ActionManifestEntry, b as AdmissionErrorCode, A as AgentInfo, I as IdentityErrorCode, f as LinkTarget, T as ToolCallView } from './protocol-BbjGSfZa.js';
3
3
 
4
4
  type ActionResultStatus = "ok" | "denied" | "error";
5
5
  interface ActionResult {
@@ -83,6 +83,8 @@ declare class ApiClient {
83
83
  private session;
84
84
  private config;
85
85
  private sessionPromise;
86
+ private supersededSessions;
87
+ private sessionGeneration;
86
88
  constructor(o: ApiClientOptions);
87
89
  private url;
88
90
  private get storageKey();
@@ -96,12 +98,15 @@ declare class ApiClient {
96
98
  private request;
97
99
  private headers;
98
100
  private parse;
99
- getConfig(force?: boolean): Promise<PublicConfig>;
101
+ /** Obtain a short-lived, URL-bound image capability without exposing the visitor bearer in src. */
102
+ imageUrl(source: string, signal?: AbortSignal): Promise<string>;
103
+ getConfig(force?: boolean, probePageContext?: boolean): Promise<PublicConfig>;
100
104
  /** When each signed-in session should be refreshed, on this browser's clock. */
101
105
  private refreshAt;
102
106
  /**
103
107
  * Session reuse. A signed-in session follows its schedule (see `scheduleRefresh`). An anonymous
104
- * token is refreshed once less than 10% of its lifetime (at most 24 h, at least 30 s) remains; a
108
+ * token is refreshed once less than 10% of its lifetime (at most 24 h, at least 30 s, and no more
109
+ * than half the lifetime) remains; a
105
110
  * stored token from before `obtainedAt` existed uses the 24 h margin. When the backend reports the
106
111
  * lifetime (`expiresIn`), it is counted from receipt, so a skewed browser clock does not matter.
107
112
  */
@@ -160,6 +165,14 @@ interface DeweeChatConfig {
160
165
  suggestions?: string[];
161
166
  /** Image URL used for the header avatar (and launcher when `launcherAvatar`). */
162
167
  avatar?: string;
168
+ /** HTTPS origins from which Markdown images may load, in addition to the page's own origin. */
169
+ imageOrigins?: string[];
170
+ /** Explicit host opt-in; the visitor must also choose to share the current page. */
171
+ pageContext?: "off" | "metadata" | "content";
172
+ contextSelector?: string;
173
+ excludeSelectors?: string[];
174
+ /** Supplies host-filtered content. Visitors still preview and opt in on each message. */
175
+ contextProvider?: () => Promise<Partial<PageContextAttachment>>;
163
176
  launcherAvatar?: boolean;
164
177
  position?: "bottom-right" | "bottom-left";
165
178
  /** "floating" (launcher + popup) or "inline" (fills its container). */
@@ -238,12 +251,17 @@ declare class DeweeChatElement extends ElementBase {
238
251
  private api;
239
252
  private apiKey;
240
253
  private server;
254
+ private sharedPage?;
255
+ private preparingPage;
256
+ private pagePending;
257
+ private pageGeneration;
241
258
  private agentId;
242
259
  private conversationId;
243
260
  private messages;
244
261
  private running;
245
262
  private jobAccessLaunch;
246
263
  private activated;
264
+ private probedPage;
247
265
  private strings;
248
266
  private mql;
249
267
  private registry;
@@ -330,6 +348,11 @@ declare class DeweeChatElement extends ElementBase {
330
348
  private applyConfig;
331
349
  private renderShell;
332
350
  private autosize;
351
+ private updatePageChoice;
352
+ private captureCurrentPage;
353
+ private pageFingerprint;
354
+ private renderPagePreview;
355
+ private choosePage;
333
356
  private updateSendButton;
334
357
  /** Disables sending for `ms` and announces it; the visible countdown is not read out every second. */
335
358
  private startCooldown;
@@ -367,7 +390,12 @@ declare class DeweeChatElement extends ElementBase {
367
390
  }
368
391
 
369
392
  type Doc = Document;
370
- declare function renderMarkdown(doc: Doc, src: string): DocumentFragment;
393
+ interface MarkdownOptions {
394
+ imageOrigins?: string[];
395
+ imageProxy?: (source: string, signal: AbortSignal) => Promise<string>;
396
+ deferImages?: boolean;
397
+ }
398
+ declare function renderMarkdown(doc: Doc, src: string, options?: MarkdownOptions): DocumentFragment;
371
399
 
372
400
  /** Registers the `<dewee-chat>` custom element (idempotent, SSR-safe). */
373
401
  declare function defineDeweeChat(tag?: string): void;
package/dist/index.js CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  defineDeweeChat,
8
8
  mount,
9
9
  renderMarkdown
10
- } from "./chunk-UGHJZAKL.js";
11
- import "./chunk-SAUDVPFH.js";
10
+ } from "./chunk-XCO4TCKF.js";
11
+ import "./chunk-KXW4EWXA.js";
12
12
  export {
13
13
  ApiClient,
14
14
  ApiError,
@@ -0,0 +1,137 @@
1
+ // src/widget/page-context.ts
2
+ var BLOCKED = "script,style,noscript,template,svg,canvas,iframe,form,input,textarea,select,option,button,[contenteditable]:not([contenteditable='false']),[hidden],[aria-hidden='true'],[inert],dewee-chat";
3
+ var MAX_CONTENT = 12e3;
4
+ function safePageUrl(href) {
5
+ try {
6
+ const url = new URL(href);
7
+ if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
8
+ url.username = "";
9
+ url.password = "";
10
+ url.hash = "";
11
+ for (const key of [...url.searchParams.keys()]) {
12
+ const words = key.replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, "$1 $2").replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, "$1 $2").replace(/(\p{L})(\p{N})/gu, "$1 $2").replace(/(\p{N})(\p{L})/gu, "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
13
+ const joined = words.join("");
14
+ const qualifiers = ["api", "access", "auth", "oauth", "secret", "private", "client", "session", "signing", "verification", "reset", "recovery", "activation", "confirmation", "invite", "license", "encryption"];
15
+ const secret = words.some((word, index) => ["token", "secret", "password", "passwd", "pwd", "auth", "authorization", "cookie", "apikey", "session", "sid", "sig", "signature", "otp", "jwt", "credential", "credentials", "nonce", "bearer", "ticket"].includes(word) || ["code", "key", "state"].includes(word) && (words.length === 1 || index > 0 && qualifiers.includes(words[index - 1]))) || /token|secret|passw|apikey|credential|sessid|session|authorization|samlresponse/.test(joined);
16
+ if (secret) url.searchParams.set(key, "[REDACTED]");
17
+ }
18
+ return new TextEncoder().encode(url.href).length <= 2048 ? url.href : void 0;
19
+ } catch {
20
+ return void 0;
21
+ }
22
+ }
23
+ function clip(text, length) {
24
+ return Array.from(text).slice(0, length).join("");
25
+ }
26
+ function limitBytes(context) {
27
+ if (context.content && new TextEncoder().encode(JSON.stringify(context)).length > 65536) {
28
+ const points = Array.from(context.content.text);
29
+ let low = 0, high = points.length;
30
+ while (low < high) {
31
+ const mid = Math.ceil((low + high) / 2);
32
+ context.content.text = points.slice(0, mid).join("");
33
+ context.content.truncated = true;
34
+ if (new TextEncoder().encode(JSON.stringify(context)).length <= 65536) low = mid;
35
+ else high = mid - 1;
36
+ }
37
+ context.content.text = points.slice(0, low).join("");
38
+ context.content.truncated = true;
39
+ }
40
+ return context;
41
+ }
42
+ function providedPageContext(raw, base, mode) {
43
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new TypeError("Invalid page context");
44
+ const value = raw;
45
+ const result = { version: 1, url: base.url, captured_at: base.captured_at };
46
+ for (const [key, max] of [["title", 300], ["description", 1e3]]) {
47
+ if (typeof value[key] === "string") result[key] = clip(value[key].replace(/\s+/g, " ").trim(), max);
48
+ }
49
+ if (mode === "content") {
50
+ if (typeof value.selection === "string") result.selection = clip(value.selection, 2e3);
51
+ if (value.content && typeof value.content === "object" && !Array.isArray(value.content)) {
52
+ const content = value.content;
53
+ if (typeof content.text !== "string" || content.format !== "text" && content.format !== "markdown") throw new TypeError("Invalid page content");
54
+ const text = clip(content.text, MAX_CONTENT);
55
+ result.content = { format: content.format, text, truncated: content.truncated === true || Array.from(content.text).length > MAX_CONTENT };
56
+ }
57
+ }
58
+ return limitBytes(result);
59
+ }
60
+ function visible(el, excludes) {
61
+ for (let node = el; node; node = node.parentElement) {
62
+ if (node.matches(BLOCKED) || excludes.some((selector) => node.matches(selector))) return false;
63
+ if (node instanceof HTMLDialogElement && !node.open) return false;
64
+ if (node instanceof HTMLDetailsElement && !node.open) {
65
+ const summary = [...node.children].find((child) => child.tagName === "SUMMARY");
66
+ if (!summary || !el || summary !== el && !summary.contains(el)) return false;
67
+ }
68
+ if (node.hasAttribute("popover")) {
69
+ try {
70
+ if (!node.matches(":popover-open")) return false;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ const style = getComputedStyle(node);
76
+ if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse" || style.opacity === "0" || style.contentVisibility === "hidden") return false;
77
+ }
78
+ if (el && typeof el.checkVisibility === "function" && !el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) return false;
79
+ return true;
80
+ }
81
+ function capturePageContext(doc, options) {
82
+ const url = safePageUrl(doc.location.href);
83
+ if (!url) return void 0;
84
+ const excludes = options.excludeSelectors ?? [];
85
+ for (const selector of excludes) doc.documentElement.matches(selector);
86
+ const root = doc.querySelector(options.selector || "main, article, [role='main']");
87
+ const context = { version: 1, url, captured_at: (/* @__PURE__ */ new Date()).toISOString() };
88
+ const title = clip(doc.title.replace(/\s+/g, " ").trim(), 300);
89
+ const description = clip((doc.querySelector('meta[name="description" i]')?.getAttribute("content") ?? "").replace(/\s+/g, " ").trim(), 1e3);
90
+ if (title) context.title = title;
91
+ if (description) context.description = description;
92
+ if (options.mode !== "content" || !root || !visible(root, excludes)) return context;
93
+ const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
94
+ const chunks = [];
95
+ let size = 0;
96
+ let visited = 0;
97
+ let truncated = false;
98
+ while (walker.nextNode()) {
99
+ if (++visited > 5e3) {
100
+ truncated = true;
101
+ break;
102
+ }
103
+ const node = walker.currentNode;
104
+ if (!visible(node.parentElement, excludes)) continue;
105
+ const text2 = (node.textContent ?? "").replace(/\s+/g, " ").trim();
106
+ if (!text2) continue;
107
+ const remaining = MAX_CONTENT - size;
108
+ const part = clip(text2, remaining);
109
+ chunks.push(part);
110
+ size += Array.from(part).length + 1;
111
+ if (Array.from(text2).length > remaining || size >= MAX_CONTENT) {
112
+ truncated = true;
113
+ break;
114
+ }
115
+ }
116
+ const text = clip(chunks.join("\n"), MAX_CONTENT);
117
+ if (text) context.content = { format: "text", text, truncated };
118
+ const selection = doc.getSelection();
119
+ if (selection && !selection.isCollapsed && selection.rangeCount) {
120
+ const range = selection.getRangeAt(0);
121
+ const ancestor = range.commonAncestorContainer;
122
+ const element = ancestor.nodeType === Node.ELEMENT_NODE ? ancestor : ancestor.parentElement;
123
+ if (element && root.contains(element) && visible(element, excludes)) {
124
+ const nodes = root.querySelectorAll("*");
125
+ if (nodes.length <= 5e3 && ![...nodes].some((el) => range.intersectsNode(el) && !visible(el, excludes))) {
126
+ const selected = clip(selection.toString().trim(), 2e3);
127
+ if (selected) context.selection = selected;
128
+ }
129
+ }
130
+ }
131
+ return limitBytes(context);
132
+ }
133
+ export {
134
+ capturePageContext,
135
+ providedPageContext,
136
+ safePageUrl
137
+ };
@@ -1,3 +1,18 @@
1
+ /** A transient, untrusted attachment for one chat turn. Never stored in browser history. */
2
+ interface PageContextAttachment {
3
+ version: 1;
4
+ url: string;
5
+ title?: string;
6
+ description?: string;
7
+ captured_at?: string;
8
+ selection?: string;
9
+ content?: {
10
+ format: "text" | "markdown";
11
+ text: string;
12
+ truncated: boolean;
13
+ };
14
+ }
15
+
1
16
  /** Server-Sent Events emitted by `POST {basePath}/chat`. */
2
17
  type ChatStreamEvent = {
3
18
  type: "start";
@@ -76,6 +91,8 @@ interface PublicConfig {
76
91
  turnstileSiteKey?: string;
77
92
  maxMessageLength: number;
78
93
  version: string;
94
+ /** Attachment versions advertised by the connected runtime. */
95
+ pageContextVersions?: number[];
79
96
  /** Present (true) when the backend signs users in through Dewee: the widget then asks `/session` on every page load. */
80
97
  identity?: boolean;
81
98
  /**
@@ -108,7 +125,7 @@ interface SessionResponse {
108
125
  */
109
126
  reuseHint?: string;
110
127
  /**
111
- * Verified sessions, and every session of the ccp transport: milliseconds until `expiresAt`, counted
128
+ * Milliseconds until `expiresAt`, counted
112
129
  * from when the response is received. The widget refreshes from this rather than `expiresAt`, so a
113
130
  * skewed browser clock does not matter.
114
131
  */
@@ -165,6 +182,8 @@ interface ChatRequestBody {
165
182
  /** Hash from the `start` event of the last completed turn; the manifest is re-sent to the agent only when it differs. */
166
183
  actionsHash?: string;
167
184
  pageContext?: unknown;
185
+ /** Ephemeral, untrusted content for this turn; distinct from the legacy page-action context. */
186
+ page_context?: PageContextAttachment;
168
187
  }
169
188
 
170
- export { type AgentInfo as A, type ChatStreamEvent as C, type ErrorBody as E, type HistoryMessage as H, type IdentityErrorCode as I, type LinkResponse as L, type PublicConfig as P, type Remediation as R, type SessionResponse as S, type ToolCallView as T, type ActionManifestEntry as a, type AdmissionErrorCode as b, ADMISSION_ERROR_CODES as c, type ActionSensitivity as d, type ChatRequestBody as e, type LinkTarget as f, isAdmissionErrorCode as i };
189
+ export { type AgentInfo as A, type ChatStreamEvent as C, type ErrorBody as E, type HistoryMessage as H, type IdentityErrorCode as I, type LinkResponse as L, type PublicConfig as P, type Remediation as R, type SessionResponse as S, type ToolCallView as T, type ActionManifestEntry as a, type AdmissionErrorCode as b, ADMISSION_ERROR_CODES as c, type ActionSensitivity as d, type ChatRequestBody as e, type LinkTarget as f, type PageContextAttachment as g, isAdmissionErrorCode as i };
package/dist/react.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { CSSProperties } from 'react';
3
3
  import { DeweeChatConfig, ActionEventDetail, DeweeChatElement } from './index.js';
4
4
  export { ActionConfirmHook, DomActionsRoot, PageAction, PageActionContext, RequestHeadersOption } from './index.js';
5
- import './protocol-CLbGE9Ns.js';
5
+ import './protocol-BbjGSfZa.js';
6
6
 
7
7
  interface DeweeChatProps extends Partial<DeweeChatConfig> {
8
8
  className?: string;
@@ -15,6 +15,8 @@ interface DeweeChatProps extends Partial<DeweeChatConfig> {
15
15
  onClose?: (e: CustomEvent) => void;
16
16
  /** Page action telemetry (`dewee:action`, detail: ActionEventDetail). */
17
17
  onAction?: (e: CustomEvent<ActionEventDetail>) => void;
18
+ onContextReady?: (e: CustomEvent) => void;
19
+ onContextError?: (e: CustomEvent) => void;
18
20
  }
19
21
  declare const DeweeChat: react.ForwardRefExoticComponent<DeweeChatProps & react.RefAttributes<DeweeChatElement | null>>;
20
22
 
package/dist/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  defineDeweeChat
3
- } from "./chunk-UGHJZAKL.js";
4
- import "./chunk-SAUDVPFH.js";
3
+ } from "./chunk-XCO4TCKF.js";
4
+ import "./chunk-KXW4EWXA.js";
5
5
 
6
6
  // src/react.ts
7
7
  import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
@@ -12,23 +12,25 @@ var EVENTS = {
12
12
  onError: "dewee:error",
13
13
  onOpen: "dewee:open",
14
14
  onClose: "dewee:close",
15
- onAction: "dewee:action"
15
+ onAction: "dewee:action",
16
+ onContextReady: "dewee:context-ready",
17
+ onContextError: "dewee:context-error"
16
18
  };
17
19
  var DeweeChat = forwardRef(function DeweeChat2(props, ref) {
18
20
  const el = useRef(null);
19
- const { className, style, onReady, onSend, onReply, onError, onOpen, onClose, onAction, actions, confirm, getPageContext, headers, fetch, domActionsRoot, ...config } = props;
20
- const handlers = { onReady, onSend, onReply, onError, onOpen, onClose, onAction };
21
+ const { className, style, onReady, onSend, onReply, onError, onOpen, onClose, onAction, onContextReady, onContextError, actions, confirm, getPageContext, headers, fetch, domActionsRoot, contextProvider, imageOrigins, ...config } = props;
22
+ const handlers = { onReady, onSend, onReply, onError, onOpen, onClose, onAction, onContextReady, onContextError };
21
23
  const handlersRef = useRef(handlers);
22
24
  handlersRef.current = handlers;
23
25
  useImperativeHandle(ref, () => el.current, []);
24
- const configKey = JSON.stringify(config);
26
+ const configKey = JSON.stringify({ ...config, imageOrigins: imageOrigins ?? [] });
25
27
  useEffect(() => {
26
28
  defineDeweeChat();
27
29
  el.current?.configure(JSON.parse(configKey));
28
30
  }, [configKey]);
29
31
  useEffect(() => {
30
32
  defineDeweeChat();
31
- el.current?.configure({ actions, confirm, getPageContext, headers, fetch, domActionsRoot });
33
+ el.current?.configure({ actions, confirm, getPageContext, headers, fetch, domActionsRoot, contextProvider });
32
34
  });
33
35
  useEffect(() => {
34
36
  const node = el.current;
@@ -1,5 +1,5 @@
1
- import { D as DeweeIdentitySdk } from './handler-DunrbnSD.js';
2
- import './protocol-CLbGE9Ns.js';
1
+ import { D as DeweeIdentitySdk } from './handler-SsDyK0TG.js';
2
+ import './protocol-BbjGSfZa.js';
3
3
 
4
4
  declare const deweeIdentitySdk: DeweeIdentitySdk;
5
5
 
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as AuthenticatedUser, C as CcpError, a as CcpOptions, b as DeweeHandler, c as DeweeHandlerOptions, d as DeweeIdentityOptions, D as DeweeIdentitySdk, e as DeweeTransport, I as IdentityPrivateJwk, f as IdentitySigner, M as MemoryRateLimits, R as RateLimitBucket, g as RateLimiter, V as VisitorClaims, h as createDeweeHandler, m as memoryRateLimiter, o as optionsFromEnv, s as signVisitorToken, v as verifyVisitorToken } from './handler-DunrbnSD.js';
1
+ export { A as AuthenticatedUser, C as CcpError, a as CcpOptions, b as DeweeHandler, c as DeweeHandlerOptions, d as DeweeIdentityOptions, D as DeweeIdentitySdk, e as DeweeTransport, I as IdentityPrivateJwk, f as IdentitySessionStore, g as IdentitySigner, M as MemoryRateLimits, R as RateLimitBucket, h as RateLimiter, S as StoredIdentitySession, V as VisitorClaims, i as createDeweeHandler, m as memoryIdentitySessionStore, j as memoryRateLimiter, o as optionsFromEnv, s as signVisitorToken, v as verifyVisitorToken } from './handler-SsDyK0TG.js';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import { R as Remediation, H as HistoryMessage, A as AgentInfo, C as ChatStreamEvent, a as ActionManifestEntry, b as AdmissionErrorCode } from './protocol-CLbGE9Ns.js';
4
- export { c as ADMISSION_ERROR_CODES, d as ActionSensitivity, e as ChatRequestBody, E as ErrorBody, I as IdentityErrorCode, L as LinkResponse, f as LinkTarget, P as PublicConfig, S as SessionResponse, i as isAdmissionErrorCode } from './protocol-CLbGE9Ns.js';
3
+ import { R as Remediation, H as HistoryMessage, A as AgentInfo, C as ChatStreamEvent, a as ActionManifestEntry, b as AdmissionErrorCode } from './protocol-BbjGSfZa.js';
4
+ export { c as ADMISSION_ERROR_CODES, d as ActionSensitivity, e as ChatRequestBody, E as ErrorBody, I as IdentityErrorCode, L as LinkResponse, f as LinkTarget, P as PublicConfig, S as SessionResponse, i as isAdmissionErrorCode } from './protocol-BbjGSfZa.js';
5
5
 
6
6
  type FetchHandler = (request: Request) => Promise<Response>;
7
7
  /** Wraps a fetch-style handler for `http.createServer`, Express (`app.all("/api/dewee/*", ...)`), etc. */
@@ -29,8 +29,9 @@ declare class GatewayError extends Error {
29
29
  code?: string | undefined;
30
30
  remediation?: Remediation | undefined;
31
31
  retryAfterMs?: unknown | undefined;
32
+ reason?: unknown | undefined;
32
33
  /** `retryAfterMs` is the gateway's raw value for retryable refusals (`RATE_LIMITED`), unvalidated. */
33
- constructor(message: string, code?: string | undefined, remediation?: Remediation | undefined, retryAfterMs?: unknown | undefined);
34
+ constructor(message: string, code?: string | undefined, remediation?: Remediation | undefined, retryAfterMs?: unknown | undefined, reason?: unknown | undefined);
34
35
  }
35
36
  /** One authenticated gateway connection. Short-lived: one per chat turn or lookup. */
36
37
  declare class GatewayConnection {
@@ -39,6 +40,7 @@ declare class GatewayConnection {
39
40
  private pending;
40
41
  private listeners;
41
42
  private closed;
43
+ pageContextVersions: number[];
42
44
  private constructor();
43
45
  static connect(creds: GatewayCredentials, actor: GatewayActor, timeoutMs?: number): Promise<GatewayConnection>;
44
46
  call<T = any>(method: string, params: unknown, timeoutMs?: number): Promise<T>;
@@ -59,6 +61,7 @@ interface StreamChatOptions {
59
61
  agent: string;
60
62
  sessionKey: string;
61
63
  message: string;
64
+ pageContext?: unknown;
62
65
  signal?: AbortSignal;
63
66
  timeoutMs?: number;
64
67
  }