pi-agent-browser-native 0.2.48 → 0.2.49

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.
Files changed (185) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +16 -6
  3. package/dist/extensions/agent-browser/index.js +785 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +448 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +816 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/RELEASE.md +22 -11
  95. package/docs/SUPPORT_MATRIX.md +4 -3
  96. package/package.json +9 -5
  97. package/scripts/config.mjs +8 -2
  98. package/scripts/doctor.mjs +8 -7
  99. package/extensions/agent-browser/index.ts +0 -961
  100. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  101. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  102. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  103. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  104. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  105. package/extensions/agent-browser/lib/config-policy.js +0 -690
  106. package/extensions/agent-browser/lib/config.ts +0 -211
  107. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  108. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  109. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  110. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  111. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  112. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  113. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  114. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  115. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  116. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  117. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  118. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  119. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  120. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  121. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  122. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  123. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  124. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  125. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  126. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  127. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  128. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  129. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  130. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  131. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  132. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  133. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  136. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  138. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  139. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  140. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  141. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  142. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  143. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  144. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  145. package/extensions/agent-browser/lib/playbook.ts +0 -142
  146. package/extensions/agent-browser/lib/process.ts +0 -516
  147. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  148. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  149. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  150. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  151. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  152. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  153. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  154. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  155. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  156. package/extensions/agent-browser/lib/results/network.ts +0 -78
  157. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  158. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  159. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  160. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  161. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  162. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  163. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  164. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  165. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  166. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  167. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  168. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  169. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  170. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  171. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  172. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  173. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  174. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  175. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  176. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  177. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  178. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  179. package/extensions/agent-browser/lib/results/text.ts +0 -40
  180. package/extensions/agent-browser/lib/runtime.ts +0 -988
  181. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  182. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  183. package/extensions/agent-browser/lib/temp.ts +0 -577
  184. package/extensions/agent-browser/lib/web-search.ts +0 -728
  185. /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
@@ -1,73 +0,0 @@
1
- /**
2
- * Purpose: Build the small JSON Schema subset used by Pi tool schemas without importing TypeBox at runtime.
3
- * Responsibilities: Preserve plain JSON Schema objects Pi consumes while keeping extension startup cheap.
4
- * Scope: Schema construction only; runtime validation still belongs to Pi and the tool input compilers.
5
- */
6
-
7
- import type { TSchema, TSchemaOptions, TUnsafe } from "typebox";
8
-
9
- const OPTIONAL_SCHEMA = Symbol("pi-agent-browser-optional-schema");
10
-
11
- type SchemaObject = TSchema & { [OPTIONAL_SCHEMA]?: true };
12
- type SchemaProperties = Record<string, TSchema>;
13
-
14
- function withOptions(schema: Record<string, unknown>, options?: TSchemaOptions): TSchema {
15
- return { ...schema, ...(options ?? {}) } as TSchema;
16
- }
17
-
18
- function literalType(value: unknown): "boolean" | "number" | "string" | undefined {
19
- const valueType = typeof value;
20
- return valueType === "string" || valueType === "number" || valueType === "boolean" ? valueType : undefined;
21
- }
22
-
23
- function propertySchema(schema: TSchema): TSchema {
24
- const clone = { ...(schema as SchemaObject & Record<PropertyKey, unknown>) };
25
- delete clone[OPTIONAL_SCHEMA];
26
- return clone as TSchema;
27
- }
28
-
29
- export const JsonSchema = {
30
- Array(items: TSchema, options?: TSchemaOptions): TSchema {
31
- return withOptions({ type: "array", items }, options);
32
- },
33
- Boolean(options?: TSchemaOptions): TSchema {
34
- return withOptions({ type: "boolean" }, options);
35
- },
36
- Integer(options?: TSchemaOptions): TSchema {
37
- return withOptions({ type: "integer" }, options);
38
- },
39
- Literal(value: unknown, options?: TSchemaOptions): TSchema {
40
- const type = literalType(value);
41
- return withOptions(type ? { type, const: value } : { const: value }, options);
42
- },
43
- Number(options?: TSchemaOptions): TSchema {
44
- return withOptions({ type: "number" }, options);
45
- },
46
- Object(properties: SchemaProperties, options?: TSchemaOptions): TSchema {
47
- const required = globalThis.Object.entries(properties)
48
- .filter(([, schema]) => (schema as SchemaObject)[OPTIONAL_SCHEMA] !== true)
49
- .map(([key]) => key);
50
- return withOptions({
51
- type: "object",
52
- properties: globalThis.Object.fromEntries(
53
- globalThis.Object.entries(properties).map(([key, schema]) => [key, propertySchema(schema)]),
54
- ),
55
- ...(required.length > 0 ? { required } : {}),
56
- }, options);
57
- },
58
- Optional(schema: TSchema): TSchema {
59
- return { ...(schema as SchemaObject), [OPTIONAL_SCHEMA]: true } as TSchema;
60
- },
61
- String(options?: TSchemaOptions): TSchema {
62
- return withOptions({ type: "string" }, options);
63
- },
64
- Union(types: TSchema[], options?: TSchemaOptions): TSchema {
65
- return withOptions({ anyOf: types }, options);
66
- },
67
- Unsafe<Value>(schema: TSchema): TUnsafe<Value> {
68
- return schema as TUnsafe<Value>;
69
- },
70
- };
71
-
72
- export type JsonSchemaBuilder = typeof JsonSchema;
73
- export type { TSchema, TSchemaOptions, TUnsafe };
@@ -1,67 +0,0 @@
1
- /**
2
- * Purpose: Canonical launch-scoped agent-browser flag metadata shared by runtime planning and agent-facing guidance.
3
- * Responsibilities: Define which upstream flags require a fresh launch, explain why, and expose stable guidance labels.
4
- * Scope: Metadata only; argv parsing and execution planning live in runtime.ts.
5
- */
6
-
7
- export interface LaunchScopedFlagDefinition {
8
- flag: string;
9
- reason: string;
10
- }
11
-
12
- export const LAUNCH_SCOPED_FLAG_DEFINITIONS = [
13
- {
14
- flag: "--auto-connect",
15
- reason: "attaches to an already-running browser at launch time instead of reusing an existing named session",
16
- },
17
- {
18
- flag: "--cdp",
19
- reason: "selects the browser/CDP endpoint used when an upstream session is launched",
20
- },
21
- {
22
- flag: "--enable",
23
- reason: "selects built-in page init scripts before the upstream browser session is launched",
24
- },
25
- {
26
- flag: "--executable-path",
27
- reason: "selects the browser executable used for the upstream launch",
28
- },
29
- {
30
- flag: "--init-script",
31
- reason: "registers page init scripts before the upstream browser session is launched",
32
- },
33
- {
34
- flag: "--device",
35
- reason: "selects the provider device for the upstream launch",
36
- },
37
- {
38
- flag: "--profile",
39
- reason: "selects Chrome profile state for the upstream launch",
40
- },
41
- {
42
- flag: "--provider",
43
- reason: "selects the upstream browser provider for the launch",
44
- },
45
- {
46
- flag: "-p",
47
- reason: "selects the upstream browser provider for the launch",
48
- },
49
- {
50
- flag: "--session-name",
51
- reason: "selects upstream saved auth/session state for the launch",
52
- },
53
- {
54
- flag: "--state",
55
- reason: "loads persisted upstream browser/auth state at launch time",
56
- },
57
- ] as const satisfies readonly LaunchScopedFlagDefinition[];
58
-
59
- export const LAUNCH_SCOPED_FLAGS = LAUNCH_SCOPED_FLAG_DEFINITIONS.map((definition) => definition.flag);
60
- export const LAUNCH_SCOPED_FLAG_LABEL = LAUNCH_SCOPED_FLAGS.join(", ");
61
-
62
- /**
63
- * The subset of launch-scoped flags that can restore browser/auth state with pre-existing tabs
64
- * and are plausible wrong-active-tab sources after a fresh launch. These trigger post-open
65
- * tab-correction (the `tab list` + re-select cycle).
66
- */
67
- export const LAUNCH_SCOPED_TAB_CORRECTION_FLAGS = new Set(["--profile", "--session-name", "--state"] as const);
@@ -1,95 +0,0 @@
1
- /**
2
- * Purpose: Keep wrapper-side navigation policy parsing and evaluation small and explicit.
3
- * Responsibilities: Parse allowed-domain argv values and detect final-page host escapes.
4
- * Scope: Wrapper diagnostics only; upstream remains responsible for browser-time enforcement.
5
- */
6
-
7
- export interface AllowedDomainsPolicy {
8
- allowedDomains: string[];
9
- display: string;
10
- }
11
-
12
- export interface AllowedDomainsViolation {
13
- allowedDomains: string[];
14
- allowedDisplay: string;
15
- observedHost: string;
16
- observedUrl: string;
17
- summary: string;
18
- }
19
-
20
- function normalizeDomainEntry(value: string): string | undefined {
21
- let candidate = value.trim().toLowerCase();
22
- if (!candidate) return undefined;
23
- try {
24
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(candidate)) {
25
- candidate = new URL(candidate).hostname;
26
- }
27
- } catch {
28
- return undefined;
29
- }
30
- candidate = candidate.replace(/^\*\./, "").replace(/\.$/, "");
31
- if (candidate.includes("/")) candidate = candidate.split("/")[0] ?? "";
32
- if (candidate.includes(":")) candidate = candidate.split(":")[0] ?? "";
33
- return candidate.length > 0 ? candidate : undefined;
34
- }
35
-
36
- function splitAllowedDomainsValue(value: string): string[] {
37
- return value.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean);
38
- }
39
-
40
- export function parseAllowedDomainsPolicyFromArgs(args: readonly string[]): AllowedDomainsPolicy | undefined {
41
- const domains: string[] = [];
42
- for (let index = 0; index < args.length; index += 1) {
43
- const arg = args[index];
44
- if (arg === "--allowed-domains") {
45
- const value = args[index + 1];
46
- if (value && !value.startsWith("-")) {
47
- domains.push(...splitAllowedDomainsValue(value));
48
- index += 1;
49
- }
50
- continue;
51
- }
52
- if (arg?.startsWith("--allowed-domains=")) {
53
- domains.push(...splitAllowedDomainsValue(arg.slice("--allowed-domains=".length)));
54
- }
55
- }
56
- const allowedDomains = [...new Set(domains.flatMap((domain) => {
57
- const normalized = normalizeDomainEntry(domain);
58
- return normalized ? [normalized] : [];
59
- }))];
60
- if (allowedDomains.length === 0) return undefined;
61
- return { allowedDomains, display: allowedDomains.join(", ") };
62
- }
63
-
64
- function normalizeObservedHost(url: string): string | undefined {
65
- try {
66
- const parsed = new URL(url);
67
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined;
68
- return parsed.hostname.toLowerCase().replace(/\.$/, "");
69
- } catch {
70
- return undefined;
71
- }
72
- }
73
-
74
- export function isHostAllowedByDomains(host: string, allowedDomains: readonly string[]): boolean {
75
- const normalizedHost = host.toLowerCase().replace(/\.$/, "");
76
- return allowedDomains.some((domain) => normalizedHost === domain || normalizedHost.endsWith(`.${domain}`));
77
- }
78
-
79
- export function getAllowedDomainsViolation(options: {
80
- policy?: AllowedDomainsPolicy;
81
- url?: string;
82
- }): AllowedDomainsViolation | undefined {
83
- if (!options.policy || !options.url) return undefined;
84
- const observedHost = normalizeObservedHost(options.url);
85
- if (!observedHost) return undefined;
86
- if (isHostAllowedByDomains(observedHost, options.policy.allowedDomains)) return undefined;
87
- const summary = `Navigation policy blocked: --allowed-domains ${options.policy.display} does not allow ${observedHost} (${options.url}).`;
88
- return {
89
- allowedDomains: options.policy.allowedDomains,
90
- allowedDisplay: options.policy.display,
91
- observedHost,
92
- observedUrl: options.url,
93
- summary,
94
- };
95
- }
@@ -1,65 +0,0 @@
1
- export type BatchCommandStep = [string, ...string[]];
2
-
3
- function validateUserBatchStep(step: unknown, index: number): { error: string; ok: false } | { ok: true; step: BatchCommandStep } {
4
- if (!Array.isArray(step)) {
5
- return {
6
- error: `agent_browser batch stdin step ${index} must be a non-empty array of string command tokens.`,
7
- ok: false,
8
- };
9
- }
10
- if (step.length === 0) {
11
- return {
12
- error: `agent_browser batch stdin step ${index} must not be empty.`,
13
- ok: false,
14
- };
15
- }
16
- const invalidTokenIndex = step.findIndex((token) => typeof token !== "string");
17
- if (invalidTokenIndex !== -1) {
18
- return {
19
- error: `agent_browser batch stdin step ${index} token ${invalidTokenIndex} must be a string.`,
20
- ok: false,
21
- };
22
- }
23
- return { ok: true, step: step as BatchCommandStep };
24
- }
25
-
26
- export function parseBatchStdinJsonArray(stdin: string | undefined): { error?: string; steps?: unknown[] } {
27
- if (stdin === undefined) {
28
- return { steps: [] };
29
- }
30
- try {
31
- const parsed = JSON.parse(stdin) as unknown;
32
- if (!Array.isArray(parsed)) {
33
- return { error: "agent_browser batch stdin must be a JSON array of command steps." };
34
- }
35
- return { steps: parsed };
36
- } catch (error) {
37
- const message = error instanceof Error ? error.message : String(error);
38
- return { error: `agent_browser batch stdin could not be parsed as JSON: ${message}` };
39
- }
40
- }
41
-
42
- export function parseUserBatchStdin(stdin: string | undefined): { error?: string; steps?: BatchCommandStep[] } {
43
- const parsed = parseBatchStdinJsonArray(stdin);
44
- if (parsed.error || parsed.steps === undefined) {
45
- return parsed.error ? { error: parsed.error } : { steps: [] };
46
- }
47
- const steps: BatchCommandStep[] = [];
48
- for (const [index, rawStep] of parsed.steps.entries()) {
49
- const validated = validateUserBatchStep(rawStep, index);
50
- if (!validated.ok) {
51
- return { error: validated.error };
52
- }
53
- steps.push(validated.step);
54
- }
55
- return { steps };
56
- }
57
-
58
- export function parseValidBatchStepEntries(stdin: string | undefined): Array<{ index: number; step: BatchCommandStep }> {
59
- const parsed = parseBatchStdinJsonArray(stdin);
60
- if (parsed.error || parsed.steps === undefined) return [];
61
- return parsed.steps.flatMap((step, index) => {
62
- const validated = validateUserBatchStep(step, index);
63
- return validated.ok ? [{ index, step: validated.step }] : [];
64
- });
65
- }
@@ -1,44 +0,0 @@
1
- import { extname, isAbsolute } from "node:path";
2
-
3
- const SCREENSHOT_VALUE_FLAGS = new Set(["--screenshot-dir", "--screenshot-format", "--screenshot-quality"]);
4
- const SCREENSHOT_IMAGE_EXTENSIONS = new Set([".jpeg", ".jpg", ".png", ".webp"]);
5
-
6
- function isImagePathToken(token: string): boolean {
7
- const extension = extname(token).toLowerCase();
8
- return SCREENSHOT_IMAGE_EXTENSIONS.has(extension);
9
- }
10
-
11
- export function getScreenshotPathTokenIndex(commandTokens: string[]): number | undefined {
12
- if (commandTokens[0] !== "screenshot") {
13
- return undefined;
14
- }
15
-
16
- const positionalIndices: number[] = [];
17
- for (let index = 1; index < commandTokens.length; index += 1) {
18
- const token = commandTokens[index];
19
- if (token === "--") {
20
- for (let positionalIndex = index + 1; positionalIndex < commandTokens.length; positionalIndex += 1) {
21
- positionalIndices.push(positionalIndex);
22
- }
23
- break;
24
- }
25
- if (token.startsWith("-")) {
26
- const normalizedToken = token.split("=", 1)[0] ?? token;
27
- if (SCREENSHOT_VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
28
- index += 1;
29
- }
30
- continue;
31
- }
32
- positionalIndices.push(index);
33
- }
34
-
35
- if (positionalIndices.length === 0) {
36
- return undefined;
37
- }
38
- const candidateIndex = positionalIndices[positionalIndices.length - 1];
39
- const candidate = commandTokens[candidateIndex];
40
- if (positionalIndices.length >= 2 || isImagePathToken(candidate) || isAbsolute(candidate) || candidate.startsWith("./") || candidate.startsWith("../")) {
41
- return candidateIndex;
42
- }
43
- return undefined;
44
- }
@@ -1,280 +0,0 @@
1
- import { isRecord } from "../../parsing.js";
2
- import { redactSensitiveText } from "../../runtime.js";
3
- import { withOptionalSessionArgs, type AgentBrowserNextAction } from "../../results/next-actions.js";
4
- import type { SessionRefSnapshot } from "../../session-page-state.js";
5
- import { runSessionCommandData } from "./session-state.js";
6
- import type { ClickDispatchDiagnostic, ClickDispatchProbe, ClickDispatchProbeTarget } from "./types.js";
7
-
8
- const CLICK_DISPATCH_MARKER_PREFIX = "__piAgentBrowserClickDispatchProbe_";
9
- const CLICK_DISPATCH_CLEANUP_TIMEOUT_MS = 2_000;
10
- const ACCESSIBLE_REF_CLICK_DISPATCH_ROLES = new Set(["button", "checkbox", "menuitem", "radio", "switch", "tab"]);
11
-
12
- function parseClickRefId(selector: string): string | undefined {
13
- const trimmed = selector.trim();
14
- const candidate = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
15
- return /^e\d+$/.test(candidate) ? candidate : undefined;
16
- }
17
-
18
- function normalizeAccessibleName(name: string): string {
19
- return name.replace(/\s+/g, " ").trim().toLowerCase();
20
- }
21
-
22
- function getAccessibleRefDuplicateIndex(refSnapshot: SessionRefSnapshot | undefined, refId: string, role: string, name: string): number | undefined {
23
- if (!refSnapshot?.refs) return undefined;
24
- const normalizedRole = role.toLowerCase();
25
- const normalizedName = normalizeAccessibleName(name);
26
- const matchingRefIds = refSnapshot.refIds.filter((candidateRefId) => {
27
- const candidate = refSnapshot.refs?.[candidateRefId];
28
- return candidate?.role.toLowerCase() === normalizedRole && normalizeAccessibleName(candidate.name) === normalizedName;
29
- });
30
- if (matchingRefIds.length <= 1) return undefined;
31
- const duplicateIndex = matchingRefIds.indexOf(refId);
32
- return duplicateIndex >= 0 ? duplicateIndex : undefined;
33
- }
34
-
35
- function getClickDispatchProbeTarget(commandTokens: string[], refSnapshot?: SessionRefSnapshot): ClickDispatchProbeTarget | undefined {
36
- if (commandTokens[0] !== "click" || commandTokens.includes("--new-tab")) return undefined;
37
- const selector = commandTokens[1];
38
- if (!selector || selector.startsWith("-")) return undefined;
39
- const refId = parseClickRefId(selector);
40
- if (refId) {
41
- const ref = refSnapshot?.refs?.[refId];
42
- if (!ref || !ACCESSIBLE_REF_CLICK_DISPATCH_ROLES.has(ref.role)) return undefined;
43
- const duplicateIndex = getAccessibleRefDuplicateIndex(refSnapshot, refId, ref.role, ref.name);
44
- return { ...(duplicateIndex === undefined ? {} : { duplicateIndex }), kind: "accessible", name: ref.name, refId, role: ref.role };
45
- }
46
- if (selector.startsWith("xpath=")) return { kind: "xpath", selector: selector.slice("xpath=".length) };
47
- return { kind: "selector", selector };
48
- }
49
-
50
- function getEvalResultRecord(data: unknown): Record<string, unknown> | undefined {
51
- return isRecord(data) && isRecord(data.result) ? data.result : undefined;
52
- }
53
-
54
- function buildClickDispatchProbeInstallScript(probe: ClickDispatchProbe): string {
55
- const target = probe.target;
56
- const resolveTarget = target.kind === "selector"
57
- ? `(() => { try { return document.querySelector(${JSON.stringify(target.selector)}); } catch { return null; } })()`
58
- : target.kind === "xpath"
59
- ? `(() => { try { return document.evaluate(${JSON.stringify(target.selector)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } catch { return null; } })()`
60
- : `(() => {
61
- const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
62
- const expectedRole = ${JSON.stringify(target.role)};
63
- const expectedName = normalize(${JSON.stringify(target.name)});
64
- const duplicateIndex = ${JSON.stringify(target.duplicateIndex)};
65
- const inferRole = (element) => {
66
- const explicit = element.getAttribute("role");
67
- if (explicit) return explicit;
68
- const tagName = element.tagName.toLowerCase();
69
- if (tagName === "button" || tagName === "select" || tagName === "textarea") return tagName;
70
- if (tagName === "a" && element.hasAttribute("href")) return "link";
71
- if (tagName === "input") {
72
- const type = (element.getAttribute("type") || "text").toLowerCase();
73
- if (["button", "submit", "reset", "image"].includes(type)) return "button";
74
- if (type === "checkbox") return "checkbox";
75
- if (type === "radio") return "radio";
76
- return "textbox";
77
- }
78
- return "";
79
- };
80
- const inferName = (element) => normalize(element.getAttribute("aria-label") || element.getAttribute("title") || element.value || element.textContent || "");
81
- const isVisible = (element) => {
82
- const style = window.getComputedStyle(element);
83
- if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return false;
84
- return element.getClientRects().length > 0;
85
- };
86
- const candidates = Array.from(document.querySelectorAll("button,a[href],input,select,textarea,summary,[role],[onclick],[tabindex]")).filter((element) => inferRole(element) === expectedRole && inferName(element) === expectedName && isVisible(element));
87
- if (typeof duplicateIndex === "number") return candidates[duplicateIndex] || null;
88
- return candidates.length === 1 ? candidates[0] : null;
89
- })()`;
90
- return `(() => {
91
- const marker = ${JSON.stringify(probe.marker)};
92
- const element = ${resolveTarget};
93
- if (!element) return { status: "target-not-found", marker };
94
- const cssEscape = (value) => {
95
- if (window.CSS && typeof window.CSS.escape === "function") return window.CSS.escape(value);
96
- return String(value).replace(/[^a-zA-Z0-9_-]/g, "\\$&");
97
- };
98
- const getSelector = (node) => {
99
- if (!(node instanceof Element)) return undefined;
100
- if (node.id) return "#" + cssEscape(node.id);
101
- const testId = node.getAttribute("data-testid") || node.getAttribute("data-test-id");
102
- if (testId) return '[data-testid="' + cssEscape(testId) + '"]';
103
- const parts = [];
104
- let current = node;
105
- while (current && current !== document.body && parts.length < 4) {
106
- const tag = current.tagName.toLowerCase();
107
- const parent = current.parentElement;
108
- if (!parent) break;
109
- const siblings = Array.from(parent.children).filter((child) => child.tagName === current.tagName);
110
- const index = siblings.indexOf(current) + 1;
111
- parts.unshift(siblings.length > 1 ? tag + ':nth-of-type(' + index + ')' : tag);
112
- current = parent;
113
- }
114
- return parts.length > 0 ? parts.join(" > ") : undefined;
115
- };
116
- const rectInfo = (rect) => ({ bottom: rect.bottom, left: rect.left, right: rect.right, top: rect.top });
117
- const targetRect = element ? element.getBoundingClientRect() : undefined;
118
- const targetOutsideViewport = targetRect ? targetRect.bottom < 0 || targetRect.right < 0 || targetRect.top > window.innerHeight || targetRect.left > window.innerWidth : undefined;
119
- let nearestScrollContainer;
120
- if (element && targetRect) {
121
- for (let current = element.parentElement; current && current !== document.body; current = current.parentElement) {
122
- if (current.scrollHeight > current.clientHeight + 1 || current.scrollWidth > current.clientWidth + 1) {
123
- const containerRect = current.getBoundingClientRect();
124
- nearestScrollContainer = {
125
- selector: getSelector(current),
126
- tagName: current.tagName.toLowerCase(),
127
- targetOutsideContainer: targetRect.bottom < containerRect.top || targetRect.top > containerRect.bottom || targetRect.right < containerRect.left || targetRect.left > containerRect.right,
128
- targetOutsideViewport,
129
- rect: rectInfo(containerRect),
130
- scrollLeft: current.scrollLeft,
131
- scrollTop: current.scrollTop,
132
- };
133
- break;
134
- }
135
- }
136
- }
137
- const state = { events: [], target: { tagName: element.tagName.toLowerCase(), nearestScrollContainer, rect: rectInfo(targetRect), targetOutsideViewport } };
138
- const eventTypes = ["pointerdown", "mousedown", "pointerup", "mouseup", "click"];
139
- const listeners = eventTypes.map((type) => {
140
- const listener = (event) => {
141
- const path = typeof event.composedPath === "function" ? event.composedPath() : [];
142
- const eventTarget = event.target;
143
- const targetMatched = path.includes(element) || eventTarget === element || (eventTarget instanceof Node && element.contains(eventTarget));
144
- state.events.push({ type: event.type, isTrusted: event.isTrusted === true, targetMatched });
145
- };
146
- document.addEventListener(type, listener, true);
147
- return [type, listener];
148
- });
149
- state.cleanup = () => listeners.forEach(([type, listener]) => document.removeEventListener(type, listener, true));
150
- window[marker] = state;
151
- return { status: "installed", marker, target: state.target };
152
- })()`;
153
- }
154
-
155
- function buildClickDispatchProbeCheckScript(probe: ClickDispatchProbe): string {
156
- return `(() => {
157
- const marker = ${JSON.stringify(probe.marker)};
158
- const state = window[marker];
159
- const finish = (payload) => {
160
- if (state && typeof state.cleanup === "function") state.cleanup();
161
- try { delete window[marker]; } catch {}
162
- return payload;
163
- };
164
- if (!state || !Array.isArray(state.events)) return finish({ status: "probe-missing", nativeEventCount: 0 });
165
- const nativeEventCount = state.events.filter((event) => event && event.isTrusted === true && event.targetMatched === true).length;
166
- if (nativeEventCount > 0) return finish({ status: "native-event-observed", nativeEventCount, target: state.target });
167
- return finish({ status: "no-native-event-observed", nativeEventCount, target: state.target });
168
- })()`;
169
- }
170
-
171
- function buildClickDispatchProbeCleanupScript(probe: ClickDispatchProbe): string {
172
- return `(() => {
173
- const marker = ${JSON.stringify(probe.marker)};
174
- const state = window[marker];
175
- if (state && typeof state.cleanup === "function") state.cleanup();
176
- try { delete window[marker]; } catch {}
177
- return { status: "cleaned-up" };
178
- })()`;
179
- }
180
-
181
- function redactClickDispatchTarget(target: ClickDispatchProbeTarget): ClickDispatchProbeTarget {
182
- if (target.kind === "selector" || target.kind === "xpath") {
183
- return { ...target, selector: redactSensitiveText(target.selector) };
184
- }
185
- return { ...target, name: redactSensitiveText(target.name) };
186
- }
187
-
188
- export function formatClickDispatchDiagnosticText(diagnostic: ClickDispatchDiagnostic): string {
189
- return `Click dispatch diagnostic: ${diagnostic.summary}`;
190
- }
191
-
192
- export function buildClickDispatchNextActions(options: { commandTokens: string[]; diagnostic?: ClickDispatchDiagnostic; sessionName?: string }): AgentBrowserNextAction[] {
193
- const retryArgs = options.commandTokens[0] === "click" || options.commandTokens[0] === "find" ? options.commandTokens : ["click", ...options.commandTokens];
194
- const actions: AgentBrowserNextAction[] = [
195
- {
196
- id: "inspect-click-dispatch-miss",
197
- params: { args: withOptionalSessionArgs(options.sessionName, ["snapshot", "-i"]) },
198
- reason: "Refresh interactive refs and verify the intended click target before retrying upstream click.",
199
- safety: "Read-only snapshot; the wrapper does not replay clicks in-page when upstream reports success without DOM events.",
200
- tool: "agent_browser",
201
- },
202
- ];
203
- if (options.diagnostic?.scrollContainer) {
204
- actions.push({
205
- id: "scroll-target-into-view-after-dispatch-miss",
206
- params: { args: withOptionalSessionArgs(options.sessionName, ["scrollintoview", retryArgs[1]].filter((item): item is string => typeof item === "string")) },
207
- reason: options.diagnostic.scrollContainer.selector
208
- ? `The target may be outside nested scroll container ${options.diagnostic.scrollContainer.selector}; scroll the target into view before retrying the click.`
209
- : "The target may be inside an offscreen nested scroll container; scroll the target into view before retrying the click.",
210
- safety: "Use only for the same current page and target; run snapshot -i again if the page rerendered.",
211
- tool: "agent_browser",
212
- });
213
- }
214
- actions.push({
215
- id: "retry-click-after-dispatch-miss",
216
- params: { args: withOptionalSessionArgs(options.sessionName, retryArgs) },
217
- reason: "Retry the same upstream click after confirming the target is visible; do not assume the prior success mutated the page.",
218
- safety: "Only retry when the target is still intended; use page-change evidence or a fresh snapshot before continuing the workflow.",
219
- tool: "agent_browser",
220
- });
221
- return actions;
222
- }
223
-
224
- export async function prepareClickDispatchProbe(options: { commandTokens: string[]; cwd: string; refSnapshot?: SessionRefSnapshot; sessionName?: string; signal?: AbortSignal }): Promise<ClickDispatchProbe | undefined> {
225
- if (!options.sessionName || options.commandTokens[0] !== "click" || options.commandTokens.includes("--new-tab")) return undefined;
226
- const target = getClickDispatchProbeTarget(options.commandTokens, options.refSnapshot);
227
- if (!target) return undefined;
228
- const probe: ClickDispatchProbe = { marker: `${CLICK_DISPATCH_MARKER_PREFIX}${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`, target };
229
- const installData = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildClickDispatchProbeInstallScript(probe) });
230
- const installResult = getEvalResultRecord(installData);
231
- return installResult?.status === "installed" ? probe : undefined;
232
- }
233
-
234
- function getClickDispatchScrollContainerDiagnostic(result: Record<string, unknown>): ClickDispatchDiagnostic["scrollContainer"] {
235
- const target = isRecord(result.target) ? result.target : undefined;
236
- const scrollContainer = isRecord(target?.nearestScrollContainer) ? target.nearestScrollContainer : undefined;
237
- const targetOutsideViewport = typeof target?.targetOutsideViewport === "boolean" ? target.targetOutsideViewport : undefined;
238
- const targetOutsideContainer = typeof scrollContainer?.targetOutsideContainer === "boolean" ? scrollContainer.targetOutsideContainer : undefined;
239
- if (!scrollContainer && !targetOutsideViewport) return undefined;
240
- if (targetOutsideContainer !== true && targetOutsideViewport !== true) return undefined;
241
- const selector = typeof scrollContainer?.selector === "string" ? redactSensitiveText(scrollContainer.selector) : undefined;
242
- const summary = selector
243
- ? `Target appears outside nested scroll container ${selector}; use scrollintoview on the target or scroll that container before retrying.`
244
- : "Target appears outside the viewport or a nested scroll container; use scrollintoview on the target before retrying.";
245
- return { selector, summary, targetOutsideContainer, targetOutsideViewport };
246
- }
247
-
248
- export async function collectClickDispatchDiagnostic(options: { cwd: string; probe?: ClickDispatchProbe; sessionName?: string; signal?: AbortSignal }): Promise<ClickDispatchDiagnostic | undefined> {
249
- if (!options.probe || !options.sessionName) return undefined;
250
- const data = await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, sessionName: options.sessionName, signal: options.signal, stdin: buildClickDispatchProbeCheckScript(options.probe) });
251
- const result = getEvalResultRecord(data);
252
- if (!result) return undefined;
253
- const status = typeof result.status === "string" ? result.status : undefined;
254
- if (status !== "no-native-event-observed") return undefined;
255
- const nativeEventCount = typeof result.nativeEventCount === "number" ? result.nativeEventCount : 0;
256
- const scrollContainer = getClickDispatchScrollContainerDiagnostic(result);
257
- const targetLabel = "no trusted DOM event reached the selected element";
258
- const summary = scrollContainer
259
- ? `Upstream click reported success but ${targetLabel}. ${scrollContainer.summary}`
260
- : `Upstream click reported success but ${targetLabel}. Gather evidence with snapshot or page-change checks, then retry upstream click or report the workflow issue; the wrapper does not replay clicks in-page.`;
261
- return {
262
- nativeEventCount,
263
- reason: "native-click-produced-no-target-dom-event",
264
- ...(scrollContainer ? { scrollContainer } : {}),
265
- status,
266
- summary,
267
- target: redactClickDispatchTarget(options.probe.target),
268
- };
269
- }
270
-
271
- export async function cleanupClickDispatchProbe(options: { cwd: string; probe?: ClickDispatchProbe; sessionName?: string }): Promise<void> {
272
- if (!options.probe || !options.sessionName) return;
273
- await runSessionCommandData({
274
- args: ["eval", "--stdin"],
275
- cwd: options.cwd,
276
- sessionName: options.sessionName,
277
- stdin: buildClickDispatchProbeCleanupScript(options.probe),
278
- timeoutMs: CLICK_DISPATCH_CLEANUP_TIMEOUT_MS,
279
- }).catch(() => undefined);
280
- }