@apmantza/greedysearch-pi 1.4.2 → 1.5.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/.pi-lens/cache/jscpd.json +112 -0
- package/.pi-lens/cache/jscpd.meta.json +3 -0
- package/.pi-lens/cache/knip.json +111 -0
- package/.pi-lens/cache/knip.meta.json +4 -0
- package/.pi-lens/fix-plan.md +13 -0
- package/.pi-lens/fix-session.json +11 -0
- package/.pi-lens/metrics-history.json +182 -0
- package/.pi-lens/reports/fix-plan.tsv +38 -0
- package/.pi-lens/turn-state.json +6 -0
- package/CHANGELOG.md +30 -0
- package/README.md +233 -219
- package/cdp.mjs +1002 -797
- package/coding-task.mjs +392 -369
- package/extractors/bing-copilot.mjs +167 -195
- package/extractors/common.mjs +237 -0
- package/extractors/consent.mjs +273 -255
- package/extractors/gemini.mjs +142 -180
- package/extractors/google-ai.mjs +156 -162
- package/extractors/perplexity.mjs +126 -181
- package/extractors/selectors.mjs +43 -43
- package/index.ts +230 -93
- package/launch.mjs +283 -161
- package/package.json +26 -26
- package/search.mjs +1219 -997
- package/skills/greedy-search/SKILL.md +38 -109
- package/test.mjs +308 -0
- package/test.sh +298 -298
- package/newfeaturesideas.md +0 -105
package/extractors/selectors.mjs
CHANGED
|
@@ -3,50 +3,50 @@
|
|
|
3
3
|
// Update selectors here when a site changes its UI.
|
|
4
4
|
|
|
5
5
|
export const SELECTORS = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
// ──────────────────────────────────────────────
|
|
7
|
+
// Perplexity (perplexity.ai)
|
|
8
|
+
// ──────────────────────────────────────────────
|
|
9
|
+
perplexity: {
|
|
10
|
+
input: "#ask-input",
|
|
11
|
+
copyButton: 'button[aria-label="Copy"]',
|
|
12
|
+
sourceItem: "[data-pplx-citation-url]",
|
|
13
|
+
sourceLink: "a",
|
|
14
|
+
consent: "#onetrust-accept-btn-handler",
|
|
15
|
+
},
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
// ──────────────────────────────────────────────
|
|
18
|
+
// Bing Copilot (copilot.microsoft.com)
|
|
19
|
+
// ──────────────────────────────────────────────
|
|
20
|
+
bing: {
|
|
21
|
+
input: "#userInput",
|
|
22
|
+
copyButton: 'button[data-testid="copy-ai-message-button"]',
|
|
23
|
+
sourceLink: 'a[href^="http"][target="_blank"]',
|
|
24
|
+
sourceExclude: "copilot.microsoft.com",
|
|
25
|
+
consent: "#onetrust-accept-btn-handler",
|
|
26
|
+
},
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
// ──────────────────────────────────────────────
|
|
29
|
+
// Google AI Mode (google.com/search?udm=50)
|
|
30
|
+
// ──────────────────────────────────────────────
|
|
31
|
+
google: {
|
|
32
|
+
answerContainer: ".pWvJNd",
|
|
33
|
+
sourceLink: 'a[href^="http"]',
|
|
34
|
+
sourceExclude: ["google.", "gstatic", "googleapis"],
|
|
35
|
+
sourceHeadingParent: "[data-snhf]",
|
|
36
|
+
consent: '#L2AGLb, button[jsname="b3VHJd"], .tHlp8d',
|
|
37
|
+
},
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
// ──────────────────────────────────────────────
|
|
40
|
+
// Gemini (gemini.google.com/app)
|
|
41
|
+
// ──────────────────────────────────────────────
|
|
42
|
+
gemini: {
|
|
43
|
+
input: "rich-textarea .ql-editor",
|
|
44
|
+
copyButton: 'button[aria-label="Copy"]',
|
|
45
|
+
sendButton: 'button[aria-label*="Send"]',
|
|
46
|
+
sourcesSidebarButton: "button.legacy-sources-sidebar-button",
|
|
47
|
+
sourcesExclude: ["gemini.google", "gstatic", "google.com/search"],
|
|
48
|
+
citationButtonPattern: 'button[aria-label*="citation from"]',
|
|
49
|
+
// For parsing citation aria-labels: "View source details for citation from {name}. Opens side panel."
|
|
50
|
+
citationNameRegex: /from\s+(.+?)\.\s/,
|
|
51
|
+
},
|
|
52
52
|
};
|
package/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
|
-
import {
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
16
16
|
import { Type } from "@sinclair/typebox";
|
|
@@ -31,13 +31,20 @@ function runSearch(
|
|
|
31
31
|
onProgress?: (engine: string, status: "done" | "error") => void,
|
|
32
32
|
): Promise<Record<string, unknown>> {
|
|
33
33
|
return new Promise((resolve, reject) => {
|
|
34
|
-
const proc = spawn(
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const proc = spawn(
|
|
35
|
+
"node",
|
|
36
|
+
[`${__dir}/search.mjs`, engine, "--inline", ...flags, query],
|
|
37
|
+
{
|
|
38
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
39
|
+
},
|
|
40
|
+
);
|
|
37
41
|
let out = "";
|
|
38
42
|
let err = "";
|
|
39
43
|
|
|
40
|
-
const onAbort = () => {
|
|
44
|
+
const onAbort = () => {
|
|
45
|
+
proc.kill("SIGTERM");
|
|
46
|
+
reject(new Error("Aborted"));
|
|
47
|
+
};
|
|
41
48
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
42
49
|
|
|
43
50
|
// Watch stderr for progress events (PROGRESS:engine:done|error)
|
|
@@ -61,7 +68,9 @@ function runSearch(
|
|
|
61
68
|
try {
|
|
62
69
|
resolve(JSON.parse(out.trim()));
|
|
63
70
|
} catch {
|
|
64
|
-
reject(
|
|
71
|
+
reject(
|
|
72
|
+
new Error(`Invalid JSON from search.mjs: ${out.slice(0, 200)}`),
|
|
73
|
+
);
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
});
|
|
@@ -85,12 +94,16 @@ function sourceUrl(source: Record<string, unknown>): string {
|
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
function sourceLabel(source: Record<string, unknown>): string {
|
|
88
|
-
return String(
|
|
97
|
+
return String(
|
|
98
|
+
source.title || source.domain || sourceUrl(source) || "Untitled source",
|
|
99
|
+
);
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
function sourceConsensus(source: Record<string, unknown>): number {
|
|
92
103
|
if (typeof source.engineCount === "number") return source.engineCount;
|
|
93
|
-
const engines = Array.isArray(source.engines)
|
|
104
|
+
const engines = Array.isArray(source.engines)
|
|
105
|
+
? (source.engines as string[])
|
|
106
|
+
: [];
|
|
94
107
|
return engines.length;
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -99,7 +112,9 @@ function formatAgreementLevel(level: string): string {
|
|
|
99
112
|
return level.charAt(0).toUpperCase() + level.slice(1);
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
function getSourceMap(
|
|
115
|
+
function getSourceMap(
|
|
116
|
+
sources: Array<Record<string, unknown>>,
|
|
117
|
+
): Map<string, Record<string, unknown>> {
|
|
103
118
|
return new Map(
|
|
104
119
|
sources
|
|
105
120
|
.map((source) => [String(source.id || ""), source] as const)
|
|
@@ -112,22 +127,33 @@ function formatSourceLine(source: Record<string, unknown>): string {
|
|
|
112
127
|
const url = sourceUrl(source);
|
|
113
128
|
const title = sourceLabel(source);
|
|
114
129
|
const domain = String(source.domain || "");
|
|
115
|
-
const engines = Array.isArray(source.engines)
|
|
130
|
+
const engines = Array.isArray(source.engines)
|
|
131
|
+
? (source.engines as string[])
|
|
132
|
+
: [];
|
|
116
133
|
const consensus = sourceConsensus(source);
|
|
117
134
|
const typeLabel = humanizeSourceType(String(source.sourceType || ""));
|
|
118
135
|
const fetch = source.fetch as Record<string, unknown> | undefined;
|
|
119
|
-
const fetchStatus = fetch?.ok
|
|
136
|
+
const fetchStatus = fetch?.ok
|
|
137
|
+
? `fetched ${fetch.status || 200}`
|
|
138
|
+
: fetch?.attempted
|
|
139
|
+
? "fetch failed"
|
|
140
|
+
: "";
|
|
120
141
|
const pieces = [
|
|
121
142
|
`${id} - [${title}](${url})`,
|
|
122
143
|
domain,
|
|
123
144
|
typeLabel,
|
|
124
|
-
engines.length
|
|
145
|
+
engines.length
|
|
146
|
+
? `cited by ${engines.map(formatEngineName).join(", ")} (${consensus}/3)`
|
|
147
|
+
: `${consensus}/3`,
|
|
125
148
|
fetchStatus,
|
|
126
149
|
].filter(Boolean);
|
|
127
150
|
return `- ${pieces.join(" - ")}`;
|
|
128
151
|
}
|
|
129
152
|
|
|
130
|
-
function renderSourceEvidence(
|
|
153
|
+
function renderSourceEvidence(
|
|
154
|
+
lines: string[],
|
|
155
|
+
source: Record<string, unknown>,
|
|
156
|
+
): void {
|
|
131
157
|
const fetch = source.fetch as Record<string, unknown> | undefined;
|
|
132
158
|
if (!fetch?.attempted) return;
|
|
133
159
|
|
|
@@ -169,18 +195,24 @@ function renderSynthesis(
|
|
|
169
195
|
const agreementLevel = String(agreement?.level || "").trim();
|
|
170
196
|
if (agreementSummary || agreementLevel) {
|
|
171
197
|
lines.push("## Consensus");
|
|
172
|
-
lines.push(
|
|
198
|
+
lines.push(
|
|
199
|
+
`- ${formatAgreementLevel(agreementLevel)}${agreementSummary ? ` - ${agreementSummary}` : ""}`,
|
|
200
|
+
);
|
|
173
201
|
lines.push("");
|
|
174
202
|
}
|
|
175
203
|
|
|
176
|
-
const differences = Array.isArray(synthesis.differences)
|
|
204
|
+
const differences = Array.isArray(synthesis.differences)
|
|
205
|
+
? (synthesis.differences as string[])
|
|
206
|
+
: [];
|
|
177
207
|
if (differences.length > 0) {
|
|
178
208
|
lines.push("## Where Engines Differ");
|
|
179
209
|
for (const difference of differences) lines.push(`- ${difference}`);
|
|
180
210
|
lines.push("");
|
|
181
211
|
}
|
|
182
212
|
|
|
183
|
-
const caveats = Array.isArray(synthesis.caveats)
|
|
213
|
+
const caveats = Array.isArray(synthesis.caveats)
|
|
214
|
+
? (synthesis.caveats as string[])
|
|
215
|
+
: [];
|
|
184
216
|
if (caveats.length > 0) {
|
|
185
217
|
lines.push("## Caveats");
|
|
186
218
|
for (const caveat of caveats) lines.push(`- ${caveat}`);
|
|
@@ -193,9 +225,13 @@ function renderSynthesis(
|
|
|
193
225
|
if (claims.length > 0) {
|
|
194
226
|
lines.push("## Key Claims");
|
|
195
227
|
for (const claim of claims) {
|
|
196
|
-
const sourceIds = Array.isArray(claim.sourceIds)
|
|
228
|
+
const sourceIds = Array.isArray(claim.sourceIds)
|
|
229
|
+
? (claim.sourceIds as string[])
|
|
230
|
+
: [];
|
|
197
231
|
const support = String(claim.support || "moderate");
|
|
198
|
-
lines.push(
|
|
232
|
+
lines.push(
|
|
233
|
+
`- ${String(claim.claim || "")} [${support}${sourceIds.length ? `; ${sourceIds.join(", ")}` : ""}]`,
|
|
234
|
+
);
|
|
199
235
|
}
|
|
200
236
|
lines.push("");
|
|
201
237
|
}
|
|
@@ -216,10 +252,14 @@ function formatResults(engine: string, data: Record<string, unknown>): string {
|
|
|
216
252
|
|
|
217
253
|
if (engine === "all") {
|
|
218
254
|
const synthesis = data._synthesis as Record<string, unknown> | undefined;
|
|
219
|
-
const dedupedSources = data._sources as
|
|
255
|
+
const dedupedSources = data._sources as
|
|
256
|
+
| Array<Record<string, unknown>>
|
|
257
|
+
| undefined;
|
|
220
258
|
if (synthesis?.answer) {
|
|
221
259
|
renderSynthesis(lines, synthesis, dedupedSources || [], 6);
|
|
222
|
-
lines.push(
|
|
260
|
+
lines.push(
|
|
261
|
+
"*Synthesized from Perplexity, Bing Copilot, and Google AI*\n",
|
|
262
|
+
);
|
|
223
263
|
return lines.join("\n").trim();
|
|
224
264
|
}
|
|
225
265
|
|
|
@@ -261,7 +301,9 @@ function formatResults(engine: string, data: Record<string, unknown>): string {
|
|
|
261
301
|
function formatDeepResearch(data: Record<string, unknown>): string {
|
|
262
302
|
const lines: string[] = [];
|
|
263
303
|
const confidence = data._confidence as Record<string, unknown> | undefined;
|
|
264
|
-
const dedupedSources = data._sources as
|
|
304
|
+
const dedupedSources = data._sources as
|
|
305
|
+
| Array<Record<string, unknown>>
|
|
306
|
+
| undefined;
|
|
265
307
|
const synthesis = data._synthesis as Record<string, unknown> | undefined;
|
|
266
308
|
|
|
267
309
|
lines.push("# Deep Research Report\n");
|
|
@@ -271,26 +313,41 @@ function formatDeepResearch(data: Record<string, unknown>): string {
|
|
|
271
313
|
const enginesFailed = (confidence.enginesFailed as string[]) || [];
|
|
272
314
|
const agreementLevel = String(confidence.agreementLevel || "mixed");
|
|
273
315
|
const firstPartySourceCount = Number(confidence.firstPartySourceCount || 0);
|
|
274
|
-
const sourceTypeBreakdown = confidence.sourceTypeBreakdown as
|
|
316
|
+
const sourceTypeBreakdown = confidence.sourceTypeBreakdown as
|
|
317
|
+
| Record<string, number>
|
|
318
|
+
| undefined;
|
|
275
319
|
|
|
276
320
|
lines.push("## Confidence\n");
|
|
277
321
|
lines.push(`- Agreement: ${formatAgreementLevel(agreementLevel)}`);
|
|
278
|
-
lines.push(
|
|
322
|
+
lines.push(
|
|
323
|
+
`- Engines responded: ${enginesResponded.map(formatEngineName).join(", ") || "none"}`,
|
|
324
|
+
);
|
|
279
325
|
if (enginesFailed.length > 0) {
|
|
280
|
-
lines.push(
|
|
326
|
+
lines.push(
|
|
327
|
+
`- Engines failed: ${enginesFailed.map(formatEngineName).join(", ")}`,
|
|
328
|
+
);
|
|
281
329
|
}
|
|
282
|
-
lines.push(
|
|
330
|
+
lines.push(
|
|
331
|
+
`- Top source consensus: ${confidence.topSourceConsensus || 0}/3 engines`,
|
|
332
|
+
);
|
|
283
333
|
lines.push(`- Total unique sources: ${confidence.sourcesCount || 0}`);
|
|
284
334
|
lines.push(`- Official sources: ${confidence.officialSourceCount || 0}`);
|
|
285
335
|
lines.push(`- First-party sources: ${firstPartySourceCount}`);
|
|
286
|
-
lines.push(
|
|
336
|
+
lines.push(
|
|
337
|
+
`- Fetch success rate: ${confidence.fetchedSourceSuccessRate || 0}`,
|
|
338
|
+
);
|
|
287
339
|
if (sourceTypeBreakdown && Object.keys(sourceTypeBreakdown).length > 0) {
|
|
288
|
-
lines.push(
|
|
340
|
+
lines.push(
|
|
341
|
+
`- Source mix: ${Object.entries(sourceTypeBreakdown)
|
|
342
|
+
.map(([type, count]) => `${humanizeSourceType(type)} ${count}`)
|
|
343
|
+
.join(", ")}`,
|
|
344
|
+
);
|
|
289
345
|
}
|
|
290
346
|
lines.push("");
|
|
291
347
|
}
|
|
292
348
|
|
|
293
|
-
if (synthesis?.answer)
|
|
349
|
+
if (synthesis?.answer)
|
|
350
|
+
renderSynthesis(lines, synthesis, dedupedSources || [], 8);
|
|
294
351
|
|
|
295
352
|
lines.push("## Engine Perspectives\n");
|
|
296
353
|
for (const engine of ["perplexity", "bing", "google"]) {
|
|
@@ -317,18 +374,22 @@ function formatDeepResearch(data: Record<string, unknown>): string {
|
|
|
317
374
|
return lines.join("\n").trim();
|
|
318
375
|
}
|
|
319
376
|
|
|
320
|
-
function formatCodingTask(
|
|
377
|
+
function formatCodingTask(
|
|
378
|
+
data: Record<string, unknown> | Record<string, Record<string, unknown>>,
|
|
379
|
+
): string {
|
|
321
380
|
const lines: string[] = [];
|
|
322
381
|
|
|
323
382
|
// Check if it's multi-engine result
|
|
324
383
|
const hasMultipleEngines = "gemini" in data || "copilot" in data;
|
|
325
|
-
|
|
384
|
+
|
|
326
385
|
if (hasMultipleEngines) {
|
|
327
386
|
// Multi-engine result
|
|
328
387
|
for (const [engineName, result] of Object.entries(data)) {
|
|
329
388
|
const r = result as Record<string, unknown>;
|
|
330
|
-
lines.push(
|
|
331
|
-
|
|
389
|
+
lines.push(
|
|
390
|
+
`## ${engineName.charAt(0).toUpperCase() + engineName.slice(1)}\n`,
|
|
391
|
+
);
|
|
392
|
+
|
|
332
393
|
if (r.error) {
|
|
333
394
|
lines.push(`⚠️ Error: ${r.error}\n`);
|
|
334
395
|
} else {
|
|
@@ -390,27 +451,47 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
390
451
|
Type.Literal("gem"),
|
|
391
452
|
],
|
|
392
453
|
{
|
|
393
|
-
description:
|
|
454
|
+
description:
|
|
455
|
+
'Engine to use. "all" fans out to Perplexity, Bing, and Google in parallel (default).',
|
|
394
456
|
default: "all",
|
|
395
457
|
},
|
|
396
458
|
),
|
|
397
|
-
synthesize: Type.Optional(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
459
|
+
synthesize: Type.Optional(
|
|
460
|
+
Type.Boolean({
|
|
461
|
+
description:
|
|
462
|
+
'When true and engine is "all", deduplicates sources across engines and feeds them to Gemini for a single grounded synthesis. Adds ~30s but saves tokens and improves answer quality.',
|
|
463
|
+
default: false,
|
|
464
|
+
}),
|
|
465
|
+
),
|
|
466
|
+
fullAnswer: Type.Optional(
|
|
467
|
+
Type.Boolean({
|
|
468
|
+
description:
|
|
469
|
+
"When true, returns the complete answer instead of a truncated preview (default: false, answers are shortened to ~300 chars to save tokens).",
|
|
470
|
+
default: false,
|
|
471
|
+
}),
|
|
472
|
+
),
|
|
405
473
|
}),
|
|
406
474
|
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
407
|
-
const {
|
|
408
|
-
query
|
|
475
|
+
const {
|
|
476
|
+
query,
|
|
477
|
+
engine = "all",
|
|
478
|
+
synthesize = false,
|
|
479
|
+
fullAnswer = false,
|
|
480
|
+
} = params as {
|
|
481
|
+
query: string;
|
|
482
|
+
engine: string;
|
|
483
|
+
synthesize?: boolean;
|
|
484
|
+
fullAnswer?: boolean;
|
|
409
485
|
};
|
|
410
486
|
|
|
411
487
|
if (!cdpAvailable()) {
|
|
412
488
|
return {
|
|
413
|
-
content: [
|
|
489
|
+
content: [
|
|
490
|
+
{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: "cdp.mjs missing — try reinstalling: pi install git:github.com/apmantza/GreedySearch-pi",
|
|
493
|
+
},
|
|
494
|
+
],
|
|
414
495
|
details: {} as { raw?: Record<string, unknown> },
|
|
415
496
|
};
|
|
416
497
|
}
|
|
@@ -422,7 +503,7 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
422
503
|
// Track progress for "all" engine mode
|
|
423
504
|
const completed = new Set<string>();
|
|
424
505
|
|
|
425
|
-
const onProgress = (eng: string,
|
|
506
|
+
const onProgress = (eng: string, _status: "done" | "error") => {
|
|
426
507
|
completed.add(eng);
|
|
427
508
|
const parts: string[] = [];
|
|
428
509
|
for (const e of ALL_ENGINES) {
|
|
@@ -432,13 +513,21 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
432
513
|
if (synthesize && completed.size >= 3) parts.push("🔄 synthesizing");
|
|
433
514
|
|
|
434
515
|
onUpdate?.({
|
|
435
|
-
content: [
|
|
516
|
+
content: [
|
|
517
|
+
{ type: "text", text: `**Searching...** ${parts.join(" · ")}` },
|
|
518
|
+
],
|
|
436
519
|
details: { _progress: true },
|
|
437
520
|
} as any);
|
|
438
521
|
};
|
|
439
522
|
|
|
440
523
|
try {
|
|
441
|
-
const data = await runSearch(
|
|
524
|
+
const data = await runSearch(
|
|
525
|
+
engine,
|
|
526
|
+
query,
|
|
527
|
+
flags,
|
|
528
|
+
signal,
|
|
529
|
+
engine === "all" ? onProgress : undefined,
|
|
530
|
+
);
|
|
442
531
|
const text = formatResults(engine, data);
|
|
443
532
|
return {
|
|
444
533
|
content: [{ type: "text", text: text || "No results returned." }],
|
|
@@ -464,7 +553,8 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
464
553
|
"deduplicates and ranks sources by consensus, fetches content from top sources, " +
|
|
465
554
|
"and synthesizes via Gemini. Returns a structured research document with confidence scores. " +
|
|
466
555
|
"Use for architecture decisions, library comparisons, best practices, or any research where the answer matters.",
|
|
467
|
-
promptSnippet:
|
|
556
|
+
promptSnippet:
|
|
557
|
+
"Deep multi-engine research with source deduplication and synthesis",
|
|
468
558
|
parameters: Type.Object({
|
|
469
559
|
query: Type.String({ description: "The research question" }),
|
|
470
560
|
}),
|
|
@@ -473,14 +563,16 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
473
563
|
|
|
474
564
|
if (!cdpAvailable()) {
|
|
475
565
|
return {
|
|
476
|
-
content: [
|
|
566
|
+
content: [
|
|
567
|
+
{ type: "text", text: "cdp.mjs missing — try reinstalling." },
|
|
568
|
+
],
|
|
477
569
|
details: {} as { raw?: Record<string, unknown> },
|
|
478
570
|
};
|
|
479
571
|
}
|
|
480
572
|
|
|
481
573
|
const completed = new Set<string>();
|
|
482
574
|
|
|
483
|
-
const onProgress = (eng: string,
|
|
575
|
+
const onProgress = (eng: string, _status: "done" | "error") => {
|
|
484
576
|
completed.add(eng);
|
|
485
577
|
const parts: string[] = [];
|
|
486
578
|
for (const e of ALL_ENGINES) {
|
|
@@ -490,14 +582,22 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
490
582
|
if (completed.size >= 3) parts.push("🔄 synthesizing");
|
|
491
583
|
|
|
492
584
|
onUpdate?.({
|
|
493
|
-
content: [
|
|
585
|
+
content: [
|
|
586
|
+
{ type: "text", text: `**Researching...** ${parts.join(" · ")}` },
|
|
587
|
+
],
|
|
494
588
|
details: { _progress: true },
|
|
495
589
|
} as any);
|
|
496
590
|
};
|
|
497
591
|
|
|
498
592
|
try {
|
|
499
593
|
// Run deep research (includes full answers, synthesis, and source fetching)
|
|
500
|
-
const data = await runSearch(
|
|
594
|
+
const data = await runSearch(
|
|
595
|
+
"all",
|
|
596
|
+
query,
|
|
597
|
+
["--deep-research"],
|
|
598
|
+
signal,
|
|
599
|
+
onProgress,
|
|
600
|
+
);
|
|
501
601
|
const text = formatDeepResearch(data);
|
|
502
602
|
return {
|
|
503
603
|
content: [{ type: "text", text: text || "No results returned." }],
|
|
@@ -529,13 +629,10 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
529
629
|
parameters: Type.Object({
|
|
530
630
|
task: Type.String({ description: "The coding task or question" }),
|
|
531
631
|
engine: Type.Union(
|
|
532
|
-
[
|
|
533
|
-
Type.Literal("all"),
|
|
534
|
-
Type.Literal("gemini"),
|
|
535
|
-
Type.Literal("copilot"),
|
|
536
|
-
],
|
|
632
|
+
[Type.Literal("all"), Type.Literal("gemini"), Type.Literal("copilot")],
|
|
537
633
|
{
|
|
538
|
-
description:
|
|
634
|
+
description:
|
|
635
|
+
'Engine to use. "all" runs both Gemini and Copilot in parallel.',
|
|
539
636
|
default: "gemini",
|
|
540
637
|
},
|
|
541
638
|
),
|
|
@@ -548,22 +645,35 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
548
645
|
Type.Literal("debug"),
|
|
549
646
|
],
|
|
550
647
|
{
|
|
551
|
-
description:
|
|
648
|
+
description:
|
|
649
|
+
"Task mode: code (default), review (code review), plan (architect review), test (write tests), debug (root cause analysis)",
|
|
552
650
|
default: "code",
|
|
553
651
|
},
|
|
554
652
|
),
|
|
555
|
-
context: Type.Optional(
|
|
556
|
-
|
|
557
|
-
|
|
653
|
+
context: Type.Optional(
|
|
654
|
+
Type.String({
|
|
655
|
+
description: "Optional code context/snippet to include with the task",
|
|
656
|
+
}),
|
|
657
|
+
),
|
|
558
658
|
}),
|
|
559
659
|
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
560
|
-
const {
|
|
561
|
-
task
|
|
660
|
+
const {
|
|
661
|
+
task,
|
|
662
|
+
engine = "gemini",
|
|
663
|
+
mode = "code",
|
|
664
|
+
context,
|
|
665
|
+
} = params as {
|
|
666
|
+
task: string;
|
|
667
|
+
engine: string;
|
|
668
|
+
mode: string;
|
|
669
|
+
context?: string;
|
|
562
670
|
};
|
|
563
671
|
|
|
564
672
|
if (!cdpAvailable()) {
|
|
565
673
|
return {
|
|
566
|
-
content: [
|
|
674
|
+
content: [
|
|
675
|
+
{ type: "text", text: "cdp.mjs missing — try reinstalling." },
|
|
676
|
+
],
|
|
567
677
|
details: {} as { raw?: Record<string, unknown> },
|
|
568
678
|
};
|
|
569
679
|
}
|
|
@@ -573,38 +683,65 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
573
683
|
|
|
574
684
|
try {
|
|
575
685
|
onUpdate?.({
|
|
576
|
-
content: [
|
|
686
|
+
content: [
|
|
687
|
+
{
|
|
688
|
+
type: "text",
|
|
689
|
+
text: `**Coding task...** 🔄 ${engine === "all" ? "Gemini + Copilot" : engine} (${mode} mode)`,
|
|
690
|
+
},
|
|
691
|
+
],
|
|
577
692
|
details: { _progress: true },
|
|
578
693
|
} as any);
|
|
579
694
|
|
|
580
|
-
const data = await new Promise<Record<string, unknown>>(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
695
|
+
const data = await new Promise<Record<string, unknown>>(
|
|
696
|
+
(resolve, reject) => {
|
|
697
|
+
const proc = spawn(
|
|
698
|
+
"node",
|
|
699
|
+
[`${__dir}/coding-task.mjs`, task, ...flags],
|
|
700
|
+
{
|
|
701
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
702
|
+
},
|
|
703
|
+
);
|
|
704
|
+
let out = "";
|
|
705
|
+
let err = "";
|
|
706
|
+
|
|
707
|
+
const onAbort = () => {
|
|
708
|
+
proc.kill("SIGTERM");
|
|
709
|
+
reject(new Error("Aborted"));
|
|
710
|
+
};
|
|
711
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
712
|
+
|
|
713
|
+
proc.stdout.on("data", (d: Buffer) => (out += d));
|
|
714
|
+
proc.stderr.on("data", (d: Buffer) => {
|
|
715
|
+
err += d;
|
|
716
|
+
});
|
|
717
|
+
proc.on("close", (code: number) => {
|
|
718
|
+
signal?.removeEventListener("abort", onAbort);
|
|
719
|
+
if (code !== 0) {
|
|
720
|
+
reject(
|
|
721
|
+
new Error(
|
|
722
|
+
err.trim() || `coding-task.mjs exited with code ${code}`,
|
|
723
|
+
),
|
|
724
|
+
);
|
|
725
|
+
} else {
|
|
726
|
+
try {
|
|
727
|
+
resolve(JSON.parse(out.trim()));
|
|
728
|
+
} catch {
|
|
729
|
+
reject(
|
|
730
|
+
new Error(
|
|
731
|
+
`Invalid JSON from coding-task.mjs: ${out.slice(0, 200)}`,
|
|
732
|
+
),
|
|
733
|
+
);
|
|
734
|
+
}
|
|
601
735
|
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// Timeout after 3 minutes
|
|
739
|
+
setTimeout(() => {
|
|
740
|
+
proc.kill("SIGTERM");
|
|
741
|
+
reject(new Error("Coding task timed out after 180s"));
|
|
742
|
+
}, 180000);
|
|
743
|
+
},
|
|
744
|
+
);
|
|
608
745
|
|
|
609
746
|
const text = formatCodingTask(data);
|
|
610
747
|
return {
|