@agentproto/browser-profiles 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1946 @@
1
+ import { ToolError } from '@agentproto/tool';
2
+ import { existsSync, readFileSync, rmSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, statSync, chmodSync, renameSync, mkdtempSync, copyFileSync } from 'fs';
3
+ import { platform, homedir, tmpdir } from 'os';
4
+ import path, { join, dirname } from 'path';
5
+ import { z } from 'zod';
6
+ import { execFileSync } from 'child_process';
7
+ import { createDecipheriv, createHash, randomUUID, pbkdf2Sync } from 'crypto';
8
+ import { browserCookieSchema, assertSpawnArgsSafe } from '@agentproto/driver-browser';
9
+
10
+ /**
11
+ * @agentproto/browser-profiles
12
+ * Browser session and profile model: descriptors, Chrome profile discovery, camofox sessions, and the injection seams.
13
+ */
14
+
15
+ var SESSION_SOURCE_UNKNOWN_CODE = "browser-profiles:unknown-source";
16
+ var SESSION_SOURCE_DUPLICATE_CODE = "browser-profiles:duplicate-source";
17
+ var SESSION_DESCRIPTOR_INVALID_CODE = "browser-profiles:invalid-descriptor";
18
+ var SessionSourceUnknownError = class extends ToolError {
19
+ kind;
20
+ registered;
21
+ constructor(kind, registered) {
22
+ super({
23
+ code: SESSION_SOURCE_UNKNOWN_CODE,
24
+ message: `no session source registered for kind "${kind}"; registered: ${registered.length ? registered.join(", ") : "none"}. Register one with registry.register({ kind, materialize }) before resolving the session.`
25
+ });
26
+ this.name = "SessionSourceUnknownError";
27
+ this.kind = kind;
28
+ this.registered = registered;
29
+ }
30
+ };
31
+ var SessionSourceDuplicateError = class extends ToolError {
32
+ kind;
33
+ constructor(kind) {
34
+ super({
35
+ code: SESSION_SOURCE_DUPLICATE_CODE,
36
+ message: `a session source for kind "${kind}" is already registered`
37
+ });
38
+ this.name = "SessionSourceDuplicateError";
39
+ this.kind = kind;
40
+ }
41
+ };
42
+ var SessionDescriptorInvalidError = class extends ToolError {
43
+ issues;
44
+ constructor(issues) {
45
+ super({
46
+ code: SESSION_DESCRIPTOR_INVALID_CODE,
47
+ message: `not a valid session descriptor: ${issues.join("; ")}`
48
+ });
49
+ this.name = "SessionDescriptorInvalidError";
50
+ this.issues = issues;
51
+ }
52
+ };
53
+ function isSessionSourceUnknownError(value) {
54
+ return value instanceof SessionSourceUnknownError;
55
+ }
56
+ var SESSION_SOURCE_RESERVED_CODE = "browser-profiles:reserved-source";
57
+ var ACCOUNT_SWITCHER_MISSING_CODE = "browser-profiles:account-switcher-missing";
58
+ var CHROME_IDENTITY_CODE = "browser-profiles:chrome-identity";
59
+ var SESSION_RESOLVE_CODE = "browser-profiles:resolve";
60
+ var SessionSourceReservedError = class extends ToolError {
61
+ kind;
62
+ constructor(kind) {
63
+ super({
64
+ code: SESSION_SOURCE_RESERVED_CODE,
65
+ message: `"${kind}" is a built-in strategy kind and cannot be registered as a session source`
66
+ });
67
+ this.name = "SessionSourceReservedError";
68
+ this.kind = kind;
69
+ }
70
+ };
71
+ var AccountSwitcherMissingError = class extends ToolError {
72
+ constructor(platform2) {
73
+ super({
74
+ code: ACCOUNT_SWITCHER_MISSING_CODE,
75
+ message: `the session pins a sub-account on "${platform2}" but no accountSwitcher hook was provided; refusing to continue, since acting as the wrong account is worse than failing`
76
+ });
77
+ this.name = "AccountSwitcherMissingError";
78
+ }
79
+ };
80
+ var ChromeIdentityError = class extends ToolError {
81
+ constructor(message) {
82
+ super({ code: CHROME_IDENTITY_CODE, message });
83
+ this.name = "ChromeIdentityError";
84
+ }
85
+ };
86
+ var SessionResolveError = class extends ToolError {
87
+ constructor(message, cause) {
88
+ super({ code: SESSION_RESOLVE_CODE, message, ...cause !== void 0 ? { cause } : {} });
89
+ this.name = "SessionResolveError";
90
+ }
91
+ };
92
+
93
+ // src/known.ts
94
+ function isKnown(k) {
95
+ return "known" in k;
96
+ }
97
+ function knownOr(k, fallback) {
98
+ return isKnown(k) ? k.known : fallback;
99
+ }
100
+
101
+ // src/cookie.ts
102
+ function domainMatches(domain, domains) {
103
+ const h = String(domain ?? "").replace(/^\./, "").toLowerCase();
104
+ return domains.some((d) => h === d || h.endsWith(`.${d}`));
105
+ }
106
+ function toSessionCookie(c) {
107
+ const o = {
108
+ name: c.name,
109
+ value: c.value,
110
+ domain: c.domain,
111
+ path: c.path || "/"
112
+ };
113
+ if (c.httpOnly != null) o.httpOnly = c.httpOnly;
114
+ if (c.secure != null) o.secure = c.secure;
115
+ if (c.sameSite) o.sameSite = String(c.sameSite);
116
+ const exp = c.expires ?? c.expirationDate;
117
+ if (!c.session && exp) o.expires = Math.floor(exp);
118
+ return o;
119
+ }
120
+ function dedupeCookies(cookies) {
121
+ const seen = /* @__PURE__ */ new Map();
122
+ for (const c of cookies) seen.set(`${c.name}|${c.domain}|${c.path}`, c);
123
+ return [...seen.values()];
124
+ }
125
+
126
+ // src/auth-signals.ts
127
+ var norm = (d) => d.replace(/^\./, "").toLowerCase();
128
+ var hostMatches = (host, domain) => host === domain || host.endsWith(`.${domain}`);
129
+ function createAuthSignalRegistry(initial = []) {
130
+ const signals = /* @__PURE__ */ new Map();
131
+ const registry = {
132
+ register(signal) {
133
+ signals.set(norm(signal.domain), signal);
134
+ },
135
+ knownDomains: () => [...signals.keys()],
136
+ isKnownDomain: (domain) => signals.has(norm(domain)),
137
+ canonicalDomain(domain) {
138
+ const d = norm(domain);
139
+ if (signals.has(d)) return d;
140
+ for (const s of signals.values()) {
141
+ if (s.aliases?.some((a) => norm(a) === d)) return norm(s.domain);
142
+ }
143
+ return d;
144
+ },
145
+ equivalentDomains(domain) {
146
+ const d = norm(domain);
147
+ const canon = registry.canonicalDomain(d);
148
+ const s = signals.get(canon);
149
+ const all = [canon, ...(s?.aliases ?? []).map(norm)];
150
+ return [...new Set(all)].filter((x) => x !== d);
151
+ },
152
+ hostBelongsTo(host, domain) {
153
+ const h = norm(host);
154
+ const s = signals.get(norm(domain));
155
+ const names = [norm(domain), ...(s?.aliases ?? []).map(norm)];
156
+ return names.some((n) => hostMatches(h, n));
157
+ },
158
+ isAuthed(domain, cookies, nowMs) {
159
+ const s = signals.get(norm(domain));
160
+ if (!s) return false;
161
+ const mine = cookies.filter((c) => registry.hostBelongsTo(c.host, domain));
162
+ if (s.detect) return s.detect(mine, nowMs);
163
+ const names = s.cookieNames ?? [];
164
+ return mine.some((c) => names.includes(c.name) && (c.expiresUnix === void 0 || c.expiresUnix > nowMs / 1e3));
165
+ }
166
+ };
167
+ for (const s of initial) registry.register(s);
168
+ return registry;
169
+ }
170
+ var defaultAuthSignalRegistry = createAuthSignalRegistry();
171
+ function chromeUserDataDir(home = homedir()) {
172
+ switch (platform()) {
173
+ case "darwin":
174
+ return join(home, "Library", "Application Support", "Google", "Chrome");
175
+ case "win32":
176
+ return join(process.env.LOCALAPPDATA ?? join(home, "AppData", "Local"), "Google", "Chrome", "User Data");
177
+ default:
178
+ return join(home, ".config", "google-chrome");
179
+ }
180
+ }
181
+ function chromeLocalStatePath(home = homedir()) {
182
+ return join(chromeUserDataDir(home), "Local State");
183
+ }
184
+ var str = (v) => typeof v === "string" ? v : void 0;
185
+ function parseLocalState(text) {
186
+ const parsed = JSON.parse(text);
187
+ const cache = parsed.profile?.info_cache ?? {};
188
+ return {
189
+ profiles: Object.entries(cache).map(([directory, info]) => ({
190
+ directory,
191
+ name: str(info.name),
192
+ userName: str(info.user_name),
193
+ gaiaName: str(info.gaia_name),
194
+ activeTime: info.active_time ?? info.last_active_time
195
+ })),
196
+ lastUsed: str(parsed.profile?.last_used) ?? null
197
+ };
198
+ }
199
+ function readLocalState(chromeRoot) {
200
+ const file = join(chromeRoot, "Local State");
201
+ if (!existsSync(file)) return { profiles: [], lastUsed: null };
202
+ try {
203
+ return parseLocalState(readFileSync(file, "utf8"));
204
+ } catch {
205
+ return { profiles: [], lastUsed: null };
206
+ }
207
+ }
208
+ function chromeTimeToIso(raw) {
209
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return "";
210
+ let unixMs;
211
+ if (raw > 1e16) {
212
+ unixMs = Math.floor(raw / 1e3) - 116444736e5;
213
+ } else if (raw > 1e12) {
214
+ unixMs = raw;
215
+ } else if (raw > 1e9) {
216
+ unixMs = raw * 1e3;
217
+ } else {
218
+ return "";
219
+ }
220
+ if (unixMs <= 0 || unixMs > Date.now() + 864e5) return "";
221
+ return new Date(unixMs).toISOString();
222
+ }
223
+ var BUILTIN_STRATEGY_KINDS = ["chrome-cookie", "stored-credential", "authed-storageState"];
224
+ var BUILTIN_INJECT_FROM = ["chrome-profile", "file", "camofox-native"];
225
+ function isBuiltinStrategyKind(kind) {
226
+ return BUILTIN_STRATEGY_KINDS.includes(kind);
227
+ }
228
+ var isChromeCookieStrategy = (s) => s.kind === "chrome-cookie";
229
+ var isStoredCredentialStrategy = (s) => s.kind === "stored-credential";
230
+ var isOwnedStorageStateStrategy = (s) => s.kind === "authed-storageState";
231
+ var isSourceStrategy = (s) => !isBuiltinStrategyKind(s.kind);
232
+ var isChromeProfileInject = (i) => i.from === "chrome-profile";
233
+ var isFileInject = (i) => i.from === "file";
234
+ var isCamofoxNativeInject = (i) => i.from === "camofox-native";
235
+ var isSourceInject = (i) => !BUILTIN_INJECT_FROM.includes(i.from);
236
+ var accountSchema = z.object({ platform: z.string(), userId: z.string() }).loose();
237
+ var chromeProfileInjectSchema = z.object({
238
+ from: z.literal("chrome-profile"),
239
+ domains: z.array(z.string()),
240
+ profile: z.string(),
241
+ account: accountSchema.optional()
242
+ }).loose();
243
+ var fileInjectSchema = z.object({ from: z.literal("file"), domains: z.array(z.string()), path: z.string() }).loose();
244
+ var camofoxNativeInjectSchema = z.object({ from: z.literal("camofox-native") }).loose();
245
+ var sourceInjectSchema = z.object({ from: z.string().min(1), domains: z.array(z.string()), sessionRef: z.string() }).loose();
246
+ var sessionInjectSchema = z.union([
247
+ chromeProfileInjectSchema,
248
+ fileInjectSchema,
249
+ camofoxNativeInjectSchema,
250
+ sourceInjectSchema
251
+ ]);
252
+ var chromeCookieStrategySchema = z.object({
253
+ kind: z.literal("chrome-cookie"),
254
+ domains: z.array(z.string()),
255
+ profile: z.string(),
256
+ profileName: z.string().optional(),
257
+ profileEmail: z.string().optional(),
258
+ account: accountSchema.optional()
259
+ }).loose();
260
+ var storedCredentialStrategySchema = z.object({ kind: z.literal("stored-credential"), platform: z.string(), account: z.string() }).loose();
261
+ var ownedStorageStateStrategySchema = z.object({ kind: z.literal("authed-storageState"), storageStatePath: z.string(), capturedAt: z.string().optional() }).loose();
262
+ var sourceStrategySchema = z.object({ kind: z.string().min(1), domains: z.array(z.string()), sessionRef: z.string() }).loose();
263
+ var strategySchema = z.union([
264
+ chromeCookieStrategySchema,
265
+ storedCredentialStrategySchema,
266
+ ownedStorageStateStrategySchema,
267
+ sourceStrategySchema
268
+ ]);
269
+ var sessionIdentitySchema = z.object({
270
+ profile: z.string().optional(),
271
+ account: z.string().optional(),
272
+ platform: z.string().optional(),
273
+ profileName: z.string().optional(),
274
+ profileEmail: z.string().optional()
275
+ }).loose();
276
+ var authStatusSchema = z.enum(["authenticated", "auth-wall", "stale", "unknown"]);
277
+ var sessionDescriptorSchema = z.object({
278
+ id: z.string().min(1),
279
+ backend: z.enum(["camofox", "chrome"]),
280
+ identity: sessionIdentitySchema.optional(),
281
+ inject: sessionInjectSchema.optional(),
282
+ strategies: z.array(strategySchema).optional(),
283
+ base: z.string().optional(),
284
+ url: z.string().optional(),
285
+ savedAt: z.string().optional(),
286
+ lastVerifiedAt: z.string().optional(),
287
+ lastAuthStatus: authStatusSchema.optional(),
288
+ lastCookieRefreshAt: z.string().optional()
289
+ }).loose();
290
+ function parseSessionDescriptor(json) {
291
+ const result = sessionDescriptorSchema.safeParse(json);
292
+ if (!result.success) {
293
+ throw new SessionDescriptorInvalidError(
294
+ result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
295
+ );
296
+ }
297
+ return json;
298
+ }
299
+ function ownedStorageStatePath(id) {
300
+ const safeId = id.replace(/[^\w.-]/g, "_");
301
+ return join(process.env["CAMOFOX_PROFILES_DIR"] ?? join(homedir(), ".agentproto", "camofox-profiles"), `${safeId}.json`);
302
+ }
303
+ function injectKindOf(from) {
304
+ if (from === "chrome-profile" || from === "file") return "chrome-cookie";
305
+ if (from === "camofox-native") return "authed-storageState";
306
+ return from;
307
+ }
308
+ function injectFromOf(kind) {
309
+ if (kind === "chrome-cookie") return "chrome-profile";
310
+ if (kind === "authed-storageState") return "camofox-native";
311
+ if (kind === "stored-credential") return void 0;
312
+ return kind;
313
+ }
314
+ function chromeCookieStrategyOf(inject, identity) {
315
+ return {
316
+ kind: "chrome-cookie",
317
+ domains: inject.domains,
318
+ profile: inject.profile,
319
+ ...identity?.profileName ? { profileName: identity.profileName } : {},
320
+ ...identity?.profileEmail ? { profileEmail: identity.profileEmail } : {},
321
+ ...inject.account ? { account: inject.account } : {}
322
+ };
323
+ }
324
+ function deriveStrategies(raw, opts) {
325
+ const out = [];
326
+ const inject = raw.inject;
327
+ if (inject) {
328
+ if (isChromeProfileInject(inject)) {
329
+ out.push(chromeCookieStrategyOf(inject, raw.identity));
330
+ } else if (isFileInject(inject)) {
331
+ out.push({
332
+ kind: "chrome-cookie",
333
+ domains: inject.domains,
334
+ profile: raw.identity?.profile ?? "",
335
+ ...raw.identity?.profileName ? { profileName: raw.identity.profileName } : {},
336
+ ...raw.identity?.profileEmail ? { profileEmail: raw.identity.profileEmail } : {}
337
+ });
338
+ } else if (isCamofoxNativeInject(inject)) {
339
+ out.push({ kind: "authed-storageState", storageStatePath: ownedStorageStatePath(raw.id) });
340
+ } else {
341
+ out.push({ kind: inject.from, domains: inject.domains, sessionRef: inject.sessionRef });
342
+ }
343
+ }
344
+ const platform2 = raw.identity?.platform;
345
+ const account = raw.identity?.account;
346
+ if (platform2 && account && opts.credentials?.some((c) => c.platform === platform2 && c.account === account)) {
347
+ out.push({ kind: "stored-credential", platform: platform2, account });
348
+ }
349
+ return out;
350
+ }
351
+ function normalizeDescriptor(raw, opts = {}) {
352
+ if (raw.strategies && raw.strategies.length > 0) return raw;
353
+ const strategies = deriveStrategies(raw, opts);
354
+ return strategies.length > 0 ? { ...raw, strategies } : raw;
355
+ }
356
+ function selectResolutionStrategy(desc) {
357
+ const strategies = desc.strategies;
358
+ if (!strategies || strategies.length === 0) return void 0;
359
+ if (desc.lastAuthStatus !== "auth-wall") {
360
+ const owned = strategies.find(isOwnedStorageStateStrategy);
361
+ if (owned) return owned;
362
+ }
363
+ const chrome = strategies.find(isChromeCookieStrategy);
364
+ if (chrome) return chrome;
365
+ return strategies.find(isSourceStrategy);
366
+ }
367
+ function resolveInjectFromStrategies(desc) {
368
+ const sel = selectResolutionStrategy(desc);
369
+ if (!sel) return desc.inject;
370
+ if (isOwnedStorageStateStrategy(sel)) return { from: "camofox-native" };
371
+ if (isChromeCookieStrategy(sel)) {
372
+ if (desc.inject && isFileInject(desc.inject)) return desc.inject;
373
+ return {
374
+ from: "chrome-profile",
375
+ domains: sel.domains,
376
+ profile: sel.profile,
377
+ ...sel.account ? { account: sel.account } : {}
378
+ };
379
+ }
380
+ if (isSourceStrategy(sel)) return { from: sel.kind, domains: sel.domains, sessionRef: sel.sessionRef };
381
+ return desc.inject;
382
+ }
383
+ var COOKIE_STALE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
384
+ function cookieFreshness(desc, now, ttlMs = COOKIE_STALE_TTL_MS) {
385
+ const eff = resolveInjectFromStrategies(desc);
386
+ if (eff && (isChromeProfileInject(eff) || isFileInject(eff))) return "fresh";
387
+ const stamp = desc.lastCookieRefreshAt;
388
+ if (!stamp) return "unknown";
389
+ const t = Date.parse(stamp);
390
+ if (Number.isNaN(t)) return "unknown";
391
+ return now - t <= ttlMs ? "fresh" : "stale";
392
+ }
393
+ function priorChromeStrategy(prev) {
394
+ const v2 = prev?.strategies?.find(isChromeCookieStrategy);
395
+ if (v2) return v2;
396
+ if (prev?.inject && isChromeProfileInject(prev.inject)) return chromeCookieStrategyOf(prev.inject, prev.identity);
397
+ return void 0;
398
+ }
399
+ function buildOwnedSessionDescriptor(input) {
400
+ const { id, platform: platform2, reuseBase, account, url, now, prev } = input;
401
+ const chrome = priorChromeStrategy(prev);
402
+ const strategies = [
403
+ ...chrome ? [chrome] : [],
404
+ { kind: "authed-storageState", storageStatePath: ownedStorageStatePath(id), capturedAt: now },
405
+ ...account ? [{ kind: "stored-credential", platform: platform2, account }] : []
406
+ ];
407
+ return {
408
+ id,
409
+ backend: "camofox",
410
+ identity: {
411
+ platform: platform2,
412
+ ...account ? { account } : {},
413
+ ...prev?.identity?.profile ? { profile: prev.identity.profile } : {},
414
+ ...prev?.identity?.profileName ? { profileName: prev.identity.profileName } : {},
415
+ ...prev?.identity?.profileEmail ? { profileEmail: prev.identity.profileEmail } : {}
416
+ },
417
+ inject: { from: "camofox-native" },
418
+ base: reuseBase,
419
+ savedAt: now,
420
+ strategies,
421
+ ...url ? { url } : {}
422
+ };
423
+ }
424
+ function fileSessionStore(dir, opts = {}) {
425
+ const fileFor = (id) => join(dir, `${id.replace(/[^\w.-]/g, "_")}.json`);
426
+ const normalizeOpts = () => opts.credentials ? { credentials: opts.credentials() } : {};
427
+ const read = (path5) => normalizeDescriptor(parseSessionDescriptor(JSON.parse(readFileSync(path5, "utf8"))), normalizeOpts());
428
+ return {
429
+ async save(desc) {
430
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
431
+ writeFileSync(fileFor(desc.id), JSON.stringify(desc, null, 2), { mode: 384 });
432
+ },
433
+ async load(id) {
434
+ try {
435
+ return read(fileFor(id));
436
+ } catch {
437
+ return null;
438
+ }
439
+ },
440
+ async list() {
441
+ if (!existsSync(dir)) return [];
442
+ const out = [];
443
+ for (const f of readdirSync(dir).filter((name) => name.endsWith(".json"))) {
444
+ try {
445
+ out.push(read(join(dir, f)));
446
+ } catch {
447
+ }
448
+ }
449
+ return out;
450
+ },
451
+ async remove(id) {
452
+ rmSync(fileFor(id), { force: true });
453
+ }
454
+ };
455
+ }
456
+ function compositeSessionStore(readStores, writeStore) {
457
+ const requireWrite = () => {
458
+ if (!writeStore) throw new Error("compositeSessionStore is read-only (no writeStore)");
459
+ return writeStore;
460
+ };
461
+ return {
462
+ async save(desc) {
463
+ await requireWrite().save(desc);
464
+ },
465
+ async load(id) {
466
+ for (const s of readStores) {
467
+ const d = await s.load(id);
468
+ if (d) return d;
469
+ }
470
+ return null;
471
+ },
472
+ async list() {
473
+ const seen = /* @__PURE__ */ new Map();
474
+ for (const s of readStores) {
475
+ for (const d of await s.list()) if (!seen.has(d.id)) seen.set(d.id, d);
476
+ }
477
+ return [...seen.values()];
478
+ },
479
+ async remove(id) {
480
+ await requireWrite().remove(id);
481
+ }
482
+ };
483
+ }
484
+
485
+ // src/session-sources.ts
486
+ function createSessionSourceRegistry(initial = []) {
487
+ const sources = /* @__PURE__ */ new Map();
488
+ const registry = {
489
+ register(source) {
490
+ if (isBuiltinStrategyKind(source.kind) || source.kind === "chrome-profile" || source.kind === "file" || source.kind === "camofox-native") {
491
+ throw new SessionSourceReservedError(source.kind);
492
+ }
493
+ if (sources.has(source.kind)) throw new SessionSourceDuplicateError(source.kind);
494
+ sources.set(source.kind, source);
495
+ },
496
+ list: () => [...sources.values()],
497
+ has: (kind) => sources.has(kind),
498
+ resolve(kind) {
499
+ const found = sources.get(kind);
500
+ if (!found) throw new SessionSourceUnknownError(kind, [...sources.keys()]);
501
+ return found;
502
+ },
503
+ materialize: (kind, ref) => registry.resolve(kind).materialize(ref)
504
+ };
505
+ for (const s of initial) registry.register(s);
506
+ return registry;
507
+ }
508
+ function metaToDescriptor(kind, m) {
509
+ return {
510
+ id: m.ref,
511
+ backend: "camofox",
512
+ ...m.identity ? { identity: m.identity } : {},
513
+ inject: { from: kind, sessionRef: m.ref, domains: m.domains },
514
+ ...m.url ? { url: m.url } : {}
515
+ };
516
+ }
517
+ function catalogSessionStore(kind, catalog) {
518
+ return {
519
+ async save() {
520
+ throw new Error(`the "${kind}" catalog store is read-only; capture sessions in the managing system`);
521
+ },
522
+ async load(id) {
523
+ const m = await catalog.get(id);
524
+ return m ? metaToDescriptor(kind, m) : null;
525
+ },
526
+ async list() {
527
+ return (await catalog.list()).map((m) => metaToDescriptor(kind, m));
528
+ },
529
+ async remove() {
530
+ throw new Error(`the "${kind}" catalog store is read-only; manage sessions in the managing system`);
531
+ }
532
+ };
533
+ }
534
+ var SQLITE_MAX_BUFFER = 256 * 1024 * 1024;
535
+ var CHROME_EPOCH_TO_UNIX_S = 11644473600;
536
+ function profileDirs(chromeRoot) {
537
+ if (!existsSync(chromeRoot)) return [];
538
+ return readdirSync(chromeRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => name === "Default" || /^Profile \d+$/.test(name)).filter((name) => existsSync(path.join(chromeRoot, name, "Cookies")));
539
+ }
540
+ function defaultChromeRoot() {
541
+ if (platform() !== "darwin") {
542
+ throw new Error("local browser sessions currently support macOS only; pass { chromeRoot } for other platforms.");
543
+ }
544
+ return path.join(homedir(), "Library", "Application Support", "Google", "Chrome");
545
+ }
546
+ function chromeUserDataRoot(opts = {}) {
547
+ return opts.chromeRoot ?? defaultChromeRoot();
548
+ }
549
+ function deriveKey(password) {
550
+ return pbkdf2Sync(password, "saltysalt", 1003, 16, "sha1");
551
+ }
552
+ function readSafeStorageKey() {
553
+ const pw = execFileSync("security", ["find-generic-password", "-wa", "Chrome", "-s", "Chrome Safe Storage"]).toString().trim();
554
+ return deriveKey(pw);
555
+ }
556
+ function decryptChromeValue(hex, key) {
557
+ const buf = Buffer.from(hex, "hex");
558
+ if (buf.subarray(0, 3).toString() !== "v10") return buf.toString("utf8");
559
+ const ct = buf.subarray(3);
560
+ const d = createDecipheriv("aes-128-cbc", key, Buffer.alloc(16, " "));
561
+ d.setAutoPadding(true);
562
+ let pt = Buffer.concat([d.update(ct), d.final()]);
563
+ if (pt.length >= 32) {
564
+ const headPrintable = [...pt.subarray(0, 32)].every((b) => b >= 32 && b < 127);
565
+ const tail = pt.subarray(32).toString("utf8");
566
+ if (!headPrintable && /^[\x20-\x7e]*$/.test(tail)) pt = pt.subarray(32);
567
+ }
568
+ return pt.toString("utf8");
569
+ }
570
+ function safeStorageKey(password) {
571
+ return deriveKey(password);
572
+ }
573
+ var errText = (e) => e instanceof Error ? e.message : String(e);
574
+ function withCookiesCopy(profileDir, prefix, fn) {
575
+ const tmpDir = mkdtempSync(path.join(tmpdir(), prefix));
576
+ try {
577
+ const db = path.join(tmpDir, "Cookies");
578
+ copyFileSync(path.join(profileDir, "Cookies"), db);
579
+ return fn(db);
580
+ } finally {
581
+ rmSync(tmpDir, { recursive: true, force: true });
582
+ }
583
+ }
584
+ var sqlite = (db, args) => execFileSync("sqlite3", [db, ...args], { maxBuffer: SQLITE_MAX_BUFFER, stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
585
+ function countCookies(profileDir) {
586
+ try {
587
+ return { known: withCookiesCopy(profileDir, "bp-ct-", (db) => Number(sqlite(db, ["SELECT count(*) FROM cookies;"]))) };
588
+ } catch (e) {
589
+ return { unknown: errText(e) };
590
+ }
591
+ }
592
+ function countCookiesByDomain(profileDir, domains) {
593
+ try {
594
+ const hosts = withCookiesCopy(
595
+ profileDir,
596
+ "bp-cd-",
597
+ (db) => sqlite(db, ["SELECT host_key FROM cookies;"]).split("\n").filter(Boolean).map((h) => h.replace(/^\./, "").toLowerCase())
598
+ );
599
+ const counts = Object.fromEntries(domains.map((d) => [d, 0]));
600
+ for (const host of hosts) {
601
+ for (const d of domains) if (host === d || host.endsWith(`.${d}`)) counts[d] = (counts[d] ?? 0) + 1;
602
+ }
603
+ return { known: counts };
604
+ } catch (e) {
605
+ return { unknown: errText(e) };
606
+ }
607
+ }
608
+ function profileLoggedInDomains(profileDir, registry = defaultAuthSignalRegistry, extraDomains = []) {
609
+ try {
610
+ const hosts = withCookiesCopy(
611
+ profileDir,
612
+ "bp-host-",
613
+ (db) => sqlite(db, ["SELECT DISTINCT host_key FROM cookies;"]).split("\n").map((h) => h.replace(/^\./, "").toLowerCase())
614
+ );
615
+ const of = [.../* @__PURE__ */ new Set([...registry.knownDomains(), ...extraDomains])];
616
+ const found = /* @__PURE__ */ new Set();
617
+ for (const host of hosts) {
618
+ for (const domain of of) if (registry.hostBelongsTo(host, domain)) found.add(domain);
619
+ }
620
+ return { known: [...found].sort() };
621
+ } catch (e) {
622
+ return { unknown: errText(e) };
623
+ }
624
+ }
625
+ function profileAuthedDomains(profileDir, registry = defaultAuthSignalRegistry, nowMs = Date.now()) {
626
+ try {
627
+ const rows = withCookiesCopy(
628
+ profileDir,
629
+ "bp-auth-",
630
+ (db) => sqlite(db, ["SELECT host_key, name, expires_utc FROM cookies;"]).split("\n")
631
+ );
632
+ const metas = [];
633
+ for (const row of rows) {
634
+ const [hostRaw, name, expiresRaw] = row.split("|");
635
+ if (!hostRaw || !name) continue;
636
+ const exp = Number(expiresRaw);
637
+ metas.push({
638
+ name,
639
+ host: hostRaw.replace(/^\./, "").toLowerCase(),
640
+ expiresUnix: !Number.isFinite(exp) || exp === 0 ? void 0 : exp / 1e6 - CHROME_EPOCH_TO_UNIX_S
641
+ });
642
+ }
643
+ const found = registry.knownDomains().filter((d) => registry.isAuthed(d, metas, nowMs));
644
+ return { known: found.sort() };
645
+ } catch (e) {
646
+ return { unknown: errText(e) };
647
+ }
648
+ }
649
+ function scanChromeIdentities(opts = {}) {
650
+ const chromeRoot = opts.chromeRoot ?? defaultChromeRoot();
651
+ const registry = opts.authSignals ?? defaultAuthSignalRegistry;
652
+ const { profiles, lastUsed } = readLocalState(chromeRoot);
653
+ const info = new Map(profiles.map((p) => [p.directory, p]));
654
+ return profileDirs(chromeRoot).map((profile) => {
655
+ const dir = path.join(chromeRoot, profile);
656
+ const meta = info.get(profile);
657
+ return {
658
+ profile,
659
+ name: meta?.name ?? null,
660
+ email: meta?.userName ?? null,
661
+ gaiaName: meta?.gaiaName ?? null,
662
+ lastUsed: profile === lastUsed,
663
+ cookieCount: knownOr(countCookies(dir), 0),
664
+ domainsLoggedIn: knownOr(profileLoggedInDomains(dir, registry, opts.extraDomains), []),
665
+ domainsAuthed: profileAuthedDomains(dir, registry)
666
+ };
667
+ });
668
+ }
669
+ function decryptChromeCookies(profileDir, domains, key) {
670
+ return withCookiesCopy(profileDir, "bp-ck-", (db) => {
671
+ const rows = sqlite(db, [
672
+ "-separator",
673
+ " ",
674
+ "SELECT name, host_key, path, hex(encrypted_value), is_secure, is_httponly, expires_utc FROM cookies;"
675
+ ]).split("\n").filter(Boolean);
676
+ const jar = [];
677
+ for (const line of rows) {
678
+ const [name, host, cpath, hex, secure, httpOnly, expires] = line.split(" ");
679
+ if (!host || !domainMatches(host, domains)) continue;
680
+ let value;
681
+ try {
682
+ value = decryptChromeValue(hex ?? "", key);
683
+ } catch {
684
+ continue;
685
+ }
686
+ if (!value) continue;
687
+ const exp = Number(expires);
688
+ const expiresUnix = exp > 0 ? Math.floor(exp / 1e6 - CHROME_EPOCH_TO_UNIX_S) : void 0;
689
+ jar.push({
690
+ name: name ?? "",
691
+ value,
692
+ domain: host,
693
+ path: cpath || "/",
694
+ secure: secure === "1",
695
+ httpOnly: httpOnly === "1",
696
+ ...expiresUnix && expiresUnix > 0 ? { expires: expiresUnix } : {}
697
+ });
698
+ }
699
+ return jar;
700
+ });
701
+ }
702
+ function createLocalBrowserSession(opts = {}) {
703
+ const chromeRoot = opts.chromeRoot ?? defaultChromeRoot();
704
+ const registry = opts.authSignals ?? defaultAuthSignalRegistry;
705
+ const getKey = opts.safeStoragePassword ? () => deriveKey(opts.safeStoragePassword()) : readSafeStorageKey;
706
+ const domainsFor = (domain) => {
707
+ const d = domain.replace(/^\./, "").toLowerCase();
708
+ const aliases = opts.domainAliases ? opts.domainAliases[d] ?? [] : registry.equivalentDomains(d);
709
+ return [d, ...aliases];
710
+ };
711
+ return {
712
+ async getDecryptedForDomain(domain, profile) {
713
+ const domains = domainsFor(domain);
714
+ const key = getKey();
715
+ const profiles = profile ? [profile] : profileDirs(chromeRoot);
716
+ for (const p of profiles) {
717
+ const dir = path.join(chromeRoot, p);
718
+ if (!existsSync(path.join(dir, "Cookies"))) continue;
719
+ const cookies = decryptChromeCookies(dir, domains, key);
720
+ if (cookies.length > 0) return { cookies, capturedAt: (/* @__PURE__ */ new Date()).toISOString() };
721
+ }
722
+ return null;
723
+ },
724
+ listProfiles() {
725
+ return profileDirs(chromeRoot).map((profile) => ({ profile, cookieCount: countCookies(path.join(chromeRoot, profile)) }));
726
+ }
727
+ };
728
+ }
729
+
730
+ // src/chrome-identity.ts
731
+ function domainAuthSignal(domain, found, registry = defaultAuthSignalRegistry) {
732
+ if (!isKnown(found.domainsAuthed)) return "unknown";
733
+ if (!registry.isKnownDomain(domain)) return "unknown";
734
+ return found.domainsAuthed.known.includes(registry.canonicalDomain(domain)) ? "authed" : "not-authed";
735
+ }
736
+ function wantDomains(inject, registry) {
737
+ return [...new Set(inject.domains.map((d) => registry.canonicalDomain(d)))];
738
+ }
739
+ function chromeIdentityLabel(s) {
740
+ const who = s.name ?? s.email ?? "?";
741
+ const email = s.email && s.email !== s.name ? ` (${s.email})` : "";
742
+ const dir = s.name && s.name !== s.profile ? ` [${s.profile}]` : "";
743
+ return `"${who}"${email}${dir}`;
744
+ }
745
+ function scanFor(inject, opts) {
746
+ return opts.scan ?? scanChromeIdentities({
747
+ ...opts.scanOptions,
748
+ ...opts.authSignals ? { authSignals: opts.authSignals } : {},
749
+ extraDomains: inject.domains
750
+ });
751
+ }
752
+ function resolveChromeProfile(inject, identity, opts = {}) {
753
+ if (!isChromeProfileInject(inject)) return void 0;
754
+ const registry = opts.authSignals ?? defaultAuthSignalRegistry;
755
+ const ids = opts.scan ?? scanChromeIdentities({ ...opts.scanOptions, authSignals: registry });
756
+ const pinned = inject.profile;
757
+ const want = wantDomains(inject, registry);
758
+ const authed = (s) => {
759
+ const da = s.domainsAuthed;
760
+ return isKnown(da) && want.some((d) => da.known.includes(d));
761
+ };
762
+ const pin = ids.find((s) => s.profile === pinned);
763
+ if (pin && authed(pin)) return pinned;
764
+ const email = identity?.account;
765
+ if (email) {
766
+ const sameAccount = ids.find((s) => s.email === email && authed(s));
767
+ if (sameAccount) return sameAccount.profile;
768
+ }
769
+ return pinned;
770
+ }
771
+ function verifyChromeIdentity(inject, identity, opts = {}) {
772
+ if (!isChromeProfileInject(inject)) return;
773
+ const registry = opts.authSignals ?? defaultAuthSignalRegistry;
774
+ const target = inject.profile ?? identity?.profile;
775
+ if (!target) return;
776
+ const ids = scanFor(inject, opts);
777
+ const found = ids.find((s) => s.profile === target);
778
+ if (!found) {
779
+ const avail = ids.map((s) => `${s.profile} (${s.name ?? s.email ?? "?"})`).join(", ");
780
+ throw new ChromeIdentityError(`chrome profile "${target}" not found; available: ${avail || "none"}`);
781
+ }
782
+ if (identity?.account && found.email && found.email !== identity.account) {
783
+ opts.warn?.(`profile "${target}" is now ${found.email}, session was pinned to ${identity.account}; using the live account`);
784
+ }
785
+ const want = wantDomains(inject, registry);
786
+ if (!isKnown(found.domainsAuthed)) {
787
+ throw new ChromeIdentityError(
788
+ `couldn't read ${chromeIdentityLabel(found)}'s cookie store, so a live ${want.join("/")} login can't be confirmed (${found.domainsAuthed.unknown}). Chrome may be holding the Cookies DB lock: fully quit and reopen Chrome, then retry. This is NOT "not logged in".`
789
+ );
790
+ }
791
+ const signals = want.map((d) => domainAuthSignal(d, found, registry));
792
+ if (signals.includes("authed")) return;
793
+ if (signals.includes("not-authed")) {
794
+ const authedProfiles = ids.filter((s) => {
795
+ const da = s.domainsAuthed;
796
+ return isKnown(da) && want.some((d) => da.known.includes(d));
797
+ });
798
+ const hint = authedProfiles.length ? ` A valid ${want.join("/")} login IS present in: ${authedProfiles.map(chromeIdentityLabel).join(", ")}; re-login to ${want.join("/")} in ${chromeIdentityLabel(found)}, or re-pin this session to "${authedProfiles[0].profile}".` : ` No profile is logged into ${want.join("/")}; log in first.`;
799
+ throw new ChromeIdentityError(`${chromeIdentityLabel(found)} has no valid ${want.join("/")} login (only stale crumbs).${hint}`);
800
+ }
801
+ const hasAnyCookie = want.some((d) => found.domainsLoggedIn.includes(d));
802
+ if (!hasAnyCookie) {
803
+ throw new ChromeIdentityError(
804
+ `${chromeIdentityLabel(found)} carries no cookies at all for ${want.join("/")}; no auth signal is registered for it (register a detector on the AuthSignalRegistry for a real check), and Chrome's on-disk cookie store holds nothing for it either. If you ARE logged in, Chrome may still be buffering the cookie in memory: fully quit and reopen Chrome, then retry.`
805
+ );
806
+ }
807
+ opts.warn?.(
808
+ `no auth signal registered for ${want.join("/")}; cannot confirm a live login, proceeding on cookie presence only`
809
+ );
810
+ }
811
+ function describeSessionAuth(inject, identity, opts = {}) {
812
+ if (!isChromeProfileInject(inject)) {
813
+ return {
814
+ state: "not-checked",
815
+ message: `not checked: the "${inject.from}" transport carries no live auth-cookie guard (only "chrome-profile" sessions are inspected); a resolvable session is the only signal available here`
816
+ };
817
+ }
818
+ const registry = opts.authSignals ?? defaultAuthSignalRegistry;
819
+ const target = inject.profile ?? identity?.profile;
820
+ if (!target) return { state: "not-checked", message: "not checked: no profile pinned to verify against" };
821
+ const ids = scanFor(inject, opts);
822
+ const preScan = ids.find((s) => s.profile === target);
823
+ if (preScan && !isKnown(preScan.domainsAuthed)) {
824
+ return {
825
+ state: "unknown",
826
+ message: `unknown: couldn't read ${chromeIdentityLabel(preScan)}'s cookie store (${preScan.domainsAuthed.unknown}); Chrome may hold the DB lock, quit and reopen it. Not "not logged in".`
827
+ };
828
+ }
829
+ try {
830
+ verifyChromeIdentity(inject, identity, { ...opts, scan: ids });
831
+ } catch (e) {
832
+ return { state: "not-authed", message: e instanceof Error ? e.message : String(e) };
833
+ }
834
+ const found = ids.find((s) => s.profile === target);
835
+ const want = wantDomains(inject, registry);
836
+ if (want.some((d) => domainAuthSignal(d, found, registry) === "authed")) {
837
+ return {
838
+ state: "authed",
839
+ message: `profile present and logged in: live ${want.join("/")} auth cookie confirmed in ${chromeIdentityLabel(found)}`
840
+ };
841
+ }
842
+ return {
843
+ state: "unknown",
844
+ message: `unknown: no auth signal registered for ${want.join("/")}; proceeding on cookie presence only, not a verified login`
845
+ };
846
+ }
847
+ function assertChromeLaunchDirSafe(userDataDir, providerId) {
848
+ assertSpawnArgsSafe([`--user-data-dir=${userDataDir}`], providerId);
849
+ }
850
+ function camofoxStatePath() {
851
+ return process.env["CAMOFOX_TABS_STATE"] ?? join(homedir(), ".agentproto", "camofox-tabs.json");
852
+ }
853
+ function defaultCamofoxBase() {
854
+ return (process.env["CAMOFOX_URL"] || `http://localhost:${process.env["CAMOFOX_PORT"] || "9377"}`).replace(/\/$/, "");
855
+ }
856
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
857
+ function readState(file) {
858
+ try {
859
+ return JSON.parse(readFileSync(file, "utf8"));
860
+ } catch {
861
+ return {};
862
+ }
863
+ }
864
+ function writeState(file, s) {
865
+ if (!existsSync(dirname(file))) mkdirSync(dirname(file), { recursive: true, mode: 448 });
866
+ writeFileSync(file, JSON.stringify(s, null, 2), { mode: 384 });
867
+ }
868
+ async function openSession(opts) {
869
+ const base = (opts.base || defaultCamofoxBase()).replace(/\/$/, "");
870
+ const userId = opts.userId;
871
+ const stateFile = opts.stateFile ?? camofoxStatePath();
872
+ const doFetch = opts.fetch ?? fetch;
873
+ const settleMs = opts.settleMs ?? 2500;
874
+ const rest = async (method, path5, body) => {
875
+ const res = await doFetch(`${base}${path5}`, {
876
+ method,
877
+ headers: { "content-type": "application/json" },
878
+ ...body ? { body: JSON.stringify(body) } : {}
879
+ });
880
+ const t = await res.text();
881
+ if (!res.ok) throw new Error(`${method} ${path5} -> ${res.status}: ${t.slice(0, 160)}`);
882
+ return t ? JSON.parse(t) : {};
883
+ };
884
+ if (opts.injectCookies !== false) {
885
+ await rest("POST", `/sessions/${encodeURIComponent(userId)}/cookies`, {
886
+ cookies: opts.cookies ? [...opts.cookies] : [],
887
+ sessionKey: userId
888
+ });
889
+ }
890
+ const state = readState(stateFile);
891
+ let tabId = "";
892
+ let listed = [];
893
+ try {
894
+ const l = await rest("GET", `/tabs?userId=${encodeURIComponent(userId)}`);
895
+ const tabs = l["tabs"] ?? l;
896
+ listed = Array.isArray(tabs) ? tabs : [];
897
+ } catch {
898
+ }
899
+ const ids = new Set(listed.map((t) => t.tabId ?? t.id));
900
+ const remembered = state[userId];
901
+ const first = listed[0];
902
+ if (remembered && ids.has(remembered)) {
903
+ tabId = remembered;
904
+ } else if (first) {
905
+ tabId = first.tabId ?? first.id ?? "";
906
+ } else {
907
+ const tab = await rest("POST", "/tabs", {
908
+ userId,
909
+ sessionKey: userId,
910
+ ...opts.storageState ? { storageState: opts.storageState } : opts.url ? { url: opts.url } : {},
911
+ ...opts.keepAlive ? { keepAlive: true } : {}
912
+ });
913
+ tabId = String(tab["tabId"] ?? tab["id"] ?? "");
914
+ if (opts.storageState && opts.url) {
915
+ await rest("POST", `/tabs/${encodeURIComponent(tabId)}/navigate`, {
916
+ userId,
917
+ url: opts.url,
918
+ waitUntil: "domcontentloaded",
919
+ timeout: 15e3
920
+ });
921
+ }
922
+ }
923
+ state[userId] = tabId;
924
+ writeState(stateFile, state);
925
+ const evaluate = async (expression) => {
926
+ const r = await rest("POST", `/tabs/${encodeURIComponent(tabId)}/evaluate`, { userId, expression });
927
+ return r["result"] ?? r["value"];
928
+ };
929
+ const goto = async (u, waitUntil = "domcontentloaded") => {
930
+ await rest("POST", `/tabs/${encodeURIComponent(tabId)}/navigate`, { userId, url: u, waitUntil });
931
+ if (settleMs > 0) await sleep(settleMs);
932
+ };
933
+ const screenshot = async (o = {}) => {
934
+ const format = o.format ?? "jpeg";
935
+ const qs = new URLSearchParams({ userId, format });
936
+ if (o.selector) qs.set("selector", o.selector);
937
+ if (o.quality != null) qs.set("quality", String(o.quality));
938
+ const res = await doFetch(`${base}/tabs/${encodeURIComponent(tabId)}/screenshot?${qs.toString()}`);
939
+ if (!res.ok) throw new Error(`screenshot -> ${res.status}: ${(await res.text()).slice(0, 160)}`);
940
+ const buf = Buffer.from(await res.arrayBuffer());
941
+ const isPng = buf.length > 8 && buf[0] === 137 && buf[1] === 80;
942
+ const isJpeg = buf.length > 2 && buf[0] === 255 && buf[1] === 216;
943
+ const mimeType = isPng ? "image/png" : isJpeg ? "image/jpeg" : format === "png" ? "image/png" : "image/jpeg";
944
+ return { imageBase64: buf.toString("base64"), mimeType };
945
+ };
946
+ const rawGet = (p) => evaluate(
947
+ `(async()=>{try{const r=await fetch(${JSON.stringify(p)},{headers:{accept:"application/json","x-requested-with":"XMLHttpRequest"},credentials:"include"});let b=null;try{b=await r.json()}catch{}return{status:r.status,body:b}}catch(e){return{status:-1,error:String(e)}}})()`
948
+ );
949
+ const refresh = async () => {
950
+ if (!opts.refreshUrl) return false;
951
+ const r = await evaluate(
952
+ `(async()=>{try{const r=await fetch(${JSON.stringify(opts.refreshUrl)},{method:"POST",credentials:"include",headers:{accept:"application/json"}});return{status:r.status}}catch(e){return{status:-1}}})()`
953
+ );
954
+ return r?.status === 200;
955
+ };
956
+ const apiRaw = async (p) => {
957
+ let r = await rawGet(p);
958
+ if (r?.status === 401 && await refresh()) r = await rawGet(p);
959
+ return r;
960
+ };
961
+ const api = async (p) => {
962
+ const r = await apiRaw(p);
963
+ if (!r || r.status < 200 || r.status >= 300) throw new Error(`GET ${p} -> ${r?.status}`);
964
+ return r.body;
965
+ };
966
+ if (opts.url) await goto(opts.url);
967
+ return {
968
+ tabId,
969
+ userId,
970
+ api,
971
+ apiRaw,
972
+ evaluate,
973
+ screenshot,
974
+ goto,
975
+ click: async (selector) => {
976
+ await rest("POST", `/tabs/${encodeURIComponent(tabId)}/click`, { userId, selector });
977
+ },
978
+ type: async (selector, text) => {
979
+ await rest("POST", `/tabs/${encodeURIComponent(tabId)}/type`, { userId, selector, text });
980
+ },
981
+ press: async (key) => {
982
+ await rest("POST", `/tabs/${encodeURIComponent(tabId)}/press`, { userId, key });
983
+ },
984
+ startNetCapture: async (urlPattern, max) => {
985
+ await rest("POST", `/tabs/${encodeURIComponent(tabId)}/capture`, {
986
+ userId,
987
+ sessionKey: userId,
988
+ ...urlPattern ? { urlPattern } : {},
989
+ ...max ? { max } : {}
990
+ });
991
+ },
992
+ readNetCapture: async () => {
993
+ const r = await rest("GET", `/tabs/${encodeURIComponent(tabId)}/capture?userId=${encodeURIComponent(userId)}`);
994
+ const reqs = r["requests"];
995
+ return Array.isArray(reqs) ? reqs : [];
996
+ },
997
+ close: async () => {
998
+ try {
999
+ await rest("DELETE", `/tabs/${encodeURIComponent(tabId)}?userId=${encodeURIComponent(userId)}`);
1000
+ } catch {
1001
+ }
1002
+ const s = readState(stateFile);
1003
+ delete s[userId];
1004
+ writeState(stateFile, s);
1005
+ }
1006
+ };
1007
+ }
1008
+ var rawCookieSchema = z.object({
1009
+ name: z.string(),
1010
+ value: z.string(),
1011
+ domain: z.string(),
1012
+ path: z.string().optional(),
1013
+ httpOnly: z.boolean().optional(),
1014
+ secure: z.boolean().optional(),
1015
+ sameSite: z.string().optional(),
1016
+ expires: z.number().optional(),
1017
+ expirationDate: z.number().optional(),
1018
+ session: z.boolean().optional()
1019
+ }).loose();
1020
+ var rawCookieFileSchema = z.union([
1021
+ z.array(rawCookieSchema),
1022
+ z.object({ cookies: z.array(rawCookieSchema).optional() }).loose()
1023
+ ]);
1024
+ function gatherFromFile(path5, domains) {
1025
+ let raw;
1026
+ try {
1027
+ raw = JSON.parse(readFileSync(path5, "utf8"));
1028
+ } catch {
1029
+ throw new SessionResolveError(`cookie file "${path5}" is not valid JSON`);
1030
+ }
1031
+ const parsed = rawCookieFileSchema.safeParse(raw);
1032
+ if (!parsed.success) throw new SessionResolveError(`cookie file "${path5}" does not hold a cookie jar`);
1033
+ const arr = Array.isArray(parsed.data) ? parsed.data : parsed.data.cookies ?? [];
1034
+ return dedupeCookies(arr.filter((c) => domainMatches(c.domain, domains)).map(toSessionCookie));
1035
+ }
1036
+ async function gatherFromChromeProfile(args, deps) {
1037
+ if (!args.profile) {
1038
+ const avail = scanChromeIdentities({ ...deps.local, ...deps.authSignals ? { authSignals: deps.authSignals } : {} }).map((s) => `${s.profile} (${s.name ?? s.email ?? "?"})`).join(", ");
1039
+ throw new SessionResolveError(
1040
+ `chrome-profile source needs an explicit "profile" (multiple accounts exist); pick one: ${avail || "none"}`
1041
+ );
1042
+ }
1043
+ const provider = createLocalBrowserSession({
1044
+ ...deps.local,
1045
+ ...deps.authSignals ? { authSignals: deps.authSignals } : {}
1046
+ });
1047
+ const out = [];
1048
+ for (const domain of args.domains) {
1049
+ const payload = await provider.getDecryptedForDomain(domain, args.profile);
1050
+ for (const c of payload?.cookies ?? []) out.push(c);
1051
+ }
1052
+ const jar = dedupeCookies(out);
1053
+ if (!args.account) return jar;
1054
+ if (!deps.accountSwitcher) throw new AccountSwitcherMissingError(args.account.platform);
1055
+ return deps.accountSwitcher(args.account.platform, jar, args.account.userId);
1056
+ }
1057
+ async function gatherCookiesForStrategy(strategy, desc, deps = {}) {
1058
+ if (!isChromeCookieStrategy(strategy)) {
1059
+ throw new SessionResolveError(`no cookies to gather for "${strategy.kind}" source`);
1060
+ }
1061
+ if (desc.inject && isFileInject(desc.inject)) return gatherFromFile(desc.inject.path, desc.inject.domains);
1062
+ return gatherFromChromeProfile(
1063
+ {
1064
+ domains: strategy.domains,
1065
+ profile: strategy.profile,
1066
+ ...strategy.account ? { account: strategy.account } : {}
1067
+ },
1068
+ deps
1069
+ );
1070
+ }
1071
+ async function gatherCookies(inject, deps = {}) {
1072
+ if (isFileInject(inject)) return gatherFromFile(inject.path, inject.domains);
1073
+ if (isChromeProfileInject(inject)) return gatherFromChromeProfile(inject, deps);
1074
+ throw new SessionResolveError(`no cookies to gather for "${inject.from}" source`);
1075
+ }
1076
+ async function resolveSession(desc, deps = {}) {
1077
+ const open = deps.openCamofox ?? openSession;
1078
+ const sources = deps.sources ?? createSessionSourceRegistry();
1079
+ const registry = deps.authSignals ?? defaultAuthSignalRegistry;
1080
+ if (desc.backend === "chrome") {
1081
+ const profile = desc.identity?.profile;
1082
+ const driver = profile && deps.driverFor ? await deps.driverFor(profile) : deps.driver;
1083
+ if (!driver) {
1084
+ const hint = profile ? `deps.driverFor("${profile}") or deps.driver` : `deps.driver (or set identity.profile plus deps.driverFor for multi-account)`;
1085
+ throw new SessionResolveError(`session "${desc.id}": backend "chrome" needs a live BrowserDriver: ${hint}`);
1086
+ }
1087
+ return { backend: "chrome", driver };
1088
+ }
1089
+ const selected = selectResolutionStrategy(desc);
1090
+ const kind = selected?.kind ?? (desc.inject ? injectKindOf(desc.inject.from) : void 0);
1091
+ if (!kind) throw new SessionResolveError(`session "${desc.id}": backend "camofox" needs inject{ from, domains }`);
1092
+ const openWith = async (extra) => ({
1093
+ backend: "camofox",
1094
+ session: await open({
1095
+ userId: desc.id,
1096
+ ...desc.base ? { base: desc.base } : {},
1097
+ ...desc.url ? { url: desc.url } : {},
1098
+ ...extra
1099
+ })
1100
+ });
1101
+ if (kind === "authed-storageState") {
1102
+ const profilePath = ownedStorageStatePath(desc.id);
1103
+ return openWith({
1104
+ injectCookies: false,
1105
+ ...existsSync(profilePath) ? { storageState: profilePath } : {},
1106
+ keepAlive: true
1107
+ });
1108
+ }
1109
+ if (kind === "chrome-cookie") return resolveChromeCookie(desc, selected, deps, openWith, registry);
1110
+ const ref = selected && isSourceStrategy(selected) ? { sessionRef: selected.sessionRef, domains: selected.domains } : desc.inject && isSourceInject(desc.inject) ? { sessionRef: desc.inject.sessionRef, domains: desc.inject.domains } : void 0;
1111
+ if (!ref) throw new SessionResolveError(`session "${desc.id}": "${kind}" source carries no sessionRef/domains`);
1112
+ const cookies = await sources.materialize(kind, ref);
1113
+ if (cookies.length === 0) {
1114
+ throw new SessionResolveError(
1115
+ `session "${desc.id}": the "${kind}" source returned no cookies for ${ref.domains.join("/")} (ref "${ref.sessionRef}"): revoked, expired, or not granted`
1116
+ );
1117
+ }
1118
+ return openWith({ cookies: dedupeCookies(cookies), injectCookies: true, keepAlive: true });
1119
+ }
1120
+ async function resolveChromeCookie(desc, selected, deps, openWith, registry) {
1121
+ if (desc.inject && isFileInject(desc.inject)) {
1122
+ const cookies2 = await gatherCookies(desc.inject, deps);
1123
+ return openWith({ cookies: cookies2, injectCookies: true, keepAlive: true });
1124
+ }
1125
+ const chromeStrat = selected && isChromeCookieStrategy(selected) ? selected : desc.inject && isChromeProfileInject(desc.inject) ? chromeCookieStrategyOf(desc.inject, desc.identity) : void 0;
1126
+ if (!chromeStrat) throw new SessionResolveError(`session "${desc.id}": backend "camofox" needs inject{ from, domains }`);
1127
+ const baseInject = {
1128
+ from: "chrome-profile",
1129
+ domains: chromeStrat.domains,
1130
+ profile: chromeStrat.profile,
1131
+ ...chromeStrat.account ? { account: chromeStrat.account } : {}
1132
+ };
1133
+ const chromeOpts = {
1134
+ authSignals: registry,
1135
+ ...deps.local ? { scanOptions: deps.local } : {},
1136
+ ...deps.warn ? { warn: deps.warn } : {}
1137
+ };
1138
+ const healed = resolveChromeProfile(baseInject, desc.identity, chromeOpts);
1139
+ const inject = healed && healed !== baseInject.profile ? { ...baseInject, profile: healed } : baseInject;
1140
+ if (inject !== baseInject) {
1141
+ deps.warn?.(
1142
+ `"${desc.id}": pinned profile "${chromeStrat.profile}" has no live ${inject.domains.join("/")} login; healed to "${inject.profile}" (same account)`
1143
+ );
1144
+ }
1145
+ verifyChromeIdentity(inject, desc.identity, chromeOpts);
1146
+ const healedStrat = inject.profile !== chromeStrat.profile ? { ...chromeStrat, profile: inject.profile } : chromeStrat;
1147
+ const cookies = await gatherCookiesForStrategy(healedStrat, desc, deps);
1148
+ if (cookies.length === 0) {
1149
+ throw new SessionResolveError(
1150
+ `session "${desc.id}": no cookies for ${healedStrat.domains.join("/")} in profile "${healedStrat.profile || "(any)"}": not logged in, or wrong profile`
1151
+ );
1152
+ }
1153
+ return openWith({ cookies, injectCookies: true, keepAlive: true });
1154
+ }
1155
+ var GRANT_DOMAIN_INVALID_CODE = "browser-profiles:grant-domain-invalid";
1156
+ var CONSENT_REQUIRED_CODE = "browser:consent_required";
1157
+ var CONSENT_FLAGS_REQUIRED_CODE = "browser-profiles:consent-flags-required";
1158
+ var CONSENT_DECLINED_CODE = "browser-profiles:consent-declined";
1159
+ var SINK_ACK_REQUIRED_CODE = "browser-profiles:sink-ack-required";
1160
+ var AGENT_GRANT_REFUSED_CODE = "browser-profiles:agent-grant-refused";
1161
+ var FULL_PROFILE_REFUSED_CODE = "browser-profiles:full-profile-refused";
1162
+ var LEDGER_RECORD_INVALID_CODE = "browser-profiles:ledger-record-invalid";
1163
+ var GRANT_STORE_INVALID_CODE = "browser-profiles:grant-store-invalid";
1164
+ var DOMAIN_HINT = {
1165
+ empty: "a grant needs at least one explicit domain",
1166
+ wildcard: "wildcards are not grantable; name each registrable domain (a grant already covers its subdomains)",
1167
+ "leading-dot": "write the domain without a leading dot",
1168
+ "bare-tld": "a bare TLD is not a registrable domain",
1169
+ "public-suffix": "a public suffix is shared by unrelated sites and cannot be granted",
1170
+ invalid: "expected a lowercase host such as github.com (ASCII or punycode, at least two labels)"
1171
+ };
1172
+ var GrantDomainError = class extends ToolError {
1173
+ reason;
1174
+ domain;
1175
+ constructor(reason, domain) {
1176
+ super({
1177
+ code: GRANT_DOMAIN_INVALID_CODE,
1178
+ message: `${domain === void 0 ? "domain list" : `"${domain}"`} rejected (${reason}): ${DOMAIN_HINT[reason]}`
1179
+ });
1180
+ this.name = "GrantDomainError";
1181
+ this.reason = reason;
1182
+ this.domain = domain;
1183
+ }
1184
+ };
1185
+ var ConsentRequiredError = class extends ToolError {
1186
+ constructor(message = "no active grant covers this request") {
1187
+ super({ code: CONSENT_REQUIRED_CODE, message });
1188
+ this.name = "ConsentRequiredError";
1189
+ }
1190
+ };
1191
+ var NonInteractiveConsentError = class extends ToolError {
1192
+ missing;
1193
+ constructor(missing) {
1194
+ super({
1195
+ code: CONSENT_FLAGS_REQUIRED_CODE,
1196
+ message: `non-interactive import refused: pass ${missing.join(" and ")}. There is no default of "all domains"; name each domain and confirm with --yes.`
1197
+ });
1198
+ this.name = "NonInteractiveConsentError";
1199
+ this.missing = missing;
1200
+ }
1201
+ };
1202
+ var ConsentDeclinedError = class extends ToolError {
1203
+ constructor(what) {
1204
+ super({ code: CONSENT_DECLINED_CODE, message: `consent declined: ${what}` });
1205
+ this.name = "ConsentDeclinedError";
1206
+ }
1207
+ };
1208
+ var SinkAckRequiredError = class extends ToolError {
1209
+ sinkId;
1210
+ constructor(sinkId) {
1211
+ super({
1212
+ code: SINK_ACK_REQUIRED_CODE,
1213
+ message: `remote sink "${sinkId}" needs an explicit human acknowledgement (its id and operator) before any session material is sent`
1214
+ });
1215
+ this.name = "SinkAckRequiredError";
1216
+ this.sinkId = sinkId;
1217
+ }
1218
+ };
1219
+ var AgentGrantRefusedError = class extends ToolError {
1220
+ attempt;
1221
+ constructor(attempt) {
1222
+ super({
1223
+ code: AGENT_GRANT_REFUSED_CODE,
1224
+ message: `refused: an agent cannot ${attempt}. Ask the user; they widen access through the operator channel, and only a human decision counts.`
1225
+ });
1226
+ this.name = "AgentGrantRefusedError";
1227
+ this.attempt = attempt;
1228
+ }
1229
+ };
1230
+ var FullProfileGrantError = class extends ToolError {
1231
+ constructor(message) {
1232
+ super({ code: FULL_PROFILE_REFUSED_CODE, message });
1233
+ this.name = "FullProfileGrantError";
1234
+ }
1235
+ };
1236
+ var LedgerRecordInvalidError = class extends ToolError {
1237
+ issues;
1238
+ constructor(issues) {
1239
+ super({ code: LEDGER_RECORD_INVALID_CODE, message: `invalid consent ledger record: ${issues.join("; ")}` });
1240
+ this.name = "LedgerRecordInvalidError";
1241
+ this.issues = issues;
1242
+ }
1243
+ };
1244
+ var GrantStoreInvalidError = class extends ToolError {
1245
+ constructor(message) {
1246
+ super({ code: GRANT_STORE_INVALID_CODE, message });
1247
+ this.name = "GrantStoreInvalidError";
1248
+ }
1249
+ };
1250
+
1251
+ // src/consent/domains.ts
1252
+ var GRANT_DOMAIN_PATTERN = /^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+([a-z]{2,63}|xn--[a-z0-9-]{1,59})$/;
1253
+ var PUBLIC_SUFFIXES = /* @__PURE__ */ new Set([
1254
+ "co.uk",
1255
+ "org.uk",
1256
+ "ac.uk",
1257
+ "gov.uk",
1258
+ "me.uk",
1259
+ "ltd.uk",
1260
+ "plc.uk",
1261
+ "net.uk",
1262
+ "sch.uk",
1263
+ "com.au",
1264
+ "net.au",
1265
+ "org.au",
1266
+ "edu.au",
1267
+ "gov.au",
1268
+ "id.au",
1269
+ "co.nz",
1270
+ "org.nz",
1271
+ "net.nz",
1272
+ "govt.nz",
1273
+ "co.jp",
1274
+ "ne.jp",
1275
+ "or.jp",
1276
+ "ac.jp",
1277
+ "go.jp",
1278
+ "co.kr",
1279
+ "or.kr",
1280
+ "go.kr",
1281
+ "com.br",
1282
+ "net.br",
1283
+ "org.br",
1284
+ "gov.br",
1285
+ "com.cn",
1286
+ "net.cn",
1287
+ "org.cn",
1288
+ "gov.cn",
1289
+ "com.mx",
1290
+ "com.ar",
1291
+ "com.tr",
1292
+ "com.tw",
1293
+ "com.hk",
1294
+ "com.sg",
1295
+ "com.my",
1296
+ "com.ph",
1297
+ "com.vn",
1298
+ "co.in",
1299
+ "net.in",
1300
+ "org.in",
1301
+ "gov.in",
1302
+ "co.id",
1303
+ "co.th",
1304
+ "co.il",
1305
+ "co.za",
1306
+ "com.ua",
1307
+ "com.pl",
1308
+ "com.es",
1309
+ "com.pt",
1310
+ "github.io",
1311
+ "gitlab.io",
1312
+ "herokuapp.com",
1313
+ "appspot.com",
1314
+ "vercel.app",
1315
+ "netlify.app",
1316
+ "pages.dev",
1317
+ "workers.dev",
1318
+ "web.app",
1319
+ "firebaseapp.com",
1320
+ "blogspot.com",
1321
+ "cloudfront.net",
1322
+ "azurewebsites.net",
1323
+ "s3.amazonaws.com",
1324
+ "fly.dev",
1325
+ "onrender.com",
1326
+ "ngrok.io",
1327
+ "trycloudflare.com"
1328
+ ]);
1329
+ function isKnownPublicSuffix(domain) {
1330
+ return PUBLIC_SUFFIXES.has(domain);
1331
+ }
1332
+ function validateGrantDomains(input, policy = {}) {
1333
+ if (input.length === 0) throw new GrantDomainError("empty");
1334
+ const out = [];
1335
+ for (const raw of input) {
1336
+ if (typeof raw !== "string" || raw.length === 0) throw new GrantDomainError("invalid", String(raw));
1337
+ if (raw.includes("*")) throw new GrantDomainError("wildcard", raw);
1338
+ if (raw.startsWith(".")) throw new GrantDomainError("leading-dot", raw);
1339
+ const domain = raw.toLowerCase();
1340
+ if (!domain.includes(".")) throw new GrantDomainError("bare-tld", raw);
1341
+ if (domain.length > 253 || !GRANT_DOMAIN_PATTERN.test(domain)) throw new GrantDomainError("invalid", raw);
1342
+ if (isKnownPublicSuffix(domain) || policy.isPublicSuffix?.(domain) === true) {
1343
+ throw new GrantDomainError("public-suffix", raw);
1344
+ }
1345
+ if (!out.includes(domain)) out.push(domain);
1346
+ }
1347
+ return out;
1348
+ }
1349
+ function hostCoveredBy(host, domain) {
1350
+ const h = host.replace(/^\./, "").toLowerCase();
1351
+ return h === domain || h.endsWith(`.${domain}`);
1352
+ }
1353
+ function defaultConsentLedgerPath(home = homedir()) {
1354
+ return join(home, ".agentproto", "bureau", "consent.jsonl");
1355
+ }
1356
+ var domainSchema = z.string().max(253).regex(GRANT_DOMAIN_PATTERN);
1357
+ var sha256Schema = z.string().regex(/^[0-9a-f]{64}$/);
1358
+ var idSchema = z.string().min(1);
1359
+ var ledgerEventSchema = z.enum(["grant", "refresh", "revoke", "sink-ack", "deny"]);
1360
+ var ledgerActorSchema = z.object({
1361
+ kind: z.enum(["human", "agent", "system"]),
1362
+ via: z.string().optional(),
1363
+ deviceId: idSchema.optional()
1364
+ }).strict();
1365
+ var ledgerRecordSchema = z.object({
1366
+ seq: z.number().int().min(0),
1367
+ at: z.iso.datetime({ offset: true }),
1368
+ event: ledgerEventSchema,
1369
+ actor: ledgerActorSchema,
1370
+ grantId: idSchema.optional(),
1371
+ sessionId: idSchema.optional(),
1372
+ domains: z.array(domainSchema).optional(),
1373
+ fullProfile: z.literal(true).optional(),
1374
+ cookieCount: z.number().int().min(0).optional(),
1375
+ nameHashes: z.array(sha256Schema).optional(),
1376
+ sinkId: z.string().optional(),
1377
+ derived: z.object({
1378
+ local: z.enum(["deleted", "none"]),
1379
+ remote: z.enum(["confirmed", "unavailable", "failed", "not-applicable"])
1380
+ }).strict().optional(),
1381
+ reason: z.string().max(500).optional(),
1382
+ prev: sha256Schema.optional()
1383
+ }).strict().superRefine((r, ctx) => {
1384
+ const need = (field) => {
1385
+ if (r[field] === void 0) ctx.addIssue({ code: "custom", message: `${r.event} needs ${field}`, path: [field] });
1386
+ };
1387
+ if (r.actor.kind === "agent" && (r.event === "grant" || r.event === "sink-ack")) {
1388
+ ctx.addIssue({ code: "custom", message: "an agent cannot grant or acknowledge a sink (C7)", path: ["actor", "kind"] });
1389
+ }
1390
+ if (r.event === "grant") {
1391
+ need("grantId");
1392
+ need("sessionId");
1393
+ need("cookieCount");
1394
+ if (r.domains === void 0 === (r.fullProfile === void 0)) {
1395
+ ctx.addIssue({ code: "custom", message: "a grant record has exactly one of domains or fullProfile", path: ["domains"] });
1396
+ }
1397
+ if (r.domains !== void 0 && r.domains.length === 0) {
1398
+ ctx.addIssue({ code: "custom", message: "domains must not be empty", path: ["domains"] });
1399
+ }
1400
+ }
1401
+ if (r.event === "refresh") {
1402
+ need("grantId");
1403
+ need("domains");
1404
+ need("cookieCount");
1405
+ if (r.domains !== void 0 && r.domains.length === 0) {
1406
+ ctx.addIssue({ code: "custom", message: "domains must not be empty", path: ["domains"] });
1407
+ }
1408
+ }
1409
+ if (r.event === "revoke") {
1410
+ need("grantId");
1411
+ need("derived");
1412
+ }
1413
+ if (r.event === "sink-ack") {
1414
+ need("grantId");
1415
+ need("sinkId");
1416
+ if (r.actor.kind !== "human") ctx.addIssue({ code: "custom", message: "a sink-ack is a human act", path: ["actor", "kind"] });
1417
+ }
1418
+ if (r.event === "deny") need("reason");
1419
+ });
1420
+ function cookieNameHash(grantId, cookieName) {
1421
+ return createHash("sha256").update(grantId).update("\0").update(cookieName).digest("hex");
1422
+ }
1423
+ var sha256 = (text) => createHash("sha256").update(text).digest("hex");
1424
+ var isPosix = process.platform !== "win32";
1425
+ function createConsentLedger(opts = {}) {
1426
+ const path5 = opts.path ?? defaultConsentLedgerPath();
1427
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
1428
+ const lines = () => {
1429
+ if (!existsSync(path5)) return [];
1430
+ return readFileSync(path5, "utf8").split("\n").filter((line) => line.length > 0);
1431
+ };
1432
+ return {
1433
+ path: path5,
1434
+ append(entry) {
1435
+ const existing = lines();
1436
+ const last = existing[existing.length - 1];
1437
+ const candidate = {
1438
+ ...entry,
1439
+ seq: existing.length,
1440
+ at: now().toISOString(),
1441
+ ...last !== void 0 ? { prev: sha256(last) } : {}
1442
+ };
1443
+ const parsed = ledgerRecordSchema.safeParse(candidate);
1444
+ if (!parsed.success) {
1445
+ throw new LedgerRecordInvalidError(parsed.error.issues.map((i) => `${i.path.join(".") || "(record)"}: ${i.message}`));
1446
+ }
1447
+ const line = JSON.stringify(candidate);
1448
+ mkdirSync(dirname(path5), { recursive: true, mode: 448 });
1449
+ appendFileSync(path5, `${line}
1450
+ `, { mode: 384 });
1451
+ if (isPosix && (statSync(path5).mode & 63) !== 0) chmodSync(path5, 384);
1452
+ return parsed.data;
1453
+ },
1454
+ read() {
1455
+ return lines().map((line) => ledgerRecordSchema.parse(JSON.parse(line)));
1456
+ },
1457
+ verifyChain() {
1458
+ const rows = lines();
1459
+ let prevLine;
1460
+ for (const [i, line] of rows.entries()) {
1461
+ const rec = ledgerRecordSchema.safeParse(JSON.parse(line));
1462
+ if (!rec.success || rec.data.seq !== i) return false;
1463
+ if (prevLine !== void 0 && rec.data.prev !== sha256(prevLine)) return false;
1464
+ prevLine = line;
1465
+ }
1466
+ return true;
1467
+ }
1468
+ };
1469
+ }
1470
+ var grantSinkSchema = z.union([
1471
+ z.object({ kind: z.literal("local") }).strict(),
1472
+ z.object({ kind: z.literal("remote"), providerId: z.string(), ackSeq: z.number().int().min(0) }).strict()
1473
+ ]);
1474
+ var grantSchema = z.object({
1475
+ id: z.string().min(1),
1476
+ sessionId: z.string().min(1),
1477
+ deviceId: z.string().min(1).optional(),
1478
+ source: z.object({ kind: z.enum(["chrome", "firefox", "file", "native-login"]), profile: z.string().min(1) }).strict(),
1479
+ domains: z.array(z.string().max(253).regex(GRANT_DOMAIN_PATTERN)).min(1).optional(),
1480
+ fullProfile: z.literal(true).optional(),
1481
+ sinks: z.array(grantSinkSchema).min(1),
1482
+ grantedAt: z.iso.datetime({ offset: true }),
1483
+ grantedVia: z.enum(["interactive", "flag"]),
1484
+ expiresAt: z.iso.datetime({ offset: true }).optional(),
1485
+ cookieCount: z.number().int().min(0),
1486
+ revokedAt: z.iso.datetime({ offset: true }).optional()
1487
+ }).strict().superRefine((g, ctx) => {
1488
+ if (g.domains === void 0 === (g.fullProfile === void 0)) {
1489
+ ctx.addIssue({ code: "custom", message: "a grant has exactly one of domains or fullProfile", path: ["domains"] });
1490
+ }
1491
+ if (g.fullProfile && g.sinks.some((s) => s.kind !== "local")) {
1492
+ ctx.addIssue({ code: "custom", message: "a full-profile grant cannot include a remote sink (C3)", path: ["sinks"] });
1493
+ }
1494
+ });
1495
+ function isGrantActive(grant, now = /* @__PURE__ */ new Date()) {
1496
+ if (grant.revokedAt !== void 0) return false;
1497
+ return grant.expiresAt === void 0 || Date.parse(grant.expiresAt) > now.getTime();
1498
+ }
1499
+ function grantServesDevice(grant, deviceId) {
1500
+ return grant.deviceId === void 0 || grant.deviceId === deviceId;
1501
+ }
1502
+ function memoryGrantStore(initial = []) {
1503
+ const map = new Map(initial.map((g) => [g.id, structuredClone(g)]));
1504
+ return {
1505
+ list: () => [...map.values()].map((g) => structuredClone(g)),
1506
+ get: (id) => {
1507
+ const g = map.get(id);
1508
+ return g ? structuredClone(g) : void 0;
1509
+ },
1510
+ put: (grant) => void map.set(grant.id, structuredClone(grant))
1511
+ };
1512
+ }
1513
+ function defaultGrantStorePath(home = homedir()) {
1514
+ return join(home, ".agentproto", "bureau", "grants.json");
1515
+ }
1516
+ var isPosix2 = process.platform !== "win32";
1517
+ function fileGrantStore(path5 = defaultGrantStorePath()) {
1518
+ const load = () => {
1519
+ if (!existsSync(path5)) return [];
1520
+ let raw;
1521
+ try {
1522
+ raw = JSON.parse(readFileSync(path5, "utf8"));
1523
+ } catch {
1524
+ throw new GrantStoreInvalidError(`grants file ${path5} is not valid JSON`);
1525
+ }
1526
+ const parsed = z.object({ version: z.literal(1), grants: z.array(grantSchema) }).safeParse(raw);
1527
+ if (!parsed.success) throw new GrantStoreInvalidError(`grants file ${path5} does not hold a grant list`);
1528
+ return parsed.data.grants;
1529
+ };
1530
+ return {
1531
+ list: load,
1532
+ get: (id) => load().find((g) => g.id === id),
1533
+ put(grant) {
1534
+ const checked = grantSchema.parse(grant);
1535
+ const all = load().filter((g) => g.id !== checked.id);
1536
+ all.push(checked);
1537
+ mkdirSync(dirname(path5), { recursive: true, mode: 448 });
1538
+ const tmp = `${path5}.tmp`;
1539
+ writeFileSync(tmp, JSON.stringify({ version: 1, grants: all }, null, 2), { mode: 384 });
1540
+ renameSync(tmp, path5);
1541
+ if (isPosix2 && (statSync(path5).mode & 63) !== 0) chmodSync(path5, 384);
1542
+ }
1543
+ };
1544
+ }
1545
+ function localChromePort(opts) {
1546
+ const dirFor = (profile) => {
1547
+ if (!profileDirs(opts.chromeRoot).includes(profile)) {
1548
+ throw new ChromeIdentityError(`Chrome profile "${profile}" not found under the given user-data dir`);
1549
+ }
1550
+ return path.join(opts.chromeRoot, profile);
1551
+ };
1552
+ return {
1553
+ countByDomain: (profile, domains) => countCookiesByDomain(dirFor(profile), domains),
1554
+ countAll: (profile) => countCookies(dirFor(profile)),
1555
+ readCookies(profile, domains) {
1556
+ const key = opts.safeStoragePassword ? safeStorageKey(opts.safeStoragePassword()) : readSafeStorageKey();
1557
+ return decryptChromeCookies(dirFor(profile), domains, key);
1558
+ }
1559
+ };
1560
+ }
1561
+ var FULL_PROFILE_WARNING = "WARNING: full-profile grant. Every cookie in this Chrome profile becomes usable by this device, for every site, not just the ones you know. It stays on this machine (local sink only). Prefer naming domains with --domains. Revoke it any time; the grant and the revoke are in the consent ledger.";
1562
+ var jarSchema = z.object({ version: z.literal(1), cookies: z.array(browserCookieSchema) });
1563
+ var SAME_SITE = { strict: "Strict", lax: "Lax", none: "None" };
1564
+ function toBrowserCookie(c) {
1565
+ const sameSite = c.sameSite ? SAME_SITE[c.sameSite.toLowerCase()] : void 0;
1566
+ return {
1567
+ name: c.name,
1568
+ value: c.value,
1569
+ domain: c.domain,
1570
+ path: c.path ?? "/",
1571
+ ...c.expires !== void 0 && c.expires > 0 ? { expires: c.expires } : {},
1572
+ ...c.httpOnly !== void 0 ? { httpOnly: c.httpOnly } : {},
1573
+ ...c.secure !== void 0 ? { secure: c.secure } : {},
1574
+ ...sameSite ? { sameSite } : {}
1575
+ };
1576
+ }
1577
+ var isPosix3 = process.platform !== "win32";
1578
+ var HUMAN = { kind: "human" };
1579
+ var SYSTEM = { kind: "system" };
1580
+ function createConsentHost(opts) {
1581
+ const { grants, ledger, store, chrome } = opts;
1582
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
1583
+ const newId = opts.newId ?? (() => `grant_${randomUUID()}`);
1584
+ const jarPath = (grantId) => path.join(opts.jarDir, `${grantId.replace(/[^\w.-]/g, "_")}.json`);
1585
+ const devicePaired = (g) => g.deviceId === void 0 || opts.isDevicePaired === void 0 || opts.isDevicePaired(g.deviceId);
1586
+ const usable = (g, deviceId) => isGrantActive(g, now()) && devicePaired(g) && grantServesDevice(g, deviceId);
1587
+ const writeJar = (grantId, cookies) => {
1588
+ mkdirSync(opts.jarDir, { recursive: true, mode: 448 });
1589
+ const file = jarPath(grantId);
1590
+ const tmp = `${file}.tmp`;
1591
+ writeFileSync(tmp, JSON.stringify({ version: 1, cookies }), { mode: 384 });
1592
+ renameSync(tmp, file);
1593
+ if (isPosix3 && (statSync(file).mode & 63) !== 0) chmodSync(file, 384);
1594
+ };
1595
+ const readJar = (grantId) => {
1596
+ const file = jarPath(grantId);
1597
+ if (!existsSync(file)) return [];
1598
+ let raw;
1599
+ try {
1600
+ raw = JSON.parse(readFileSync(file, "utf8"));
1601
+ } catch {
1602
+ return [];
1603
+ }
1604
+ const parsed = jarSchema.safeParse(raw);
1605
+ return parsed.success ? parsed.data.cookies : [];
1606
+ };
1607
+ const nameHashes = (grantId, cookies) => [...new Set(cookies.map((c) => c.name))].map((n) => cookieNameHash(grantId, n));
1608
+ const syncDescriptor = async (sessionId) => {
1609
+ const active = grants.list().filter((g) => g.sessionId === sessionId && g.domains !== void 0 && isGrantActive(g, now()) && existsSync(jarPath(g.id))).sort((a, b) => a.grantedAt.localeCompare(b.grantedAt));
1610
+ const prev = await store.load(sessionId);
1611
+ const latest = active[active.length - 1];
1612
+ if (!latest || !latest.domains) {
1613
+ if (prev?.inject && isFileInject(prev.inject) && path.dirname(prev.inject.path) === opts.jarDir) await store.remove(sessionId);
1614
+ return;
1615
+ }
1616
+ const desc = {
1617
+ ...prev ?? { id: sessionId, backend: "camofox" },
1618
+ identity: { ...prev?.identity ?? {}, profile: latest.source.profile },
1619
+ inject: { from: "file", domains: latest.domains, path: jarPath(latest.id) },
1620
+ savedAt: now().toISOString()
1621
+ };
1622
+ await store.save(desc);
1623
+ };
1624
+ const refuseAgent = (attempt, ctx) => {
1625
+ const named = ctx.domains && ctx.domains.length > 0 ? ` (${ctx.domains.join(", ")})` : "";
1626
+ ledger.append({
1627
+ event: "deny",
1628
+ actor: ctx.actor,
1629
+ ...ctx.grantId ? { grantId: ctx.grantId } : {},
1630
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {},
1631
+ reason: `agent attempted to ${attempt}${named}`.slice(0, 500)
1632
+ });
1633
+ throw new AgentGrantRefusedError(attempt);
1634
+ };
1635
+ const guardHuman = (actor, attempt, ctx) => {
1636
+ const a = actor ?? HUMAN;
1637
+ if (a.kind === "agent") refuseAgent(attempt, { actor: a, sessionId: ctx.sessionId, ...ctx.domains ? { domains: ctx.domains } : {} });
1638
+ return a;
1639
+ };
1640
+ return {
1641
+ async importFromChrome(req) {
1642
+ const actor = guardHuman(req.actor, "grant access to a Chrome profile", { sessionId: req.sessionId, ...req.domains ? { domains: req.domains } : {} });
1643
+ const named = req.domains ?? [];
1644
+ if (!opts.prompt) {
1645
+ const missing = [];
1646
+ if (named.length === 0) missing.push("--domains");
1647
+ if (req.yes !== true) missing.push("--yes");
1648
+ if (missing.length > 0) throw new NonInteractiveConsentError(missing);
1649
+ }
1650
+ const domains = validateGrantDomains(named, opts.domainPolicy);
1651
+ const scan = chrome.countByDomain(req.profile, domains);
1652
+ const presence = "known" in scan ? scan.known : void 0;
1653
+ if (req.yes !== true && opts.prompt) {
1654
+ for (const domain of domains) {
1655
+ const ok = await opts.prompt.confirm({
1656
+ kind: "domain",
1657
+ domain,
1658
+ profile: req.profile,
1659
+ presentCount: presence?.[domain],
1660
+ ...req.deviceId ? { deviceId: req.deviceId } : {}
1661
+ });
1662
+ if (!ok) throw new ConsentDeclinedError(`domain ${domain}`);
1663
+ }
1664
+ }
1665
+ const remoteSinks = [];
1666
+ const sinks = [];
1667
+ for (const s of req.sinks ?? [{ kind: "local" }]) {
1668
+ if (s.kind === "local") {
1669
+ if (!sinks.some((x) => x.kind === "local")) sinks.push({ kind: "local" });
1670
+ continue;
1671
+ }
1672
+ let acked = s.acknowledged === true;
1673
+ if (!acked && opts.prompt) acked = await opts.prompt.confirm({ kind: "sink", providerId: s.providerId, sessionId: req.sessionId });
1674
+ if (!acked) throw new SinkAckRequiredError(s.providerId);
1675
+ remoteSinks.push({ providerId: s.providerId });
1676
+ }
1677
+ if (sinks.length === 0 && remoteSinks.length === 0) sinks.push({ kind: "local" });
1678
+ const cookies = chrome.readCookies(req.profile, domains).map(toBrowserCookie);
1679
+ const id = newId();
1680
+ const via = req.yes === true ? "flag" : "interactive";
1681
+ const grantActor = { ...actor, via, ...req.deviceId ? { deviceId: req.deviceId } : {} };
1682
+ ledger.append({
1683
+ event: "grant",
1684
+ actor: grantActor,
1685
+ grantId: id,
1686
+ sessionId: req.sessionId,
1687
+ domains,
1688
+ cookieCount: cookies.length,
1689
+ nameHashes: nameHashes(id, cookies)
1690
+ });
1691
+ writeJar(id, cookies);
1692
+ for (const r of remoteSinks) {
1693
+ const row = ledger.append({ event: "sink-ack", actor: grantActor, grantId: id, sessionId: req.sessionId, sinkId: r.providerId });
1694
+ sinks.push({ kind: "remote", providerId: r.providerId, ackSeq: row.seq });
1695
+ }
1696
+ const grant = {
1697
+ id,
1698
+ sessionId: req.sessionId,
1699
+ ...req.deviceId ? { deviceId: req.deviceId } : {},
1700
+ source: { kind: "chrome", profile: req.profile },
1701
+ domains,
1702
+ sinks,
1703
+ grantedAt: now().toISOString(),
1704
+ grantedVia: via,
1705
+ ...req.expiresAt ? { expiresAt: req.expiresAt } : {},
1706
+ cookieCount: cookies.length
1707
+ };
1708
+ grants.put(grant);
1709
+ await syncDescriptor(req.sessionId);
1710
+ const warnings = presence ? domains.filter((d) => (presence[d] ?? 0) === 0).map((d) => `no cookies found for ${d} in profile ${req.profile}`) : ["presence scan failed; the import may hold fewer cookies than expected"];
1711
+ return { grant, presence, warnings };
1712
+ },
1713
+ async grantFullProfile(req) {
1714
+ const actor = guardHuman(req.actor, "grant a full Chrome profile", { sessionId: req.sessionId });
1715
+ if (!opts.prompt && req.yes !== true) throw new NonInteractiveConsentError(["--full-profile", "--yes"]);
1716
+ if (req.yes !== true && opts.prompt) {
1717
+ const ok = await opts.prompt.confirm({
1718
+ kind: "full-profile",
1719
+ profile: req.profile,
1720
+ warning: FULL_PROFILE_WARNING,
1721
+ ...req.deviceId ? { deviceId: req.deviceId } : {}
1722
+ });
1723
+ if (!ok) throw new ConsentDeclinedError(`full profile ${req.profile}`);
1724
+ }
1725
+ const count = chrome.countAll(req.profile);
1726
+ const cookieCount = "known" in count ? count.known : 0;
1727
+ const id = newId();
1728
+ const via = req.yes === true ? "flag" : "interactive";
1729
+ ledger.append({
1730
+ event: "grant",
1731
+ actor: { ...actor, via, ...req.deviceId ? { deviceId: req.deviceId } : {} },
1732
+ grantId: id,
1733
+ sessionId: req.sessionId,
1734
+ fullProfile: true,
1735
+ cookieCount
1736
+ });
1737
+ const grant = {
1738
+ id,
1739
+ sessionId: req.sessionId,
1740
+ ...req.deviceId ? { deviceId: req.deviceId } : {},
1741
+ source: { kind: "chrome", profile: req.profile },
1742
+ fullProfile: true,
1743
+ sinks: [{ kind: "local" }],
1744
+ grantedAt: now().toISOString(),
1745
+ grantedVia: via,
1746
+ ...req.expiresAt ? { expiresAt: req.expiresAt } : {},
1747
+ cookieCount
1748
+ };
1749
+ grants.put(grant);
1750
+ const warnings = "known" in count ? [] : ["cookie count unavailable; recorded as 0"];
1751
+ return { grant, warning: FULL_PROFILE_WARNING, warnings };
1752
+ },
1753
+ async refresh(grantId, o = {}) {
1754
+ const actor = o.actor ?? SYSTEM;
1755
+ const grant = grants.get(grantId);
1756
+ if (!grant || !usable(grant, actor.deviceId) || !grant.domains) throw new ConsentRequiredError();
1757
+ const cookies = chrome.readCookies(grant.source.profile, grant.domains).map(toBrowserCookie);
1758
+ ledger.append({
1759
+ event: "refresh",
1760
+ actor,
1761
+ grantId,
1762
+ sessionId: grant.sessionId,
1763
+ domains: grant.domains,
1764
+ cookieCount: cookies.length,
1765
+ nameHashes: nameHashes(grantId, cookies)
1766
+ });
1767
+ writeJar(grantId, cookies);
1768
+ const next = { ...grant, cookieCount: cookies.length };
1769
+ grants.put(next);
1770
+ await syncDescriptor(grant.sessionId);
1771
+ return { grant: next, cookieCount: cookies.length };
1772
+ },
1773
+ async revoke(grantId, o = {}) {
1774
+ const actor = o.actor ?? HUMAN;
1775
+ const grant = grants.get(grantId);
1776
+ if (!grant) throw new ConsentRequiredError(`no grant "${grantId}"`);
1777
+ if (grant.revokedAt !== void 0) return { grant, derived: { local: "none", remote: "not-applicable" } };
1778
+ const hadJar = existsSync(jarPath(grantId));
1779
+ rmSync(jarPath(grantId), { force: true });
1780
+ const extra = opts.deleteExtraDerived ? await opts.deleteExtraDerived(grant) : false;
1781
+ const hasRemote = grant.sinks.some((s) => s.kind === "remote");
1782
+ const remote = !hasRemote ? "not-applicable" : opts.revokeRemote ? await opts.revokeRemote(grant) : "unavailable";
1783
+ const derived = { local: hadJar || extra ? "deleted" : "none", remote };
1784
+ const revoked = { ...grant, revokedAt: now().toISOString() };
1785
+ grants.put(revoked);
1786
+ await syncDescriptor(grant.sessionId);
1787
+ ledger.append({ event: "revoke", actor, grantId, sessionId: grant.sessionId, derived });
1788
+ return { grant: revoked, derived };
1789
+ },
1790
+ fullProfileProof(sel) {
1791
+ const match = (g) => g.fullProfile === true && (sel.grantId === void 0 || g.id === sel.grantId) && (sel.sessionId === void 0 || g.sessionId === sel.sessionId) && (sel.profile === void 0 || g.source.profile === sel.profile) && usable(g, sel.deviceId);
1792
+ const found = grants.list().find(match);
1793
+ if (!found) {
1794
+ throw new FullProfileGrantError(
1795
+ "no active full-profile grant covers this request. --full-profile needs an explicit, recorded grant (run the full-profile consent first)."
1796
+ );
1797
+ }
1798
+ return {
1799
+ grantId: found.id,
1800
+ isActive: () => {
1801
+ const g = grants.get(found.id);
1802
+ return g !== void 0 && g.fullProfile === true && usable(g, sel.deviceId);
1803
+ }
1804
+ };
1805
+ },
1806
+ // Serves domain grants only. A full-profile grant is honored through `fullProfileProof`, never as injected cookies.
1807
+ cookieSourceFor(scope = {}) {
1808
+ return ({ providerId, profile }) => {
1809
+ const out = [];
1810
+ for (const g of grants.list()) {
1811
+ if (g.sessionId !== profile || !g.domains || !usable(g, scope.deviceId)) continue;
1812
+ const servesProvider = g.sinks.some((s) => s.kind === "local" || s.providerId === providerId);
1813
+ if (!servesProvider) continue;
1814
+ const allowed = g.domains;
1815
+ out.push(...readJar(g.id).filter((c) => allowed.some((d) => hostCoveredBy(c.domain, d))));
1816
+ }
1817
+ return out;
1818
+ };
1819
+ },
1820
+ assertCovered({ sessionId, host, deviceId }) {
1821
+ const hit = grants.list().find((g) => g.sessionId === sessionId && g.domains?.some((d) => hostCoveredBy(host, d)) === true && usable(g, deviceId));
1822
+ if (!hit) throw new ConsentRequiredError(`no active grant covers ${host} for this device`);
1823
+ return hit;
1824
+ },
1825
+ refuseAgent,
1826
+ getGrant: (id) => grants.get(id),
1827
+ listGrants: () => grants.list()
1828
+ };
1829
+ }
1830
+
1831
+ // src/consent/agent.ts
1832
+ function createAgentConsentSurface(host, who = {}) {
1833
+ const actor = {
1834
+ kind: "agent",
1835
+ ...who.via ? { via: who.via } : {},
1836
+ ...who.deviceId ? { deviceId: who.deviceId } : {}
1837
+ };
1838
+ const own = (grantId) => {
1839
+ const g = host.getGrant(grantId);
1840
+ if (!g || !grantServesDevice(g, who.deviceId)) throw new ConsentRequiredError();
1841
+ return g;
1842
+ };
1843
+ const sameSet = (a, b) => {
1844
+ const left = new Set(a.map((x) => x.toLowerCase()));
1845
+ const right = new Set(b.map((x) => x.toLowerCase()));
1846
+ return left.size === right.size && [...left].every((x) => right.has(x));
1847
+ };
1848
+ return {
1849
+ async sync(input) {
1850
+ const grant = own(input.grantId);
1851
+ const base = { actor, grantId: grant.id, sessionId: grant.sessionId };
1852
+ if (input.profile !== void 0 && input.profile !== grant.source.profile) {
1853
+ host.refuseAgent("change the Chrome profile of a grant", base);
1854
+ }
1855
+ if (input.domains !== void 0 && !sameSet(input.domains, grant.domains ?? [])) {
1856
+ host.refuseAgent("add a domain to a grant", { ...base, domains: input.domains });
1857
+ }
1858
+ const { cookieCount } = await host.refresh(grant.id, { actor });
1859
+ return { grantId: grant.id, cookieCount };
1860
+ },
1861
+ async revoke(grantId) {
1862
+ own(grantId);
1863
+ await host.revoke(grantId, { actor });
1864
+ },
1865
+ async grant(input) {
1866
+ return host.refuseAgent("grant access to a Chrome profile", {
1867
+ actor,
1868
+ sessionId: input.sessionId,
1869
+ ...input.domains ? { domains: input.domains } : {}
1870
+ });
1871
+ },
1872
+ async addSink(input) {
1873
+ const g = own(input.grantId);
1874
+ return host.refuseAgent("add a sink to a grant", { actor, grantId: g.id, sessionId: g.sessionId });
1875
+ }
1876
+ };
1877
+ }
1878
+ var NATIVE_LOGIN_RECOMMENDATION = "Sign in natively instead: run the native-login flow in the Bureau browser. It needs no access to your Chrome profile, Keychain or Full Disk Access.";
1879
+ var errCode = (e) => typeof e === "object" && e !== null && "code" in e && typeof e.code === "string" ? e.code : void 0;
1880
+ function classify(e, binary, what) {
1881
+ const code = errCode(e);
1882
+ if (code === "EPERM" || code === "EACCES") {
1883
+ return {
1884
+ failure: "full-disk-access",
1885
+ detail: `${what} was blocked (${code}). Full Disk Access is missing for "${binary}".`,
1886
+ fix: `System Settings > Privacy & Security > Full Disk Access: enable "${binary}", then restart it.`
1887
+ };
1888
+ }
1889
+ if (code === "ENOENT") {
1890
+ return { failure: "missing", detail: `${what} does not exist.`, fix: "Check the Chrome profile name (Default, Profile 1) and that Chrome is installed." };
1891
+ }
1892
+ return { failure: "unreadable", detail: `${what} could not be read (${code ?? "unknown error"}).` };
1893
+ }
1894
+ function runDoctor(opts) {
1895
+ const { port } = opts;
1896
+ const checks = [];
1897
+ try {
1898
+ const state = parseLocalState(port.readLocalState());
1899
+ checks.push({ id: "local-state", status: "ok", detail: `Local State readable, ${state.profiles.length} profile(s) listed.` });
1900
+ } catch (e) {
1901
+ checks.push({ id: "local-state", status: "fail", ...classify(e, port.binary, "Local State") });
1902
+ }
1903
+ try {
1904
+ port.copyCookiesDb(opts.profile);
1905
+ checks.push({ id: "cookies-db", status: "ok", detail: `Cookies db of "${opts.profile}" is copyable.` });
1906
+ } catch (e) {
1907
+ checks.push({ id: "cookies-db", status: "fail", ...classify(e, port.binary, `Cookies db of "${opts.profile}"`) });
1908
+ }
1909
+ if (opts.checkKeychain === true && port.probeKeychain) {
1910
+ try {
1911
+ port.probeKeychain();
1912
+ checks.push({ id: "keychain", status: "ok", detail: "Keychain returned the Safe Storage key (value not shown)." });
1913
+ } catch {
1914
+ checks.push({
1915
+ id: "keychain",
1916
+ status: "fail",
1917
+ failure: "keychain-denied",
1918
+ detail: "The Keychain did not release the Safe Storage key.",
1919
+ fix: "Approve the Keychain prompt for this run, or use native login."
1920
+ });
1921
+ }
1922
+ } else {
1923
+ checks.push({ id: "keychain", status: "skipped", detail: "Keychain not touched. Pass the explicit keychain check to probe it." });
1924
+ }
1925
+ const ok = checks.every((c) => c.status !== "fail");
1926
+ return { ok, checks, ...ok ? {} : { recommendation: NATIVE_LOGIN_RECOMMENDATION } };
1927
+ }
1928
+ function localDoctorPort(opts) {
1929
+ return {
1930
+ binary: opts.binary ?? process.execPath,
1931
+ readLocalState: () => readFileSync(path.join(opts.chromeRoot, "Local State"), "utf8"),
1932
+ copyCookiesDb(profile) {
1933
+ const dir = mkdtempSync(path.join(tmpdir(), "bp-doctor-"));
1934
+ try {
1935
+ copyFileSync(path.join(opts.chromeRoot, profile, "Cookies"), path.join(dir, "Cookies"));
1936
+ } finally {
1937
+ rmSync(dir, { recursive: true, force: true });
1938
+ }
1939
+ },
1940
+ ...opts.probeKeychain ? { probeKeychain: opts.probeKeychain } : {}
1941
+ };
1942
+ }
1943
+
1944
+ export { ACCOUNT_SWITCHER_MISSING_CODE, AGENT_GRANT_REFUSED_CODE, AccountSwitcherMissingError, AgentGrantRefusedError, BUILTIN_INJECT_FROM, BUILTIN_STRATEGY_KINDS, CHROME_IDENTITY_CODE, CONSENT_DECLINED_CODE, CONSENT_FLAGS_REQUIRED_CODE, CONSENT_REQUIRED_CODE, COOKIE_STALE_TTL_MS, ChromeIdentityError, ConsentDeclinedError, ConsentRequiredError, FULL_PROFILE_REFUSED_CODE, FULL_PROFILE_WARNING, FullProfileGrantError, GRANT_DOMAIN_INVALID_CODE, GRANT_DOMAIN_PATTERN, GRANT_STORE_INVALID_CODE, GrantDomainError, GrantStoreInvalidError, LEDGER_RECORD_INVALID_CODE, LedgerRecordInvalidError, NonInteractiveConsentError, SESSION_DESCRIPTOR_INVALID_CODE, SESSION_RESOLVE_CODE, SESSION_SOURCE_DUPLICATE_CODE, SESSION_SOURCE_RESERVED_CODE, SESSION_SOURCE_UNKNOWN_CODE, SINK_ACK_REQUIRED_CODE, SessionDescriptorInvalidError, SessionResolveError, SessionSourceDuplicateError, SessionSourceReservedError, SessionSourceUnknownError, SinkAckRequiredError, assertChromeLaunchDirSafe, authStatusSchema, buildOwnedSessionDescriptor, camofoxStatePath, catalogSessionStore, chromeCookieStrategyOf, chromeIdentityLabel, chromeLocalStatePath, chromeTimeToIso, chromeUserDataDir, chromeUserDataRoot, compositeSessionStore, cookieFreshness, cookieNameHash, countCookies, countCookiesByDomain, createAgentConsentSurface, createAuthSignalRegistry, createConsentHost, createConsentLedger, createLocalBrowserSession, createSessionSourceRegistry, decryptChromeCookies, decryptChromeValue, dedupeCookies, defaultAuthSignalRegistry, defaultCamofoxBase, defaultConsentLedgerPath, defaultGrantStorePath, describeSessionAuth, domainAuthSignal, domainMatches, fileGrantStore, fileSessionStore, gatherCookies, gatherCookiesForStrategy, grantSchema, grantServesDevice, hostCoveredBy, injectFromOf, injectKindOf, isBuiltinStrategyKind, isCamofoxNativeInject, isChromeCookieStrategy, isChromeProfileInject, isFileInject, isGrantActive, isKnown, isKnownPublicSuffix, isOwnedStorageStateStrategy, isSessionSourceUnknownError, isSourceInject, isSourceStrategy, isStoredCredentialStrategy, knownOr, ledgerActorSchema, ledgerEventSchema, ledgerRecordSchema, localChromePort, localDoctorPort, memoryGrantStore, normalizeDescriptor, openSession, ownedStorageStatePath, parseLocalState, parseSessionDescriptor, priorChromeStrategy, profileAuthedDomains, profileDirs, profileLoggedInDomains, readLocalState, readSafeStorageKey, resolveChromeProfile, resolveInjectFromStrategies, resolveSession, runDoctor, safeStorageKey, scanChromeIdentities, selectResolutionStrategy, sessionDescriptorSchema, sessionIdentitySchema, sessionInjectSchema, strategySchema, toSessionCookie, validateGrantDomains, verifyChromeIdentity };
1945
+ //# sourceMappingURL=index.mjs.map
1946
+ //# sourceMappingURL=index.mjs.map