@asiyst/sdk 0.1.6 → 0.1.8

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.cts CHANGED
@@ -69,7 +69,7 @@ type AsiystEventName = keyof AsiystEventMap;
69
69
 
70
70
  type AnchorPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
71
71
  type AssistantMode = "guided" | "assist";
72
- type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
72
+ type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open" | "close" | "filter" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
73
73
  type HighlightStyle = "spotlight" | "outline" | "glow" | "pointer" | "pulse" | "dim";
74
74
  type ElementKind = "button" | "link" | "input" | "select" | "textarea" | "form" | "navigation" | "heading" | "menu" | "dialog" | "tab" | "card" | "search" | "section" | "other";
75
75
  interface TargetRef {
@@ -102,6 +102,28 @@ interface BehaviorConfig {
102
102
  voiceSpeed?: number;
103
103
  speechBubble?: boolean;
104
104
  }
105
+ interface AvatarRuntimeRule {
106
+ action?: string;
107
+ allowed?: boolean;
108
+ target?: string;
109
+ route?: string;
110
+ routes?: string[];
111
+ domain?: string;
112
+ domains?: string[];
113
+ enabled?: boolean;
114
+ sourceId?: string;
115
+ dataSourceId?: string;
116
+ }
117
+ interface AvatarDataSource {
118
+ id: string;
119
+ name?: string;
120
+ type?: string;
121
+ enabled?: boolean;
122
+ url?: string;
123
+ method?: string;
124
+ headers?: Record<string, string>;
125
+ credentials?: string;
126
+ }
105
127
  interface ProjectConfig {
106
128
  schemaVersion: number;
107
129
  version: number;
@@ -116,6 +138,11 @@ interface ProjectConfig {
116
138
  behavior: BehaviorConfig;
117
139
  mode: AssistantMode;
118
140
  allowedActions: ActionKind[];
141
+ allowedDomains: string[];
142
+ allowedRoutes: string[];
143
+ blockedRoutes: string[];
144
+ rules: AvatarRuntimeRule[];
145
+ dataSources: AvatarDataSource[];
119
146
  elementSelectors: Record<string, string>;
120
147
  }
121
148
  interface Rect {
@@ -210,7 +237,7 @@ declare const Asiyst: {
210
237
  off<K extends keyof AsiystEventMap>(event: K, handler: (payload: AsiystEventMap[K]) => void): void;
211
238
  };
212
239
 
213
- declare const SDK_VERSION = "0.1.5";
240
+ declare const SDK_VERSION: string;
214
241
 
215
242
  declare class AsiystError extends Error {
216
243
  readonly code: string;
@@ -299,4 +326,18 @@ declare function anchorToViewport(position: "bottom-right" | "bottom-left" | "to
299
326
  y: number;
300
327
  };
301
328
 
302
- export { type ActionKind, ActionNotAllowedError, Asiyst, AsiystError, type AsiystEventMap, type AsiystEventName, type AssistantMode, AuthenticationError, ConfigurationError, type HighlightStyle, type InitOptions, InitializationError, type MappedElement, NetworkError, type ProjectConfig, SDK_VERSION, type TargetInput, TargetNotFoundError, type TargetRef, type TaskDefinition, TaskExecutionError, TaskStatus, type TaskStep, type WebsiteMapSnapshot, type WorkflowDefinition, anchorToViewport, canTransition, computeAvatarDestination, fallbackConfig, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions };
329
+ declare function validateWebsiteDomain(config: ProjectConfig, currentUrl: string): {
330
+ allowed: boolean;
331
+ reason?: string;
332
+ };
333
+ declare function canNavigateTo(config: ProjectConfig, targetUrl: string, currentUrl: string): {
334
+ allowed: boolean;
335
+ reason?: string;
336
+ };
337
+ declare function evaluateActionPermission(action: ActionKind, config: ProjectConfig, currentUrl: string, target?: string): {
338
+ allowed: boolean;
339
+ reason?: string;
340
+ };
341
+ declare function isActionAllowed(action: ActionKind, config: ProjectConfig, source: "developer" | "cloud" | "local", currentUrl?: string, target?: string): boolean;
342
+
343
+ export { type ActionKind, ActionNotAllowedError, Asiyst, AsiystError, type AsiystEventMap, type AsiystEventName, type AssistantMode, AuthenticationError, ConfigurationError, type HighlightStyle, type InitOptions, InitializationError, type MappedElement, NetworkError, type ProjectConfig, SDK_VERSION, type TargetInput, TargetNotFoundError, type TargetRef, type TaskDefinition, TaskExecutionError, TaskStatus, type TaskStep, type WebsiteMapSnapshot, type WorkflowDefinition, anchorToViewport, canNavigateTo, canTransition, computeAvatarDestination, evaluateActionPermission, fallbackConfig, isActionAllowed, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions, validateWebsiteDomain };
package/dist/index.d.ts CHANGED
@@ -69,7 +69,7 @@ type AsiystEventName = keyof AsiystEventMap;
69
69
 
70
70
  type AnchorPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
71
71
  type AssistantMode = "guided" | "assist";
72
- type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
72
+ type ActionKind = "navigate" | "click" | "highlight" | "scroll" | "type" | "select" | "open" | "close" | "filter" | "open-menu" | "open-modal" | "search" | "wait" | "explain" | "complete";
73
73
  type HighlightStyle = "spotlight" | "outline" | "glow" | "pointer" | "pulse" | "dim";
74
74
  type ElementKind = "button" | "link" | "input" | "select" | "textarea" | "form" | "navigation" | "heading" | "menu" | "dialog" | "tab" | "card" | "search" | "section" | "other";
75
75
  interface TargetRef {
@@ -102,6 +102,28 @@ interface BehaviorConfig {
102
102
  voiceSpeed?: number;
103
103
  speechBubble?: boolean;
104
104
  }
105
+ interface AvatarRuntimeRule {
106
+ action?: string;
107
+ allowed?: boolean;
108
+ target?: string;
109
+ route?: string;
110
+ routes?: string[];
111
+ domain?: string;
112
+ domains?: string[];
113
+ enabled?: boolean;
114
+ sourceId?: string;
115
+ dataSourceId?: string;
116
+ }
117
+ interface AvatarDataSource {
118
+ id: string;
119
+ name?: string;
120
+ type?: string;
121
+ enabled?: boolean;
122
+ url?: string;
123
+ method?: string;
124
+ headers?: Record<string, string>;
125
+ credentials?: string;
126
+ }
105
127
  interface ProjectConfig {
106
128
  schemaVersion: number;
107
129
  version: number;
@@ -116,6 +138,11 @@ interface ProjectConfig {
116
138
  behavior: BehaviorConfig;
117
139
  mode: AssistantMode;
118
140
  allowedActions: ActionKind[];
141
+ allowedDomains: string[];
142
+ allowedRoutes: string[];
143
+ blockedRoutes: string[];
144
+ rules: AvatarRuntimeRule[];
145
+ dataSources: AvatarDataSource[];
119
146
  elementSelectors: Record<string, string>;
120
147
  }
121
148
  interface Rect {
@@ -210,7 +237,7 @@ declare const Asiyst: {
210
237
  off<K extends keyof AsiystEventMap>(event: K, handler: (payload: AsiystEventMap[K]) => void): void;
211
238
  };
212
239
 
213
- declare const SDK_VERSION = "0.1.5";
240
+ declare const SDK_VERSION: string;
214
241
 
215
242
  declare class AsiystError extends Error {
216
243
  readonly code: string;
@@ -299,4 +326,18 @@ declare function anchorToViewport(position: "bottom-right" | "bottom-left" | "to
299
326
  y: number;
300
327
  };
301
328
 
302
- export { type ActionKind, ActionNotAllowedError, Asiyst, AsiystError, type AsiystEventMap, type AsiystEventName, type AssistantMode, AuthenticationError, ConfigurationError, type HighlightStyle, type InitOptions, InitializationError, type MappedElement, NetworkError, type ProjectConfig, SDK_VERSION, type TargetInput, TargetNotFoundError, type TargetRef, type TaskDefinition, TaskExecutionError, TaskStatus, type TaskStep, type WebsiteMapSnapshot, type WorkflowDefinition, anchorToViewport, canTransition, computeAvatarDestination, fallbackConfig, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions };
329
+ declare function validateWebsiteDomain(config: ProjectConfig, currentUrl: string): {
330
+ allowed: boolean;
331
+ reason?: string;
332
+ };
333
+ declare function canNavigateTo(config: ProjectConfig, targetUrl: string, currentUrl: string): {
334
+ allowed: boolean;
335
+ reason?: string;
336
+ };
337
+ declare function evaluateActionPermission(action: ActionKind, config: ProjectConfig, currentUrl: string, target?: string): {
338
+ allowed: boolean;
339
+ reason?: string;
340
+ };
341
+ declare function isActionAllowed(action: ActionKind, config: ProjectConfig, source: "developer" | "cloud" | "local", currentUrl?: string, target?: string): boolean;
342
+
343
+ export { type ActionKind, ActionNotAllowedError, Asiyst, AsiystError, type AsiystEventMap, type AsiystEventName, type AssistantMode, AuthenticationError, ConfigurationError, type HighlightStyle, type InitOptions, InitializationError, type MappedElement, NetworkError, type ProjectConfig, SDK_VERSION, type TargetInput, TargetNotFoundError, type TargetRef, type TaskDefinition, TaskExecutionError, TaskStatus, type TaskStep, type WebsiteMapSnapshot, type WorkflowDefinition, anchorToViewport, canNavigateTo, canTransition, computeAvatarDestination, evaluateActionPermission, fallbackConfig, isActionAllowed, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions, validateWebsiteDomain };
package/dist/index.js CHANGED
@@ -57,6 +57,9 @@ var ALL_ACTION_KINDS = [
57
57
  "scroll",
58
58
  "type",
59
59
  "select",
60
+ "open",
61
+ "close",
62
+ "filter",
60
63
  "open-menu",
61
64
  "open-modal",
62
65
  "search",
@@ -66,8 +69,8 @@ var ALL_ACTION_KINDS = [
66
69
  ];
67
70
 
68
71
  // src/core/constants.ts
69
- var SDK_VERSION = "0.1.5";
70
- var DEFAULT_API_BASE_URL = "https://asiyst.com/api/v1";
72
+ var SDK_VERSION = "0.1.7" ;
73
+ var DEFAULT_API_BASE_URL = "https://nqhxpgsjofzqudyqkqib.supabase.co/functions/v1/api";
71
74
  var CONFIG_SCHEMA_VERSION = 1;
72
75
  var HOST_ELEMENT_ID = "asiyst-host";
73
76
  var DATA_ATTR = "data-asiyst";
@@ -122,6 +125,19 @@ var ANCHORS = [
122
125
  "top-right",
123
126
  "top-left"
124
127
  ];
128
+ var PUBLIC_IDENTIFIER_PATTERN = /^(?=.{1,128}$)[A-Za-z0-9_][A-Za-z0-9_-]*$/;
129
+ function isValidPublicIdentifier(value) {
130
+ if (typeof value !== "string") return false;
131
+ const trimmed = value.trim();
132
+ if (!trimmed || trimmed.length > 128) return false;
133
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
134
+ return false;
135
+ }
136
+ return PUBLIC_IDENTIFIER_PATTERN.test(trimmed);
137
+ }
138
+ function isValidProjectId(value) {
139
+ return isValidPublicIdentifier(value);
140
+ }
125
141
  function isRecord(value) {
126
142
  return typeof value === "object" && value !== null && !Array.isArray(value);
127
143
  }
@@ -150,6 +166,88 @@ function parseAllowedActions(value, fallback) {
150
166
  }
151
167
  return next.length > 0 ? next : [...fallback];
152
168
  }
169
+ function parseStringList(value) {
170
+ if (typeof value === "string") {
171
+ return value.split(",").map((item) => sanitizeText(item.trim(), 200)).filter(Boolean);
172
+ }
173
+ if (!Array.isArray(value)) {
174
+ return [];
175
+ }
176
+ return value.filter((item) => typeof item === "string").map((item) => sanitizeText(item.trim(), 200)).filter(Boolean);
177
+ }
178
+ function parseRule(value) {
179
+ if (!isRecord(value)) return null;
180
+ const action = typeof value.action === "string" ? sanitizeText(value.action, 64) : void 0;
181
+ if (action && !ALL_ACTION_KINDS.includes(action)) {
182
+ return null;
183
+ }
184
+ const routeList = parseStringList(value.routes ?? value.route);
185
+ const domainList = parseStringList(value.domains ?? value.domain);
186
+ const hasAllowedValue = typeof value.allowed === "boolean";
187
+ const hasAnySemanticField = hasAllowedValue || typeof value.target === "string" || typeof value.route === "string" || routeList.length > 0 || typeof value.domain === "string" || domainList.length > 0 || typeof value.enabled === "boolean" || typeof value.sourceId === "string" || typeof value.dataSourceId === "string";
188
+ if (!hasAnySemanticField) {
189
+ return null;
190
+ }
191
+ const rule = {
192
+ action,
193
+ allowed: typeof value.allowed === "boolean" ? value.allowed : void 0,
194
+ target: typeof value.target === "string" ? sanitizeText(value.target, 200) : void 0,
195
+ route: typeof value.route === "string" ? sanitizeText(value.route, 200) : void 0,
196
+ routes: routeList,
197
+ domain: typeof value.domain === "string" ? sanitizeText(value.domain, 200) : void 0,
198
+ domains: domainList,
199
+ enabled: typeof value.enabled === "boolean" ? value.enabled : void 0,
200
+ sourceId: typeof value.sourceId === "string" ? sanitizeText(value.sourceId, 128) : void 0,
201
+ dataSourceId: typeof value.dataSourceId === "string" ? sanitizeText(value.dataSourceId, 128) : void 0
202
+ };
203
+ if (typeof value.allowed !== "undefined" && typeof value.allowed !== "boolean") {
204
+ return null;
205
+ }
206
+ return rule;
207
+ }
208
+ function parseRules(value) {
209
+ if (!Array.isArray(value)) {
210
+ return [];
211
+ }
212
+ const rules = [];
213
+ for (const entry of value) {
214
+ const rule = parseRule(entry);
215
+ if (rule) {
216
+ rules.push(rule);
217
+ }
218
+ }
219
+ return rules;
220
+ }
221
+ function parseDataSources(value) {
222
+ if (!Array.isArray(value)) {
223
+ return [];
224
+ }
225
+ const next = [];
226
+ for (const entry of value) {
227
+ if (!isRecord(entry)) continue;
228
+ const id = typeof entry.id === "string" ? sanitizeText(entry.id, 128) : "";
229
+ if (!id) continue;
230
+ next.push({
231
+ id,
232
+ name: typeof entry.name === "string" ? sanitizeText(entry.name, 128) : void 0,
233
+ type: typeof entry.type === "string" ? sanitizeText(entry.type, 64) : void 0,
234
+ enabled: typeof entry.enabled === "boolean" ? entry.enabled : void 0,
235
+ url: typeof entry.url === "string" ? sanitizeText(entry.url, 400) : void 0,
236
+ method: typeof entry.method === "string" ? sanitizeText(entry.method, 20) : void 0,
237
+ headers: isRecord(entry.headers) ? (() => {
238
+ const safeHeaders = {};
239
+ for (const [key, value2] of Object.entries(entry.headers)) {
240
+ if (typeof value2 === "string") {
241
+ safeHeaders[sanitizeText(key, 64)] = sanitizeText(value2, 200);
242
+ }
243
+ }
244
+ return safeHeaders;
245
+ })() : void 0,
246
+ credentials: typeof entry.credentials === "string" ? "[redacted]" : void 0
247
+ });
248
+ }
249
+ return next;
250
+ }
153
251
  function parseTheme(value) {
154
252
  if (!isRecord(value)) {
155
253
  return {};
@@ -220,6 +318,11 @@ function fallbackConfig() {
220
318
  },
221
319
  mode: "guided",
222
320
  allowedActions: ["navigate", "highlight", "scroll", "wait", "explain", "complete"],
321
+ allowedDomains: [],
322
+ allowedRoutes: [],
323
+ blockedRoutes: [],
324
+ rules: [],
325
+ dataSources: [],
223
326
  elementSelectors: {}
224
327
  };
225
328
  }
@@ -243,21 +346,30 @@ function normalizeProjectConfig(raw) {
243
346
  behavior: { ...base.behavior, ...parseBehavior(raw.behavior) },
244
347
  mode: raw.mode === "assist" ? "assist" : "guided",
245
348
  allowedActions: parseAllowedActions(raw.allowedActions, base.allowedActions),
349
+ allowedDomains: parseStringList(raw.allowedDomains ?? raw.allowed_domains),
350
+ allowedRoutes: parseStringList(raw.allowedRoutes ?? raw.allowed_routes),
351
+ blockedRoutes: parseStringList(raw.blockedRoutes ?? raw.blocked_routes),
352
+ rules: parseRules(raw.rules ?? raw.avatarRules),
353
+ dataSources: parseDataSources(raw.dataSources ?? raw.enabledDataSources),
246
354
  elementSelectors: parseElementSelectors(raw.elementSelectors)
247
355
  };
248
356
  }
249
357
  function validateInitOptions(options) {
250
- if (typeof options.projectId !== "string" || !options.projectId.trim()) {
251
- throw new ConfigurationError("projectId is required");
358
+ const projectIdValue = typeof options.projectId === "string" ? options.projectId.trim() : "";
359
+ if (!projectIdValue) {
360
+ throw new ConfigurationError("Asiyst SDK: projectId is required.");
361
+ }
362
+ if (!isValidProjectId(projectIdValue)) {
363
+ throw new ConfigurationError("Asiyst SDK: projectId is invalid. Use a public Project ID.");
252
364
  }
253
365
  if (typeof options.publicKey !== "string" || !options.publicKey.trim()) {
254
366
  throw new ConfigurationError("publicKey is required");
255
367
  }
256
- if (options.projectId.length > 128 || options.publicKey.length > 256) {
368
+ if (projectIdValue.length > 128 || options.publicKey.trim().length > 256) {
257
369
  throw new ConfigurationError("project credentials exceed allowed length");
258
370
  }
259
371
  return {
260
- projectId: options.projectId.trim(),
372
+ projectId: projectIdValue,
261
373
  publicKey: options.publicKey.trim()
262
374
  };
263
375
  }
@@ -1635,14 +1747,110 @@ var AvatarController = class {
1635
1747
  };
1636
1748
 
1637
1749
  // src/interaction/permissions.ts
1638
- function isActionAllowed(action, config, source) {
1750
+ function normalizeHost(host) {
1751
+ return host.replace(/^https?:\/\//i, "").replace(/\/$/, "").toLowerCase();
1752
+ }
1753
+ function matchesRoutePattern(pathname, pattern) {
1754
+ const candidate = pathname;
1755
+ const normalized = pattern.trim();
1756
+ if (!normalized) {
1757
+ return false;
1758
+ }
1759
+ const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
1760
+ return new RegExp(`^${escaped}$`, "i").test(candidate);
1761
+ }
1762
+ function isLikelyJavaScriptUrl(value) {
1763
+ const lower = value.trim().toLowerCase();
1764
+ return lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.includes("eval(") || lower.includes("new function") || lower.includes("document.cookie");
1765
+ }
1766
+ function validateWebsiteDomain(config, currentUrl) {
1767
+ const allowedDomains = config.allowedDomains.map((domain) => normalizeHost(domain));
1768
+ if (allowedDomains.length === 0) {
1769
+ return { allowed: true };
1770
+ }
1771
+ try {
1772
+ const parsed = new URL(currentUrl);
1773
+ const host = normalizeHost(parsed.host);
1774
+ const allowed = allowedDomains.some((domain) => host === domain || host.endsWith(`.${domain}`));
1775
+ if (!allowed) {
1776
+ return { allowed: false, reason: "DOMAIN_NOT_AUTHORIZED" };
1777
+ }
1778
+ return { allowed: true };
1779
+ } catch {
1780
+ return { allowed: false, reason: "DOMAIN_NOT_AUTHORIZED" };
1781
+ }
1782
+ }
1783
+ function canNavigateTo(config, targetUrl, currentUrl) {
1784
+ if (!targetUrl || typeof targetUrl !== "string") {
1785
+ return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
1786
+ }
1787
+ if (isLikelyJavaScriptUrl(targetUrl)) {
1788
+ return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
1789
+ }
1790
+ let target;
1791
+ try {
1792
+ target = new URL(targetUrl, currentUrl);
1793
+ } catch {
1794
+ return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
1795
+ }
1796
+ if (!["http:", "https:"].includes(target.protocol)) {
1797
+ return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
1798
+ }
1799
+ const domainCheck = validateWebsiteDomain(config, currentUrl);
1800
+ if (!domainCheck.allowed) {
1801
+ return domainCheck;
1802
+ }
1803
+ const host = normalizeHost(target.host);
1804
+ const allowedDomains = config.allowedDomains.map((domain) => normalizeHost(domain));
1805
+ if (allowedDomains.length > 0 && !allowedDomains.some((domain) => host === domain || host.endsWith(`.${domain}`))) {
1806
+ return { allowed: false, reason: "DOMAIN_NOT_ALLOWED" };
1807
+ }
1808
+ const pathname = target.pathname || "/";
1809
+ if (config.blockedRoutes.some((route) => matchesRoutePattern(pathname, route))) {
1810
+ return { allowed: false, reason: "ROUTE_BLOCKED" };
1811
+ }
1812
+ if (config.allowedRoutes.length > 0 && !config.allowedRoutes.some((route) => matchesRoutePattern(pathname, route))) {
1813
+ return { allowed: false, reason: "ROUTE_NOT_PERMITTED" };
1814
+ }
1815
+ return { allowed: true };
1816
+ }
1817
+ function evaluateActionPermission(action, config, currentUrl, target) {
1818
+ const explicitRule = config.rules.find((rule) => rule.action === action && rule.enabled !== false);
1819
+ if (explicitRule && typeof explicitRule.allowed === "boolean") {
1820
+ if (!explicitRule.allowed) {
1821
+ return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
1822
+ }
1823
+ }
1824
+ if (!config.allowedActions.includes(action)) {
1825
+ return { allowed: false, reason: "ACTION_NOT_PERMITTED" };
1826
+ }
1827
+ if (action === "navigate") {
1828
+ if (!target) {
1829
+ return { allowed: true };
1830
+ }
1831
+ return canNavigateTo(config, target, currentUrl);
1832
+ }
1833
+ const allowedDomains = config.allowedDomains;
1834
+ if (allowedDomains.length > 0) {
1835
+ const domainCheck = validateWebsiteDomain(config, currentUrl);
1836
+ if (!domainCheck.allowed) {
1837
+ return domainCheck;
1838
+ }
1839
+ }
1840
+ return { allowed: true };
1841
+ }
1842
+ function isActionAllowed(action, config, source, currentUrl, target) {
1639
1843
  if (source === "developer") {
1640
1844
  return true;
1641
1845
  }
1642
- return config.allowedActions.includes(action);
1846
+ if (currentUrl) {
1847
+ return evaluateActionPermission(action, config, currentUrl, target).allowed;
1848
+ }
1849
+ return config.allowedActions.includes(action) && !config.rules.some((rule) => rule.action === action && rule.allowed === false);
1643
1850
  }
1644
- function assertActionAllowed(action, config, source) {
1645
- if (!isActionAllowed(action, config, source)) {
1851
+ function assertActionAllowed(action, config, source, currentUrl, target) {
1852
+ const allowed = isActionAllowed(action, config, source, currentUrl, target);
1853
+ if (!allowed) {
1646
1854
  throw new ActionNotAllowedError(`Action "${action}" is not permitted for this project`);
1647
1855
  }
1648
1856
  }
@@ -1672,7 +1880,12 @@ var InteractionEngine = class {
1672
1880
  }
1673
1881
  async execute(step, source) {
1674
1882
  const config = this.getConfig();
1675
- assertActionAllowed(step.action, config, source);
1883
+ const currentUrl = this.win.location.href;
1884
+ const target = step.url ?? (typeof step.target === "string" ? step.target : void 0);
1885
+ const allowed = this.evaluatePermission(step.action, config, currentUrl, target, source);
1886
+ if (!allowed.ok) {
1887
+ return { ok: false, waitedForUser: false, allowed: false, reason: allowed.reason ?? "ACTION_NOT_PERMITTED" };
1888
+ }
1676
1889
  const wait = shouldWaitForUser(step.action, config.mode, step.waitForUser);
1677
1890
  switch (step.action) {
1678
1891
  case "explain":
@@ -1703,6 +1916,14 @@ var InteractionEngine = class {
1703
1916
  throw new TaskExecutionError("Unsupported action");
1704
1917
  }
1705
1918
  }
1919
+ evaluatePermission(action, config, currentUrl, target, source) {
1920
+ try {
1921
+ assertActionAllowed(action, config, source, currentUrl, target);
1922
+ return { ok: true };
1923
+ } catch {
1924
+ return { ok: false, reason: "ACTION_NOT_PERMITTED" };
1925
+ }
1926
+ }
1706
1927
  async highlight(step, source, wait) {
1707
1928
  if (!step.target) {
1708
1929
  throw new TaskExecutionError("Highlight requires a target");
@@ -1886,6 +2107,11 @@ var TaskEngine = class {
1886
2107
  }
1887
2108
  this.move(TaskStatus.ActionStarted);
1888
2109
  const result = await this.interaction.execute(step, source);
2110
+ if (!result.ok) {
2111
+ this.events.emit("asiyst:task:failed", { taskId: task.id, reason: result.reason ?? "ACTION_NOT_PERMITTED" });
2112
+ this.analytics.track("task_failed", { taskId: task.id });
2113
+ return this.finish(TaskStatus.Failed, result.reason ?? "ACTION_NOT_PERMITTED");
2114
+ }
1889
2115
  if (result.waitedForUser && result.elementId) {
1890
2116
  this.move(TaskStatus.WaitingForUser);
1891
2117
  const clicked = await this.interaction.waitForElementClick(result.elementId, step.timeoutMs ?? 3e4);
@@ -2188,6 +2414,15 @@ var AsiystRuntime = class {
2188
2414
  async start() {
2189
2415
  this.events.emit("asiyst:initialized", { projectId: this.options.projectId });
2190
2416
  const cfg = await isolateAsync(() => this.config.refresh(), this.config.get());
2417
+ const domainCheck = validateWebsiteDomain(cfg, window.location.href);
2418
+ if (!domainCheck.allowed) {
2419
+ this.connectionStatus = "disconnected";
2420
+ this.events.emit("asiyst:error", {
2421
+ code: "domain_not_authorized",
2422
+ message: "This website is not authorized for the current Asiyst project."
2423
+ });
2424
+ return;
2425
+ }
2191
2426
  this.avatar.applyConfig(cfg);
2192
2427
  this.avatar.show();
2193
2428
  try {
@@ -2382,6 +2617,6 @@ var Asiyst = {
2382
2617
  }
2383
2618
  };
2384
2619
 
2385
- export { ActionNotAllowedError, Asiyst, AsiystError, AuthenticationError, ConfigurationError, InitializationError, NetworkError, SDK_VERSION, TargetNotFoundError, TaskExecutionError, TaskStatus, anchorToViewport, canTransition, computeAvatarDestination, fallbackConfig, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions };
2620
+ export { ActionNotAllowedError, Asiyst, AsiystError, AuthenticationError, ConfigurationError, InitializationError, NetworkError, SDK_VERSION, TargetNotFoundError, TaskExecutionError, TaskStatus, anchorToViewport, canNavigateTo, canTransition, computeAvatarDestination, evaluateActionPermission, fallbackConfig, isActionAllowed, isSafeSelector, normalizeProjectConfig, sanitizeText, validateInitOptions, validateWebsiteDomain };
2386
2621
  //# sourceMappingURL=index.js.map
2387
2622
  //# sourceMappingURL=index.js.map