dewee-webchat 0.4.1 → 0.5.0-beta.1

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,10 @@ 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[];
249
293
  /**
250
294
  * Legacy gateway transport: let visitors bring their own gateway URL + token (sent per request, never stored).
251
295
  * Meant for playgrounds. `allowedHosts` restricts which gateways may be targeted.
@@ -295,8 +339,9 @@ interface DeweeHandler {
295
339
  * and IP (without it, the origin of the request the session was issued for is used). `token` is the
296
340
  * widget's reuse hint (`reuseHint()`) or visitor token, sent along by your logout page: its identity
297
341
  * 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.
342
+ * requests still ends the exact session. Best effort: never throws; a store failure is logged and
343
+ * the exact supplied token is still attempted. Without a shared store, other instances' sessions
344
+ * end at expiry (`sessionTtlSeconds`). Without `identity` it does nothing.
300
345
  */
301
346
  revoke(subject: string, request?: Request, token?: string): Promise<void>;
302
347
  }
@@ -304,4 +349,4 @@ declare function createDeweeHandler(options: DeweeHandlerOptions): DeweeHandler;
304
349
  /** Reads options from environment variables (process.env, Workers env, import.meta.env…). */
305
350
  declare function optionsFromEnv(env: Record<string, string | undefined>, overrides?: Partial<DeweeHandlerOptions>): DeweeHandlerOptions;
306
351
 
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 };
352
+ 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 {
@@ -39,6 +39,10 @@ type ActionConfirmHook = (action: PageActionInfo & {
39
39
  }, args: Record<string, unknown>) => boolean | Promise<boolean>;
40
40
  type ActionCallStatus = "pending" | "awaiting-confirmation" | "running" | "done" | "denied" | "error";
41
41
 
42
+ declare const DOM_ACTION_NAMES: readonly ["page.snapshot", "page.scroll", "page.click", "page.fill"];
43
+ /** Where the agent may act: a selector, an element, or (default) the whole document body. */
44
+ type DomActionsRoot = string | Element;
45
+
42
46
  /** The only launch data allowed to reach the browser. Never an identity session. */
43
47
  interface JobAccessLaunch {
44
48
  bootstrap_url: string;
@@ -79,6 +83,8 @@ declare class ApiClient {
79
83
  private session;
80
84
  private config;
81
85
  private sessionPromise;
86
+ private supersededSessions;
87
+ private sessionGeneration;
82
88
  constructor(o: ApiClientOptions);
83
89
  private url;
84
90
  private get storageKey();
@@ -92,12 +98,15 @@ declare class ApiClient {
92
98
  private request;
93
99
  private headers;
94
100
  private parse;
95
- 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>;
96
104
  /** When each signed-in session should be refreshed, on this browser's clock. */
97
105
  private refreshAt;
98
106
  /**
99
107
  * Session reuse. A signed-in session follows its schedule (see `scheduleRefresh`). An anonymous
100
- * 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
101
110
  * stored token from before `obtainedAt` existed uses the 24 h margin. When the backend reports the
102
111
  * lifetime (`expiresIn`), it is counted from receipt, so a skewed browser clock does not matter.
103
112
  */
@@ -110,6 +119,8 @@ declare class ApiClient {
110
119
  private scheduleRefresh;
111
120
  /** The stored anonymous visitor session, if any. Signed-in sessions are never stored. */
112
121
  private stored;
122
+ /** The visitor of the session in use, or null before one is held. Backend conversations are keyed by it. */
123
+ visitorId(): string | null;
113
124
  /** Returns a valid visitor session, creating or refreshing it when needed. */
114
125
  ensureSession(): Promise<SessionResponse>;
115
126
  private loadSession;
@@ -154,6 +165,14 @@ interface DeweeChatConfig {
154
165
  suggestions?: string[];
155
166
  /** Image URL used for the header avatar (and launcher when `launcherAvatar`). */
156
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>>;
157
176
  launcherAvatar?: boolean;
158
177
  position?: "bottom-right" | "bottom-left";
159
178
  /** "floating" (launcher + popup) or "inline" (fills its container). */
@@ -188,6 +207,13 @@ interface DeweeChatConfig {
188
207
  maxActionRounds?: number;
189
208
  /** Per-handler timeout. Default 30000 ms. */
190
209
  actionTimeoutMs?: number;
210
+ /**
211
+ * Built-in page actions that read and operate the page DOM (page.snapshot, page.scroll, page.click,
212
+ * page.fill). Off by default. Host actions with the same name win. click/fill always ask the visitor.
213
+ */
214
+ domActions?: boolean;
215
+ /** Limits the built-in DOM actions to this element or selector. Default: document.body. */
216
+ domActionsRoot?: DomActionsRoot;
191
217
  /**
192
218
  * Extra headers for every request to `endpoint` (e.g. a CSRF token). A function is called before
193
219
  * each request. Cannot override content-type, authorization or the gateway credential headers.
@@ -225,17 +251,24 @@ declare class DeweeChatElement extends ElementBase {
225
251
  private api;
226
252
  private apiKey;
227
253
  private server;
254
+ private sharedPage?;
255
+ private preparingPage;
256
+ private pagePending;
257
+ private pageGeneration;
228
258
  private agentId;
229
259
  private conversationId;
230
260
  private messages;
231
261
  private running;
232
262
  private jobAccessLaunch;
233
263
  private activated;
264
+ private probedPage;
234
265
  private strings;
235
266
  private mql;
236
267
  private registry;
237
268
  private propActionNames;
238
269
  private hooks;
270
+ /** Built-in DOM actions, created on first use when `domActions` is on (keeps the snapshot refs). */
271
+ private dom;
239
272
  private acks;
240
273
  /** Pause after a RATE_LIMITED refusal; send stays disabled until it ends. */
241
274
  private cooldown;
@@ -255,8 +288,10 @@ declare class DeweeChatElement extends ElementBase {
255
288
  /** Custom fetch for widget requests (property only; same as `configure({ fetch })`). */
256
289
  get fetch(): typeof fetch | undefined;
257
290
  set fetch(v: typeof fetch | undefined);
258
- /** Names of the currently registered page actions. */
291
+ /** Names of the page actions offered to the agent (host actions, then enabled built-ins). */
259
292
  get actionNames(): string[];
293
+ /** Host actions plus the enabled built-in DOM actions whose names the host did not take. */
294
+ private actionRegistry;
260
295
  private setPropActions;
261
296
  open(): void;
262
297
  close(): void;
@@ -268,6 +303,10 @@ declare class DeweeChatElement extends ElementBase {
268
303
  private chatBody;
269
304
  /** Host snapshot, dropped (with a warning) when it fails or exceeds the backend cap. */
270
305
  private pageContext;
306
+ /**
307
+ * The backend keys a conversation by visitor as well as by id, so a sign-in, a sign-out or another
308
+ * user starts a conversation that has not seen the manifest, even with the same conversation id.
309
+ */
271
310
  private ackKey;
272
311
  private getAck;
273
312
  private setAck;
@@ -309,6 +348,11 @@ declare class DeweeChatElement extends ElementBase {
309
348
  private applyConfig;
310
349
  private renderShell;
311
350
  private autosize;
351
+ private updatePageChoice;
352
+ private captureCurrentPage;
353
+ private pageFingerprint;
354
+ private renderPagePreview;
355
+ private choosePage;
312
356
  private updateSendButton;
313
357
  /** Disables sending for `ms` and announces it; the visible countdown is not read out every second. */
314
358
  private startCooldown;
@@ -346,7 +390,12 @@ declare class DeweeChatElement extends ElementBase {
346
390
  }
347
391
 
348
392
  type Doc = Document;
349
- 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;
350
399
 
351
400
  /** Registers the `<dewee-chat>` custom element (idempotent, SSR-safe). */
352
401
  declare function defineDeweeChat(tag?: string): void;
@@ -358,4 +407,4 @@ declare global {
358
407
  }
359
408
  }
360
409
 
361
- export { type ActionCallStatus, type ActionCallView, type ActionConfirmHook, type ActionEventDetail, type ActionResult, type ActionResultStatus, ActionSensitivity, ApiClient, type ApiClientOptions, ApiError, ChatRequestBody, ChatStreamEvent, type DeweeChatConfig, DeweeChatElement, HistoryMessage, type JsonSchema, LinkResponse, type PageAction, type PageActionContext, type PageActionInfo, PublicConfig, RESERVED_HEADERS, Remediation, type RequestHeadersOption, SessionResponse, defineDeweeChat, mount, renderMarkdown };
410
+ export { type ActionCallStatus, type ActionCallView, type ActionConfirmHook, type ActionEventDetail, type ActionResult, type ActionResultStatus, ActionSensitivity, ApiClient, type ApiClientOptions, ApiError, ChatRequestBody, ChatStreamEvent, DOM_ACTION_NAMES, type DeweeChatConfig, DeweeChatElement, type DomActionsRoot, HistoryMessage, type JsonSchema, LinkResponse, type PageAction, type PageActionContext, type PageActionInfo, PublicConfig, RESERVED_HEADERS, Remediation, type RequestHeadersOption, SessionResponse, defineDeweeChat, mount, renderMarkdown };
package/dist/index.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  ApiClient,
3
3
  ApiError,
4
+ DOM_ACTION_NAMES,
4
5
  DeweeChatElement,
5
6
  RESERVED_HEADERS,
6
7
  defineDeweeChat,
7
8
  mount,
8
9
  renderMarkdown
9
- } from "./chunk-VU2M42GA.js";
10
- import "./chunk-EVZ5K7BN.js";
10
+ } from "./chunk-M6J3IXEI.js";
11
+ import "./chunk-WHHQ6FKK.js";
11
12
  export {
12
13
  ApiClient,
13
14
  ApiError,
15
+ DOM_ACTION_NAMES,
14
16
  DeweeChatElement,
15
17
  RESERVED_HEADERS,
16
18
  defineDeweeChat,
@@ -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
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties } from 'react';
3
3
  import { DeweeChatConfig, ActionEventDetail, DeweeChatElement } from './index.js';
4
- export { ActionConfirmHook, PageAction, PageActionContext, RequestHeadersOption } from './index.js';
5
- import './protocol-CLbGE9Ns.js';
4
+ export { ActionConfirmHook, DomActionsRoot, PageAction, PageActionContext, RequestHeadersOption } from './index.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-VU2M42GA.js";
4
- import "./chunk-EVZ5K7BN.js";
3
+ } from "./chunk-M6J3IXEI.js";
4
+ import "./chunk-WHHQ6FKK.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, ...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 });
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-B-c6tDr7.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-B-c6tDr7.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
  }