agentic-browser 2.2.0 → 2.3.0

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/AGENTS.md CHANGED
@@ -108,7 +108,7 @@ AgenticBrowserCore → ControlApi → SessionManager → BrowserController (CDP)
108
108
 
109
109
  ## Environment Variables
110
110
 
111
- - `AGENTIC_BROWSER_LOG_DIR` — base dir for sessions/memory/events (default: `.agentic-browser`)
111
+ - `AGENTIC_BROWSER_DIR` — base dir for sessions/memory/events (default: `.agentic-browser`)
112
112
  - `AGENTIC_BROWSER_CHROME_EXECUTABLE_PATH` — explicit Chrome path (auto-discovered if not set)
113
113
  - `AGENTIC_BROWSER_CDP_URL` — connect to an already-running Chrome via CDP (e.g. `http://127.0.0.1:9222`)
114
114
  - `AGENTIC_BROWSER_USER_PROFILE` — use real Chrome profile (`default`, `true`, or absolute path)
package/README.md CHANGED
@@ -267,7 +267,7 @@ agentic-browser memory:stats
267
267
  ## Important Notes for LLMs
268
268
 
269
269
  - Exactly **one** managed session is supported at a time.
270
- - Session state is persisted in `.agentic-browser/`.
270
+ - Session state is persisted in `~/.agentic-browser/` (override with `AGENTIC_BROWSER_DIR`).
271
271
  - All commands print exactly one JSON line to `stdout`.
272
272
  - The typed `agent` commands are the preferred interface for LLMs. Use low-level `command:run` only when you need raw JSON payload control.
273
273
  - Parse only `stdout` as result object and use exit code for success/failure.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as createCliRuntime } from "../runtime-CRkUsvhV.mjs";
2
+ import { r as createCliRuntime } from "../runtime-BCGndjhR.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import crypto from "node:crypto";
@@ -7,7 +7,7 @@ import { Command } from "commander";
7
7
  //#region src/cli/commands/agent.ts
8
8
  const STATE_FILE_NAME = "agent-state.json";
9
9
  function stateFilePath(runtime) {
10
- return path.join(runtime.context.config.logDir, STATE_FILE_NAME);
10
+ return path.join(runtime.context.config.dataDir, STATE_FILE_NAME);
11
11
  }
12
12
  function loadState(runtime) {
13
13
  const filePath = stateFilePath(runtime);
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-CRkUsvhV.mjs";
2
+ import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-BCGndjhR.mjs";
3
3
  export { AgenticBrowserCore, createAgenticBrowserCore, createMockAgenticBrowserCore };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createAgenticBrowserCore } from "../runtime-CRkUsvhV.mjs";
2
+ import { n as createAgenticBrowserCore } from "../runtime-BCGndjhR.mjs";
3
3
  import { z } from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -186,6 +186,14 @@ function compactMemoryResults(results) {
186
186
  if (Array.isArray(compactAlias.fallbackSelectors) && compactAlias.fallbackSelectors.length === 0) delete compactAlias.fallbackSelectors;
187
187
  return compactAlias;
188
188
  });
189
+ if (compact.siteProfile) {
190
+ const profile = compact.siteProfile;
191
+ compact.siteProfile = {
192
+ selectorPatterns: (profile.selectorPatterns ?? []).map((p) => `${p.pattern} (x${p.frequency})`),
193
+ cookieBanner: profile.cookieBanner,
194
+ visitCount: profile.visitCount
195
+ };
196
+ }
189
197
  return compact;
190
198
  }) };
191
199
  }
@@ -2114,7 +2114,7 @@ var SessionManager = class {
2114
2114
  constructor(ctx, browser) {
2115
2115
  this.ctx = ctx;
2116
2116
  this.browser = browser;
2117
- this.store = new SessionStore(this.ctx.config.logDir);
2117
+ this.store = new SessionStore(this.ctx.config.dataDir);
2118
2118
  }
2119
2119
  async createSession(input) {
2120
2120
  if (input.browser !== "chrome") throw new Error("Only chrome is supported");
@@ -2235,7 +2235,19 @@ var SessionManager = class {
2235
2235
  }
2236
2236
  async getContent(sessionId, options) {
2237
2237
  const record = await this.ensureSession(sessionId);
2238
- return await this.browser.getContent(record.targetWsUrl, options);
2238
+ const result = await this.browser.getContent(record.targetWsUrl, options);
2239
+ if (options.mode === "summary" && result.structuredContent) {
2240
+ const sc = result.structuredContent;
2241
+ const url = typeof sc.url === "string" ? sc.url : record.lastUrl;
2242
+ const parsed = this.parseUrl(url);
2243
+ const headingTexts = (sc.headings ?? []).map((h) => typeof h === "string" ? h : h.text ?? "");
2244
+ const landmarkTexts = (sc.landmarks ?? []).map((l) => typeof l === "string" ? l : l.role ?? "");
2245
+ this.ctx.memoryService.updateProfileFromContent(parsed.domain, parsed.pathPattern, {
2246
+ headings: headingTexts,
2247
+ landmarks: landmarkTexts
2248
+ });
2249
+ }
2250
+ return result;
2239
2251
  }
2240
2252
  async getInteractiveElements(sessionId, options) {
2241
2253
  const record = await this.ensureSession(sessionId);
@@ -2243,7 +2255,10 @@ var SessionManager = class {
2243
2255
  }
2244
2256
  async dismissCookieBanner(sessionId) {
2245
2257
  const record = await this.ensureSession(sessionId);
2246
- return await this.browser.dismissCookieBanner(record.targetWsUrl);
2258
+ const result = await this.browser.dismissCookieBanner(record.targetWsUrl);
2259
+ const parsed = this.parseUrl(record.lastUrl);
2260
+ this.ctx.memoryService.updateProfileCookieBanner(parsed.domain, result);
2261
+ return result;
2247
2262
  }
2248
2263
  setStatus(status, reason) {
2249
2264
  const active = this.store.getActive();
@@ -2361,7 +2376,7 @@ var SessionManager = class {
2361
2376
  removedSessionIds.push(record.session.sessionId);
2362
2377
  }
2363
2378
  const keepIds = new Set(keep.map((record) => record.session.sessionId));
2364
- const profilesDir = path.join(this.ctx.config.logDir, "profiles");
2379
+ const profilesDir = path.join(this.ctx.config.dataDir, "profiles");
2365
2380
  const removedProfileDirs = [];
2366
2381
  if (fs.existsSync(profilesDir)) for (const entry of fs.readdirSync(profilesDir)) {
2367
2382
  const fullPath = path.join(profilesDir, entry);
@@ -2549,7 +2564,7 @@ function loadConfig(env = process.env) {
2549
2564
  host: env.AGENTIC_BROWSER_HOST ?? "127.0.0.1",
2550
2565
  wsPort,
2551
2566
  commandTimeoutMs,
2552
- logDir: env.AGENTIC_BROWSER_LOG_DIR ?? path.resolve(process.cwd(), ".agentic-browser"),
2567
+ dataDir: env.AGENTIC_BROWSER_DIR ?? path.join(os.homedir(), ".agentic-browser"),
2553
2568
  browserExecutablePath: env.AGENTIC_BROWSER_CHROME_EXECUTABLE_PATH ?? env.AGENTIC_BROWSER_CHROME_PATH,
2554
2569
  cdpUrl: env.AGENTIC_BROWSER_CDP_URL,
2555
2570
  userProfileDir,
@@ -2751,7 +2766,7 @@ function selectorSignal(insight) {
2751
2766
  const evidenceStrength = Math.min(selectorEvidence / 5, 1);
2752
2767
  return .7 * recipeCoverage + .3 * evidenceStrength;
2753
2768
  }
2754
- function scoreInsight(insight, normalizedIntent, normalizedDomain) {
2769
+ function scoreInsight(insight, normalizedIntent, normalizedDomain, profileLookup) {
2755
2770
  const insightIntent = normalize(insight.taskIntent);
2756
2771
  const intentMatch = insightIntent === normalizedIntent ? 1 : 0;
2757
2772
  const intentPartial = intentMatch === 1 || insightIntent.includes(normalizedIntent) || normalizedIntent.includes(insightIntent) ? .65 : 0;
@@ -2759,7 +2774,7 @@ function scoreInsight(insight, normalizedIntent, normalizedDomain) {
2759
2774
  const reliability = .6 * confidenceFromCounts(insight.successCount, insight.failureCount) + .4 * freshnessWeight(insight.freshness);
2760
2775
  const selectorQuality = selectorSignal(insight);
2761
2776
  const score = .5 * Math.max(intentMatch, intentPartial) + .2 * domainMatch + .15 * reliability + .15 * selectorQuality;
2762
- return {
2777
+ const result = {
2763
2778
  insightId: insight.insightId,
2764
2779
  taskIntent: insight.taskIntent,
2765
2780
  siteDomain: insight.siteDomain,
@@ -2770,6 +2785,14 @@ function scoreInsight(insight, normalizedIntent, normalizedDomain) {
2770
2785
  selectorAliases: buildSelectorAliases(insight),
2771
2786
  score
2772
2787
  };
2788
+ const profile = profileLookup?.(insight.siteDomain);
2789
+ if (profile) result.siteProfile = {
2790
+ selectorPatterns: profile.selectorPatterns.slice(0, 5),
2791
+ layoutFingerprints: profile.layoutFingerprints.slice(0, 3),
2792
+ cookieBanner: profile.cookieBanner,
2793
+ visitCount: profile.visitCount
2794
+ };
2795
+ return result;
2773
2796
  }
2774
2797
  var MemoryIndex = class {
2775
2798
  /** Domain → insights index, rebuilt lazily when insight list changes. */
@@ -2790,7 +2813,7 @@ var MemoryIndex = class {
2790
2813
  }
2791
2814
  this.indexedRefs = insights.slice();
2792
2815
  }
2793
- search(insights, input) {
2816
+ search(insights, input, profileLookup) {
2794
2817
  const normalizedIntent = normalize(input.taskIntent);
2795
2818
  const normalizedDomain = input.siteDomain ? normalize(input.siteDomain) : void 0;
2796
2819
  const limit = input.limit ?? 10;
@@ -2799,7 +2822,7 @@ var MemoryIndex = class {
2799
2822
  if (limit === 1 && normalizedDomain) {
2800
2823
  let best;
2801
2824
  for (const insight of candidates) {
2802
- const result = scoreInsight(insight, normalizedIntent, normalizedDomain);
2825
+ const result = scoreInsight(insight, normalizedIntent, normalizedDomain, profileLookup);
2803
2826
  if (result.score > 0 && (!best || result.score > best.score)) {
2804
2827
  best = result;
2805
2828
  if (best.score >= .95) break;
@@ -2807,7 +2830,7 @@ var MemoryIndex = class {
2807
2830
  }
2808
2831
  return best ? [best] : [];
2809
2832
  }
2810
- return candidates.map((insight) => scoreInsight(insight, normalizedIntent, normalizedDomain)).filter((result) => result.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
2833
+ return candidates.map((insight) => scoreInsight(insight, normalizedIntent, normalizedDomain, profileLookup)).filter((result) => result.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
2811
2834
  }
2812
2835
  };
2813
2836
  //#endregion
@@ -2838,6 +2861,20 @@ function applyFailure(insight, signal) {
2838
2861
  updatedAt: now
2839
2862
  };
2840
2863
  }
2864
+ function applyAgeStaleness(insight) {
2865
+ const ageDays = (Date.now() - Date.parse(insight.lastVerifiedAt)) / (1440 * 60 * 1e3);
2866
+ if (insight.freshness === "fresh" && ageDays > 30) return {
2867
+ ...insight,
2868
+ freshness: "suspect",
2869
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2870
+ };
2871
+ if (insight.freshness === "suspect" && ageDays > 44) return {
2872
+ ...insight,
2873
+ freshness: "stale",
2874
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2875
+ };
2876
+ return insight;
2877
+ }
2841
2878
  function applySuccess(insight) {
2842
2879
  const now = (/* @__PURE__ */ new Date()).toISOString();
2843
2880
  const successCount = insight.successCount + 1;
@@ -2904,6 +2941,147 @@ const TaskInsightSchema = z.object({
2904
2941
  selectorAliases: z.array(SelectorAliasSchema).default([])
2905
2942
  });
2906
2943
  const MemoryStateSchema = z.object({ insights: z.array(TaskInsightSchema) });
2944
+ const SelectorPatternSchema = z.object({
2945
+ pattern: z.string().min(1),
2946
+ frequency: z.number().int().nonnegative(),
2947
+ lastSeenAt: z.string().datetime()
2948
+ });
2949
+ const PageLayoutFingerprintSchema = z.object({
2950
+ pathPattern: z.string().min(1),
2951
+ headings: z.array(z.string()).default([]),
2952
+ landmarks: z.array(z.string()).default([]),
2953
+ lastSeenAt: z.string().datetime()
2954
+ });
2955
+ const SiteProfileSchema = z.object({
2956
+ domain: z.string().min(1),
2957
+ selectorPatterns: z.array(SelectorPatternSchema).default([]),
2958
+ layoutFingerprints: z.array(PageLayoutFingerprintSchema).default([]),
2959
+ navigationPaths: z.array(z.string()).default([]),
2960
+ cookieBanner: z.object({
2961
+ detected: z.boolean(),
2962
+ autoDismissed: z.boolean().optional(),
2963
+ method: z.string().optional()
2964
+ }).optional(),
2965
+ visitCount: z.number().int().nonnegative().default(0),
2966
+ lastVisitAt: z.string().datetime(),
2967
+ createdAt: z.string().datetime()
2968
+ });
2969
+ const SiteProfileStateSchema = z.object({ profiles: z.array(SiteProfileSchema) });
2970
+ //#endregion
2971
+ //#region src/memory/site-profile-store.ts
2972
+ const EMPTY_STATE$1 = { profiles: [] };
2973
+ const FLUSH_DELAY_MS$1 = 500;
2974
+ const REGISTERED_EXIT_HANDLERS$1 = Symbol.for("agentic-browser.site-profile-store.exit-handlers");
2975
+ const REGISTERED_STORES$1 = Symbol.for("agentic-browser.site-profile-store.instances");
2976
+ var SiteProfileStore = class {
2977
+ filePath;
2978
+ cached = null;
2979
+ dirty = false;
2980
+ flushTimer = null;
2981
+ constructor(baseDir) {
2982
+ const memoryDir = path.join(baseDir, "memory");
2983
+ fs.mkdirSync(memoryDir, { recursive: true });
2984
+ this.filePath = path.join(memoryDir, "site-profiles.json");
2985
+ const tmpPath = `${this.filePath}.tmp`;
2986
+ try {
2987
+ if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
2988
+ } catch {}
2989
+ if (!fs.existsSync(this.filePath)) this.writeDisk(EMPTY_STATE$1);
2990
+ this.registerProcessHandlers();
2991
+ }
2992
+ list() {
2993
+ return this.getCache();
2994
+ }
2995
+ get(domain) {
2996
+ const normalized = domain.toLowerCase();
2997
+ return this.getCache().find((p) => p.domain === normalized);
2998
+ }
2999
+ upsert(profile) {
3000
+ SiteProfileSchema.parse(profile);
3001
+ const profiles = this.getCache();
3002
+ const index = profiles.findIndex((p) => p.domain === profile.domain);
3003
+ if (index >= 0) profiles[index] = profile;
3004
+ else profiles.push(profile);
3005
+ this.markDirty();
3006
+ }
3007
+ flushSync() {
3008
+ if (this.flushTimer) {
3009
+ clearTimeout(this.flushTimer);
3010
+ this.flushTimer = null;
3011
+ }
3012
+ if (this.dirty && this.cached) {
3013
+ this.writeDisk({ profiles: this.cached });
3014
+ this.dirty = false;
3015
+ }
3016
+ }
3017
+ getCache() {
3018
+ if (this.cached) return this.cached;
3019
+ this.cached = this.readDisk().profiles;
3020
+ return this.cached;
3021
+ }
3022
+ markDirty() {
3023
+ this.dirty = true;
3024
+ if (!this.flushTimer) this.flushTimer = setTimeout(() => {
3025
+ this.flushTimer = null;
3026
+ this.flushSync();
3027
+ }, FLUSH_DELAY_MS$1);
3028
+ }
3029
+ readDisk() {
3030
+ let raw;
3031
+ try {
3032
+ raw = JSON.parse(fs.readFileSync(this.filePath, "utf8"));
3033
+ } catch {
3034
+ this.backupAndReset();
3035
+ return EMPTY_STATE$1;
3036
+ }
3037
+ try {
3038
+ return SiteProfileStateSchema.parse(raw);
3039
+ } catch {
3040
+ const obj = raw;
3041
+ if (Array.isArray(obj?.profiles)) {
3042
+ const salvaged = obj.profiles.filter((item) => SiteProfileSchema.safeParse(item).success);
3043
+ if (salvaged.length > 0) {
3044
+ const state = { profiles: salvaged };
3045
+ this.backupAndReset();
3046
+ this.writeDisk(state);
3047
+ return state;
3048
+ }
3049
+ }
3050
+ this.backupAndReset();
3051
+ return EMPTY_STATE$1;
3052
+ }
3053
+ }
3054
+ backupAndReset() {
3055
+ const corruptPath = `${this.filePath}.corrupt-${Date.now()}`;
3056
+ try {
3057
+ fs.copyFileSync(this.filePath, corruptPath);
3058
+ } catch {}
3059
+ }
3060
+ writeDisk(state) {
3061
+ const tempPath = `${this.filePath}.tmp`;
3062
+ fs.writeFileSync(tempPath, JSON.stringify(state, null, 2), "utf8");
3063
+ fs.renameSync(tempPath, this.filePath);
3064
+ }
3065
+ registerProcessHandlers() {
3066
+ const proc = process;
3067
+ const stores = proc[REGISTERED_STORES$1] ??= /* @__PURE__ */ new Set();
3068
+ stores.add(this);
3069
+ if (proc[REGISTERED_EXIT_HANDLERS$1]) return;
3070
+ proc[REGISTERED_EXIT_HANDLERS$1] = true;
3071
+ const flushAll = () => {
3072
+ for (const store of stores) store.flushSync();
3073
+ };
3074
+ process.on("exit", flushAll);
3075
+ process.on("SIGINT", () => {
3076
+ flushAll();
3077
+ process.exit(0);
3078
+ });
3079
+ process.on("SIGTERM", () => {
3080
+ flushAll();
3081
+ process.exit(0);
3082
+ });
3083
+ }
3084
+ };
2907
3085
  //#endregion
2908
3086
  //#region src/memory/task-insight-store.ts
2909
3087
  const EMPTY_STATE = { insights: [] };
@@ -3030,25 +3208,42 @@ var TaskInsightStore = class {
3030
3208
  //#endregion
3031
3209
  //#region src/memory/memory-service.ts
3032
3210
  const SEARCH_CACHE_TTL_MS = 2e3;
3211
+ const MAX_NAVIGATION_PATHS = 20;
3212
+ const MAX_LAYOUT_FINGERPRINTS = 10;
3213
+ const MAX_SELECTOR_PATTERNS = 15;
3033
3214
  var MemoryService = class {
3034
3215
  store;
3216
+ profileStore;
3035
3217
  index;
3036
3218
  /** Simple TTL cache for search results keyed by intent+domain+limit. */
3037
3219
  searchCache = /* @__PURE__ */ new Map();
3038
3220
  constructor(baseDir) {
3039
3221
  this.store = new TaskInsightStore(baseDir);
3222
+ this.profileStore = new SiteProfileStore(baseDir);
3040
3223
  this.index = new MemoryIndex();
3041
3224
  }
3042
3225
  /** Force an immediate synchronous flush of pending writes. */
3043
3226
  flushSync() {
3044
3227
  this.store.flushSync();
3228
+ this.profileStore.flushSync();
3045
3229
  }
3046
3230
  search(input) {
3047
3231
  const cacheKey = `${input.taskIntent}\0${input.siteDomain ?? ""}\0${input.limit ?? 10}`;
3048
3232
  const now = Date.now();
3049
3233
  const cached = this.searchCache.get(cacheKey);
3050
3234
  if (cached && now - cached.ts < SEARCH_CACHE_TTL_MS) return cached.results;
3051
- const results = this.index.search(this.store.list(), input);
3235
+ const insights = this.store.list();
3236
+ let mutated = false;
3237
+ for (let i = 0; i < insights.length; i++) {
3238
+ const decayed = applyAgeStaleness(insights[i]);
3239
+ if (decayed.freshness !== insights[i].freshness) {
3240
+ insights[i] = decayed;
3241
+ this.store.upsert(decayed);
3242
+ mutated = true;
3243
+ }
3244
+ }
3245
+ if (mutated) this.searchCache.clear();
3246
+ const results = this.index.search(insights, input, (domain) => this.profileStore.get(domain));
3052
3247
  this.searchCache.set(cacheKey, {
3053
3248
  results,
3054
3249
  ts: now
@@ -3092,6 +3287,7 @@ var MemoryService = class {
3092
3287
  return verified;
3093
3288
  }
3094
3289
  recordSuccess(input) {
3290
+ this.updateSiteProfile(input.siteDomain, input.selector, input.url, input.sitePathPattern);
3095
3291
  const insights = this.store.list();
3096
3292
  const matched = this.findBestExactMatch(insights, input);
3097
3293
  const evidence = this.createEvidence(input, "success");
@@ -3149,6 +3345,7 @@ var MemoryService = class {
3149
3345
  return withAliases;
3150
3346
  }
3151
3347
  recordFailure(input, errorMessage) {
3348
+ this.updateSiteProfile(input.siteDomain, input.selector, input.url, input.sitePathPattern);
3152
3349
  const insights = this.store.list();
3153
3350
  const matched = this.findBestExactMatch(insights, input);
3154
3351
  if (!matched) return;
@@ -3163,6 +3360,91 @@ var MemoryService = class {
3163
3360
  this.invalidateSearchCache();
3164
3361
  return failed;
3165
3362
  }
3363
+ /** Update site profile from page content (call after getContent with mode="summary"). */
3364
+ updateProfileFromContent(domain, pathPattern, structuredContent) {
3365
+ if (!domain || domain === "unknown") return;
3366
+ const normalized = domain.toLowerCase();
3367
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3368
+ const profile = this.profileStore.get(normalized) ?? this.createEmptyProfile(normalized, now);
3369
+ const fingerprint = {
3370
+ pathPattern,
3371
+ headings: (structuredContent.headings ?? []).slice(0, 8),
3372
+ landmarks: (structuredContent.landmarks ?? []).slice(0, 8),
3373
+ lastSeenAt: now
3374
+ };
3375
+ const fpIndex = profile.layoutFingerprints.findIndex((fp) => fp.pathPattern === pathPattern);
3376
+ if (fpIndex >= 0) profile.layoutFingerprints[fpIndex] = fingerprint;
3377
+ else {
3378
+ profile.layoutFingerprints.push(fingerprint);
3379
+ if (profile.layoutFingerprints.length > MAX_LAYOUT_FINGERPRINTS) profile.layoutFingerprints = profile.layoutFingerprints.sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt)).slice(0, MAX_LAYOUT_FINGERPRINTS);
3380
+ }
3381
+ profile.lastVisitAt = now;
3382
+ this.profileStore.upsert(profile);
3383
+ }
3384
+ /** Update site profile with cookie banner behavior. */
3385
+ updateProfileCookieBanner(domain, result) {
3386
+ if (!domain || domain === "unknown") return;
3387
+ const normalized = domain.toLowerCase();
3388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3389
+ const profile = this.profileStore.get(normalized) ?? this.createEmptyProfile(normalized, now);
3390
+ profile.cookieBanner = {
3391
+ detected: true,
3392
+ autoDismissed: result.dismissed,
3393
+ method: result.method
3394
+ };
3395
+ profile.lastVisitAt = now;
3396
+ this.profileStore.upsert(profile);
3397
+ }
3398
+ /** Look up a site profile by domain. */
3399
+ getSiteProfile(domain) {
3400
+ return this.profileStore.get(domain);
3401
+ }
3402
+ updateSiteProfile(domain, selector, url, pathPattern) {
3403
+ if (!domain || domain === "unknown") return;
3404
+ const normalized = domain.toLowerCase();
3405
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3406
+ const profile = this.profileStore.get(normalized) ?? this.createEmptyProfile(normalized, now);
3407
+ profile.visitCount += 1;
3408
+ profile.lastVisitAt = now;
3409
+ if (selector) {
3410
+ const pattern = this.classifySelectorPattern(selector);
3411
+ if (pattern) {
3412
+ const existing = profile.selectorPatterns.find((p) => p.pattern === pattern);
3413
+ if (existing) {
3414
+ existing.frequency += 1;
3415
+ existing.lastSeenAt = now;
3416
+ } else if (profile.selectorPatterns.length < MAX_SELECTOR_PATTERNS) profile.selectorPatterns.push({
3417
+ pattern,
3418
+ frequency: 1,
3419
+ lastSeenAt: now
3420
+ });
3421
+ }
3422
+ }
3423
+ if (pathPattern && pathPattern !== "/") {
3424
+ if (!profile.navigationPaths.includes(pathPattern) && profile.navigationPaths.length < MAX_NAVIGATION_PATHS) profile.navigationPaths.push(pathPattern);
3425
+ }
3426
+ this.profileStore.upsert(profile);
3427
+ }
3428
+ createEmptyProfile(domain, now) {
3429
+ return {
3430
+ domain,
3431
+ selectorPatterns: [],
3432
+ layoutFingerprints: [],
3433
+ navigationPaths: [],
3434
+ visitCount: 0,
3435
+ lastVisitAt: now,
3436
+ createdAt: now
3437
+ };
3438
+ }
3439
+ classifySelectorPattern(selector) {
3440
+ if (selector.includes("[data-testid=")) return "data-testid";
3441
+ if (selector.includes("[data-cy=")) return "data-cy";
3442
+ if (selector.includes("[data-test=")) return "data-test";
3443
+ if (selector.includes("[aria-label=")) return "aria-label";
3444
+ if (selector.includes("[name=")) return "name-attribute";
3445
+ if (/^#[\w-]+$/.test(selector)) return "id-selector";
3446
+ if (selector.includes("[role=")) return "role-attribute";
3447
+ }
3166
3448
  findBestExactMatch(insights, input) {
3167
3449
  const intentLower = input.taskIntent.toLowerCase();
3168
3450
  const domainLower = input.siteDomain.toLowerCase();
@@ -3251,7 +3533,7 @@ var MemoryService = class {
3251
3533
  function createAppContext(env = process.env) {
3252
3534
  const config = loadConfig(env);
3253
3535
  const logger = new Logger("app");
3254
- const eventStore = new EventStore(config.logDir);
3536
+ const eventStore = new EventStore(config.dataDir);
3255
3537
  const tokenService = new SessionTokenService();
3256
3538
  return {
3257
3539
  config,
@@ -3263,7 +3545,7 @@ function createAppContext(env = process.env) {
3263
3545
  port: config.wsPort,
3264
3546
  tokenService
3265
3547
  }),
3266
- memoryService: new MemoryService(config.logDir)
3548
+ memoryService: new MemoryService(config.dataDir)
3267
3549
  };
3268
3550
  }
3269
3551
  //#endregion
@@ -3332,7 +3614,7 @@ var AgenticBrowserCore = class {
3332
3614
  };
3333
3615
  function createAgenticBrowserCore(options = {}) {
3334
3616
  const context = createAppContext(options.env);
3335
- return new AgenticBrowserCore(context, options.browserController ?? new ChromeCdpBrowserController(context.config.logDir));
3617
+ return new AgenticBrowserCore(context, options.browserController ?? new ChromeCdpBrowserController(context.config.dataDir));
3336
3618
  }
3337
3619
  function createMockAgenticBrowserCore(env) {
3338
3620
  return new AgenticBrowserCore(createAppContext(env), new MockBrowserController());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-browser",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -53,10 +53,10 @@
53
53
  "@semantic-release/github": "^12.0.6",
54
54
  "@semantic-release/npm": "^13.1.5",
55
55
  "@semantic-release/release-notes-generator": "^14.1.0",
56
- "@types/node": "^25.3.5",
56
+ "@types/node": "^25.4.0",
57
57
  "@types/ws": "^8.18.1",
58
- "oxfmt": "^0.36.0",
59
- "oxlint": "^1.51.0",
58
+ "oxfmt": "^0.37.0",
59
+ "oxlint": "^1.52.0",
60
60
  "react": "^19.2.4",
61
61
  "react-dom": "^19.2.4",
62
62
  "semantic-release": "^25.0.3",
@@ -65,5 +65,5 @@
65
65
  "vitest": "^4.0.18",
66
66
  "vocs": "1.4.1"
67
67
  },
68
- "packageManager": "pnpm@10.31.0"
68
+ "packageManager": "pnpm@10.32.0"
69
69
  }