donsetch 5.0.0-beta.3 → 5.1.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **The web, for AI agents.** Fetch, search, crawl and screenshot from one local binary. Zero API keys, zero accounts.
4
4
 
5
- **v5 beta:** a new fetch engine. No browser in the fetch path: a wall is answered by the right client identity, the site's own challenge program run in-process, or the site's own public interface.
5
+ **v5:** a new fetch engine. No browser in the fetch path: a wall is answered by the right client identity, the site's own challenge program run in-process, or the site's own public interface.
6
6
 
7
7
  [![Release](https://img.shields.io/github/v/release/dondai44423/donsetch?color=00d4aa&style=flat-square)](https://github.com/dondai44423/donsetch/releases)
8
8
  [![CI](https://img.shields.io/github/actions/workflow/status/dondai44423/donsetch/ci.yml?label=CI&style=flat-square)](https://github.com/dondai44423/donsetch/actions/workflows/ci.yml)
@@ -103,7 +103,7 @@ donsetch update
103
103
  | `web_fetch` | Any URL as clean markdown, or only the part that answers: `must_contain` probes, `focus` passages, `toc` → `section`. Walls answered in-process, never by a browser. PDFs with OCR and per-page confidence, site adapters, page memory, batches of up to 12. |
104
104
  | `web_search` | Keyless multi-engine search: 10+ backends, consensus plus semantic reranking, query-aware official-source placement. Ranked URLs and snippets, never a scraped article dump. |
105
105
  | `web_crawl` | Best-first same-domain crawl. Sitemap plus frontier, `focus` ranking, elastic pacing, resume tokens, honest stop reasons. |
106
- | `web_screenshot` | Rendered PNG of any URL through DonSeTch's browser (used only for screenshots and login), with the usual URL safety guards. |
106
+ | `web_screenshot` | Rendered PNG of any URL through DonSeTch's browser (used only for screenshots), with the usual URL safety guards. |
107
107
 
108
108
  MCP results default to self-contained text: leading `[meta]` JSON followed by source evidence, including for unknown client bridges. PNGs remain image blocks.
109
109
 
@@ -115,7 +115,7 @@ Every failure is structured: a stable `code` (`wall.challenge`, `guard.ssrf`, `d
115
115
  - **PDFs done properly.** A custom PDFium FFI, no Python subprocess. Tables and borders come from the rendered bitmap, text from the glyph stream, and the trust audit flags exactly the regions that need OCR.
116
116
  - **Crawl with manners.** Per-host adaptive pacing that honors `Retry-After` and robots `Crawl-delay`, cross-process politeness so two crawls do not double a site's rate, near-duplicate detection, resume tokens that survive restarts, and honest stop reasons.
117
117
  - **Fetch that answers honestly.** `content_ok`, `thin`, `changed` with section diffs, and `must_contain` says `unverified` instead of a false "not found" when part of a page was unread. No fake success.
118
- - **Walls named, not guessed.** A bot wall names its vendor and what it asks for; a login wall is a declared `wall.auth` (fetch carries no session in v5; session support is planned).
118
+ - **Walls named, not guessed.** A bot wall names its vendor and what it asks for; a login wall is a declared `wall.auth` (the fetch carries no session; authenticated work belongs to a browser driver).
119
119
  - **Works everywhere.** Linux, macOS, Windows. npm, Homebrew, the Pi agent as a native extension, and the DeepSeek Harness as a first-class plugin.
120
120
 
121
121
  ## Sponsors
package/install.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  const http = require('http');
8
8
  const https = require('https');
9
+ const net = require('net');
9
10
  const tls = require('tls');
10
11
  const crypto = require('crypto');
11
12
  const fs = require('fs');
@@ -159,15 +160,30 @@ function requestDirect(target) {
159
160
  });
160
161
  }
161
162
 
163
+ // Basic credentials from the proxy URL's user:pass@ part, or null when
164
+ // it has none. The URL parser keeps them percent-encoded.
165
+ function proxyAuthorization(proxy) {
166
+ if (!proxy.username && !proxy.password) return null;
167
+ const decode = (part) => {
168
+ try { return decodeURIComponent(part); } catch (_) { return part; }
169
+ };
170
+ const pair = `${decode(proxy.username)}:${decode(proxy.password)}`;
171
+ return `Basic ${Buffer.from(pair).toString('base64')}`;
172
+ }
173
+
162
174
  function requestThroughProxy(target, proxy) {
163
175
  return new Promise((resolve, reject) => {
176
+ const authority = `${target.hostname}:${target.port || 443}`;
177
+ const connectHeaders = { Host: authority };
178
+ const authorization = proxyAuthorization(proxy);
179
+ if (authorization) connectHeaders['Proxy-Authorization'] = authorization;
164
180
  const connectTransport = proxy.protocol === 'https:' ? https : http;
165
181
  const proxyReq = connectTransport.request({
166
182
  hostname: proxy.hostname,
167
183
  port: proxy.port || (proxy.protocol === 'https:' ? 443 : 80),
168
184
  method: 'CONNECT',
169
- path: `${target.hostname}:${target.port || 443}`,
170
- headers: { Host: `${target.hostname}:${target.port || 443}` },
185
+ path: authority,
186
+ headers: connectHeaders,
171
187
  });
172
188
  proxyReq.once('connect', (response, socket) => {
173
189
  if (response.statusCode !== 200) {
@@ -184,10 +200,17 @@ function requestThroughProxy(target, proxy) {
184
200
  Accept: 'application/octet-stream',
185
201
  'User-Agent': 'donsetch-npm-installer',
186
202
  },
187
- agent: false,
203
+ // No `agent` key: Node uses createConnection only when no agent
204
+ // is in use, and `agent: false` means a fresh default Agent,
205
+ // which dials the host directly and leaves the tunnel unused.
206
+ // Without an agent the request also closes its connection, and
207
+ // with it the tunnel, once the response ends.
208
+ // The certificate is verified (tls.connect's default) against
209
+ // the release host; SNI takes host names only (RFC 6066 3).
188
210
  createConnection: () => tls.connect({
189
211
  socket,
190
- servername: target.hostname,
212
+ host: target.hostname,
213
+ servername: net.isIP(target.hostname) ? undefined : target.hostname,
191
214
  }),
192
215
  }, resolve);
193
216
  req.setTimeout(REQUEST_TIMEOUT_MS, () => req.destroy(new Error('request timeout')));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "donsetch",
3
- "version": "5.0.0-beta.3",
3
+ "version": "5.1.0",
4
4
  "description": "MCP server that gives AI agents the web: fetch, search, crawl and screenshot from one local Rust binary. Zero API keys, zero accounts, Chrome-true TLS.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "Bishesh Bhandari",
package/pi-extension.ts CHANGED
@@ -199,8 +199,8 @@ function startServer(): Promise<void> {
199
199
  }
200
200
  });
201
201
 
202
- // The daemon writes diagnostics to stderr (Xvfb warnings,
203
- // fallback notices, ghost telemetry). Pi's TUI surfaces stderr
202
+ // The daemon writes diagnostics to stderr (config warnings,
203
+ // rerank and OCR notices). Pi's TUI surfaces stderr
204
204
  // as user-facing output: raw forwarding used to pop non-fatal
205
205
  // warnings up (issue #95). Gate it:
206
206
  // - DONSETCH_DEBUG / DEBUG set: forward everything (dev mode)
@@ -449,14 +449,12 @@ function getSearchProvider(sc: any, dbg: any): string {
449
449
  }
450
450
 
451
451
  /**
452
- * Extract the fetch source label from model state with a
453
- * client-only diagnostics fallback. Compact MCP contracts keep
454
- * transport telemetry in _meta["com.donsetch/fetch-debug"], so the
455
- * tier can live there; older servers still expose it on
456
- * structuredContent.
457
- * Returns "cache" when tier is "1(warm)" (warm cookies, not a
458
- * fresh fetch), "ghost" when tier starts with "2" (browser
459
- * escalation), or "" for a normal tier-1 fetch.
452
+ * Extract the fetch source label from the `tier` field that servers
453
+ * before 5.0 report, in _meta["com.donsetch/fetch-debug"] or on
454
+ * structuredContent. Returns "cache" when tier is "1(warm)" (warm
455
+ * cookies, not a fresh fetch), "ghost" when tier starts with "2"
456
+ * (their browser tier), or "" otherwise. A 5.x server sends no
457
+ * tier and has no browser tier, so the result is always "".
460
458
  */
461
459
  function getFetchSource(sc: any, dbg: any): string {
462
460
  const tier = String(