@asiyst/sdk 0.1.4 → 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.0";
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.0";
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.0";
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
  }
@@ -980,7 +1092,17 @@ var TargetResolver = class {
980
1092
  if (!best || bestScore < 2) {
981
1093
  return null;
982
1094
  }
983
- return findElementByAsiystId(this.doc, best.id) ?? this.doc.querySelector(`[id="${best.id.replace(/^dom:/, "")}"]`);
1095
+ return findElementByAsiystId(this.doc, best.id) ?? this.doc.querySelector(`[id="${best.id.replace(/^dom:/, "")}"]`) ?? this.findMappedElement(best);
1096
+ }
1097
+ findMappedElement(mapped) {
1098
+ const candidates = Array.from(this.doc.querySelectorAll("a[href], button, input, select, textarea, form, nav, h1, h2, h3, [role]"));
1099
+ return candidates.find((element) => {
1100
+ if (!isElementVisible(element) || isDisabled(element)) return false;
1101
+ const text = normalize(`${element.getAttribute("aria-label") ?? ""} ${element.textContent ?? ""}`);
1102
+ const wanted = normalize(`${mapped.label} ${mapped.text}`);
1103
+ const role = normalize(element.getAttribute("role") ?? "");
1104
+ return wanted.length > 0 && (text.includes(wanted) || wanted.includes(text)) || mapped.role !== null && role === normalize(mapped.role);
1105
+ }) ?? null;
984
1106
  }
985
1107
  matchesMapped(item, element, ref) {
986
1108
  if (ref.id && item.id === ref.id) {
@@ -1625,14 +1747,110 @@ var AvatarController = class {
1625
1747
  };
1626
1748
 
1627
1749
  // src/interaction/permissions.ts
1628
- 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) {
1629
1843
  if (source === "developer") {
1630
1844
  return true;
1631
1845
  }
1632
- 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);
1633
1850
  }
1634
- function assertActionAllowed(action, config, source) {
1635
- 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) {
1636
1854
  throw new ActionNotAllowedError(`Action "${action}" is not permitted for this project`);
1637
1855
  }
1638
1856
  }
@@ -1662,7 +1880,12 @@ var InteractionEngine = class {
1662
1880
  }
1663
1881
  async execute(step, source) {
1664
1882
  const config = this.getConfig();
1665
- 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
+ }
1666
1889
  const wait = shouldWaitForUser(step.action, config.mode, step.waitForUser);
1667
1890
  switch (step.action) {
1668
1891
  case "explain":
@@ -1693,6 +1916,14 @@ var InteractionEngine = class {
1693
1916
  throw new TaskExecutionError("Unsupported action");
1694
1917
  }
1695
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
+ }
1696
1927
  async highlight(step, source, wait) {
1697
1928
  if (!step.target) {
1698
1929
  throw new TaskExecutionError("Highlight requires a target");
@@ -1876,6 +2107,11 @@ var TaskEngine = class {
1876
2107
  }
1877
2108
  this.move(TaskStatus.ActionStarted);
1878
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
+ }
1879
2115
  if (result.waitedForUser && result.elementId) {
1880
2116
  this.move(TaskStatus.WaitingForUser);
1881
2117
  const clicked = await this.interaction.waitForElementClick(result.elementId, step.timeoutMs ?? 3e4);
@@ -2178,6 +2414,15 @@ var AsiystRuntime = class {
2178
2414
  async start() {
2179
2415
  this.events.emit("asiyst:initialized", { projectId: this.options.projectId });
2180
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
+ }
2181
2426
  this.avatar.applyConfig(cfg);
2182
2427
  this.avatar.show();
2183
2428
  try {
@@ -2372,6 +2617,6 @@ var Asiyst = {
2372
2617
  }
2373
2618
  };
2374
2619
 
2375
- 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 };
2376
2621
  //# sourceMappingURL=index.js.map
2377
2622
  //# sourceMappingURL=index.js.map