surf-cli 2.13.1 → 2.15.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/README.md +65 -34
- package/agents/gpt-pro.md +19 -0
- package/dist/content/index.js +116 -0
- package/dist/content/index.js.map +1 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/manifest.json +2 -11
- package/dist/options/options.js +3 -3
- package/dist/options/options.js.map +1 -1
- package/dist/service-worker/index.js +261 -61
- package/dist/service-worker/index.js.map +1 -1
- package/native/browser-scheduler.cjs +348 -0
- package/native/browser-session-store.cjs +271 -0
- package/native/chatgpt-client-selection.cjs +20 -10
- package/native/chatgpt-client-ui.cjs +35 -7
- package/native/cli.cjs +333 -62
- package/native/do-executor.cjs +5 -0
- package/native/host-helpers.cjs +19 -3
- package/native/host-sessions.cjs +8 -1
- package/native/host.cjs +766 -19
- package/native/mcp-server.cjs +1 -1
- package/native/oracle-cli.cjs +2 -2
- package/native/oracle-jobs.cjs +25 -3
- package/native/playbook-cli.cjs +16 -3
- package/native/surf-error.cjs +47 -0
- package/native/tool-scope.cjs +107 -0
- package/native/workflow-definition.cjs +7 -0
- package/package.json +8 -2
- package/pi-extension/surf.ts +275 -2
- package/skills/surf/SKILL.md +52 -23
- package/dist/content/accessibility-tree.js +0 -11
- package/dist/content/accessibility-tree.js.map +0 -1
- package/dist/content/visual-indicator.js +0 -111
- package/dist/content/visual-indicator.js.map +0 -1
package/pi-extension/surf.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { Type } from "typebox";
|
|
3
4
|
|
|
4
5
|
const require = createRequire(import.meta.url);
|
|
@@ -26,6 +27,9 @@ const ORACLE_FINISHED_CHANNEL = "surf-oracle:finished";
|
|
|
26
27
|
const BACKGROUND_WORK_PROTOCOL_VERSION = 1;
|
|
27
28
|
const BACKGROUND_WORK_REGISTRY_KEY = "pi-subagents.background-work.v1";
|
|
28
29
|
const BACKGROUND_WORK_MODULE_SPECIFIER = "pi-subagents/background-work";
|
|
30
|
+
const EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION = 1;
|
|
31
|
+
const EXTERNAL_JOB_PROVIDER_REGISTRY_KEY = "pi-subagents.external-job-providers.v1";
|
|
32
|
+
const EXTERNAL_JOB_PROVIDER_MODULE_SPECIFIER = "pi-subagents/external-job-provider";
|
|
29
33
|
|
|
30
34
|
type Pi = {
|
|
31
35
|
registerTool(tool: Record<string, unknown>): void;
|
|
@@ -37,6 +41,20 @@ type SurfEndpoint = { kind?: string };
|
|
|
37
41
|
|
|
38
42
|
type ToolResult = { content: Array<{ type: "text" | "image"; text?: string; data?: string; mimeType?: string }>; details?: unknown; isError?: boolean };
|
|
39
43
|
|
|
44
|
+
type OracleJob = {
|
|
45
|
+
id: string;
|
|
46
|
+
state: string;
|
|
47
|
+
conversationUrl?: string | null;
|
|
48
|
+
model?: string | null;
|
|
49
|
+
modelRequested?: string | null;
|
|
50
|
+
modelVerified?: string | null;
|
|
51
|
+
effortRequested?: string | null;
|
|
52
|
+
effortVerified?: string | null;
|
|
53
|
+
promptDigest?: string | null;
|
|
54
|
+
response?: string;
|
|
55
|
+
error?: { code?: string; message?: string } | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
40
58
|
type BackgroundWorkProvider = {
|
|
41
59
|
name: string;
|
|
42
60
|
wakeChannels: string[];
|
|
@@ -54,6 +72,46 @@ type BackgroundWorkRegistry = {
|
|
|
54
72
|
providers: Map<string, BackgroundWorkProvider>;
|
|
55
73
|
};
|
|
56
74
|
|
|
75
|
+
type OracleExternalJob = {
|
|
76
|
+
provider: "surf-oracle";
|
|
77
|
+
id: string;
|
|
78
|
+
state: string;
|
|
79
|
+
conversationUrl: string | null;
|
|
80
|
+
requestedModel: string | null;
|
|
81
|
+
verifiedModel: string | null;
|
|
82
|
+
requestedEffort: string | null;
|
|
83
|
+
verifiedEffort: string | null;
|
|
84
|
+
promptDigest: string | null;
|
|
85
|
+
resultText?: string;
|
|
86
|
+
resultArtifact?: { kind: "inline-text"; bytes: number };
|
|
87
|
+
failure?: { code: string; message: string };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type OracleExternalJobProvider = {
|
|
91
|
+
name: "surf-oracle";
|
|
92
|
+
kind: "external-job";
|
|
93
|
+
wakeChannels: string[];
|
|
94
|
+
start(input: Record<string, unknown>): Promise<OracleExternalJob>;
|
|
95
|
+
status(id: string): Promise<OracleExternalJob>;
|
|
96
|
+
result(id: string, input?: Record<string, unknown>): Promise<OracleExternalJob>;
|
|
97
|
+
reattach(id: string, input?: Record<string, unknown>): Promise<OracleExternalJob>;
|
|
98
|
+
follow(id: string, message: string, input?: Record<string, unknown>): Promise<OracleExternalJob>;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
type RegisterExternalJobProvider = (provider: OracleExternalJobProvider) => () => void;
|
|
102
|
+
|
|
103
|
+
type ExternalJobProviderModule = {
|
|
104
|
+
registerExternalJobProvider?: unknown;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
type ExternalJobProviderRegistry = {
|
|
108
|
+
version: typeof EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION;
|
|
109
|
+
providers: Map<string, OracleExternalJobProvider>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type RememberOracleJob = (jobId: string) => boolean;
|
|
113
|
+
type EmitOracleJob = (job: Pick<OracleExternalJob, "id" | "state">) => boolean;
|
|
114
|
+
|
|
57
115
|
function textResult(value: unknown, isError = false): ToolResult {
|
|
58
116
|
const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
59
117
|
const bounded = text.length > MAX_OUTPUT_CHARS
|
|
@@ -64,7 +122,12 @@ function textResult(value: unknown, isError = false): ToolResult {
|
|
|
64
122
|
|
|
65
123
|
export function resultFromHost(response: Record<string, unknown>): ToolResult {
|
|
66
124
|
const error = response.error as { content?: Array<{ text?: string }> } | undefined;
|
|
67
|
-
if (error)
|
|
125
|
+
if (error) {
|
|
126
|
+
return {
|
|
127
|
+
...textResult(error.content?.map((item) => item.text ?? "").join("\n") || "Surf request failed", true),
|
|
128
|
+
details: error,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
68
131
|
const result = response.result as { content?: ToolResult["content"] } | undefined;
|
|
69
132
|
if (!result?.content) return textResult(result ?? "OK");
|
|
70
133
|
const text = result.content.find((item) => item.type === "text")?.text;
|
|
@@ -138,6 +201,33 @@ export function registerGlobalBackgroundProvider(provider: BackgroundWorkProvide
|
|
|
138
201
|
};
|
|
139
202
|
}
|
|
140
203
|
|
|
204
|
+
export function registerGlobalExternalJobProvider(provider: OracleExternalJobProvider): () => void {
|
|
205
|
+
const key = Symbol.for(EXTERNAL_JOB_PROVIDER_REGISTRY_KEY);
|
|
206
|
+
const globalObject = globalThis as Record<PropertyKey, unknown>;
|
|
207
|
+
const existing = globalObject[key];
|
|
208
|
+
let registry: ExternalJobProviderRegistry;
|
|
209
|
+
|
|
210
|
+
if (existing === undefined) {
|
|
211
|
+
registry = { version: EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION, providers: new Map() };
|
|
212
|
+
globalObject[key] = registry;
|
|
213
|
+
} else if (
|
|
214
|
+
existing &&
|
|
215
|
+
typeof existing === "object" &&
|
|
216
|
+
!Array.isArray(existing) &&
|
|
217
|
+
(existing as Partial<ExternalJobProviderRegistry>).version === EXTERNAL_JOB_PROVIDER_PROTOCOL_VERSION &&
|
|
218
|
+
(existing as Partial<ExternalJobProviderRegistry>).providers instanceof Map
|
|
219
|
+
) {
|
|
220
|
+
registry = existing as ExternalJobProviderRegistry;
|
|
221
|
+
} else {
|
|
222
|
+
throw new Error(`Unsupported external-job provider registry at Symbol.for("${EXTERNAL_JOB_PROVIDER_REGISTRY_KEY}").`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
registry.providers.set(provider.name, provider);
|
|
226
|
+
return () => {
|
|
227
|
+
if (registry.providers.get(provider.name) === provider) registry.providers.delete(provider.name);
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
141
231
|
export async function resolveBackgroundWorkRegister(
|
|
142
232
|
loadModule: () => Promise<BackgroundWorkModule> = () => import(BACKGROUND_WORK_MODULE_SPECIFIER) as Promise<BackgroundWorkModule>,
|
|
143
233
|
): Promise<RegisterBackgroundWorkProvider> {
|
|
@@ -152,6 +242,151 @@ export async function resolveBackgroundWorkRegister(
|
|
|
152
242
|
return registerGlobalBackgroundProvider;
|
|
153
243
|
}
|
|
154
244
|
|
|
245
|
+
export async function resolveExternalJobProviderRegister(
|
|
246
|
+
loadModule: () => Promise<ExternalJobProviderModule> = () => import(EXTERNAL_JOB_PROVIDER_MODULE_SPECIFIER) as Promise<ExternalJobProviderModule>,
|
|
247
|
+
): Promise<RegisterExternalJobProvider> {
|
|
248
|
+
try {
|
|
249
|
+
const module = await loadModule();
|
|
250
|
+
if (typeof module.registerExternalJobProvider === "function") {
|
|
251
|
+
return module.registerExternalJobProvider as RegisterExternalJobProvider;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
// The provider bridge is optional until pi-subagents ships this consumer API.
|
|
255
|
+
}
|
|
256
|
+
return registerGlobalExternalJobProvider;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function digestPrompt(prompt: string) {
|
|
260
|
+
return `sha256:${createHash("sha256").update(prompt).digest("hex")}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function asOracleJob(value: unknown): OracleJob {
|
|
264
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Surf oracle response did not include job metadata");
|
|
265
|
+
const job = value as Partial<OracleJob>;
|
|
266
|
+
if (typeof job.id !== "string" || typeof job.state !== "string") throw new Error("Surf oracle response did not include job id and state");
|
|
267
|
+
return job as OracleJob;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function oracleExternalJob(job: OracleJob): OracleExternalJob {
|
|
271
|
+
const resultText = typeof job.response === "string" ? job.response : undefined;
|
|
272
|
+
const failure = job.error
|
|
273
|
+
? { code: job.error.code || "failed", message: job.error.message || "Surf oracle job failed" }
|
|
274
|
+
: undefined;
|
|
275
|
+
const requestedModel = Object.hasOwn(job, "modelRequested") ? job.modelRequested ?? null : job.model ?? null;
|
|
276
|
+
return {
|
|
277
|
+
provider: "surf-oracle",
|
|
278
|
+
id: job.id,
|
|
279
|
+
state: job.state,
|
|
280
|
+
conversationUrl: job.conversationUrl ?? null,
|
|
281
|
+
requestedModel,
|
|
282
|
+
verifiedModel: job.modelVerified ?? null,
|
|
283
|
+
requestedEffort: job.effortRequested ?? null,
|
|
284
|
+
verifiedEffort: job.effortVerified ?? null,
|
|
285
|
+
promptDigest: job.promptDigest ?? null,
|
|
286
|
+
...(resultText === undefined ? {} : { resultText, resultArtifact: { kind: "inline-text", bytes: Buffer.byteLength(resultText, "utf8") } }),
|
|
287
|
+
...(failure ? { failure } : {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function requestOracleJob(request: typeof requestSurf, tool: string, args: Record<string, unknown>) {
|
|
292
|
+
const result = await request(tool, args);
|
|
293
|
+
if (result.isError) {
|
|
294
|
+
const details = result.details as { code?: unknown; jobId?: unknown; message?: unknown } | undefined;
|
|
295
|
+
const message = typeof details?.message === "string"
|
|
296
|
+
? details.message
|
|
297
|
+
: result.content.map((item) => item.text ?? "").join("\n") || "Surf oracle request failed";
|
|
298
|
+
const error = new Error(message);
|
|
299
|
+
if (typeof details?.code === "string") Object.assign(error, { code: details.code });
|
|
300
|
+
if (typeof details?.jobId === "string") Object.assign(error, { jobId: details.jobId });
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
return oracleExternalJob(asOracleJob(result.details));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function emitFailedOracleJob(error: unknown, emitTerminal: EmitOracleJob) {
|
|
307
|
+
if (!error || typeof error !== "object" || !("jobId" in error) || typeof error.jobId !== "string") return;
|
|
308
|
+
emitTerminal({ id: error.jobId, state: "failed" });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function oracleOption(input: Record<string, unknown>, key: "model" | "effort"): string | undefined {
|
|
312
|
+
const options = input.options;
|
|
313
|
+
if (options && typeof options === "object" && !Array.isArray(options)) {
|
|
314
|
+
const value = (options as Record<string, unknown>)[key];
|
|
315
|
+
if (typeof value === "string") return value;
|
|
316
|
+
}
|
|
317
|
+
const direct = input[key];
|
|
318
|
+
return typeof direct === "string" ? direct : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function createOracleExternalJobProvider(
|
|
322
|
+
sessionId: string,
|
|
323
|
+
jobIds: Set<string>,
|
|
324
|
+
request: typeof requestSurf = requestSurf,
|
|
325
|
+
rememberJob: RememberOracleJob = (jobId) => {
|
|
326
|
+
jobIds.add(jobId);
|
|
327
|
+
return true;
|
|
328
|
+
},
|
|
329
|
+
emitTerminal: EmitOracleJob = () => false,
|
|
330
|
+
): OracleExternalJobProvider {
|
|
331
|
+
return {
|
|
332
|
+
name: "surf-oracle",
|
|
333
|
+
kind: "external-job",
|
|
334
|
+
wakeChannels: [ORACLE_FINISHED_CHANNEL],
|
|
335
|
+
async start(input) {
|
|
336
|
+
const prompt = typeof input.prompt === "string" ? input.prompt : "";
|
|
337
|
+
if (!prompt.trim()) throw new Error("prompt required");
|
|
338
|
+
const model = oracleOption(input, "model");
|
|
339
|
+
const effort = oracleOption(input, "effort");
|
|
340
|
+
const job = await requestOracleJob(request, "oracle.ask", {
|
|
341
|
+
prompt,
|
|
342
|
+
...(model !== undefined ? { model } : {}),
|
|
343
|
+
...(effort !== undefined ? { effort } : {}),
|
|
344
|
+
});
|
|
345
|
+
rememberJob(job.id);
|
|
346
|
+
return { ...job, promptDigest: job.promptDigest ?? digestPrompt(prompt) };
|
|
347
|
+
},
|
|
348
|
+
status(id) {
|
|
349
|
+
return requestOracleJob(request, "oracle.status", { id });
|
|
350
|
+
},
|
|
351
|
+
result(id, input = {}) {
|
|
352
|
+
return requestOracleJob(request, "oracle.result", { id, ...(typeof input.timeout === "number" ? { timeout: input.timeout } : {}) })
|
|
353
|
+
.then((job) => {
|
|
354
|
+
emitTerminal({ id: job.id, state: job.state });
|
|
355
|
+
return job;
|
|
356
|
+
})
|
|
357
|
+
.catch((error) => {
|
|
358
|
+
emitFailedOracleJob(error, emitTerminal);
|
|
359
|
+
throw error;
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
reattach(id, input = {}) {
|
|
363
|
+
return requestOracleJob(request, "oracle.result", { id, ...(typeof input.timeout === "number" ? { timeout: input.timeout } : {}) })
|
|
364
|
+
.then((job) => {
|
|
365
|
+
rememberJob(job.id);
|
|
366
|
+
emitTerminal({ id: job.id, state: job.state });
|
|
367
|
+
return job;
|
|
368
|
+
})
|
|
369
|
+
.catch((error) => {
|
|
370
|
+
emitFailedOracleJob(error, emitTerminal);
|
|
371
|
+
throw error;
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
async follow(id, message, input = {}) {
|
|
375
|
+
if (!message.trim()) throw new Error("message required");
|
|
376
|
+
const model = oracleOption(input, "model");
|
|
377
|
+
const effort = oracleOption(input, "effort");
|
|
378
|
+
const job = await requestOracleJob(request, "oracle.ask", {
|
|
379
|
+
follow: id,
|
|
380
|
+
prompt: message,
|
|
381
|
+
...(model !== undefined ? { model } : {}),
|
|
382
|
+
...(effort !== undefined ? { effort } : {}),
|
|
383
|
+
});
|
|
384
|
+
rememberJob(job.id);
|
|
385
|
+
return { ...job, promptDigest: job.promptDigest ?? digestPrompt(message) };
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
155
390
|
export function registerOptionalBackgroundProvider(sessionId: string, jobIds: Set<string>, listJobs: () => Array<{ id: string; state: string }>, register: RegisterBackgroundWorkProvider) {
|
|
156
391
|
return register({
|
|
157
392
|
name: "surf-oracle",
|
|
@@ -162,6 +397,17 @@ export function registerOptionalBackgroundProvider(sessionId: string, jobIds: Se
|
|
|
162
397
|
});
|
|
163
398
|
}
|
|
164
399
|
|
|
400
|
+
export function registerOptionalExternalJobProvider(
|
|
401
|
+
sessionId: string,
|
|
402
|
+
jobIds: Set<string>,
|
|
403
|
+
register: RegisterExternalJobProvider,
|
|
404
|
+
request: typeof requestSurf = requestSurf,
|
|
405
|
+
rememberJob?: RememberOracleJob,
|
|
406
|
+
emitTerminal?: EmitOracleJob,
|
|
407
|
+
) {
|
|
408
|
+
return register(createOracleExternalJobProvider(sessionId, jobIds, request, rememberJob, emitTerminal));
|
|
409
|
+
}
|
|
410
|
+
|
|
165
411
|
export function rememberOracleJobForSession(jobIds: Set<string>, jobId: unknown, requestGeneration: number, currentGeneration: number, sessionActive: boolean): boolean {
|
|
166
412
|
if (typeof jobId !== "string" || !sessionActive || requestGeneration !== currentGeneration) return false;
|
|
167
413
|
jobIds.add(jobId);
|
|
@@ -219,7 +465,12 @@ export default function surfExtension(pi: Pi) {
|
|
|
219
465
|
async execute(_id: string, args: Record<string, unknown>) {
|
|
220
466
|
try {
|
|
221
467
|
const result = await requestSurf("oracle.result", args);
|
|
222
|
-
|
|
468
|
+
const errorDetails = result.details as { jobId?: unknown } | undefined;
|
|
469
|
+
if (result.isError) {
|
|
470
|
+
if (typeof errorDetails?.jobId === "string") emitOracleFinished(pi, { id: errorDetails.jobId, state: "failed" });
|
|
471
|
+
} else {
|
|
472
|
+
emitOracleFinished(pi, result.details);
|
|
473
|
+
}
|
|
223
474
|
return result;
|
|
224
475
|
} catch (error) {
|
|
225
476
|
return textResult(error instanceof Error ? error.message : String(error), true);
|
|
@@ -249,12 +500,15 @@ export default function surfExtension(pi: Pi) {
|
|
|
249
500
|
});
|
|
250
501
|
|
|
251
502
|
let dispose: (() => void) | undefined;
|
|
503
|
+
let disposeExternal: (() => void) | undefined;
|
|
252
504
|
pi.on("session_start", (_event, ctx) => {
|
|
253
505
|
sessionGeneration++;
|
|
254
506
|
const generation = sessionGeneration;
|
|
255
507
|
sessionActive = false;
|
|
256
508
|
dispose?.();
|
|
509
|
+
disposeExternal?.();
|
|
257
510
|
dispose = undefined;
|
|
511
|
+
disposeExternal = undefined;
|
|
258
512
|
oracleJobIds.clear();
|
|
259
513
|
|
|
260
514
|
const session = ctx as { sessionManager?: { getSessionId?: () => string }; sessionId?: string };
|
|
@@ -262,7 +516,10 @@ export default function surfExtension(pi: Pi) {
|
|
|
262
516
|
if (!sessionId) return;
|
|
263
517
|
try {
|
|
264
518
|
const jobs = require("../native/oracle-jobs.cjs") as { listJobs(): Array<{ id: string; state: string }> };
|
|
519
|
+
const rememberForGeneration = (jobId: string) => rememberOracleJobForSession(oracleJobIds, jobId, generation, sessionGeneration, sessionActive);
|
|
520
|
+
const emitFinished = (job: Pick<OracleExternalJob, "id" | "state">) => emitOracleFinished(pi, job);
|
|
265
521
|
dispose = registerOptionalBackgroundProvider(sessionId, oracleJobIds, jobs.listJobs, registerGlobalBackgroundProvider);
|
|
522
|
+
disposeExternal = registerOptionalExternalJobProvider(sessionId, oracleJobIds, registerGlobalExternalJobProvider, requestSurf, rememberForGeneration, emitFinished);
|
|
266
523
|
sessionActive = true;
|
|
267
524
|
void resolveBackgroundWorkRegister().then((register) => {
|
|
268
525
|
try {
|
|
@@ -278,6 +535,20 @@ export default function surfExtension(pi: Pi) {
|
|
|
278
535
|
// Keep the already-registered fallback provider.
|
|
279
536
|
}
|
|
280
537
|
});
|
|
538
|
+
void resolveExternalJobProviderRegister().then((register) => {
|
|
539
|
+
try {
|
|
540
|
+
if (register === registerGlobalExternalJobProvider || generation !== sessionGeneration) return;
|
|
541
|
+
const nextDispose = registerOptionalExternalJobProvider(sessionId, oracleJobIds, register, requestSurf, rememberForGeneration, emitFinished);
|
|
542
|
+
if (generation !== sessionGeneration) {
|
|
543
|
+
nextDispose();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
disposeExternal?.();
|
|
547
|
+
disposeExternal = nextDispose;
|
|
548
|
+
} catch {
|
|
549
|
+
// Keep the already-registered fallback provider.
|
|
550
|
+
}
|
|
551
|
+
});
|
|
281
552
|
} catch {
|
|
282
553
|
// The Pi bridge is optional. Browser tools work without pi-subagents.
|
|
283
554
|
}
|
|
@@ -286,7 +557,9 @@ export default function surfExtension(pi: Pi) {
|
|
|
286
557
|
sessionGeneration++;
|
|
287
558
|
sessionActive = false;
|
|
288
559
|
dispose?.();
|
|
560
|
+
disposeExternal?.();
|
|
289
561
|
dispose = undefined;
|
|
562
|
+
disposeExternal = undefined;
|
|
290
563
|
oracleJobIds.clear();
|
|
291
564
|
});
|
|
292
565
|
}
|
package/skills/surf/SKILL.md
CHANGED
|
@@ -47,6 +47,17 @@ surf --find <term> # Search tools
|
|
|
47
47
|
surf --help-topic <topic> # Topic guide (refs, semantic, frames, devices, windows)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
## First Command for Independent Agents
|
|
51
|
+
|
|
52
|
+
Before the first browser command in each independent agent shell, choose a unique valid session name and ensure its target exists:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export SURF_SESSION="$(basename "$PWD" | sed 's/[^A-Za-z0-9._-]/-/g')"
|
|
56
|
+
surf session.ensure "$SURF_SESSION" about:blank
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`session.ensure` is idempotent. It creates a missing session, reuses a live binding, and reopens a stale or closed tab. Keep `SURF_SESSION` set for every later tab-scoped command in that shell. Use a distinct worktree/directory name per agent; when agents share one directory, append a stable agent identifier. Use `surf session.info "$SURF_SESSION"` to inspect the target and queue state.
|
|
60
|
+
|
|
50
61
|
## Core Workflow
|
|
51
62
|
|
|
52
63
|
```bash
|
|
@@ -78,7 +89,7 @@ Query AI models using your browser's logged-in session. Must be logged into the
|
|
|
78
89
|
```bash
|
|
79
90
|
surf chatgpt "explain this code"
|
|
80
91
|
surf chatgpt "summarize" --with-page # Include current page context
|
|
81
|
-
surf chatgpt "review" --model gpt-
|
|
92
|
+
surf chatgpt "review" --model gpt-5.5 # Specify model
|
|
82
93
|
surf chatgpt "analyze" --file document.pdf # With file attachment
|
|
83
94
|
```
|
|
84
95
|
|
|
@@ -91,7 +102,7 @@ For agent workflows, detach after dispatch and keep the returned `.id`:
|
|
|
91
102
|
```bash
|
|
92
103
|
surf oracle ask "Review this change and identify release risks" \
|
|
93
104
|
--files "src/**/*.ts" --files "package.json" \
|
|
94
|
-
--model
|
|
105
|
+
--model gpt-5.5 --effort pro --detach --json
|
|
95
106
|
|
|
96
107
|
surf oracle status <job-id> --json
|
|
97
108
|
surf oracle result <job-id> --json
|
|
@@ -101,7 +112,11 @@ surf oracle result <job-id> --wait --json
|
|
|
101
112
|
|
|
102
113
|
`status` reads persisted state without touching Chrome. `result` attempts to harvest the answer and returns the job object with `response` once its state is `captured`. A Ctrl-C during waiting exits with status 130 and prints `Recover with: surf oracle result <id>`. Once the job is `awaiting`, the persisted ChatGPT conversation URL is its durable key, so `surf oracle result <id>` can recover after CLI exit, native-host restart, or Chrome restart by reopening that conversation.
|
|
103
114
|
|
|
104
|
-
Treat Pro quota as scarce. Oracle never selects Pro implicitly; request it with `--model pro`. Accepted `--effort` values are `light`, `standard`, `extended`, and `
|
|
115
|
+
Treat Pro quota as scarce. Oracle never selects Pro implicitly; request it with `--model pro` or `--effort pro`. ChatGPT model aliases include `instant`, `thinking`, `pro`, `gpt-5.5`, and `gpt-5.6-sol`. Accepted `--effort` values are `light`, `standard`, `extended`, `heavy`, and `pro`. Requested model and effort selections are read back before submission, and an unverifiable selection fails with `model_verification_failed` instead of silently continuing. Capacity is one non-terminal oracle job. A `capacity` error includes the in-flight job ID; poll that job or wait for it to finish rather than submitting the same consult again.
|
|
116
|
+
|
|
117
|
+
When loaded as a Pi extension, Surf also registers a `surf-oracle` external-job provider when the runtime exposes that bridge. The provider maps `start`, `status`, `result`, `reattach`, and `follow` to durable Surf Oracle jobs. It honors `options.model` and `options.effort` for starts and follows, so `model: pro` selects ChatGPT GPT-5.6 Sol Pro web mode through the browser. It returns the conversation URL, requested and verified model and effort, prompt digest, result text, and failure details. `reattach` only harvests an existing job by ID; it never submits the prompt again.
|
|
118
|
+
|
|
119
|
+
When Surf is installed as a Pi package, it exposes an optional `gpt-pro` package agent for `pi-subagents`. That profile uses `runner.type: external-job`, provider `surf-oracle`, and `options.model: pro`. Surf remains useful without Pi or `pi-subagents`.
|
|
105
120
|
|
|
106
121
|
Context comes from repeatable `--files` globs. Surf fails closed when a glob matches nothing or a matched file is unreadable, binary, or invalid UTF-8. It also blocks gitignored files and basenames matching `.env*`, `*.pem`, `*.key`, `id_rsa*`, `id_ed25519*`, `*.p12`, `*.pfx`, `credentials*`, or `secrets*`. Use `--allow-sensitive` only after intentionally reviewing those files; it overrides the block rather than redacting content. Context up to 60,000 evidence characters is inserted inline, while larger context becomes one private text attachment. The assembly manifest records each path, byte count, SHA-256, inline or bundle disposition, and deny-list outcome.
|
|
107
122
|
|
|
@@ -245,20 +260,27 @@ surf window.resize --id 123 --width 1920 --height 1080
|
|
|
245
260
|
surf window.resize --id 123 --state maximized # States: normal, minimized, maximized, fullscreen
|
|
246
261
|
```
|
|
247
262
|
|
|
248
|
-
**
|
|
263
|
+
**Concurrent agent sessions:**
|
|
264
|
+
|
|
249
265
|
```bash
|
|
250
|
-
#
|
|
251
|
-
|
|
252
|
-
surf
|
|
253
|
-
|
|
266
|
+
# Required first command rule for each independent agent shell
|
|
267
|
+
export SURF_SESSION="$(basename "$PWD" | sed 's/[^A-Za-z0-9._-]/-/g')"
|
|
268
|
+
surf session.ensure "$SURF_SESSION" about:blank
|
|
269
|
+
|
|
270
|
+
# Explicit form when an environment variable is inconvenient
|
|
271
|
+
surf --session research go "https://example.com"
|
|
272
|
+
surf --session research read
|
|
254
273
|
|
|
255
|
-
#
|
|
256
|
-
surf
|
|
257
|
-
surf
|
|
258
|
-
surf tab.switch agent-a
|
|
274
|
+
# Inspect bindings and scheduler state
|
|
275
|
+
surf session.list --refresh
|
|
276
|
+
surf session.info research --refresh
|
|
259
277
|
```
|
|
260
278
|
|
|
261
|
-
|
|
279
|
+
Each session owns one explicit tab and defaults to a separate unfocused window. Commands for the same tab are FIFO; different session tabs may run concurrently. Browser-wide writers wait for tab lanes to drain. `--no-wait` returns `tab_busy` or `browser_busy` immediately. On `tab_gone` or `session_epoch_stale`, run the exact command printed after `Recovery:`—normally `surf session.reopen <name>`.
|
|
280
|
+
|
|
281
|
+
Browser-login provider commands (`chatgpt`, `gemini`, `perplexity`, `grok`, `kimi`, `aistudio`, and `oracle ask`) take exclusive browser access and print a warning before dispatch. Do not assume Surf is hung while that warning is visible; inspect `surf session.info <name>` from another shell to see the active writer.
|
|
282
|
+
|
|
283
|
+
Sessions share cookies, authentication, same-origin storage, downloads, history, bookmarks, and other Chrome-profile state. Use separate browser/profile instances and `SURF_SOCKET` values only when hard isolation is required. Explicit `--tab-id`, `--window-id`, and named tabs remain available for one-off targeting.
|
|
262
284
|
|
|
263
285
|
## Input Methods
|
|
264
286
|
|
|
@@ -437,14 +459,16 @@ surf upload --ref e5 --files "/path/file1.txt,/path/file2.txt"
|
|
|
437
459
|
|
|
438
460
|
```bash
|
|
439
461
|
surf frame.list # List frames with IDs
|
|
440
|
-
surf frame.switch "
|
|
462
|
+
surf frame.switch --selector "#payment-iframe"
|
|
463
|
+
surf frame.switch --name "checkout"
|
|
464
|
+
surf frame.switch --index 0 # First iframe
|
|
441
465
|
surf frame.main # Return to main frame
|
|
442
|
-
surf frame.js
|
|
466
|
+
surf frame.js "return document.title" --id "FRAME_ID"
|
|
443
467
|
|
|
444
468
|
# After frame.switch, subsequent commands target that frame:
|
|
445
|
-
surf frame.switch "iframe
|
|
469
|
+
surf frame.switch --selector "#payment-iframe"
|
|
446
470
|
surf page.read # Reads iframe content
|
|
447
|
-
surf click
|
|
471
|
+
surf click --selector "#pay" # Clicks in iframe
|
|
448
472
|
surf frame.main # Back to main page
|
|
449
473
|
```
|
|
450
474
|
|
|
@@ -548,6 +572,9 @@ surf do 'go "https://example.com" | click e5 | screenshot'
|
|
|
548
572
|
# Multi-step login flow
|
|
549
573
|
surf do 'go "https://example.com/login" | type "user@example.com" --selector "#email" | type "pass" --selector "#password" | click --selector "button[type=submit]"'
|
|
550
574
|
|
|
575
|
+
# JSON action batch. Uses SURF_SESSION when it is set.
|
|
576
|
+
surf batch --actions '[{"type":"frame.switch","index":0},{"type":"click","selector":"#pay"}]'
|
|
577
|
+
|
|
551
578
|
# Validate without executing
|
|
552
579
|
surf do 'go "url" | click e5' --dry-run
|
|
553
580
|
```
|
|
@@ -681,9 +708,11 @@ surf wait.element ".missing" --auto-capture --timeout 2000
|
|
|
681
708
|
## Common Options
|
|
682
709
|
|
|
683
710
|
```bash
|
|
684
|
-
--
|
|
685
|
-
--
|
|
686
|
-
--
|
|
711
|
+
--session <name> # Target a durable named session (or set SURF_SESSION)
|
|
712
|
+
--tab-id <id> # Target a specific tab
|
|
713
|
+
--window-id <id> # Target a specific window
|
|
714
|
+
--no-wait # Return tab_busy/browser_busy instead of queueing
|
|
715
|
+
--json # Raw JSON including target metadata
|
|
687
716
|
--auto-capture # Screenshot + console on error
|
|
688
717
|
--timeout <ms> # Override default timeout
|
|
689
718
|
```
|
|
@@ -701,12 +730,12 @@ surf wait.element ".missing" --auto-capture --timeout 2000
|
|
|
701
730
|
9. **AI Studio for unrestricted Gemini** - `surf aistudio` gives less filtered responses than `surf gemini` for the same models
|
|
702
731
|
10. **Use `surf do` for multi-step tasks** - Reduces token overhead and improves reliability
|
|
703
732
|
11. **Dry-run workflows first** - `surf do '...' --dry-run` validates without executing
|
|
704
|
-
12. **
|
|
705
|
-
13. **
|
|
733
|
+
12. **Session first** - Set a unique `SURF_SESSION` and run `session.ensure` before the first browser command in every independent agent shell
|
|
734
|
+
13. **Queue diagnostics** - `session.info` distinguishes the session's own tab queue, other active tabs, and browser-wide writers; use `--no-wait` for immediate busy errors
|
|
706
735
|
14. **Native host diagnostics** - If commands fail with socket/native-host errors, run `surf doctor` or `surf doctor --browser all` before guessing at reinstall steps
|
|
707
736
|
15. **HTML export** - Use `surf page.html > artifact.html` to save Claude artifacts or any rendered page as static HTML
|
|
708
737
|
16. **Animation capture** - Use `surf record --duration 2000 --fps 10 --output /tmp/anim.gif` when the agent needs to see motion; use `animate-audit` for numeric timelines and `perf-audit` for jank/layout-shift snapshots
|
|
709
|
-
17. **Hard isolation** -
|
|
738
|
+
17. **Hard isolation** - Sessions share a Chrome profile; use separate browser/profile instances plus separate `SURF_SOCKET` values when profile state must not be shared
|
|
710
739
|
18. **Semantic locators** - `locate.role`, `locate.text`, `locate.label` for more robust element finding
|
|
711
740
|
19. **Frame context** - Use `frame.switch` before interacting with iframe content
|
|
712
741
|
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const J=new Set(["alert","alertdialog","application","article","banner","blockquote","button","caption","cell","checkbox","code","columnheader","combobox","complementary","contentinfo","definition","deletion","dialog","directory","document","emphasis","feed","figure","form","generic","grid","gridcell","group","heading","img","insertion","link","list","listbox","listitem","log","main","mark","marquee","math","menu","menubar","menuitem","menuitemcheckbox","menuitemradio","meter","navigation","none","note","option","paragraph","presentation","progressbar","radio","radiogroup","region","row","rowgroup","rowheader","scrollbar","search","searchbox","separator","slider","spinbutton","status","strong","subscript","superscript","switch","tab","table","tablist","tabpanel","term","textbox","time","timer","toolbar","tooltip","tree","treegrid","treeitem"]);function Q(i){const g=i.tagName.toLowerCase();if(["button","input","select","textarea"].includes(g))return!i.disabled;if(g==="a"&&i.hasAttribute("href"))return!0;if(i.hasAttribute("tabindex")){const r=parseInt(i.getAttribute("tabindex")||"",10);return!isNaN(r)&&r>=0}return i.getAttribute("contenteditable")==="true"}function Z(i){const g=i.getAttribute("role");if(!g)return null;const r=g.split(/\s+/).filter(e=>e);for(const e of r)if(J.has(e))return e;return null}function X(i){const g=i.tagName.toLowerCase(),r=i.getAttribute("type"),e={a:o=>o.hasAttribute("href")?"link":"generic",article:"article",aside:"complementary",button:"button",datalist:"listbox",dd:"definition",details:"group",dialog:"dialog",dt:"term",fieldset:"group",figure:"figure",footer:o=>o.closest("article, aside, main, nav, section")?"generic":"contentinfo",form:o=>o.hasAttribute("aria-label")||o.hasAttribute("aria-labelledby")?"form":"generic",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",header:o=>o.closest("article, aside, main, nav, section")?"generic":"banner",hr:"separator",img:o=>o.getAttribute("alt")===""?"presentation":"img",li:"listitem",main:"main",math:"math",menu:"list",meter:"meter",nav:"navigation",ol:"list",optgroup:"group",option:"option",output:"status",p:"paragraph",progress:"progressbar",search:"search",section:o=>o.hasAttribute("aria-label")||o.hasAttribute("aria-labelledby")?"region":"generic",select:o=>{const t=o;return t.hasAttribute("multiple")||t.size&&t.size>1?"listbox":"combobox"},table:"table",tbody:"rowgroup",td:"cell",textarea:"textbox",tfoot:"rowgroup",th:"columnheader",thead:"rowgroup",time:"time",tr:"row",ul:"list"};if(g==="input")return{button:"button",checkbox:"checkbox",email:"textbox",file:"button",image:"button",number:"spinbutton",radio:"radio",range:"slider",reset:"button",search:"searchbox",submit:"button",tel:"textbox",text:"textbox",url:"textbox"}[r||""]||"textbox";const c=e[g];return typeof c=="function"?c(i):c||"generic"}function U(i){const g=Z(i);return!g||(g==="none"||g==="presentation")&&Q(i)?X(i):g}window.__piElementMap||(window.__piElementMap={});let ee=0;function q(i,g,r){const e=i._piRef;if(e&&e.role===g&&e.name===r)return e.ref;const c=`e${++ee}`;return i._piRef={role:g,name:r,ref:c},c}function Y(){const i=[];return document.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog[open]').forEach(r=>{const e=window.getComputedStyle(r);if(!(e.display!=="none"&&e.visibility!=="hidden"&&e.opacity!=="0"&&r.offsetWidth>0&&r.offsetHeight>0))return;const o=r.getAttribute("role")||"dialog";let t=r.getAttribute("aria-label")||r.querySelector('[role="heading"], h1, h2, h3')?.textContent?.trim()||"Dialog";t.length>100&&(t=t.substring(0,100)+"..."),i.push({type:o,description:`${o}: ${t}`,clearedBy:"computer(action=key, text=Escape)"})}),i}const j={wait(i){return new Promise(g=>setTimeout(g,i))},async waitForSelector(i,g={}){const{state:r="visible",timeout:e=2e4}=g,c=t=>{if(!t)return!1;const l=window.getComputedStyle(t);return l.display!=="none"&&l.visibility!=="hidden"&&l.opacity!=="0"&&t.offsetWidth>0&&t.offsetHeight>0},o=()=>{const t=document.querySelector(i);switch(r){case"attached":return t;case"detached":return t?null:document.body;case"hidden":return t?c(t)?null:t:document.body;default:return c(t)?t:null}};return new Promise((t,l)=>{const u=o();if(u){t(r==="detached"||r==="hidden"?null:u);return}const d=new MutationObserver(()=>{const a=o();a&&(d.disconnect(),clearTimeout(w),t(r==="detached"||r==="hidden"?null:a))}),w=setTimeout(()=>{d.disconnect(),l(new Error(`Timeout waiting for "${i}" to be ${r}`))},e);d.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["style","class","hidden"]})})},async waitForText(i,g={}){const{selector:r,timeout:e=2e4}=g,c=()=>{const o=r?document.querySelector(r):document.body;if(!o)return null;const t=document.createTreeWalker(o,NodeFilter.SHOW_TEXT);for(;t.nextNode();)if(t.currentNode.textContent?.includes(i))return t.currentNode.parentElement;return null};return new Promise((o,t)=>{const l=c();if(l){o(l);return}const u=new MutationObserver(()=>{const w=c();w&&(u.disconnect(),clearTimeout(d),o(w))}),d=setTimeout(()=>{u.disconnect(),t(new Error(`Timeout waiting for text "${i}"`))},e);u.observe(document.documentElement,{childList:!0,subtree:!0,characterData:!0})})},async waitForHidden(i,g=2e4){await j.waitForSelector(i,{state:"hidden",timeout:g})},getByRole(i,g={}){const{name:r}=g,e={button:["button",'input[type="button"]','input[type="submit"]','input[type="reset"]'],link:["a[href]"],textbox:["input:not([type])",'input[type="text"]','input[type="email"]','input[type="password"]','input[type="search"]','input[type="tel"]','input[type="url"]',"textarea"],checkbox:['input[type="checkbox"]'],radio:['input[type="radio"]'],combobox:["select"],heading:["h1","h2","h3","h4","h5","h6"],list:["ul","ol"],listitem:["li"],navigation:["nav"],main:["main"],banner:["header"],contentinfo:["footer"],form:["form"],img:["img"],table:["table"]},c=[];c.push(...document.querySelectorAll(`[role="${i}"]`));const o=e[i];if(o)for(const l of o)c.push(...document.querySelectorAll(`${l}:not([role])`));if(!r)return c[0]||null;const t=r.toLowerCase().trim();for(const l of c){const u=l.getAttribute("aria-label")?.toLowerCase().trim(),d=l.textContent?.toLowerCase().trim(),w=l.getAttribute("title")?.toLowerCase().trim(),a=l.getAttribute("placeholder")?.toLowerCase().trim();if(u===t||d===t||w===t||a===t||u?.includes(t)||d?.includes(t))return l}return null}};window.__piHelpers||(window.__piHelpers=j,window.piHelpers=j);function I(){return window.__piElementMap}function B(i="interactive",g=15,r,e=!1,c=!1){try{let o=function(n){return U(n)},t=function(n){const h=n.tagName.toLowerCase(),_=n.getAttribute("aria-labelledby");if(_){const y=_.split(/\s+/).map(E=>document.getElementById(E)?.textContent?.trim()||"").filter(Boolean);if(y.length){const E=y.join(" ");return E.length>100?E.substring(0,100)+"...":E}}if(h==="select"){const y=n,E=y.querySelector("option[selected]")||(y.selectedIndex>=0?y.options[y.selectedIndex]:null);if(E?.textContent?.trim())return E.textContent.trim()}const L=n.getAttribute("aria-label");if(L?.trim())return L.trim();const N=n.getAttribute("placeholder");if(N?.trim())return N.trim();const D=n.getAttribute("title");if(D?.trim())return D.trim();const k=n.getAttribute("alt");if(k?.trim())return k.trim();if(n.id){const y=document.querySelector(`label[for="${n.id}"]`);if(y?.textContent?.trim())return y.textContent.trim()}if(h==="input"){const y=n,E=n.getAttribute("type")||"",H=n.getAttribute("value");if(E==="submit"&&H?.trim())return H.trim();if(y.value&&y.value.length<50&&y.value.trim())return y.value.trim()}if(["button","a","summary"].includes(h)){let y="";for(const E of n.childNodes)E.nodeType===Node.TEXT_NODE&&(y+=E.textContent);if(y.trim())return y.trim()}if(/^h[1-6]$/.test(h)){const y=n.textContent;if(y?.trim()){const E=y.trim();return E.length>100?E.substring(0,100)+"...":E}}if(h==="img")return"";let S="";for(const y of n.childNodes)y.nodeType===Node.TEXT_NODE&&(S+=y.textContent);if(S?.trim()&&S.trim().length>=3){const y=S.trim();return y.length>100?y.substring(0,100)+"...":y}return""},l=function(n){const h={},_=n.getAttribute("aria-checked");_==="true"?h.checked=!0:_==="false"?h.checked=!1:_==="mixed"?h.checked="mixed":n instanceof HTMLInputElement&&(n.type==="checkbox"||n.type==="radio")&&(n.type==="checkbox"&&n.indeterminate?h.checked="mixed":h.checked=n.checked);const L=n instanceof HTMLButtonElement||n instanceof HTMLInputElement||n instanceof HTMLSelectElement||n instanceof HTMLTextAreaElement;(n.getAttribute("aria-disabled")==="true"||L&&n.disabled||n.closest("fieldset:disabled"))&&(h.disabled=!0);const N=n.getAttribute("aria-expanded");N==="true"?h.expanded=!0:N==="false"&&(h.expanded=!1);const D=n.getAttribute("aria-pressed");D==="true"?h.pressed=!0:D==="false"?h.pressed=!1:D==="mixed"&&(h.pressed="mixed");const k=n.getAttribute("aria-selected");k==="true"?h.selected=!0:k==="false"&&(h.selected=!1);const S=n.getAttribute("aria-current");S&&S!=="false"&&(h.active=!0);const y=n.tagName.toLowerCase();if(/^h[1-6]$/.test(y))h.level=parseInt(y[1],10);else{const E=n.getAttribute("aria-level");E&&(h.level=parseInt(E,10))}return h},u=function(n){const h=[];return n.checked!==void 0&&h.push(n.checked==="mixed"?"[checked=mixed]":n.checked?"[checked]":"[unchecked]"),n.disabled&&h.push("[disabled]"),n.expanded!==void 0&&h.push(n.expanded?"[expanded]":"[collapsed]"),n.pressed!==void 0&&h.push(n.pressed==="mixed"?"[pressed=mixed]":n.pressed?"[pressed]":"[not-pressed]"),n.selected!==void 0&&h.push(n.selected?"[selected]":"[not-selected]"),n.active&&h.push("[active]"),n.level!==void 0&&h.push(`[level=${n.level}]`),h.join(" ")},d=function(n){const h=window.getComputedStyle(n);return h.display!=="none"&&h.visibility!=="hidden"&&h.opacity!=="0"&&n.offsetWidth>0&&n.offsetHeight>0},w=function(n){const h=n.tagName.toLowerCase();return["a","button","input","select","textarea","details","summary"].includes(h)||n.hasAttribute("onclick")||n.hasAttribute("tabindex")||n.getAttribute("role")==="button"||n.getAttribute("role")==="link"||n.getAttribute("contenteditable")==="true"},a=function(n){const h=n.tagName.toLowerCase();return["h1","h2","h3","h4","h5","h6","nav","main","header","footer","section","article","aside"].includes(h)||n.hasAttribute("role")},f=function(n){return window.getComputedStyle(n).cursor==="pointer"},m=function(n,h){const _=n.tagName.toLowerCase();if(["script","style","meta","link","title","noscript"].includes(_)||h.filter!=="all"&&n.getAttribute("aria-hidden")==="true"||h.filter!=="all"&&!d(n))return!1;if(h.filter!=="all"&&!h.refId){const N=n.getBoundingClientRect();if(!(N.top<window.innerHeight&&N.bottom>0&&N.left<window.innerWidth&&N.right>0))return!1}if(h.filter==="interactive")return w(n);if(w(n)||a(n)||t(n).length>0)return!0;const L=o(n);return h.compact&&new Set(["generic","group","region","article","section","complementary"]).has(L)&&t(n).length===0?!1:L!=="generic"&&L!=="img"},b=function(n,h){const _=[],L={filter:i,refId:r||null,compact:c},N=I(),D=m(n,L)||r&&h===0;if(D){const k=o(n),S=t(n),y=l(n),E=q(n,k,S);window.__piRefs[E]=n,N[E]={element:new WeakRef(n),role:k,name:S};let W=`${" ".repeat(h)}${k}`;if(S){const K=S.replace(/\s+/g," ").replace(/"/g,'\\"');W+=` "${K}"`}W+=` [${E}]`;const R=u(y);R&&(W+=` ${R}`),f(n)&&(W+=" [cursor=pointer]");const P=n.getAttribute("href");P&&(W+=` href="${P}"`);const G=n.getAttribute("type");G&&(W+=` type="${G}"`);const V=n.getAttribute("placeholder");V&&(W+=` placeholder="${V}"`),_.push(W)}if(h<g)for(const k of n.children)_.push(...b(k,D?h+1:h));return _},s=function(n){return n.replace(/\[e\d+\]/g,"[REF]")},p=function(n){const h=new Map;for(const _ of n){if(!_.trim())continue;const L=s(_);h.set(L,(h.get(L)||0)+1)}return h},T=function(n,h){const _=n.split(`
|
|
2
|
-
`),L=h.split(`
|
|
3
|
-
`),N=p(_),D=p(L),k=[],S=[];for(const H of L){if(!H.trim())continue;const W=s(H),R=N.get(W)||0;(D.get(W)||0)>R&&(k.push(H),N.set(W,R+1))}const y=p(_);for(const H of _){if(!H.trim())continue;const W=s(H),R=y.get(W)||0,P=D.get(W)||0;R>P&&(S.push(H),y.set(W,R-1))}if(k.length===0&&S.length===0)return{diff:"[NO CHANGES]",hasChanges:!1};const E=[];return S.length>0&&E.push(...S.map(H=>`- ${H}`)),k.length>0&&E.push(...k.map(H=>`+ ${H}`)),{diff:E.join(`
|
|
4
|
-
`),hasChanges:!0}};window.__piRefs={};const A=I();let C=null;if(r){const n=A[r];if(!n)return{error:`Element with ref_id '${r}' not found. Use read_page without ref_id to get current elements.`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}};const h=n.element.deref();if(!h)return delete A[r],{error:`Element with ref_id '${r}' no longer exists. Use read_page without ref_id to get current elements.`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}};C=h}else C=document.body;const O=C?b(C,0):[];for(const n of Object.keys(A))A[n].element.deref()||delete A[n];const $=O.join(`
|
|
5
|
-
`);if($.length>5e4)return{error:`Output exceeds 50000 character limit (${$.length} characters). Try using filter="interactive" or specify a ref_id.`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}};const M=Y();let x,v=!1;const F=window.__piLastSnapshot;return!e&&!r&&F&&Date.now()-F.timestamp<5e3&&(x=T(F.content,$).diff,v=!0),window.__piLastSnapshot={content:$,timestamp:Date.now()},{pageContent:$+`
|
|
6
|
-
|
|
7
|
-
[Viewport: ${window.innerWidth}x${window.innerHeight}]`,diff:v?x:void 0,viewport:{width:window.innerWidth,height:window.innerHeight},modalStates:M.length>0?M:void 0,modalLimitations:"Only custom modals ([role=dialog]) detected. Native alert/confirm/prompt dialogs and system file choosers cannot be detected from content scripts.",isIncremental:v}}catch(o){return{error:`Error generating accessibility tree: ${o instanceof Error?o.message:"Unknown error"}`,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}}}}function z(i){return i.length?/[\n\r]/.test(i)||/^[\s]/.test(i)||/[\s]$/.test(i)||/[:"{}[\]]/.test(i)?'"'+i.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")+'"':i:'""'}function te(i="interactive",g=15){try{let r=function(s){return U(s)},e=function(s){const p=s.tagName.toLowerCase(),T=s.getAttribute("aria-labelledby");if(T){const x=T.split(/\s+/).map(v=>document.getElementById(v)?.textContent?.trim()||"").filter(Boolean);if(x.length){const v=x.join(" ");return v.length>100?v.substring(0,100)+"...":v}}if(p==="select"){const x=s,v=x.querySelector("option[selected]")||(x.selectedIndex>=0?x.options[x.selectedIndex]:null);if(v?.textContent?.trim())return v.textContent.trim()}const A=s.getAttribute("aria-label");if(A?.trim())return A.trim();const C=s.getAttribute("placeholder");if(C?.trim())return C.trim();const O=s.getAttribute("title");if(O?.trim())return O.trim();const $=s.getAttribute("alt");if($?.trim())return $.trim();if(s.id){const x=document.querySelector(`label[for="${s.id}"]`);if(x?.textContent?.trim())return x.textContent.trim()}if(p==="input"){const x=s,v=s.getAttribute("type")||"",F=s.getAttribute("value");if(v==="submit"&&F?.trim())return F.trim();if(x.value&&x.value.length<50&&x.value.trim())return x.value.trim()}if(["button","a","summary"].includes(p)){let x="";for(const v of s.childNodes)v.nodeType===Node.TEXT_NODE&&(x+=v.textContent);if(x.trim())return x.trim()}if(/^h[1-6]$/.test(p)){const x=s.textContent;if(x?.trim()){const v=x.trim();return v.length>100?v.substring(0,100)+"...":v}}if(p==="img")return"";let M="";for(const x of s.childNodes)x.nodeType===Node.TEXT_NODE&&(M+=x.textContent);if(M?.trim()&&M.trim().length>=3){const x=M.trim();return x.length>100?x.substring(0,100)+"...":x}return""},c=function(s){const p={},T=s.getAttribute("aria-checked");T==="true"?p.checked=!0:T==="false"?p.checked=!1:T==="mixed"?p.checked="mixed":s instanceof HTMLInputElement&&(s.type==="checkbox"||s.type==="radio")&&(s.type==="checkbox"&&s.indeterminate?p.checked="mixed":p.checked=s.checked);const A=s instanceof HTMLButtonElement||s instanceof HTMLInputElement||s instanceof HTMLSelectElement||s instanceof HTMLTextAreaElement;(s.getAttribute("aria-disabled")==="true"||A&&s.disabled||s.closest("fieldset:disabled"))&&(p.disabled=!0);const C=s.getAttribute("aria-expanded");C==="true"?p.expanded=!0:C==="false"&&(p.expanded=!1);const O=s.getAttribute("aria-pressed");O==="true"?p.pressed=!0:O==="false"?p.pressed=!1:O==="mixed"&&(p.pressed="mixed");const $=s.getAttribute("aria-selected");$==="true"?p.selected=!0:$==="false"&&(p.selected=!1);const M=s.getAttribute("aria-current");M&&M!=="false"&&(p.active=!0);const x=s.tagName.toLowerCase();if(/^h[1-6]$/.test(x))p.level=parseInt(x[1],10);else{const v=s.getAttribute("aria-level");v&&(p.level=parseInt(v,10))}return p},o=function(s){const p=[];return s.checked!==void 0&&p.push(s.checked==="mixed"?"[checked=mixed]":s.checked?"[checked]":"[unchecked]"),s.disabled&&p.push("[disabled]"),s.expanded!==void 0&&p.push(s.expanded?"[expanded]":"[collapsed]"),s.pressed!==void 0&&p.push(s.pressed==="mixed"?"[pressed=mixed]":s.pressed?"[pressed]":"[not-pressed]"),s.selected!==void 0&&p.push(s.selected?"[selected]":"[not-selected]"),s.active&&p.push("[active]"),s.level!==void 0&&p.push(`[level=${s.level}]`),p.join(" ")},t=function(s){const p=window.getComputedStyle(s);return p.display!=="none"&&p.visibility!=="hidden"&&p.opacity!=="0"&&s.offsetWidth>0&&s.offsetHeight>0},l=function(s){const p=s.tagName.toLowerCase();return["a","button","input","select","textarea","details","summary"].includes(p)||s.hasAttribute("onclick")||s.hasAttribute("tabindex")||s.getAttribute("role")==="button"||s.getAttribute("role")==="link"||s.getAttribute("contenteditable")==="true"},u=function(s){const p=s.tagName.toLowerCase();return["h1","h2","h3","h4","h5","h6","nav","main","header","footer","section","article","aside"].includes(p)||s.hasAttribute("role")},d=function(s){return window.getComputedStyle(s).cursor==="pointer"},w=function(s,p,T,A){let C=s;p&&(C+=" "+z(p));const O=q(T,s,p);window.__piRefs[O]=T,C+=` [ref=${O}]`;const $=o(A);return $&&(C+=` ${$}`),d(T)&&(C+=" [cursor=pointer]"),C},a=function(s){const p={},T=s.getAttribute("href");T&&(p.url=T);const A=s.getAttribute("placeholder");return A&&(p.placeholder=A),p},f=function(s,p,T){if(p>g)return;const A=s.tagName.toLowerCase();if(["script","style","meta","link","title","noscript"].includes(A)||i!=="all"&&s.getAttribute("aria-hidden")==="true"||i!=="all"&&!t(s))return;if(i!=="all"){const n=s.getBoundingClientRect();if(!(n.top<window.innerHeight&&n.bottom>0&&n.left<window.innerWidth&&n.right>0))return}const C=r(s),O=e(s),$=c(s),M=l(s),x=u(s),v=O.length>0;let F;if(i==="interactive"?F=M:i==="all"?F=!0:F=M||x||v||C!=="generic"&&C!=="img",F){const n=" ".repeat(p),h=w(C,O,s,$),_=a(s),L=[];for(const k of s.children)L.push(k);const N=L.length>0,D=Object.keys(_).length>0;if(!N&&!D)m.push(`${n}- ${h}`);else{m.push(`${n}- ${h}:`);for(const[k,S]of Object.entries(_))m.push(`${n} - /${k}: ${z(S)}`);for(const k of L)f(k,p+1,!0)}}else for(const n of s.children)f(n,p,T)};window.__piRefs={};const m=[];f(document.body,0,!1);const b=m.join(`
|
|
8
|
-
`);return b.length>5e4?{error:`Output exceeds 50000 character limit (${b.length} characters). Try using filter="interactive".`,yaml:"",viewport:{width:window.innerWidth,height:window.innerHeight}}:{yaml:b+`
|
|
9
|
-
|
|
10
|
-
[Viewport: ${window.innerWidth}x${window.innerHeight}]`,viewport:{width:window.innerWidth,height:window.innerHeight}}}catch(r){return{error:`Error generating YAML tree: ${r instanceof Error?r.message:"Unknown error"}`,yaml:"",viewport:{width:window.innerWidth,height:window.innerHeight}}}}function re(i){const g=I(),r=g[i];let e;if(r&&(e=r.element.deref(),e||delete g[i]),!e&&window.__piRefs&&(e=window.__piRefs[i]),!e)return{x:0,y:0,error:`Element ${i} not found. Use read_page to get current elements.`};const c=e.getBoundingClientRect(),o=Math.round(c.left+c.width/2),t=Math.round(c.top+c.height/2);return{x:o,y:t}}function ne(i,g){const r=I(),e=r[i];let c;if(e&&(c=e.element.deref(),c||delete r[i]),!c&&window.__piRefs&&(c=window.__piRefs[i]),!c)return{success:!1,error:`Element ${i} not found. Use read_page to get current elements.`};const o=c.tagName.toLowerCase();try{if(o==="input"){const t=c,l=t.type.toLowerCase();l==="checkbox"||l==="radio"?(t.checked=!!g,t.dispatchEvent(new Event("change",{bubbles:!0}))):(t.value=String(g),t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0})))}else if(o==="textarea"){const t=c;t.value=String(g),t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0}))}else if(o==="select"){const t=c,l=String(g);let u=!1;for(const d of t.options)if(d.value===l||d.textContent?.trim()===l){t.value=d.value,u=!0;break}if(!u)return{success:!1,error:`Option "${g}" not found in select element ${i}`};t.dispatchEvent(new Event("change",{bubbles:!0}))}else if(c.getAttribute("contenteditable")==="true")c.textContent=String(g),c.dispatchEvent(new Event("input",{bubbles:!0}));else return{success:!1,error:`Element ${i} (${o}) is not a form field`};return{success:!0}}catch(t){return{success:!1,error:`Failed to set value: ${t instanceof Error?t.message:"Unknown error"}`}}}function ie(){try{const i=document.querySelector("article"),g=document.querySelector("main");return{text:(i||g||document.body).textContent?.replace(/\s+/g," ").trim().substring(0,5e4)||"",title:document.title,url:window.location.href}}catch(i){return{text:"",title:"",url:"",error:`Failed to extract text: ${i instanceof Error?i.message:"Unknown error"}`}}}function oe(i){const g=I(),r=g[i];let e;return r&&(e=r.element.deref(),e||delete g[i]),!e&&window.__piRefs&&(e=window.__piRefs[i]),e?(e.scrollIntoView({behavior:"smooth",block:"center"}),{success:!0}):{success:!1,error:`Element ${i} not found. Run read_page to get current element refs.`}}function se(i,g,r,e="screenshot.png"){try{const c=atob(i),o=new ArrayBuffer(c.length),t=new Uint8Array(o);for(let f=0;f<c.length;f++)t[f]=c.charCodeAt(f);const l=new Blob([o],{type:"image/png"}),u=new File([l],e,{type:"image/png"});let d=null;if(g){const f=I(),m=f[g];if(m&&(d=m.element.deref(),d||delete f[g]),!d&&window.__piRefs&&(d=window.__piRefs[g]),!d)return{success:!1,error:`Element ${g} not found. Run read_page to get current element refs.`}}else if(r&&(d=document.elementFromPoint(r[0],r[1]),!d))return{success:!1,error:`No element at (${r[0]}, ${r[1]})`};if(!d)return{success:!1,error:"No target element"};if(d.tagName==="INPUT"&&d.type==="file"){const f=d,m=new DataTransfer;return m.items.add(u),f.files=m.files,f.dispatchEvent(new Event("change",{bubbles:!0})),{success:!0}}const w=new DataTransfer;w.items.add(u);const a=new DragEvent("drop",{bubbles:!0,cancelable:!0,dataTransfer:w});return d.dispatchEvent(a),{success:!0}}catch(c){return{success:!1,error:c instanceof Error?c.message:"Upload failed"}}}chrome.runtime.onMessage.addListener((i,g,r)=>{switch(i.type){case"GENERATE_ACCESSIBILITY_TREE":{const e=i.options||{};if(e.format==="yaml"){const c=te(e.filter||"interactive",e.depth??15),o=Y();c.error?r({error:c.error,pageContent:"",viewport:c.viewport}):r({pageContent:c.yaml,viewport:c.viewport,modalStates:o.length>0?o:void 0,modalLimitations:"Only custom modals ([role=dialog]) detected. Native alert/confirm/prompt dialogs and system file choosers cannot be detected from content scripts."})}else{const c=B(e.filter||"interactive",e.depth??15,e.refId,e.forceFullSnapshot??!1,e.compact??!1);r(c)}break}case"GET_ELEMENT_COORDINATES":{const e=re(i.ref);r(e);break}case"CLICK_ELEMENT":{const e=I(),c=e[i.ref];let o;if(c&&(o=c.element.deref(),o||delete e[i.ref]),!o&&window.__piRefs&&(o=window.__piRefs[i.ref]),!o){r({error:`Element ${i.ref} not found. Use read_page to get current elements.`});break}if(i.button==="triple"){const t=new MouseEvent("click",{bubbles:!0,cancelable:!0,view:window,detail:3});o.dispatchEvent(t)}else i.button==="double"?o.dispatchEvent(new MouseEvent("dblclick",{bubbles:!0,cancelable:!0,view:window})):i.button==="right"?o.dispatchEvent(new MouseEvent("contextmenu",{bubbles:!0,cancelable:!0,view:window})):o.click();r({success:!0});break}case"FORM_INPUT":{const e=ne(i.ref,i.value);r(e);break}case"EVAL_IN_PAGE":{try{const e=document.createElement("script");e.textContent=`(function() { ${i.code} })();`,document.documentElement.appendChild(e),e.remove(),r({success:!0})}catch(e){r({success:!1,error:e instanceof Error?e.message:String(e)})}break}case"GET_PAGE_TEXT":{const e=ie();r(e);break}case"GET_FRAME_BY_SELECTOR":{try{const e=document.querySelector(i.selector);if(!e||e.tagName.toLowerCase()!=="iframe"){r({error:`No iframe found with selector "${i.selector}"`});break}r({url:e.src,name:e.name||void 0})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"GET_FRAME_NAME":{try{r({name:window.name||null})}catch{r({name:null})}break}case"LOCATE_ROLE":{try{const{role:e,name:c,all:o}=i,t=I(),u={button:["button",'input[type="button"]','input[type="submit"]','input[type="reset"]','[role="button"]'],link:["a[href]",'[role="link"]'],textbox:["input:not([type])",'input[type="text"]','input[type="email"]','input[type="password"]','input[type="search"]','input[type="tel"]','input[type="url"]',"textarea",'[role="textbox"]'],checkbox:['input[type="checkbox"]','[role="checkbox"]'],radio:['input[type="radio"]','[role="radio"]'],combobox:["select",'[role="combobox"]'],listbox:['[role="listbox"]',"select[multiple]"],option:["option",'[role="option"]'],heading:["h1","h2","h3","h4","h5","h6",'[role="heading"]'],navigation:["nav",'[role="navigation"]'],main:["main",'[role="main"]'],img:["img[alt]",'[role="img"]'],dialog:["dialog",'[role="dialog"]','[role="alertdialog"]'],tab:['[role="tab"]'],tabpanel:['[role="tabpanel"]'],menu:['[role="menu"]'],menuitem:['[role="menuitem"]']}[e]||[`[role="${e}"]`],d=[];for(const m of u)try{d.push(...document.querySelectorAll(m))}catch{}const w=d.filter(m=>{const b=window.getComputedStyle(m);return b.display!=="none"&&b.visibility!=="hidden"&&m.offsetWidth>0&&m.offsetHeight>0});let a=w;if(c){const m=c.toLowerCase();a=w.filter(b=>{const s=b.getAttribute("aria-label")?.toLowerCase(),p=b.textContent?.trim().toLowerCase(),T=b.getAttribute("title")?.toLowerCase(),A=b.placeholder?.toLowerCase(),C=b.value?.toLowerCase();return s?.includes(m)||p?.includes(m)||T?.includes(m)||A?.includes(m)||C?.includes(m)})}if(a.length===0){r({error:`No element found with role "${e}"${c?` and name "${c}"`:""}`});break}const f=a.map(m=>{const b=q(m,e,c||"");return window.__piRefs=window.__piRefs||{},window.__piRefs[b]=m,t[b]={element:new WeakRef(m),role:e,name:c||""},{ref:b,text:m.textContent?.trim().slice(0,50)}});r(o?{matches:f}:{ref:f[0].ref,text:f[0].text})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"LOCATE_TEXT":{try{const{text:e,exact:c}=i,o=I(),t=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),l=[];for(;t.nextNode();){const a=t.currentNode.textContent||"";if(c?a.trim()===e:a.toLowerCase().includes(e.toLowerCase())){const m=t.currentNode.parentElement;if(m&&!l.includes(m)){const b=window.getComputedStyle(m);b.display!=="none"&&b.visibility!=="hidden"&&l.push(m)}}}if(l.length===0){r({error:`No element found with text "${e}"`});break}const u=l.sort((a,f)=>(a.textContent?.length||0)-(f.textContent?.length||0))[0],d=U(u),w=q(u,d,e);window.__piRefs=window.__piRefs||{},window.__piRefs[w]=u,o[w]={element:new WeakRef(u),role:d,name:e},r({ref:w,text:u.textContent?.trim().slice(0,50)})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"LOCATE_LABEL":{try{const{label:e}=i,c=I(),o=document.querySelectorAll("label");let t=null;for(const d of o)if(d.textContent?.trim().toLowerCase()?.includes(e.toLowerCase())){const a=d.getAttribute("for");if(a&&(t=document.getElementById(a)),t||(t=d.querySelector("input, select, textarea")),t)break}if(!t){const d=e.toLowerCase();t=document.querySelector(`input[aria-label*="${e}" i], input[placeholder*="${e}" i], textarea[aria-label*="${e}" i], textarea[placeholder*="${e}" i], select[aria-label*="${e}" i]`)}if(!t){r({error:`No form field found with label "${e}"`});break}const l=U(t),u=q(t,l,e);window.__piRefs=window.__piRefs||{},window.__piRefs[u]=t,c[u]={element:new WeakRef(t),role:l,name:e},r({ref:u,label:e})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"GET_ELEMENT_STYLES":{try{const{selector:e}=i,c=I(),o=t=>{const l=getComputedStyle(t),u=t.getBoundingClientRect();return{tag:t.tagName.toLowerCase(),text:t.innerText?.trim().slice(0,80)||null,box:{x:Math.round(u.x),y:Math.round(u.y),width:Math.round(u.width),height:Math.round(u.height)},styles:{fontSize:l.fontSize,fontWeight:l.fontWeight,fontFamily:l.fontFamily.split(",")[0].trim().replace(/"/g,""),color:l.color,backgroundColor:l.backgroundColor,borderRadius:l.borderRadius,border:l.border!=="none"&&l.borderWidth!=="0px"?l.border:null,boxShadow:l.boxShadow!=="none"?l.boxShadow:null,padding:l.padding}}};if(/^e\d+$/.test(e)){const t=c[e];let l;if(t&&(l=t.element.deref(),l||delete c[e]),!l&&window.__piRefs&&(l=window.__piRefs[e]),!l){r({error:`Element ${e} not found`});break}r({styles:[o(l)]})}else{const t=document.querySelectorAll(e);if(t.length===0){r({error:`No elements found matching "${e}"`});break}const l=Array.from(t).map(o);r({styles:l})}}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"SELECT_OPTION":{try{const{selector:e,values:c,by:o}=i,t=I();let l=null;if(/^e\d+$/.test(e)){const a=t[e];let f;if(a&&(f=a.element.deref(),f||delete t[e]),!f&&window.__piRefs&&(f=window.__piRefs[e]),!f){r({error:`Element ${e} not found`});break}if(f.tagName!=="SELECT"){r({error:`Element ${e} is not a <select>`});break}l=f}else{if(l=document.querySelector(e),!l){r({error:`No element found matching "${e}"`});break}if(l.tagName!=="SELECT"){r({error:`Element "${e}" is not a <select>`});break}}if(l.multiple)for(const a of l.options)a.selected=!1;const u=[],d=[],w=l.multiple?c:[c[0]];for(const a of w){let f=!1;for(const m of l.options){let b=!1;if(o==="index"?b=m.index===parseInt(a,10):o==="label"?b=m.text.toLowerCase().includes(a.toLowerCase()):b=m.value===a,b){m.selected=!0,u.push(m.value),f=!0;break}}f||d.push(a)}l.dispatchEvent(new Event("change",{bubbles:!0})),d.length>0?r({selected:u,warning:`Values not found: ${d.join(", ")}`}):r({selected:u})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"GET_ELEMENT_TEXT":{try{const{ref:e}=i,c=I(),o=c[e];let t;if(o&&(t=o.element.deref(),t||delete c[e]),!t&&window.__piRefs&&(t=window.__piRefs[e]),!t){r({error:`Element ${e} not found`});break}r({text:t.textContent?.trim()||""})}catch(e){r({error:e instanceof Error?e.message:String(e)})}break}case"SCROLL_TO_ELEMENT":{const e=oe(i.ref);r(e);break}case"UPLOAD_IMAGE":{const e=se(i.base64,i.ref,i.coordinate,i.filename);r(e);break}case"WAIT_FOR_ELEMENT":{const{selector:e,state:c="visible",timeout:o=2e4}=i,t=Math.min(o,6e4),l=a=>{if(!a)return!1;const f=window.getComputedStyle(a);return f.display!=="none"&&f.visibility!=="hidden"&&f.opacity!=="0"&&a.offsetWidth>0&&a.offsetHeight>0},u=()=>{const a=document.querySelector(e);switch(c){case"attached":return!!a;case"detached":return!a;case"hidden":return!a||!l(a);default:return l(a)}},d=Date.now();return new Promise(a=>{if(u()){a({success:!0,waited:Date.now()-d});return}const f=new MutationObserver(()=>{u()&&(f.disconnect(),clearTimeout(m),a({success:!0,waited:Date.now()-d}))}),m=setTimeout(()=>{f.disconnect(),a({success:!1,waited:Date.now()-d,error:`Timeout waiting for "${e}" to be ${c}`})},t);f.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["style","class","hidden","disabled"]})}).then(a=>{if(!a.success){r({error:a.error,waited:a.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const f=B("interactive",15,void 0,!0);r({...f,waited:a.waited})}),!0}case"WAIT_FOR_URL":{const{pattern:e,timeout:c=2e4}=i,o=Math.min(c,6e4),t=d=>{if(e.includes("*")){const w=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"<<<GLOBSTAR>>>").replace(/\*/g,"[^/]*").replace(/<<<GLOBSTAR>>>/g,".*");return new RegExp(`^${w}$`).test(d)}return d.includes(e)},l=Date.now();return new Promise(d=>{if(t(window.location.href)){d({success:!0,waited:Date.now()-l});return}let w=!1;const a=()=>{w||t(window.location.href)&&(w=!0,clearInterval(f),clearTimeout(m),window.removeEventListener("popstate",a),window.removeEventListener("hashchange",a),d({success:!0,waited:Date.now()-l}))},f=setInterval(a,100),m=setTimeout(()=>{w||(w=!0,clearInterval(f),window.removeEventListener("popstate",a),window.removeEventListener("hashchange",a),d({success:!1,waited:Date.now()-l,error:`Timeout waiting for URL to match "${e}". Current: ${window.location.href}`}))},o);window.addEventListener("popstate",a),window.addEventListener("hashchange",a)}).then(d=>{if(!d.success){r({error:d.error,waited:d.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const w=B("interactive",15,void 0,!0);r({...w,waited:d.waited})}),!0}case"WAIT_FOR_DOM_STABLE":{const{stable:e=100,timeout:c=5e3}=i,o=Math.min(c,3e4),t=Date.now();return new Promise(u=>{let d=Date.now(),w=!1;const a=()=>{if(w)return;Date.now()-d>=e&&(w=!0,f.disconnect(),clearTimeout(m),clearInterval(b),u({success:!0,waited:Date.now()-t}))},f=new MutationObserver(()=>{d=Date.now()}),m=setTimeout(()=>{w||(w=!0,f.disconnect(),clearInterval(b),u({success:!1,waited:Date.now()-t,error:`Timeout: DOM did not stabilize within ${o}ms`}))},o),b=setInterval(a,Math.max(10,Math.min(50,e/2)));f.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),a()}).then(u=>{if(!u.success){r({error:u.error,waited:u.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const d=B("interactive",15,void 0,!0);r({...d,waited:u.waited})}),!0}case"FORM_FILL":{const{data:e}=i;if(!Array.isArray(e))return r({error:"data must be an array of {ref, value} pairs"}),!0;const c=I(),o=[];for(const l of e){const{ref:u,value:d}=l;if(!u){o.push({ref:u||"unknown",success:!1,error:"Missing ref"});continue}const w=c[u];if(!w){o.push({ref:u,success:!1,error:"Element not found (run page.read first)"});continue}const a=w.element.deref();if(!a){delete c[u],o.push({ref:u,success:!1,error:"Element no longer exists"});continue}try{if(a instanceof HTMLInputElement){const f=a.type.toLowerCase();if(f==="checkbox"||f==="radio"){const m=d===!0||d==="true"||d==="1"||d==="checked";a.checked=m,a.dispatchEvent(new Event("change",{bubbles:!0}))}else a.focus(),a.value=String(d),a.dispatchEvent(new Event("input",{bubbles:!0})),a.dispatchEvent(new Event("change",{bubbles:!0}));o.push({ref:u,success:!0})}else a instanceof HTMLTextAreaElement?(a.focus(),a.value=String(d),a.dispatchEvent(new Event("input",{bubbles:!0})),a.dispatchEvent(new Event("change",{bubbles:!0})),o.push({ref:u,success:!0})):a instanceof HTMLSelectElement?(a.value=String(d),a.dispatchEvent(new Event("change",{bubbles:!0})),o.push({ref:u,success:!0})):a.isContentEditable?(a.focus(),a.textContent=String(d),a.dispatchEvent(new Event("input",{bubbles:!0})),o.push({ref:u,success:!0})):o.push({ref:u,success:!1,error:"Element is not fillable"})}catch(f){o.push({ref:u,success:!1,error:f instanceof Error?f.message:String(f)})}}const t=o.filter(l=>!l.success);return r({success:t.length===0,filled:o.filter(l=>l.success).length,failed:t.length,results:o}),!0}case"GET_FILE_INPUT_SELECTOR":{const{ref:e}=i;if(!e)return r({error:"No ref provided"}),!0;const c=I(),o=c[e];if(!o)return r({error:"Element not found (run page.read first)"}),!0;const t=o.element.deref();if(!t)return delete c[e],r({error:"Element no longer exists"}),!0;if(!(t instanceof HTMLInputElement)||t.type!=="file")return r({error:"Element is not a file input"}),!0;const l=`__pi_file_${Date.now()}`;return t.setAttribute("data-pi-file-id",l),r({selector:`[data-pi-file-id="${l}"]`}),!0}case"WAIT_FOR_NETWORK_IDLE":{const{timeout:e=1e4}=i,c=Math.min(e,6e4),o=["doubleclick.net","googlesyndication.com","googletagmanager.com","google-analytics.com","facebook.net","connect.facebook.net","analytics","ads","tracking","pixel","hotjar.com","clarity.ms","mixpanel.com","segment.com","newrelic.com","nr-data.net","/tracker/","/collector/","/beacon/","/telemetry/","/log/","/events/","/track.","/metrics/"],t=["img","image","font","icon"],l=f=>o.some(m=>f.includes(m)),u=f=>{const m=f.initiatorType||"unknown";return!!(t.includes(m)||/\.(jpg|jpeg|png|gif|webp|svg|ico|woff|woff2|ttf|eot)(\?|$)/i.test(f.name))},d=()=>{const f=performance.now();return performance.getEntriesByType("resource").filter(b=>{if(b.responseEnd!==0||b.name.startsWith("data:")||b.name.length>500||l(b.name))return!1;const s=f-b.startTime;return!(s>1e4||u(b)&&s>3e3)})},w=Date.now();return new Promise(f=>{const m=()=>{const b=d(),s=Date.now()-w;if(b.length===0){f({success:!0,waited:s});return}if(s>=c){f({success:!1,waited:s,pendingCount:b.length});return}setTimeout(m,100)};m()}).then(f=>{if(!f.success){r({error:`Network not idle after ${f.waited}ms (${f.pendingCount} requests pending)`,waited:f.waited,pageContent:"",viewport:{width:window.innerWidth,height:window.innerHeight}});return}const m=B("interactive",15,void 0,!0);r({...m,waited:f.waited})}),!0}case"SEARCH_PAGE":{const{term:e,caseSensitive:c,limit:o}=i,t=ce(e,c||!1,o||10);r({query:e,count:t.length,matches:t});break}case"GET_ELEMENT_BOUNDS_FOR_ANNOTATION":{const e=I(),c=[];for(const[o,t]of Object.entries(e)){const l=t.element.deref();if(!l)continue;const u=l.getBoundingClientRect();u.width<=0||u.height<=0||u.bottom<0||u.top>window.innerHeight||u.right<0||u.left>window.innerWidth||c.push({ref:o,tag:l.tagName.toLowerCase(),bounds:{x:u.x,y:u.y,width:u.width,height:u.height}})}r({elements:c});break}default:return!1}return!1});function ce(i,g,r){const e=[],c=g?i:i.toLowerCase(),o=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),t=I();let l=0;for(;o.nextNode()&&e.length<r;){const u=o.currentNode,d=u.textContent||"",w=g?d:d.toLowerCase();let a=0;for(;(a=w.indexOf(c,a))!==-1&&e.length<r;){const f=u.parentElement;if(!f){a++;continue}const m=document.createRange();m.setStart(u,a),m.setEnd(u,Math.min(a+i.length,d.length));const b=m.getBoundingClientRect();if(b.width===0||b.height===0){a++;continue}const s=u.textContent||"",p=Math.max(0,a-30),T=Math.min(s.length,a+i.length+30),A=s.slice(p,T).trim();let C=null;for(const[O,$]of Object.entries(t)){const M=$.element.deref();if(M&&(M===f||M.contains(f))){C=O;break}}e.push({ref:`m${++l}`,text:s.slice(a,a+i.length),context:A,bounds:{x:Math.round(b.x),y:Math.round(b.y),width:Math.round(b.width),height:Math.round(b.height)},elementRef:C}),a++}}return e}
|
|
11
|
-
//# sourceMappingURL=accessibility-tree.js.map
|