@duckmind/dm-windows-x64 0.62.3 → 0.62.9

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 (93) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +43 -183
  3. package/extensions/dm-context/src/context.js +1 -2
  4. package/extensions/dm-context/src/index.js +1 -2
  5. package/extensions/{greedysearch-dm/bin/cdp.mjs → dm-cua/bin/browser-cdp.mjs} +3 -3
  6. package/extensions/dm-cua/bin/browser-cua.mjs +1 -1
  7. package/extensions/{greedysearch-dm/bin/launch.mjs → dm-cua/bin/browser-launch.mjs} +12 -11
  8. package/extensions/dm-cua/index.js +1 -1
  9. package/extensions/dm-cua/src/browser-cua-lib.mjs +17 -12
  10. package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/constants.mjs +11 -11
  11. package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/node-runtime.mjs +1 -1
  12. package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/port-pid.mjs +1 -1
  13. package/extensions/dm-ponytail/extensions/ponytail.mjs +186 -0
  14. package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +41 -0
  15. package/extensions/dm-ponytail/hooks/copilot-hooks.json +21 -0
  16. package/extensions/dm-ponytail/hooks/ponytail-activate.js +59 -0
  17. package/extensions/dm-ponytail/hooks/ponytail-config.js +116 -0
  18. package/extensions/dm-ponytail/hooks/ponytail-instructions.js +81 -0
  19. package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +96 -0
  20. package/extensions/dm-ponytail/hooks/ponytail-runtime.js +79 -0
  21. package/extensions/dm-ponytail/hooks/ponytail-statusline.ps1 +24 -0
  22. package/extensions/dm-ponytail/hooks/ponytail-statusline.sh +18 -0
  23. package/extensions/dm-ponytail/hooks/ponytail-subagent.js +50 -0
  24. package/extensions/dm-ponytail/hooks/qoder-hooks.json +26 -0
  25. package/extensions/dm-ponytail/package.json +19 -0
  26. package/extensions/dm-ponytail/pi-extension/index.js +186 -0
  27. package/extensions/dm-ponytail/pi-extension/package.json +8 -0
  28. package/extensions/dm-ponytail/skills/ponytail/SKILL.md +120 -0
  29. package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +41 -0
  30. package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +44 -0
  31. package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +50 -0
  32. package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +71 -0
  33. package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +57 -0
  34. package/package.json +2 -6
  35. package/extensions/dm-caveman/extensions/caveman.js +0 -300
  36. package/extensions/dm-caveman/package.json +0 -20
  37. package/extensions/dm-goal/package.json +0 -23
  38. package/extensions/dm-goal/src/goal.js +0 -914
  39. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +0 -40
  40. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +0 -5
  41. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +0 -5
  42. package/extensions/greedysearch-dm/bin/gschrome.mjs +0 -30
  43. package/extensions/greedysearch-dm/bin/kill-visible.mjs +0 -7
  44. package/extensions/greedysearch-dm/bin/launch-visible.mjs +0 -13
  45. package/extensions/greedysearch-dm/bin/mcp.mjs +0 -400
  46. package/extensions/greedysearch-dm/bin/search.mjs +0 -635
  47. package/extensions/greedysearch-dm/bin/visible.mjs +0 -22
  48. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +0 -344
  49. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +0 -354
  50. package/extensions/greedysearch-dm/extractors/common.mjs +0 -794
  51. package/extensions/greedysearch-dm/extractors/consensus.mjs +0 -527
  52. package/extensions/greedysearch-dm/extractors/consent.mjs +0 -449
  53. package/extensions/greedysearch-dm/extractors/gemini.mjs +0 -291
  54. package/extensions/greedysearch-dm/extractors/google-ai.mjs +0 -86
  55. package/extensions/greedysearch-dm/extractors/logically.mjs +0 -589
  56. package/extensions/greedysearch-dm/extractors/perplexity.mjs +0 -370
  57. package/extensions/greedysearch-dm/extractors/selectors.mjs +0 -32
  58. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +0 -199
  59. package/extensions/greedysearch-dm/index.js +0 -123
  60. package/extensions/greedysearch-dm/package.json +0 -27
  61. package/extensions/greedysearch-dm/skills/greedy-search/skill.md +0 -18
  62. package/extensions/greedysearch-dm/src/fetcher.mjs +0 -577
  63. package/extensions/greedysearch-dm/src/formatters/results.js +0 -95
  64. package/extensions/greedysearch-dm/src/formatters/sources.js +0 -57
  65. package/extensions/greedysearch-dm/src/formatters/synthesis.js +0 -49
  66. package/extensions/greedysearch-dm/src/github.mjs +0 -228
  67. package/extensions/greedysearch-dm/src/reddit.mjs +0 -154
  68. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +0 -340
  69. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +0 -156
  70. package/extensions/greedysearch-dm/src/search/chrome.mjs +0 -510
  71. package/extensions/greedysearch-dm/src/search/defaults.mjs +0 -10
  72. package/extensions/greedysearch-dm/src/search/engines.mjs +0 -79
  73. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +0 -470
  74. package/extensions/greedysearch-dm/src/search/file-sources.mjs +0 -31
  75. package/extensions/greedysearch-dm/src/search/output.mjs +0 -51
  76. package/extensions/greedysearch-dm/src/search/paths.mjs +0 -48
  77. package/extensions/greedysearch-dm/src/search/pdf.mjs +0 -64
  78. package/extensions/greedysearch-dm/src/search/progress.mjs +0 -109
  79. package/extensions/greedysearch-dm/src/search/query.mjs +0 -21
  80. package/extensions/greedysearch-dm/src/search/recovery.mjs +0 -49
  81. package/extensions/greedysearch-dm/src/search/research.mjs +0 -2255
  82. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +0 -61
  83. package/extensions/greedysearch-dm/src/search/simple-research.mjs +0 -418
  84. package/extensions/greedysearch-dm/src/search/sources.mjs +0 -412
  85. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +0 -127
  86. package/extensions/greedysearch-dm/src/search/synthesis.mjs +0 -202
  87. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +0 -209
  88. package/extensions/greedysearch-dm/src/tools/shared.js +0 -226
  89. package/extensions/greedysearch-dm/src/types.js +0 -0
  90. package/extensions/greedysearch-dm/src/utils/content.mjs +0 -38
  91. package/extensions/greedysearch-dm/src/utils/helpers.js +0 -22
  92. /package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/minimize.mjs +0 -0
  93. /package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/system-cmds.mjs +0 -0
@@ -1,202 +0,0 @@
1
- import { ALL_ENGINES } from "./constants.mjs";
2
- import { trimText } from "./sources.mjs";
3
- function escapeControlCharsInsideJsonStrings(text) {
4
- let out = "";
5
- let inString = false;
6
- let escaped = false;
7
- for (const char of String(text)) {
8
- if (escaped) {
9
- out += char;
10
- escaped = false;
11
- continue;
12
- }
13
- if (char === "\\") {
14
- out += char;
15
- escaped = true;
16
- continue;
17
- }
18
- if (char === '"') {
19
- inString = !inString;
20
- out += char;
21
- continue;
22
- }
23
- if (inString && char === `
24
- `)
25
- out += "\\n";
26
- else if (inString && char === "\r")
27
- out += "\\r";
28
- else if (inString && char === "\t")
29
- out += "\\t";
30
- else
31
- out += char;
32
- }
33
- return out;
34
- }
35
- export function parseStructuredJson(text) {
36
- if (!text)
37
- return null;
38
- let trimmed = String(text).trim();
39
- const beginIdx = trimmed.indexOf("BEGIN_JSON");
40
- const endIdx = trimmed.indexOf("END_JSON");
41
- if (beginIdx !== -1 && endIdx !== -1 && beginIdx < endIdx) {
42
- trimmed = trimmed.slice(beginIdx + "BEGIN_JSON".length, endIdx).trim();
43
- } else {
44
- const jsonStart = trimmed.indexOf("{");
45
- if (jsonStart > 0) {
46
- trimmed = trimmed.slice(jsonStart);
47
- }
48
- }
49
- const candidates = [
50
- trimmed,
51
- trimmed.replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/```$/i, "").trim()
52
- ];
53
- const firstBrace = trimmed.indexOf("{");
54
- const lastBrace = trimmed.lastIndexOf("}");
55
- if (firstBrace !== -1 && lastBrace !== -1 && firstBrace < lastBrace) {
56
- candidates.push(trimmed.slice(firstBrace, lastBrace + 1));
57
- }
58
- for (const candidate of [...candidates]) {
59
- const repaired = escapeControlCharsInsideJsonStrings(candidate);
60
- if (repaired !== candidate)
61
- candidates.push(repaired);
62
- }
63
- for (const candidate of candidates) {
64
- try {
65
- return JSON.parse(candidate);
66
- } catch {}
67
- }
68
- return null;
69
- }
70
- export function normalizeSynthesisPayload(payload, sources, fallbackAnswer = "") {
71
- const sourceIds = new Set(sources.map((source) => source.id));
72
- const agreementLevel = [
73
- "high",
74
- "medium",
75
- "low",
76
- "mixed",
77
- "conflicting"
78
- ].includes(payload?.agreement?.level) ? payload.agreement.level : "mixed";
79
- const claims = Array.isArray(payload?.claims) ? payload.claims.map((claim) => ({
80
- claim: trimText(claim?.claim || "", 260),
81
- support: ["strong", "moderate", "weak", "conflicting"].includes(claim?.support) ? claim.support : "moderate",
82
- sourceIds: Array.isArray(claim?.sourceIds) ? claim.sourceIds.filter((id) => sourceIds.has(id)) : []
83
- })).filter((claim) => claim.claim) : [];
84
- const recommendedSources = Array.isArray(payload?.recommendedSources) ? payload.recommendedSources.filter((id) => sourceIds.has(id)).slice(0, 6) : [];
85
- let cleanFallback = "";
86
- if (fallbackAnswer) {
87
- const firstBrace = fallbackAnswer.indexOf("{");
88
- const lastBrace = fallbackAnswer.lastIndexOf("}");
89
- if (firstBrace !== -1 && lastBrace !== -1 && firstBrace < lastBrace) {
90
- cleanFallback = fallbackAnswer.slice(firstBrace, lastBrace + 1);
91
- } else {
92
- cleanFallback = fallbackAnswer;
93
- }
94
- }
95
- return {
96
- answer: trimText(payload?.answer || cleanFallback || fallbackAnswer, 4000),
97
- agreement: {
98
- level: agreementLevel,
99
- summary: trimText(payload?.agreement?.summary || "", 280)
100
- },
101
- differences: Array.isArray(payload?.differences) ? payload.differences.map((item) => trimText(item, 220)).filter(Boolean).slice(0, 5) : [],
102
- caveats: Array.isArray(payload?.caveats) ? payload.caveats.map((item) => trimText(item, 220)).filter(Boolean).slice(0, 5) : [],
103
- claims,
104
- recommendedSources
105
- };
106
- }
107
- export function buildSynthesisPrompt(query, results, sources, { grounded = false } = {}) {
108
- const engineSummaries = {};
109
- for (const engine of ["perplexity", "bing", "google"]) {
110
- const result = results[engine];
111
- if (!result)
112
- continue;
113
- if (result.error) {
114
- engineSummaries[engine] = {
115
- status: "error",
116
- error: String(result.error)
117
- };
118
- continue;
119
- }
120
- engineSummaries[engine] = {
121
- status: "ok",
122
- answer: trimText(result.answer || "", grounded ? 4500 : 2200),
123
- sourceIds: sources.filter((source) => source.engines.includes(engine)).sort((a, b) => (a.perEngine[engine]?.rank || 99) - (b.perEngine[engine]?.rank || 99)).map((source) => source.id).slice(0, 6)
124
- };
125
- }
126
- const snippetChars = grounded ? 700 : 300;
127
- const sourceRegistry = sources.slice(0, grounded ? 10 : 8).map((source) => ({
128
- id: source.id,
129
- title: source.title,
130
- domain: source.domain,
131
- canonicalUrl: source.canonicalUrl,
132
- sourceType: source.sourceType,
133
- isOfficial: source.isOfficial,
134
- engines: source.engines,
135
- engineCount: source.engineCount,
136
- fetch: source.fetch?.attempted ? {
137
- ok: source.fetch.ok,
138
- publishedTime: source.fetch.publishedTime || "",
139
- byline: source.fetch.byline || "",
140
- snippet: trimText(source.fetch.snippet || "", snippetChars)
141
- } : undefined
142
- }));
143
- return [
144
- "You are a research synthesizer. Combine these search engine results into a single authoritative answer.",
145
- "",
146
- `Query: ${query}`,
147
- "",
148
- `Engine summaries:
149
- ${JSON.stringify(engineSummaries, null, 2)}`,
150
- "",
151
- `Source registry:
152
- ${JSON.stringify(sourceRegistry, null, 2)}`,
153
- "",
154
- "Instructions:",
155
- "- Write a clear, direct answer in markdown (use headers/bullets where they help readability)",
156
- "- Cite sources inline as [S1], [S2] etc. when making specific claims",
157
- "- Prefer sources with content (fetch.ok=true and non-empty snippet) for citations",
158
- "- Note where the engines agree or meaningfully disagree",
159
- "- List any important caveats or limitations",
160
- "- recommendedSources: the 2-4 source IDs most worth reading for this query",
161
- "",
162
- "Respond ONLY with a JSON object wrapped in BEGIN_JSON / END_JSON markers:",
163
- "",
164
- "BEGIN_JSON",
165
- JSON.stringify({
166
- answer: "<your markdown answer here>",
167
- agreement: {
168
- level: "high|medium|mixed|conflicting",
169
- summary: "<one sentence>"
170
- },
171
- differences: ["<notable difference between engines, if any>"],
172
- caveats: ["<important caveat or limitation>"],
173
- recommendedSources: ["S1", "S2"]
174
- }, null, 2),
175
- "END_JSON"
176
- ].join(`
177
- `);
178
- }
179
- export function buildConfidence(out) {
180
- const sources = Array.isArray(out._sources) ? out._sources : [];
181
- const topConsensus = sources.length > 0 ? sources[0]?.engineCount || 0 : 0;
182
- const officialSourceCount = sources.filter((source) => source.isOfficial).length;
183
- const firstPartySourceCount = sources.filter((source) => source.isOfficial || source.sourceType === "maintainer-blog").length;
184
- const fetchedAttempted = sources.filter((source) => source.fetch?.attempted).length;
185
- const fetchedSucceeded = sources.filter((source) => source.fetch?.ok).length;
186
- const sourceTypeBreakdown = sources.reduce((acc, source) => {
187
- acc[source.sourceType] = (acc[source.sourceType] || 0) + 1;
188
- return acc;
189
- }, {});
190
- const synthesisLevel = out._synthesis?.agreement?.level;
191
- return {
192
- sourcesCount: sources.length,
193
- topSourceConsensus: topConsensus,
194
- agreementLevel: synthesisLevel || (topConsensus >= 3 ? "high" : topConsensus >= 2 ? "medium" : "low"),
195
- enginesResponded: ALL_ENGINES.filter((engine) => out[engine]?.answer && !out[engine]?.error),
196
- enginesFailed: ALL_ENGINES.filter((engine) => out[engine]?.error),
197
- officialSourceCount,
198
- firstPartySourceCount,
199
- fetchedSourceSuccessRate: fetchedAttempted > 0 ? Number((fetchedSucceeded / fetchedAttempted).toFixed(2)) : 0,
200
- sourceTypeBreakdown
201
- };
202
- }
@@ -1,209 +0,0 @@
1
- import { Type } from "@sinclair/typebox";
2
- import { formatResults } from "../formatters/results.js";
3
- import {
4
- ALL_ENGINES,
5
- cdpAvailable,
6
- cdpMissingResult,
7
- errorResult,
8
- makeProgressTracker,
9
- runSearch,
10
- stripQuotes
11
- } from "./shared.js";
12
-
13
- class Text {
14
- text;
15
- paddingX;
16
- paddingY;
17
- constructor(text, paddingX = 0, paddingY = 0) {
18
- this.text = text;
19
- this.paddingX = paddingX;
20
- this.paddingY = paddingY;
21
- }
22
- render(width) {
23
- const horizontal = " ".repeat(this.paddingX);
24
- const blank = "";
25
- const contentWidth = Math.max(1, width - this.paddingX * 2);
26
- const lines = this.text.split(`
27
- `).flatMap((line) => {
28
- if (line.length <= contentWidth)
29
- return [`${horizontal}${line}`];
30
- const wrapped = [];
31
- for (let i = 0;i < line.length; i += contentWidth) {
32
- wrapped.push(`${horizontal}${line.slice(i, i + contentWidth)}`);
33
- }
34
- return wrapped;
35
- });
36
- return [
37
- ...Array.from({ length: this.paddingY }, () => blank),
38
- ...lines,
39
- ...Array.from({ length: this.paddingY }, () => blank)
40
- ];
41
- }
42
- invalidate() {}
43
- }
44
- export function registerGreedySearchTool(dm, baseDir) {
45
- dm.registerTool({
46
- name: "greedy_search",
47
- label: "Greedy Search",
48
- description: "WEB/RESEARCH SEARCH ONLY — searches live web via Perplexity, Google AI, ChatGPT, and Gemini, plus opt-in research through Semantic Scholar and Logically. " + "Research mode reuses the configured ~/.dm/greedyconfig engines for child searches and Gemini for planning/final synthesis. " + "Research mode is the centerpiece: it plans follow-up actions, fetches sources, audits citations, " + "and writes a structured research bundle on disk. Scale-aware: simple queries auto-classify and use a fast single-pass path. " + "Use for: library docs, recent framework changes, error messages, best practices, current events. " + "Reports streaming progress as each engine completes.",
49
- promptSnippet: "Multi-engine AI web search with streaming progress",
50
- parameters: Type.Object({
51
- query: Type.String({ description: "The search query" }),
52
- engine: Type.String({
53
- description: 'Engine to use: "all" (default), "perplexity", "google", "chatgpt", "gemini", "gem". Research engines: "semantic-scholar" (alias "s2") and "logically". "all" fans out to the configured engines and fetches top sources. Customize via ~/.dm/greedyconfig. Bing Copilot is still available as "bing" for signed-in users.',
54
- default: "all"
55
- }),
56
- synthesize: Type.Optional(Type.Boolean({
57
- description: 'Only for engine="all": synthesize the multi-engine results and fetched sources. Default: false.',
58
- default: false
59
- })),
60
- synthesizer: Type.Optional(Type.String({
61
- description: 'Synthesis engine for synthesize=true. Defaults to ~/.dm/greedyconfig synthesizer (currently "gemini" by default). Supported: "gemini", "chatgpt".'
62
- })),
63
- depth: Type.Optional(Type.String({
64
- description: 'Deprecated except "research". Use depth="research" for the iterative research workflow. Research child searches use ~/.dm/greedyconfig engines; Gemini handles research planning/final synthesis. Legacy values: "fast" skips source fetching; "standard"/"deep" alias synthesize=true.'
65
- })),
66
- breadth: Type.Optional(Type.Number({
67
- description: 'Only for depth="research": number of parallel research directions per round, 1-5 (default: 3).',
68
- default: 3
69
- })),
70
- iterations: Type.Optional(Type.Number({
71
- description: 'Only for depth="research": number of iterative research rounds, 1-3 (default: 2).',
72
- default: 2
73
- })),
74
- maxSources: Type.Optional(Type.Number({
75
- description: 'Only for depth="research": maximum fetched sources for the final report, 3-12.'
76
- })),
77
- researchOutDir: Type.Optional(Type.String({
78
- description: 'Only for depth="research": optional directory for the structured research bundle. Defaults to .dm/greedysearch-research/<timestamp>_<query>.'
79
- })),
80
- writeResearchBundle: Type.Optional(Type.Boolean({
81
- description: 'Only for depth="research": write the structured research bundle to disk (default true).',
82
- default: true
83
- })),
84
- fullAnswer: Type.Optional(Type.Boolean({
85
- description: "When true, returns the complete answer instead of a truncated preview (default: false, answers are shortened to ~300 chars to save tokens).",
86
- default: false
87
- })),
88
- headless: Type.Optional(Type.Boolean({
89
- description: "Set to false to show Chrome window (headless is the default). Set GREEDY_SEARCH_VISIBLE=1 to disable headless globally.",
90
- default: true
91
- })),
92
- visible: Type.Optional(Type.Boolean({
93
- description: "Set to true to always use visible Chrome for this search. Alias for headless: false.",
94
- default: false
95
- })),
96
- alwaysVisible: Type.Optional(Type.Boolean({
97
- description: "Set to true to keep GreedySearch in visible Chrome mode for this search. Alias for visible: true.",
98
- default: false
99
- }))
100
- }),
101
- execute: async (_toolCallId, params, signal, onUpdate) => {
102
- const { query, fullAnswer: fullAnswerParam } = params;
103
- const engine = stripQuotes(params.engine ?? "all") || "all";
104
- const depthRaw = stripQuotes(params.depth ?? "");
105
- const researchMode = depthRaw === "research";
106
- const legacyFast = depthRaw === "fast";
107
- const legacySynthesisDepth = depthRaw === "standard" || depthRaw === "deep";
108
- const synthesize = engine === "all" && !legacyFast && (params.synthesize === true || legacySynthesisDepth);
109
- const effectiveEngine = researchMode ? "all" : engine;
110
- const visible = params.visible === true || params.alwaysVisible === true || params.headless === false || process.env.GREEDY_SEARCH_VISIBLE === "1" || process.env.GREEDY_SEARCH_ALWAYS_VISIBLE === "1";
111
- const headless = !visible;
112
- if (!cdpAvailable(baseDir))
113
- return cdpMissingResult();
114
- const flags = [];
115
- const fullAnswer = fullAnswerParam ?? effectiveEngine !== "all";
116
- if (fullAnswer)
117
- flags.push("--full");
118
- if (researchMode) {
119
- flags.push("--depth", "research");
120
- if (typeof params.breadth === "number")
121
- flags.push("--breadth", String(params.breadth));
122
- if (typeof params.iterations === "number")
123
- flags.push("--iterations", String(params.iterations));
124
- if (typeof params.maxSources === "number")
125
- flags.push("--max-sources", String(params.maxSources));
126
- if (typeof params.researchOutDir === "string")
127
- flags.push("--research-out-dir", params.researchOutDir);
128
- if (params.writeResearchBundle === false)
129
- flags.push("--no-research-bundle");
130
- } else if (legacyFast)
131
- flags.push("--fast");
132
- else if (depthRaw === "deep")
133
- flags.push("--depth", "deep");
134
- else if (synthesize)
135
- flags.push("--synthesize");
136
- if (synthesize && typeof params.synthesizer === "string") {
137
- flags.push("--synthesizer", params.synthesizer);
138
- }
139
- const onProgress = makeProgressTracker(effectiveEngine === "all" ? ALL_ENGINES : [effectiveEngine], onUpdate, researchMode ? "Researching" : "Searching", synthesize && effectiveEngine === "all", query);
140
- try {
141
- const data = await runSearch(effectiveEngine, query, flags, `${baseDir}/bin/search.mjs`, signal, onProgress, { headless });
142
- const text = formatResults(effectiveEngine, data);
143
- return {
144
- content: [{ type: "text", text: text || "No results returned." }],
145
- details: { raw: data }
146
- };
147
- } catch (e) {
148
- return errorResult("Search failed", e);
149
- }
150
- },
151
- renderCall(args, theme) {
152
- const q = (args.query || "").slice(0, 60);
153
- const qDisplay = q.length < (args.query || "").length ? `${q}...` : q;
154
- const engineDisplay = args.engine && args.engine !== "all" ? theme.fg("dim", ` (${args.engine})`) : "";
155
- return new Text(`${theme.fg("toolTitle", theme.bold("greedy_search"))} "${theme.fg("accent", qDisplay)}"${engineDisplay}`, 0, 0);
156
- },
157
- renderResult(result, { expanded, isPartial }, theme) {
158
- if (isPartial) {
159
- const progressText = result.content.find((c) => c.type === "text")?.text;
160
- const display = progressText ? progressText.replace(/\*\*/g, "") : "Searching...";
161
- return new Text(theme.fg("warning", display), 0, 0);
162
- }
163
- const textContent = result.content.find((c) => c.type === "text");
164
- const raw = result.details?.raw;
165
- if (!expanded) {
166
- const needsHuman = raw?._needsHumanVerification;
167
- if (needsHuman) {
168
- return new Text(theme.fg("warning", " → Manual verification required"), 0, 0);
169
- }
170
- const synthesis = raw?._synthesis;
171
- const sources = raw?._sources;
172
- if (synthesis) {
173
- const sourceCount = Array.isArray(sources) ? sources.length : 0;
174
- const agreement = synthesis.agreement?.level;
175
- let summary = " → Synthesized";
176
- if (sourceCount > 0)
177
- summary += ` · ${sourceCount} source${sourceCount > 1 ? "s" : ""}`;
178
- if (agreement)
179
- summary += ` · ${agreement}`;
180
- return new Text(theme.fg("muted", summary), 0, 0);
181
- }
182
- const engineKeys = Object.keys(raw || {}).filter((k) => !k.startsWith("_"));
183
- let totalSources = 0;
184
- for (const key of engineKeys) {
185
- const eng = raw?.[key];
186
- const s = eng?.sources;
187
- if (Array.isArray(s))
188
- totalSources += s.length;
189
- }
190
- if (totalSources > 0) {
191
- return new Text(theme.fg("muted", ` → ${totalSources} source${totalSources > 1 ? "s" : ""}`), 0, 0);
192
- }
193
- const snippet = textContent?.text;
194
- if (snippet) {
195
- return new Text(theme.fg("warning", ` → ${snippet.slice(0, 80)}`), 0, 0);
196
- }
197
- return new Text(theme.fg("muted", " → Done"), 0, 0);
198
- }
199
- if (!textContent || textContent.type !== "text") {
200
- return new Text("", 0, 0);
201
- }
202
- const lines = textContent.text.split(`
203
- `).map((line) => theme.fg("toolOutput", line)).join(`
204
- `);
205
- return new Text(`
206
- ${lines}`, 0, 0);
207
- }
208
- });
209
- }
@@ -1,226 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { nodeRuntimeCommand } from "../utils/node-runtime.mjs";
3
- import { existsSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { ALL_ENGINES } from "../search/constants.mjs";
6
-
7
- export { ALL_ENGINES };
8
- export function stripQuotes(val) {
9
- return val.replace(/^"|"$/g, "");
10
- }
11
- export function cdpAvailable(baseDir) {
12
- return existsSync(join(baseDir, "bin", "cdp.mjs"));
13
- }
14
- export function cdpMissingResult() {
15
- return {
16
- content: [
17
- {
18
- type: "text",
19
- text: "cdp.mjs missing — try reinstalling: pi install git:github.com/apmantza/greedysearch-dm"
20
- }
21
- ],
22
- details: {}
23
- };
24
- }
25
- export function errorResult(prefix, e) {
26
- const msg = e instanceof Error ? e.message : String(e);
27
- return {
28
- content: [{ type: "text", text: `${prefix}: ${msg}` }],
29
- details: {}
30
- };
31
- }
32
- export function runSearch(engine, query, flags, searchBin, signal, onProgress, options = {}) {
33
- return new Promise((resolve, reject) => {
34
- const { headless = true } = options;
35
- const allFlags = [...flags];
36
- if (headless !== false && process.env.GREEDY_SEARCH_VISIBLE !== "1")
37
- allFlags.push("--headless");
38
- if (headless === false)
39
- allFlags.push("--always-visible");
40
- const procEnv = { ...process.env };
41
- if (headless === false) {
42
- procEnv.GREEDY_SEARCH_VISIBLE = "1";
43
- procEnv.GREEDY_SEARCH_ALWAYS_VISIBLE = "1";
44
- }
45
- const proc = spawn(nodeRuntimeCommand(), [searchBin, engine, "--inline", "--stdin", ...allFlags], { stdio: ["pipe", "pipe", "pipe"], env: procEnv });
46
- proc.stdin.write(query);
47
- proc.stdin.end();
48
- let out = "";
49
- let err = "";
50
- const MAX_ERR = 50 * 1024;
51
- let finished = false;
52
- const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
53
- const parsedTimeout = parseInt(process.env.GREEDY_SEARCH_TIMEOUT_MS || "", 10);
54
- const timeoutMs = Number.isNaN(parsedTimeout) ? DEFAULT_TIMEOUT_MS : parsedTimeout;
55
- const timer = setTimeout(() => {
56
- if (finished)
57
- return;
58
- finished = true;
59
- signal?.removeEventListener("abort", onAbort);
60
- if (process.platform === "win32")
61
- proc.kill();
62
- else
63
- proc.kill("SIGTERM");
64
- reject(new Error(`greedy-search child timed out after ${timeoutMs}ms (watchdog)`));
65
- }, timeoutMs);
66
- timer.unref();
67
- const onAbort = () => {
68
- if (finished)
69
- return;
70
- finished = true;
71
- clearTimeout(timer);
72
- proc.kill("SIGTERM");
73
- reject(new Error("Aborted"));
74
- };
75
- signal?.addEventListener("abort", onAbort, { once: true });
76
- proc.stderr.on("data", (d) => {
77
- err += d;
78
- if (err.length > MAX_ERR)
79
- err = err.slice(-MAX_ERR);
80
- const ENGINE_PROGRESS_RE = /^PROGRESS:(perplexity|google|chatgpt|bing|gemini|semantic-scholar|semanticscholar|s2|logically):(done|error|needs-human)$/;
81
- for (const line of d.toString().split(`
82
- `)) {
83
- const engineMatch = line.match(ENGINE_PROGRESS_RE);
84
- if (engineMatch && onProgress) {
85
- onProgress({
86
- type: "engine",
87
- engine: engineMatch[1],
88
- status: engineMatch[2]
89
- });
90
- }
91
- const synthMatch = line.match(/^PROGRESS:synthesis:(done|error|skipped)$/);
92
- if (synthMatch && onProgress) {
93
- onProgress({
94
- type: "engine",
95
- engine: "synthesis",
96
- status: synthMatch[1]
97
- });
98
- }
99
- const researchMatch = line.match(/^PROGRESS:research:(.+)$/);
100
- if (researchMatch && onProgress) {
101
- onProgress({
102
- type: "text",
103
- text: researchMatch[1]
104
- });
105
- }
106
- const barMatch = line.match(/^\[greedysearch\] (\[.+?\] .+)$/);
107
- if (barMatch && onProgress) {
108
- onProgress({
109
- type: "text",
110
- text: barMatch[1]
111
- });
112
- }
113
- const stageMatch = line.match(/^\[(perplexity|google|chatgpt|bing|gemini|semantic-scholar|logically)\] stage: (.+) \(\+\d+ms\)$/);
114
- if (stageMatch && onProgress) {
115
- onProgress({
116
- type: "text",
117
- text: `${stageMatch[1]}: ${stageMatch[2]}`
118
- });
119
- }
120
- }
121
- });
122
- proc.stdout.on("data", (d) => out += d);
123
- proc.on("close", (code) => {
124
- if (finished)
125
- return;
126
- finished = true;
127
- clearTimeout(timer);
128
- signal?.removeEventListener("abort", onAbort);
129
- if (code !== 0) {
130
- reject(new Error(err.trim() || `search.mjs exited with code ${code}`));
131
- } else {
132
- if (onProgress && engine !== "all") {
133
- onProgress({
134
- type: "engine",
135
- engine,
136
- status: "done"
137
- });
138
- }
139
- try {
140
- resolve(JSON.parse(out.trim()));
141
- } catch {
142
- reject(new Error(`Invalid JSON from search.mjs: ${out.slice(0, 200)}`));
143
- }
144
- }
145
- });
146
- });
147
- }
148
- function renderBar(done, total) {
149
- if (total <= 0)
150
- return "";
151
- const width = 16;
152
- const filled = Math.round(done / total * width);
153
- return "[" + "█".repeat(Math.min(filled, width)) + "░".repeat(Math.max(0, width - filled)) + "]";
154
- }
155
- function fmtDuration(ms) {
156
- if (ms < 1000)
157
- return "—";
158
- const s = Math.round(ms / 1000);
159
- if (s < 60)
160
- return `${s}s`;
161
- return `${Math.floor(s / 60)}m ${s % 60}s`;
162
- }
163
- export function makeProgressTracker(engines, onUpdate, suffix, showSynthesis, query) {
164
- const startedAt = Date.now();
165
- const completed = new Map;
166
- let latestBarText = "";
167
- function render() {
168
- const lines = [];
169
- lines.push(`**${suffix}...** ${query || ""}`.trim());
170
- const done = completed.size;
171
- if (engines.length > 1 && done > 0 && !latestBarText) {
172
- const elapsed = Date.now() - startedAt;
173
- const frac = Math.min(1, done / engines.length);
174
- const etaMs = frac > 0.01 ? Math.round(elapsed / frac - elapsed) : null;
175
- const bar = renderBar(done, engines.length);
176
- const eta = etaMs != null && etaMs > 0 ? fmtDuration(etaMs) : "—";
177
- lines.push(`${bar} ${done}/${engines.length} engines (ETA ${eta})`);
178
- }
179
- if (latestBarText)
180
- lines.push(latestBarText);
181
- const parts = [];
182
- for (const e of engines) {
183
- const s = completed.get(e);
184
- if (s === "done")
185
- parts.push(`✅ ${e} done`);
186
- else if (s === "error")
187
- parts.push(`❌ ${e} failed`);
188
- else if (s === "needs-human")
189
- parts.push(`\uD83D\uDD13 ${e} needs manual verification`);
190
- else
191
- parts.push(`⏳ ${e}`);
192
- }
193
- if (showSynthesis && done >= engines.length) {
194
- const synStatus = completed.get("synthesis");
195
- if (synStatus === "done")
196
- parts.push("✅ synthesized");
197
- else if (synStatus === "error")
198
- parts.push("❌ synthesis failed");
199
- else if (synStatus === "needs-human")
200
- parts.push("⏭️ synthesis skipped");
201
- else
202
- parts.push("\uD83D\uDD04 synthesizing");
203
- }
204
- if (parts.length > 0) {
205
- const statusLine = parts.join(" · ");
206
- lines.push(statusLine.length > 90 ? statusLine.slice(0, 88) + "…" : statusLine);
207
- }
208
- onUpdate?.({
209
- content: [{ type: "text", text: lines.join(`
210
- `) }],
211
- details: { _progress: true }
212
- });
213
- }
214
- return (update) => {
215
- if (update.type === "text") {
216
- if (update.text.startsWith("[")) {
217
- latestBarText = update.text;
218
- }
219
- render();
220
- return;
221
- }
222
- const { engine, status } = update;
223
- completed.set(engine, status);
224
- render();
225
- };
226
- }
File without changes
@@ -1,38 +0,0 @@
1
- export function trimContentHeadTail(content, maxChars = 8000) {
2
- if (!content || content.length <= maxChars) {
3
- return content;
4
- }
5
- const marker = `
6
-
7
- [...content trimmed...]
8
-
9
- `;
10
- const budget = maxChars - marker.length;
11
- const headSize = Math.floor(budget * 0.75);
12
- const tailSize = budget - headSize;
13
- let headEnd = headSize;
14
- while (headEnd > headSize - 100 && content[headEnd] !== `
15
- `) {
16
- headEnd--;
17
- }
18
- if (headEnd <= headSize - 100)
19
- headEnd = headSize;
20
- let tailStart = content.length - tailSize;
21
- while (tailStart < content.length - tailSize + 100 && content[tailStart] !== `
22
- `) {
23
- tailStart++;
24
- }
25
- if (tailStart >= content.length - tailSize + 100)
26
- tailStart = content.length - tailSize;
27
- const head = content.slice(0, headEnd).trimEnd();
28
- const tail = content.slice(tailStart).trimStart();
29
- return `${head}${marker}${tail}`;
30
- }
31
- export function truncateContent(content, maxChars = 8000) {
32
- if (!content || content.length <= maxChars) {
33
- return content;
34
- }
35
- const truncated = content.slice(0, maxChars);
36
- const lastSpace = truncated.lastIndexOf(" ");
37
- return lastSpace > 0 ? `${truncated.slice(0, lastSpace)}...` : `${truncated}...`;
38
- }