@apmantza/greedysearch-pi 1.7.2 → 1.7.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.7.4 (2026-04-10)
4
+
5
+ ### Refactor
6
+ - **Shared `waitForCopyButton()`** — consolidated duplicate copy-button polling loops from `bing-copilot`, `gemini`, and `coding-task` into a single `waitForCopyButton(tab, selector, { timeout, onPoll })` in `common.mjs`. Gemini's scroll-to-bottom logic passed as `onPoll` callback.
7
+ - **Shared `TIMING` constants** — replaced 30+ scattered `setTimeout` magic numbers with named constants (`postNav`, `postNavSlow`, `postClick`, `postType`, `inputPoll`, `copyPoll`, `afterVerify`) in `common.mjs`.
8
+ - **`waitForStreamComplete` improvements** — added `minLength` option and graceful last-value fallback; `google-ai` now uses the shared implementation instead of its own copy.
9
+ - **Removed dead code** — deleted unused `_getOrReuseBlankTab` and `_getOrOpenEngineTab` from `bin/search.mjs`; removed unused `STREAM_POLL_INTERVAL` and `STREAM_STABLE_ROUNDS` from `coding-task`.
10
+
11
+ ### Fixes
12
+ - **Synthesis tab regression** — `getOrOpenEngineTab("gemini")` call during synthesis was broken by the dead-code removal; replaced with `openNewTab()`.
13
+
14
+ ## v1.7.3 (2026-04-10)
15
+
16
+ ### Fixes
17
+ - **Force English in Google AI results** — Added `hl=en` query parameter to Google AI Mode search URL so responses are always returned in English, regardless of the user's IP-based region (fixes #1).
18
+
3
19
  ## v1.7.2 (2026-04-08)
4
20
 
5
21
  ### Release
@@ -19,97 +35,97 @@
19
35
  - **README simplified** — rewritten into a shorter, concise format with quick install, usage, and layout guidance.
20
36
 
21
37
  ## v1.6.5 (2026-04-04)
22
-
23
- ### Security
24
- - **Private URL blocking** — Added validation to block requests to localhost, RFC1918 private addresses (10.x, 192.168.x), and .local/.internal domains. Prevents accidental exposure of internal services.
25
-
26
- ### Features
27
- - **GitHub URL rewriting** — GitHub blob URLs (`github.com/owner/repo/blob/...`) are automatically rewritten to `raw.githubusercontent.com` for faster, cleaner raw file access.
28
- - **GitHub repo cloning** — Root and tree URLs now trigger `git clone --depth 1` for complete repo access. Agent can explore files locally instead of parsing rendered HTML. Includes README preview and directory tree listing.
29
- - **Head+tail content trimming** — Large documents now use smart truncation: keeps 75% from the beginning (introduction) + 25% from the end (conclusions/examples) with `[...content trimmed...]` marker, instead of simple truncation.
30
- - **Anubis bot detection** — Added detection for the new Anubis proof-of-work anti-bot system (`protected by anubis`, `anubis uses a proof-of-work`).
31
-
32
- ### Fixes
33
- - **Perplexity clipboard retry** — Added single retry with 2s delay when clipboard extraction fails, improving reliability.
34
-
35
- ## v1.6.4 (2026-04-02)
36
-
37
- ### Fixes
38
- - **Gemini scroll-to-bottom** — Changed from small random jitter scrolls to actual bottom-of-page scrolls every ~6 seconds while waiting for the copy button. This ensures lazy-loaded content is triggered and the full answer is captured.
39
- - **Restored missing files** — `.mjs` source files (extractors, search.mjs, launch.mjs, etc.) were incorrectly removed in v1.6.2 cleanup; now properly tracked again.
40
-
41
- ## v1.6.3 (2026-04-02)
42
-
43
- ### Fixes
44
- - **Debug output removed** — Cleaned up stderr passthrough that was causing CDP connection issues in some environments.
45
-
46
- ## v1.6.2 (2026-04-01)
47
-
48
- ### Fixes
49
- - **Anti-bot detection evasion** — Gemini synthesis now performs gentle scroll every ~6 seconds while waiting for the copy button. This prevents the button from hanging due to anti-bot "human activity" checks.
50
-
51
- ## v1.6.1 (2026-03-31)
52
-
53
- ### Features
54
- - **Single-engine full answers by default** — when using `engine: "perplexity"`, `engine: "bing"`, `engine: "google"`, or `engine: "gemini"`, the full answer is now returned by default instead of truncated previews. Multi-engine (`engine: "all"`) still uses truncated previews (~300 chars) to save tokens during synthesis. Explicit `fullAnswer: true/false` always overrides.
55
-
56
- ### Code Quality
57
- - **Major refactoring** — extracted 438 lines from `index.ts` (856 → 418 lines) into modular formatters:
58
- - `src/formatters/coding.ts` — coding task formatting
59
- - `src/formatters/results.ts` — search and deep research formatting
60
- - `src/formatters/sources.ts` — source utilities (URL, label, consensus, formatting)
61
- - `src/formatters/synthesis.ts` — synthesis rendering
62
- - `src/utils/helpers.ts` — shared formatting utilities
63
- - **Complexity reduced** — cognitive complexity dropped from 360 to ~60, maintainability index improved from 11.2 to ~40+
64
- - **Eliminated code duplication** — removed 6 duplicate blocks, consolidated 4+ single-use helper functions
65
-
66
- ### Documentation
67
- - Clarified `greedy_search` is WEB SEARCH ONLY — removed "NOT for codebase search" from tool description (still in skill documentation)
68
-
69
- ## v1.6.0 (2026-03-29)
70
-
71
- ### Breaking Changes (Backward Compatible)
72
- - **Merged deep_research into greedy_search** — new `depth` parameter with three levels:
73
- - `fast`: single engine (~15-30s)
74
- - `standard`: 3 engines + synthesis (~30-90s, default for `engine: "all"`)
75
- - `deep`: 3 engines + source fetching + synthesis + confidence (~60-180s)
76
- - **Simpler mental model** — one tool with clear speed/quality tradeoffs instead of separate tools with overlapping flags
77
- - **Deprecated flags still work** — `--synthesize` maps to `depth: "standard"`, `--deep-research` maps to `depth: "deep"`
78
- - **deep_research tool aliased** — still works, calls `greedy_search` with `depth: "deep"`
79
-
80
- ### Documentation
81
- - Updated README with new `depth` parameter and examples
82
- - Updated skill documentation (SKILL.md) to reflect simplified API
83
-
84
- ## v1.5.1 (2026-03-29)
85
-
86
- - **Fixed npm package** — added `.pi-lens/` and test files to `.npmignore` to reduce package size
87
-
88
- ## v1.5.0 (2026-03-29)
89
-
90
- ### Features
91
- - **Code extraction fixed** — `coding_task` now uses clipboard interception to preserve markdown code blocks (was losing them via DOM scraping)
92
- - **Chrome targeting hardened** — all tools now consistently target the dedicated GreedySearch Chrome via `CDP_PROFILE_DIR`, preventing fallback to user's main Chrome session
93
- - **Shared utilities** — extracted ~220 lines of duplicate code from extractors into `common.mjs` (cdp wrapper, tab management, clipboard interception)
94
- - **Documentation leaner** — skill documentation reduced 61% (180 → 70 lines) while preserving all decision-making info
95
-
96
- ### Notable
97
- - **NO API KEYS** — updated messaging to emphasize this works via browser automation, no API keys needed
98
-
99
- ## v1.4.2 (2026-03-25)
100
-
101
- - **Fresh isolated tabs** — each search now always creates a new `about:blank` tab via `Target.createTarget` and refreshes the CDP page cache immediately after, preventing SPA navigation failures and stale DOM state from prior queries
102
- - **Regex-based citation extraction** — all extractors (Perplexity, Bing, Gemini) now parse sources from clipboard Markdown links (`[title](url)`) instead of DOM selectors that break on UI updates
103
- - **Relaxed verification detection** — `consent.mjs` now uses broad keyword matching (`includes('verify')`, `includes('human')`) instead of anchored regexes, correctly catching button text variants like "Verify you are human" across Cloudflare, Microsoft, and generic modals
104
-
105
- ## v1.4.1
106
-
107
- - **Fixed parallel synthesis** — multiple `greedy_search` calls with `synthesize: true` now run safely in parallel. Each search creates a fresh Gemini tab that gets cleaned up after synthesis, preventing tab conflicts and "Uncaught" errors.
108
-
109
- ## v1.4.0
110
-
111
- - **Grounded synthesis** — Gemini now receives a normalized source registry with stable source IDs, agreement summaries, caveats, and cited claims
112
- - **Real deep research** — top sources are fetched before synthesis so deep research answers are grounded in fetched evidence, not just engine summaries
113
- - **Richer source metadata** — source output now includes canonical URLs, domains, source types, per-engine attribution, and confidence metadata
114
- - **Cleaner tab lifecycle** — temporary Perplexity, Bing, and Google tabs are closed after each fan-out search, and synthesis finishes on the Gemini tab
115
- - **Isolated Chrome targeting** — GreedySearch now refuses to fall back to your normal Chrome session, preventing stray remote-debugging prompts
38
+
39
+ ### Security
40
+ - **Private URL blocking** — Added validation to block requests to localhost, RFC1918 private addresses (10.x, 192.168.x), and .local/.internal domains. Prevents accidental exposure of internal services.
41
+
42
+ ### Features
43
+ - **GitHub URL rewriting** — GitHub blob URLs (`github.com/owner/repo/blob/...`) are automatically rewritten to `raw.githubusercontent.com` for faster, cleaner raw file access.
44
+ - **GitHub repo cloning** — Root and tree URLs now trigger `git clone --depth 1` for complete repo access. Agent can explore files locally instead of parsing rendered HTML. Includes README preview and directory tree listing.
45
+ - **Head+tail content trimming** — Large documents now use smart truncation: keeps 75% from the beginning (introduction) + 25% from the end (conclusions/examples) with `[...content trimmed...]` marker, instead of simple truncation.
46
+ - **Anubis bot detection** — Added detection for the new Anubis proof-of-work anti-bot system (`protected by anubis`, `anubis uses a proof-of-work`).
47
+
48
+ ### Fixes
49
+ - **Perplexity clipboard retry** — Added single retry with 2s delay when clipboard extraction fails, improving reliability.
50
+
51
+ ## v1.6.4 (2026-04-02)
52
+
53
+ ### Fixes
54
+ - **Gemini scroll-to-bottom** — Changed from small random jitter scrolls to actual bottom-of-page scrolls every ~6 seconds while waiting for the copy button. This ensures lazy-loaded content is triggered and the full answer is captured.
55
+ - **Restored missing files** — `.mjs` source files (extractors, search.mjs, launch.mjs, etc.) were incorrectly removed in v1.6.2 cleanup; now properly tracked again.
56
+
57
+ ## v1.6.3 (2026-04-02)
58
+
59
+ ### Fixes
60
+ - **Debug output removed** — Cleaned up stderr passthrough that was causing CDP connection issues in some environments.
61
+
62
+ ## v1.6.2 (2026-04-01)
63
+
64
+ ### Fixes
65
+ - **Anti-bot detection evasion** — Gemini synthesis now performs gentle scroll every ~6 seconds while waiting for the copy button. This prevents the button from hanging due to anti-bot "human activity" checks.
66
+
67
+ ## v1.6.1 (2026-03-31)
68
+
69
+ ### Features
70
+ - **Single-engine full answers by default** — when using `engine: "perplexity"`, `engine: "bing"`, `engine: "google"`, or `engine: "gemini"`, the full answer is now returned by default instead of truncated previews. Multi-engine (`engine: "all"`) still uses truncated previews (~300 chars) to save tokens during synthesis. Explicit `fullAnswer: true/false` always overrides.
71
+
72
+ ### Code Quality
73
+ - **Major refactoring** — extracted 438 lines from `index.ts` (856 → 418 lines) into modular formatters:
74
+ - `src/formatters/coding.ts` — coding task formatting
75
+ - `src/formatters/results.ts` — search and deep research formatting
76
+ - `src/formatters/sources.ts` — source utilities (URL, label, consensus, formatting)
77
+ - `src/formatters/synthesis.ts` — synthesis rendering
78
+ - `src/utils/helpers.ts` — shared formatting utilities
79
+ - **Complexity reduced** — cognitive complexity dropped from 360 to ~60, maintainability index improved from 11.2 to ~40+
80
+ - **Eliminated code duplication** — removed 6 duplicate blocks, consolidated 4+ single-use helper functions
81
+
82
+ ### Documentation
83
+ - Clarified `greedy_search` is WEB SEARCH ONLY — removed "NOT for codebase search" from tool description (still in skill documentation)
84
+
85
+ ## v1.6.0 (2026-03-29)
86
+
87
+ ### Breaking Changes (Backward Compatible)
88
+ - **Merged deep_research into greedy_search** — new `depth` parameter with three levels:
89
+ - `fast`: single engine (~15-30s)
90
+ - `standard`: 3 engines + synthesis (~30-90s, default for `engine: "all"`)
91
+ - `deep`: 3 engines + source fetching + synthesis + confidence (~60-180s)
92
+ - **Simpler mental model** — one tool with clear speed/quality tradeoffs instead of separate tools with overlapping flags
93
+ - **Deprecated flags still work** — `--synthesize` maps to `depth: "standard"`, `--deep-research` maps to `depth: "deep"`
94
+ - **deep_research tool aliased** — still works, calls `greedy_search` with `depth: "deep"`
95
+
96
+ ### Documentation
97
+ - Updated README with new `depth` parameter and examples
98
+ - Updated skill documentation (SKILL.md) to reflect simplified API
99
+
100
+ ## v1.5.1 (2026-03-29)
101
+
102
+ - **Fixed npm package** — added `.pi-lens/` and test files to `.npmignore` to reduce package size
103
+
104
+ ## v1.5.0 (2026-03-29)
105
+
106
+ ### Features
107
+ - **Code extraction fixed** — `coding_task` now uses clipboard interception to preserve markdown code blocks (was losing them via DOM scraping)
108
+ - **Chrome targeting hardened** — all tools now consistently target the dedicated GreedySearch Chrome via `CDP_PROFILE_DIR`, preventing fallback to user's main Chrome session
109
+ - **Shared utilities** — extracted ~220 lines of duplicate code from extractors into `common.mjs` (cdp wrapper, tab management, clipboard interception)
110
+ - **Documentation leaner** — skill documentation reduced 61% (180 → 70 lines) while preserving all decision-making info
111
+
112
+ ### Notable
113
+ - **NO API KEYS** — updated messaging to emphasize this works via browser automation, no API keys needed
114
+
115
+ ## v1.4.2 (2026-03-25)
116
+
117
+ - **Fresh isolated tabs** — each search now always creates a new `about:blank` tab via `Target.createTarget` and refreshes the CDP page cache immediately after, preventing SPA navigation failures and stale DOM state from prior queries
118
+ - **Regex-based citation extraction** — all extractors (Perplexity, Bing, Gemini) now parse sources from clipboard Markdown links (`[title](url)`) instead of DOM selectors that break on UI updates
119
+ - **Relaxed verification detection** — `consent.mjs` now uses broad keyword matching (`includes('verify')`, `includes('human')`) instead of anchored regexes, correctly catching button text variants like "Verify you are human" across Cloudflare, Microsoft, and generic modals
120
+
121
+ ## v1.4.1
122
+
123
+ - **Fixed parallel synthesis** — multiple `greedy_search` calls with `synthesize: true` now run safely in parallel. Each search creates a fresh Gemini tab that gets cleaned up after synthesis, preventing tab conflicts and "Uncaught" errors.
124
+
125
+ ## v1.4.0
126
+
127
+ - **Grounded synthesis** — Gemini now receives a normalized source registry with stable source IDs, agreement summaries, caveats, and cited claims
128
+ - **Real deep research** — top sources are fetched before synthesis so deep research answers are grounded in fetched evidence, not just engine summaries
129
+ - **Richer source metadata** — source output now includes canonical URLs, domains, source types, per-engine attribution, and confidence metadata
130
+ - **Cleaner tab lifecycle** — temporary Perplexity, Bing, and Google tabs are closed after each fan-out search, and synthesis finishes on the Gemini tab
131
+ - **Isolated Chrome targeting** — GreedySearch now refuses to fall back to your normal Chrome session, preventing stray remote-debugging prompts
package/README.md CHANGED
@@ -1,73 +1,73 @@
1
- # GreedySearch for Pi
2
-
3
- Multi-engine AI web search for Pi via browser automation.
4
-
5
- - No API keys
6
- - Real browser results (Perplexity, Bing Copilot, Google AI)
7
- - Optional Gemini synthesis with source grounding
8
-
9
- ## Install
10
-
11
- ```bash
12
- pi install npm:@apmantza/greedysearch-pi
13
- ```
14
-
15
- Or from git:
16
-
17
- ```bash
18
- pi install git:github.com/apmantza/GreedySearch-pi
19
- ```
20
-
21
- ## Tools
22
-
23
- - `greedy_search` - fast or grounded multi-engine search
24
- - `coding_task` - browser-routed Gemini/Copilot coding assistance
25
-
26
- ## Quick usage
27
-
28
- ```js
29
- greedy_search({ query: "React 19 changes" })
30
- greedy_search({ query: "Prisma vs Drizzle", engine: "all", depth: "fast" })
31
- greedy_search({ query: "Best auth architecture 2026", engine: "all", depth: "deep" })
32
- ```
33
-
34
- ## Parameters (`greedy_search`)
35
-
36
- - `query` (required)
37
- - `engine`: `all` (default), `perplexity`, `bing`, `google`, `gemini`
38
- - `depth`: `standard` (default), `fast`, `deep`
39
- - `fullAnswer`: return full single-engine output instead of preview
40
-
41
- ## Depth modes
42
-
43
- - `fast` - quickest, no synthesis/source fetching
44
- - `standard` - balanced default for `engine: "all"` (synthesis + fetched sources)
45
- - `deep` - strongest grounding and confidence metadata
46
-
47
- ## Runtime commands
48
-
49
- ```bash
50
- node ~/.pi/agent/git/GreedySearch-pi/bin/launch.mjs
51
- node ~/.pi/agent/git/GreedySearch-pi/bin/launch.mjs --status
52
- node ~/.pi/agent/git/GreedySearch-pi/bin/launch.mjs --kill
53
- ```
54
-
55
- ## Requirements
56
-
57
- - Chrome
58
- - Node.js 22+
59
-
60
- ## Project layout
61
-
62
- - `bin/` - runtime CLIs (`search.mjs`, `launch.mjs`, `cdp.mjs`, `coding-task.mjs`)
63
- - `extractors/` - engine-specific automation
64
- - `src/` - ranking/fetching/formatting internals
65
- - `skills/` - Pi skill metadata
66
-
67
- ## Changelog
68
-
69
- See `CHANGELOG.md`.
70
-
71
- ## License
72
-
73
- MIT
1
+ # GreedySearch for Pi
2
+
3
+ Multi-engine AI web search for Pi via browser automation.
4
+
5
+ - No API keys
6
+ - Real browser results (Perplexity, Bing Copilot, Google AI)
7
+ - Optional Gemini synthesis with source grounding
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pi install npm:@apmantza/greedysearch-pi
13
+ ```
14
+
15
+ Or from git:
16
+
17
+ ```bash
18
+ pi install git:github.com/apmantza/GreedySearch-pi
19
+ ```
20
+
21
+ ## Tools
22
+
23
+ - `greedy_search` - fast or grounded multi-engine search
24
+ - `coding_task` - browser-routed Gemini/Copilot coding assistance
25
+
26
+ ## Quick usage
27
+
28
+ ```js
29
+ greedy_search({ query: "React 19 changes" })
30
+ greedy_search({ query: "Prisma vs Drizzle", engine: "all", depth: "fast" })
31
+ greedy_search({ query: "Best auth architecture 2026", engine: "all", depth: "deep" })
32
+ ```
33
+
34
+ ## Parameters (`greedy_search`)
35
+
36
+ - `query` (required)
37
+ - `engine`: `all` (default), `perplexity`, `bing`, `google`, `gemini`
38
+ - `depth`: `standard` (default), `fast`, `deep`
39
+ - `fullAnswer`: return full single-engine output instead of preview
40
+
41
+ ## Depth modes
42
+
43
+ - `fast` - quickest, no synthesis/source fetching
44
+ - `standard` - balanced default for `engine: "all"` (synthesis + fetched sources)
45
+ - `deep` - strongest grounding and confidence metadata
46
+
47
+ ## Runtime commands
48
+
49
+ ```bash
50
+ node ~/.pi/agent/git/GreedySearch-pi/bin/launch.mjs
51
+ node ~/.pi/agent/git/GreedySearch-pi/bin/launch.mjs --status
52
+ node ~/.pi/agent/git/GreedySearch-pi/bin/launch.mjs --kill
53
+ ```
54
+
55
+ ## Requirements
56
+
57
+ - Chrome
58
+ - Node.js 22+
59
+
60
+ ## Project layout
61
+
62
+ - `bin/` - runtime CLIs (`search.mjs`, `launch.mjs`, `cdp.mjs`, `coding-task.mjs`)
63
+ - `extractors/` - engine-specific automation
64
+ - `src/` - ranking/fetching/formatting internals
65
+ - `skills/` - Pi skill metadata
66
+
67
+ ## Changelog
68
+
69
+ See `CHANGELOG.md`.
70
+
71
+ ## License
72
+
73
+ MIT
@@ -12,8 +12,8 @@
12
12
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { tmpdir } from "node:os";
14
14
  import { fileURLToPath } from "node:url";
15
- import { cdp, injectClipboardInterceptor } from "../extractors/common.mjs";
16
- import { dismissConsent, handleVerification } from "../extractors/consent.mjs";
15
+ import { cdp, injectClipboardInterceptor, waitForCopyButton } from "../extractors/common.mjs";
16
+ import { dismissConsent, handleVerification } from "../extractors/consent.mjs";
17
17
 
18
18
  const __dir = fileURLToPath(new URL(".", import.meta.url));
19
19
  const PAGES_CACHE = `${tmpdir().replace(/\\/g, "/")}/cdp-pages.json`;
@@ -31,9 +31,7 @@ const MODE_PROMPTS = {
31
31
  debug: `You are a senior engineer debugging someone else's code. You have fresh eyes — no prior assumptions about what should work. Given the bug description and relevant code: (1) identify the most likely root cause, being specific about the exact line or condition, (2) explain why it manifests the way it does, (3) suggest the minimal fix, (4) flag any other latent bugs you notice while reading. Do not guess vaguely — reason from the code. If you need information that isn't provided, say exactly what you'd add to narrow it down.`,
32
32
  };
33
33
 
34
- const STREAM_POLL_INTERVAL = 800;
35
- const STREAM_STABLE_ROUNDS = 4;
36
- const STREAM_TIMEOUT = 120000; // coding tasks take longer
34
+ const STREAM_TIMEOUT = 120000; // coding tasks take longer — passed to waitForCopyButton
37
35
  const MIN_RESPONSE_LENGTH = 50;
38
36
 
39
37
  // ---------------------------------------------------------------------------
@@ -104,17 +102,7 @@ const ENGINES = {
104
102
  },
105
103
 
106
104
  async waitForCopyButton(tab) {
107
- const deadline = Date.now() + STREAM_TIMEOUT;
108
- while (Date.now() < deadline) {
109
- await new Promise((r) => setTimeout(r, STREAM_POLL_INTERVAL));
110
- const found = await cdp([
111
- "eval",
112
- tab,
113
- `!!document.querySelector('button[aria-label="Copy"]')`,
114
- ]).catch(() => "false");
115
- if (found === "true") return;
116
- }
117
- throw new Error("Gemini copy button did not appear within timeout");
105
+ await waitForCopyButton(tab, 'button[aria-label="Copy"]', { timeout: STREAM_TIMEOUT });
118
106
  },
119
107
 
120
108
  async extract(tab) {
@@ -162,17 +150,7 @@ const ENGINES = {
162
150
  },
163
151
 
164
152
  async waitForCopyButton(tab) {
165
- const deadline = Date.now() + STREAM_TIMEOUT;
166
- while (Date.now() < deadline) {
167
- await new Promise((r) => setTimeout(r, STREAM_POLL_INTERVAL));
168
- const found = await cdp([
169
- "eval",
170
- tab,
171
- `!!document.querySelector('button[data-testid="copy-ai-message-button"]')`,
172
- ]).catch(() => "false");
173
- if (found === "true") return;
174
- }
175
- throw new Error("Copilot copy button did not appear within timeout");
153
+ await waitForCopyButton(tab, 'button[data-testid="copy-ai-message-button"]', { timeout: STREAM_TIMEOUT });
176
154
  },
177
155
 
178
156
  async extract(tab) {