@apmantza/greedysearch-pi 1.7.7 → 1.8.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/CHANGELOG.md +18 -0
- package/bin/coding-task.mjs +27 -1
- package/bin/search.mjs +260 -1539
- package/index.ts +134 -421
- package/package.json +1 -1
- package/src/github.mjs +6 -1
- package/src/search/chrome.mjs +223 -0
- package/src/search/constants.mjs +38 -0
- package/src/search/defaults.mjs +15 -0
- package/src/search/engines.mjs +58 -0
- package/src/search/fetch-source.mjs +230 -0
- package/src/search/output.mjs +59 -0
- package/src/search/sources.mjs +446 -0
- package/src/search/synthesis-runner.mjs +64 -0
- package/src/search/synthesis.mjs +212 -0
- package/src/tools/deep-research-handler.ts +37 -0
- package/src/tools/greedy-search-handler.ts +58 -0
- package/src/tools/shared.ts +131 -0
- package/src/types.ts +104 -0
package/index.ts
CHANGED
|
@@ -1,421 +1,134 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GreedySearch Pi Extension
|
|
3
|
-
*
|
|
4
|
-
* Adds
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Reports streaming progress as each engine completes.
|
|
8
|
-
* Requires Chrome to be running (or it auto-launches a dedicated instance).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}),
|
|
136
|
-
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
137
|
-
const {
|
|
138
|
-
query,
|
|
139
|
-
engine = "all",
|
|
140
|
-
depth = "standard",
|
|
141
|
-
fullAnswer: fullAnswerParam,
|
|
142
|
-
} = params as {
|
|
143
|
-
query: string;
|
|
144
|
-
engine: string;
|
|
145
|
-
depth?: "fast" | "standard" | "deep";
|
|
146
|
-
fullAnswer?: boolean;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
if (!cdpAvailable()) {
|
|
150
|
-
return {
|
|
151
|
-
content: [
|
|
152
|
-
{
|
|
153
|
-
type: "text",
|
|
154
|
-
text: "cdp.mjs missing — try reinstalling: pi install git:github.com/apmantza/GreedySearch-pi",
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
details: {} as { raw?: Record<string, unknown> },
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const flags: string[] = [];
|
|
162
|
-
// Default to full answer for single-engine queries (unless explicitly set to false)
|
|
163
|
-
// For multi-engine, default to truncated to save tokens during synthesis
|
|
164
|
-
const fullAnswer = fullAnswerParam ?? (engine !== "all");
|
|
165
|
-
if (fullAnswer) flags.push("--full");
|
|
166
|
-
if (depth === "deep") flags.push("--depth", "deep");
|
|
167
|
-
else if (depth === "standard" && engine === "all") flags.push("--synthesize");
|
|
168
|
-
|
|
169
|
-
const completed = new Set<string>();
|
|
170
|
-
|
|
171
|
-
const onProgress = (eng: string, _status: "done" | "error") => {
|
|
172
|
-
completed.add(eng);
|
|
173
|
-
const parts: string[] = [];
|
|
174
|
-
for (const e of ALL_ENGINES) {
|
|
175
|
-
if (completed.has(e)) parts.push(`✅ ${e} done`);
|
|
176
|
-
else parts.push(`⏳ ${e}`);
|
|
177
|
-
}
|
|
178
|
-
if (depth !== "fast" && completed.size >= 3)
|
|
179
|
-
parts.push("🔄 synthesizing");
|
|
180
|
-
|
|
181
|
-
onUpdate?.({
|
|
182
|
-
content: [
|
|
183
|
-
{ type: "text", text: `**Searching...** ${parts.join(" · ")}` },
|
|
184
|
-
],
|
|
185
|
-
details: { _progress: true },
|
|
186
|
-
} as any);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
const data = await runSearch(
|
|
191
|
-
engine,
|
|
192
|
-
query,
|
|
193
|
-
flags,
|
|
194
|
-
signal,
|
|
195
|
-
engine === "all" ? onProgress : undefined,
|
|
196
|
-
);
|
|
197
|
-
const text = formatResults(engine, data);
|
|
198
|
-
return {
|
|
199
|
-
content: [{ type: "text", text: text || "No results returned." }],
|
|
200
|
-
details: { raw: data },
|
|
201
|
-
};
|
|
202
|
-
} catch (e) {
|
|
203
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
204
|
-
return {
|
|
205
|
-
content: [{ type: "text", text: `Search failed: ${msg}` }],
|
|
206
|
-
details: {} as { raw?: Record<string, unknown> },
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// ─── deep_research ─────────────────────────────────────────────────────────
|
|
213
|
-
pi.registerTool({
|
|
214
|
-
name: "deep_research",
|
|
215
|
-
label: "Deep Research (legacy)",
|
|
216
|
-
description:
|
|
217
|
-
"DEPRECATED — Use greedy_search with depth: 'deep' instead. " +
|
|
218
|
-
"Comprehensive multi-engine research with source fetching and synthesis.",
|
|
219
|
-
promptSnippet: "Deep multi-engine research (legacy alias to greedy_search)",
|
|
220
|
-
parameters: Type.Object({
|
|
221
|
-
query: Type.String({ description: "The research question" }),
|
|
222
|
-
}),
|
|
223
|
-
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
224
|
-
const { query } = params as { query: string };
|
|
225
|
-
|
|
226
|
-
if (!cdpAvailable()) {
|
|
227
|
-
return {
|
|
228
|
-
content: [
|
|
229
|
-
{ type: "text", text: "cdp.mjs missing — try reinstalling." },
|
|
230
|
-
],
|
|
231
|
-
details: {} as { raw?: Record<string, unknown> },
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const completed = new Set<string>();
|
|
236
|
-
|
|
237
|
-
const onProgress = (eng: string, _status: "done" | "error") => {
|
|
238
|
-
completed.add(eng);
|
|
239
|
-
const parts: string[] = [];
|
|
240
|
-
for (const e of ALL_ENGINES) {
|
|
241
|
-
if (completed.has(e)) parts.push(`✅ ${e}`);
|
|
242
|
-
else parts.push(`⏳ ${e}`);
|
|
243
|
-
}
|
|
244
|
-
if (completed.size >= 3) parts.push("🔄 synthesizing");
|
|
245
|
-
|
|
246
|
-
onUpdate?.({
|
|
247
|
-
content: [
|
|
248
|
-
{ type: "text", text: `**Researching...** ${parts.join(" · ")}` },
|
|
249
|
-
],
|
|
250
|
-
details: { _progress: true },
|
|
251
|
-
} as any);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
const data = await runSearch(
|
|
256
|
-
"all",
|
|
257
|
-
query,
|
|
258
|
-
["--deep"],
|
|
259
|
-
signal,
|
|
260
|
-
onProgress,
|
|
261
|
-
);
|
|
262
|
-
const text = formatDeepResearch(data);
|
|
263
|
-
return {
|
|
264
|
-
content: [{ type: "text", text: text || "No results returned." }],
|
|
265
|
-
details: { raw: data },
|
|
266
|
-
};
|
|
267
|
-
} catch (e) {
|
|
268
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
269
|
-
return {
|
|
270
|
-
content: [{ type: "text", text: `Deep research failed: ${msg}` }],
|
|
271
|
-
details: {} as { raw?: Record<string, unknown> },
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// ─── coding_task ───────────────────────────────────────────────────────────
|
|
278
|
-
pi.registerTool({
|
|
279
|
-
name: "coding_task",
|
|
280
|
-
label: "Coding Task",
|
|
281
|
-
description:
|
|
282
|
-
"Delegate a coding task to Gemini and/or Copilot via browser automation. " +
|
|
283
|
-
"Returns extracted code blocks and explanations. Supports multiple modes: " +
|
|
284
|
-
"'code' (write/modify code), 'review' (senior engineer code review), " +
|
|
285
|
-
"'plan' (architect risk assessment), 'test' (edge case testing), " +
|
|
286
|
-
"'debug' (fresh-eyes root cause analysis). " +
|
|
287
|
-
"Best for getting a 'second opinion' on hard problems, debugging tricky issues, " +
|
|
288
|
-
"or risk-assessing major refactors. Use engine 'all' for both perspectives.",
|
|
289
|
-
promptSnippet: "Browser-based coding assistant with Gemini and Copilot",
|
|
290
|
-
parameters: Type.Object({
|
|
291
|
-
task: Type.String({ description: "The coding task or question" }),
|
|
292
|
-
engine: Type.Union(
|
|
293
|
-
[Type.Literal("all"), Type.Literal("gemini"), Type.Literal("copilot")],
|
|
294
|
-
{
|
|
295
|
-
description:
|
|
296
|
-
'Engine to use. "all" runs both Gemini and Copilot in parallel.',
|
|
297
|
-
default: "gemini",
|
|
298
|
-
},
|
|
299
|
-
),
|
|
300
|
-
mode: Type.Union(
|
|
301
|
-
[
|
|
302
|
-
Type.Literal("code"),
|
|
303
|
-
Type.Literal("review"),
|
|
304
|
-
Type.Literal("plan"),
|
|
305
|
-
Type.Literal("test"),
|
|
306
|
-
Type.Literal("debug"),
|
|
307
|
-
],
|
|
308
|
-
{
|
|
309
|
-
description:
|
|
310
|
-
"Task mode: code (default), review (code review), plan (architect review), test (write tests), debug (root cause analysis)",
|
|
311
|
-
default: "code",
|
|
312
|
-
},
|
|
313
|
-
),
|
|
314
|
-
context: Type.Optional(
|
|
315
|
-
Type.String({
|
|
316
|
-
description: "Optional code context/snippet to include with the task",
|
|
317
|
-
}),
|
|
318
|
-
),
|
|
319
|
-
}),
|
|
320
|
-
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
321
|
-
const {
|
|
322
|
-
task,
|
|
323
|
-
engine = "gemini",
|
|
324
|
-
mode = "code",
|
|
325
|
-
context,
|
|
326
|
-
} = params as {
|
|
327
|
-
task: string;
|
|
328
|
-
engine: string;
|
|
329
|
-
mode: string;
|
|
330
|
-
context?: string;
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
if (!cdpAvailable()) {
|
|
334
|
-
return {
|
|
335
|
-
content: [
|
|
336
|
-
{ type: "text", text: "cdp.mjs missing — try reinstalling." },
|
|
337
|
-
],
|
|
338
|
-
details: {} as { raw?: Record<string, unknown> },
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const flags: string[] = ["--engine", engine, "--mode", mode];
|
|
343
|
-
if (context) flags.push("--context", context);
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
onUpdate?.({
|
|
347
|
-
content: [
|
|
348
|
-
{
|
|
349
|
-
type: "text",
|
|
350
|
-
text: `**Coding task...** 🔄 ${engine === "all" ? "Gemini + Copilot" : engine} (${mode} mode)`,
|
|
351
|
-
},
|
|
352
|
-
],
|
|
353
|
-
details: { _progress: true },
|
|
354
|
-
} as any);
|
|
355
|
-
|
|
356
|
-
const data = await new Promise<Record<string, unknown>>(
|
|
357
|
-
(resolve, reject) => {
|
|
358
|
-
const proc = spawn(
|
|
359
|
-
"node",
|
|
360
|
-
[join(__dir, "bin", "coding-task.mjs"), task, ...flags],
|
|
361
|
-
{
|
|
362
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
363
|
-
},
|
|
364
|
-
);
|
|
365
|
-
let out = "";
|
|
366
|
-
let err = "";
|
|
367
|
-
|
|
368
|
-
const onAbort = () => {
|
|
369
|
-
proc.kill("SIGTERM");
|
|
370
|
-
reject(new Error("Aborted"));
|
|
371
|
-
};
|
|
372
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
373
|
-
|
|
374
|
-
proc.stdout.on("data", (d: Buffer) => (out += d));
|
|
375
|
-
proc.stderr.on("data", (d: Buffer) => {
|
|
376
|
-
err += d;
|
|
377
|
-
});
|
|
378
|
-
proc.on("close", (code: number) => {
|
|
379
|
-
signal?.removeEventListener("abort", onAbort);
|
|
380
|
-
if (code !== 0) {
|
|
381
|
-
reject(
|
|
382
|
-
new Error(
|
|
383
|
-
err.trim() || `coding-task.mjs exited with code ${code}`,
|
|
384
|
-
),
|
|
385
|
-
);
|
|
386
|
-
} else {
|
|
387
|
-
try {
|
|
388
|
-
resolve(JSON.parse(out.trim()));
|
|
389
|
-
} catch {
|
|
390
|
-
reject(
|
|
391
|
-
new Error(
|
|
392
|
-
`Invalid JSON from coding-task.mjs: ${out.slice(0, 200)}`,
|
|
393
|
-
),
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// Timeout after 3 minutes
|
|
400
|
-
setTimeout(() => {
|
|
401
|
-
proc.kill("SIGTERM");
|
|
402
|
-
reject(new Error("Coding task timed out after 180s"));
|
|
403
|
-
}, 180000);
|
|
404
|
-
},
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
const text = formatCodingTask(data);
|
|
408
|
-
return {
|
|
409
|
-
content: [{ type: "text", text: text || "No response." }],
|
|
410
|
-
details: { raw: data },
|
|
411
|
-
};
|
|
412
|
-
} catch (e) {
|
|
413
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
414
|
-
return {
|
|
415
|
-
content: [{ type: "text", text: `Coding task failed: ${msg}` }],
|
|
416
|
-
details: {} as { raw?: Record<string, unknown> },
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* GreedySearch Pi Extension
|
|
3
|
+
*
|
|
4
|
+
* Adds `greedy_search`, `deep_research`, and `coding_task` tools to Pi.
|
|
5
|
+
* Tool handlers are split into separate modules for maintainability.
|
|
6
|
+
*
|
|
7
|
+
* Reports streaming progress as each engine completes.
|
|
8
|
+
* Requires Chrome to be running (or it auto-launches a dedicated instance).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
import { Type } from "@sinclair/typebox";
|
|
16
|
+
|
|
17
|
+
import { formatCodingTask } from "./src/formatters/coding.js";
|
|
18
|
+
import { registerGreedySearchTool } from "./src/tools/greedy-search-handler.js";
|
|
19
|
+
import { registerDeepResearchTool } from "./src/tools/deep-research-handler.js";
|
|
20
|
+
import { cdpAvailable, type ProgressUpdate } from "./src/tools/shared.js";
|
|
21
|
+
import { DEFAULTS } from "./src/search/defaults.js";
|
|
22
|
+
|
|
23
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
26
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
27
|
+
if (!cdpAvailable(__dir)) {
|
|
28
|
+
ctx.ui.notify(
|
|
29
|
+
"GreedySearch: cdp.mjs missing from package directory — try reinstalling: pi install git:github.com/apmantza/GreedySearch-pi",
|
|
30
|
+
"warning",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ─── greedy_search ────────────────────────────────────────────────────────
|
|
36
|
+
registerGreedySearchTool(pi, __dir);
|
|
37
|
+
|
|
38
|
+
// ─── deep_research ────────────────────────────────────────────────────────
|
|
39
|
+
registerDeepResearchTool(pi, __dir);
|
|
40
|
+
|
|
41
|
+
// ─── coding_task ───────────────────────────────────────────────────────────
|
|
42
|
+
pi.registerTool({
|
|
43
|
+
name: "coding_task",
|
|
44
|
+
label: "Coding Task",
|
|
45
|
+
description:
|
|
46
|
+
"Delegate a coding task to Gemini and/or Copilot via browser automation. " +
|
|
47
|
+
"Returns extracted code blocks and explanations. Supports multiple modes: " +
|
|
48
|
+
"'code' (write/modify code), 'review' (senior engineer code review), " +
|
|
49
|
+
"'plan' (architect risk assessment), 'test' (edge case testing), " +
|
|
50
|
+
"'debug' (fresh-eyes root cause analysis). " +
|
|
51
|
+
"Best for getting a 'second opinion' on hard problems, debugging tricky issues, " +
|
|
52
|
+
"or risk-assessing major refactors. Use engine 'all' for both perspectives.",
|
|
53
|
+
promptSnippet: "Browser-based coding assistant with Gemini and Copilot",
|
|
54
|
+
parameters: Type.Object({
|
|
55
|
+
task: Type.String({ description: "The coding task or question" }),
|
|
56
|
+
engine: Type.Union(
|
|
57
|
+
[Type.Literal("all"), Type.Literal("gemini"), Type.Literal("copilot")],
|
|
58
|
+
{
|
|
59
|
+
description:
|
|
60
|
+
'Engine to use. "all" runs both Gemini and Copilot in parallel.',
|
|
61
|
+
default: "gemini",
|
|
62
|
+
},
|
|
63
|
+
),
|
|
64
|
+
mode: Type.Union(
|
|
65
|
+
[Type.Literal("code"), Type.Literal("review"), Type.Literal("plan"), Type.Literal("test"), Type.Literal("debug")],
|
|
66
|
+
{
|
|
67
|
+
description: "Task mode: code (default), review (code review), plan (architect review), test (write tests), debug (root cause analysis)",
|
|
68
|
+
default: "code",
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
context: Type.Optional(Type.String({ description: "Optional code context/snippet to include with the task" })),
|
|
72
|
+
}),
|
|
73
|
+
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
74
|
+
const { task, engine = "gemini", mode = "code", context } = params as {
|
|
75
|
+
task: string; engine: string; mode: string; context?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (!cdpAvailable(__dir)) {
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: "cdp.mjs missing — try reinstalling." }],
|
|
81
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const flags: string[] = ["--engine", engine, "--mode", mode];
|
|
86
|
+
if (context) flags.push("--context", context);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
onUpdate?.({
|
|
90
|
+
content: [{ type: "text", text: `**Coding task...** 🔄 ${engine === "all" ? "Gemini + Copilot" : engine} (${mode} mode)` }],
|
|
91
|
+
details: { _progress: true },
|
|
92
|
+
} satisfies ProgressUpdate);
|
|
93
|
+
|
|
94
|
+
const data = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
95
|
+
const proc = spawn("node", [join(__dir, "bin", "coding-task.mjs"), task, ...flags], {
|
|
96
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
97
|
+
});
|
|
98
|
+
let out = "";
|
|
99
|
+
let err = "";
|
|
100
|
+
|
|
101
|
+
const onAbort = () => { proc.kill("SIGTERM"); reject(new Error("Aborted")); };
|
|
102
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
103
|
+
|
|
104
|
+
proc.stdout.on("data", (d: Buffer) => (out += d));
|
|
105
|
+
proc.stderr.on("data", (d: Buffer) => (err += d));
|
|
106
|
+
proc.on("close", (code: number) => {
|
|
107
|
+
signal?.removeEventListener("abort", onAbort);
|
|
108
|
+
if (code !== 0) {
|
|
109
|
+
reject(new Error(err.trim() || `coding-task.mjs exited with code ${code}`));
|
|
110
|
+
} else {
|
|
111
|
+
try { resolve(JSON.parse(out.trim())); }
|
|
112
|
+
catch { reject(new Error(`Invalid JSON from coding-task.mjs: ${out.slice(0, 200)}`)); }
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Timeout after 3 minutes
|
|
117
|
+
setTimeout(() => { proc.kill("SIGTERM"); reject(new Error(`Coding task timed out after ${DEFAULTS.CODING_TASK_TIMEOUT / 1000}s`)); }, DEFAULTS.CODING_TASK_TIMEOUT);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const text = formatCodingTask(data);
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: text || "No response." }],
|
|
123
|
+
details: { raw: data },
|
|
124
|
+
};
|
|
125
|
+
} catch (e) {
|
|
126
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: `Coding task failed: ${msg}` }],
|
|
129
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apmantza/greedysearch-pi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Pi extension: multi-engine AI search (Perplexity, Bing Copilot, Google AI) via browser automation -- NO API KEYS needed. Extracts answers with sources, optional Gemini synthesis. Grounded AI answers from real browser interactions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/src/github.mjs
CHANGED
|
@@ -173,7 +173,12 @@ export async function fetchGitHubContent(url) {
|
|
|
173
173
|
fetchTree(owner, repo, ref || "HEAD"),
|
|
174
174
|
]);
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
// If repo info failed (e.g. 404 — repo doesn't exist), bail out
|
|
177
|
+
if (repoInfo.status === "rejected") {
|
|
178
|
+
return { ok: false, error: repoInfo.reason?.message || "Repo not found" };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const info = repoInfo.value;
|
|
177
182
|
const readmeText = readme.status === "fulfilled" ? readme.value : "";
|
|
178
183
|
const treeItems = tree.status === "fulfilled" ? tree.value : [];
|
|
179
184
|
|