@wrongstack/core 0.256.1 → 0.257.2

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.
@@ -3,6 +3,8 @@ export { T as TaskItem, c as computeTaskItemProgress, f as formatTaskList, a as
3
3
  export { a as WstackPathOptions, W as WstackPaths, p as projectHash, b as projectSlug, r as resolveWstackPaths, w as wstackGlobalRoot } from '../wstack-paths-CJjEwPXn.js';
4
4
  export { expectDefined } from './expect-defined.js';
5
5
  import { a as ModelsDevPayload, t as CustomModelDefinition } from '../config-DuAu23zm.js';
6
+ import * as https from 'node:https';
7
+ import { Dispatcher } from 'undici';
6
8
  export { a as TaskPriority, b as TaskStatus, T as TaskType } from '../task-graph-u1q9Jkyk.js';
7
9
 
8
10
  interface AtomicWriteOptions {
@@ -602,4 +604,100 @@ declare function mergeModelsPayload(base: ModelsDevPayload, overlay: ModelsDevPa
602
604
  */
603
605
  declare function mergeCustomModelDefs(providerCustomModels: Record<string, CustomModelDefinition> | undefined, configModels: Record<string, CustomModelDefinition> | undefined): Record<string, CustomModelDefinition> | undefined;
604
606
 
605
- export { type AtomicWriteOptions, type BuildChildEnvOptions, type CompileFail, type CompileResult, type DeepMergeOptions, FORBIDDEN_PROTO_KEYS, type FileLockOptions, type MessageRepairReport, type MessageRepairResult, type NewlineStyle, type OutputLineGuard, type RequestTokenBreakdown, type SafeParseResult, type ToolOutputSerializerOptions, type UnifiedDiffOptions, type ValidationError, type ValidationResult, assertNever, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, recordActualUsage, repairToolUseAdjacency, resetCalibration, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut };
607
+ /**
608
+ * Shared IP-address guards for SSRF protection.
609
+ *
610
+ * Exported so `fetch.ts` (tools), `web-search/index.ts` (plugins), and any
611
+ * other package that needs to validate IPs can all consume the same logic.
612
+ * Any future additions (e.g. extra CIDR blocks) need only be made here.
613
+ */
614
+ /**
615
+ * True if `addr` is in a private / loopback / link-local / reserved / CGNAT /
616
+ * multicast range. `net.isIP` is called by the caller first so `addr` is
617
+ * guaranteed to be a canonical dotted-quad at this point.
618
+ */
619
+ declare function isPrivateIPv4(addr: string): boolean;
620
+ /**
621
+ * True if `raw` (an IPv6 literal, already lowercased) is loopback / unique-local /
622
+ * link-local / unspecified / IPv4-mapped-private.
623
+ */
624
+ declare function isPrivateIPv6(raw: string): boolean;
625
+ /**
626
+ * Expand an IPv6 string into exactly 8 16-bit numbers. Handles `::` compression.
627
+ * Returns null on malformed input — caller should treat that as "block".
628
+ */
629
+ declare function expandIPv6(addr: string): number[] | null;
630
+ /**
631
+ * Convenience: throw if `hostname` resolves to a private / loopback IP.
632
+ * Use as a pre-flight check before opening a socket.
633
+ *
634
+ * ⚠️ This is not sufficient alone — connections must also use a pinned
635
+ * dispatcher (so the OS re-uses the already-resolved address) or the same
636
+ * check must be applied after every redirect hop. See `guardedLookup` in
637
+ * `fetch.ts` for the connection-level enforcement.
638
+ */
639
+ declare function assertNotPrivateHost(hostname: string): Promise<void>;
640
+
641
+ /**
642
+ * `dispatcher-types.d.ts` — Unify `https.Agent` and `undici.Dispatcher` types.
643
+ *
644
+ * Problem: `https.Agent` (Node.js built-in) and `undici.Dispatcher` implement the
645
+ * same interface at runtime — `fetch`'s `RequestInit.dispatcher` accepts anything
646
+ * with a `dispatch(req, opts)` method. However, TypeScript's type definitions
647
+ * treat them as unrelated types because they come from different `@types/*` packages
648
+ * (or undici@7 bundles its own Dispatcher type that conflicts with @types/node's
649
+ * copy of the same concept via undici-types).
650
+ *
651
+ * Solution: This module augments the global `RequestInit` type so that
652
+ * `https.Agent` is accepted as a valid `dispatcher` value without a cast.
653
+ *
654
+ * Usage:
655
+ * import '@wrongstack/core/utils/dispatcher-types';
656
+ * const agent = new https.Agent({ rejectUnauthorized: false });
657
+ * fetch(url, { dispatcher: agent }); // ✅ no cast needed
658
+ *
659
+ * Alternatively, use `as HttpsAgentAsDispatcher` to silence any remaining
660
+ * conflicts at the call site.
661
+ *
662
+ * Verified at runtime: `https.Agent` has a `dispatch(req, opts)` method and
663
+ * is callable by the built-in fetch implementation — this shim is a type-level
664
+ * correction only, not a runtime polyfill.
665
+ */
666
+
667
+
668
+
669
+ /**
670
+ * Marker type: a value that fetch's `RequestInit.dispatcher` accepts at runtime.
671
+ * Both `https.Agent` and `undici.Dispatcher` satisfy this structural interface.
672
+ */
673
+ type HttpDispatcher = Pick<Dispatcher, 'dispatch'>;
674
+
675
+ /**
676
+ * Augment `RequestInit` so that `https.Agent` is a valid dispatcher type.
677
+ * Without this, TypeScript rejects `https.Agent` for `dispatcher` because the
678
+ * two agent types are not structurally compatible in the installed @types set.
679
+ */
680
+ declare global {
681
+ interface RequestInit {
682
+ /**
683
+ * Accepts `https.Agent` in addition to `undici.Dispatcher`.
684
+ * Runtime type-check is performed by Node.js / undici — this declaration
685
+ * only tells TypeScript the same thing.
686
+ */
687
+ dispatcher?: HttpDispatcher | undefined;
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Use this cast at call sites where `https.Agent` must be passed to a function
693
+ * typed for `undici.Dispatcher`. Documents the trust boundary: the cast is safe
694
+ * because both types share a `dispatch(req, opts)` method at runtime.
695
+ *
696
+ * @example
697
+ * import type { HttpsAgentAsDispatcher } from '@wrongstack/core';
698
+ * const agent = new https.Agent({ rejectUnauthorized: false });
699
+ * fetch(url, { dispatcher: agent as HttpsAgentAsDispatcher });
700
+ */
701
+ type HttpsAgentAsDispatcher = https.Agent;
702
+
703
+ export { type AtomicWriteOptions, type BuildChildEnvOptions, type CompileFail, type CompileResult, type DeepMergeOptions, FORBIDDEN_PROTO_KEYS, type FileLockOptions, type HttpDispatcher, type HttpsAgentAsDispatcher, type MessageRepairReport, type MessageRepairResult, type NewlineStyle, type OutputLineGuard, type RequestTokenBreakdown, type SafeParseResult, type ToolOutputSerializerOptions, type UnifiedDiffOptions, type ValidationError, type ValidationResult, assertNever, assertNotPrivateHost, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expandIPv6, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, recordActualUsage, repairToolUseAdjacency, resetCalibration, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut };
@@ -3,6 +3,8 @@ import * as fs from 'fs/promises';
3
3
  import * as path2 from 'path';
4
4
  import { isAbsolute, resolve } from 'path';
5
5
  import * as os from 'os';
6
+ import * as dns from 'dns/promises';
7
+ import * as net from 'net';
6
8
 
7
9
  // src/utils/atomic-write.ts
8
10
  async function atomicWrite(targetPath, content, opts = {}) {
@@ -1004,12 +1006,15 @@ function getCachedEstimate(key, compute) {
1004
1006
  const existing = ESTIMATE_CACHE.get(key);
1005
1007
  if (existing !== void 0) return existing;
1006
1008
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1007
- const keys = [...ESTIMATE_CACHE.keys()];
1008
- for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
1009
- ESTIMATE_CACHE.delete(expectDefined(keys[i]));
1009
+ let evicted = 0;
1010
+ const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
1011
+ for (const k of ESTIMATE_CACHE.keys()) {
1012
+ if (evicted >= maxEvict) break;
1013
+ ESTIMATE_CACHE.delete(k);
1014
+ evicted++;
1010
1015
  }
1011
1016
  }
1012
- const estimate = compute();
1017
+ const estimate = compute(key);
1013
1018
  ESTIMATE_CACHE.set(key, estimate);
1014
1019
  return estimate;
1015
1020
  }
@@ -1018,13 +1023,11 @@ function estimateToolInputTokens(input) {
1018
1023
  if (input === null || typeof input !== "object") {
1019
1024
  return RoughTokenEstimate(String(input));
1020
1025
  }
1021
- const key = JSON.stringify(input);
1022
- return getCachedEstimate(key, () => RoughTokenEstimate(key));
1026
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
1023
1027
  }
1024
1028
  function estimateToolResultTokens(content) {
1025
1029
  if (typeof content === "string") return RoughTokenEstimate(content);
1026
- const key = JSON.stringify(content);
1027
- return getCachedEstimate(key, () => RoughTokenEstimate(key));
1030
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
1028
1031
  }
1029
1032
  function estimateTextTokens(text) {
1030
1033
  return RoughTokenEstimate(text);
@@ -1660,7 +1663,96 @@ function mergeCustomModelDefs(providerCustomModels, configModels) {
1660
1663
  if (Object.keys(out).length === 0) return void 0;
1661
1664
  return out;
1662
1665
  }
1666
+ function isPrivateIPv4(addr) {
1667
+ const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
1668
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
1669
+ return true;
1670
+ }
1671
+ const [a, b, c] = parts;
1672
+ if (a === 0) return true;
1673
+ if (a === 10) return true;
1674
+ if (a === 127) return true;
1675
+ if (a === 169 && b === 254) return true;
1676
+ if (a === 172 && b >= 16 && b <= 31) return true;
1677
+ if (a === 192 && b === 168) return true;
1678
+ if (a === 192 && b === 0 && c === 0) return true;
1679
+ if (a === 100 && b >= 64 && b <= 127) return true;
1680
+ if (a >= 224) return true;
1681
+ return false;
1682
+ }
1683
+ function isPrivateIPv6(raw) {
1684
+ const lower = raw.toLowerCase();
1685
+ if (lower === "::" || lower === "::1") return true;
1686
+ const groups = expandIPv6(lower);
1687
+ if (!groups) return true;
1688
+ if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
1689
+ const a = (groups[6] ?? 0) >> 8;
1690
+ const b = (groups[6] ?? 0) & 255;
1691
+ const c = (groups[7] ?? 0) >> 8;
1692
+ const d = (groups[7] ?? 0) & 255;
1693
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
1694
+ }
1695
+ const high = groups[0] ?? 0;
1696
+ if ((high & 65024) === 64512) return true;
1697
+ if ((high & 65472) === 65152) return true;
1698
+ if ((high & 65280) === 65280) return true;
1699
+ return false;
1700
+ }
1701
+ function expandIPv6(addr) {
1702
+ const parts = addr.split("::");
1703
+ if (parts.length > 2) return null;
1704
+ const parseGroups = (s) => {
1705
+ if (s === "") return [];
1706
+ const out = [];
1707
+ for (const g of s.split(":")) {
1708
+ if (g.length === 0 || g.length > 4) return null;
1709
+ const n = Number.parseInt(g, 16);
1710
+ if (Number.isNaN(n) || n < 0 || n > 65535) return null;
1711
+ out.push(n);
1712
+ }
1713
+ return out;
1714
+ };
1715
+ if (parts.length === 1) {
1716
+ const groups = parseGroups(parts[0] ?? "");
1717
+ if (!groups || groups.length !== 8) return null;
1718
+ return groups;
1719
+ }
1720
+ const head = parseGroups(parts[0] ?? "");
1721
+ const tail = parseGroups(parts[1] ?? "");
1722
+ if (!head || !tail) return null;
1723
+ const fill = 8 - head.length - tail.length;
1724
+ if (fill < 0) return null;
1725
+ return [...head, ...new Array(fill).fill(0), ...tail];
1726
+ }
1727
+ async function assertNotPrivateHost(hostname) {
1728
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1729
+ if (host === "localhost" || host.endsWith(".localhost")) {
1730
+ throw new Error("fetch: blocked localhost target");
1731
+ }
1732
+ const ipVersion = net.isIP(host);
1733
+ if (ipVersion === 4) {
1734
+ if (isPrivateIPv4(host)) {
1735
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
1736
+ }
1737
+ } else if (ipVersion === 6) {
1738
+ if (isPrivateIPv6(host)) {
1739
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
1740
+ }
1741
+ } else {
1742
+ try {
1743
+ const records = await dns.lookup(host, { all: true });
1744
+ for (const r of records) {
1745
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
1746
+ if (bad) {
1747
+ throw new Error(`fetch: resolved to private address ${r.address}`);
1748
+ }
1749
+ }
1750
+ } catch (err) {
1751
+ if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
1752
+ }
1753
+ }
1754
+ }
1663
1755
 
1664
- export { FORBIDDEN_PROTO_KEYS, assertNever, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, computeTaskItemProgress, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expectDefined, formatTaskList, formatTaskProgress, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, projectHash, projectSlug, recordActualUsage, repairToolUseAdjacency, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut, wstackGlobalRoot };
1756
+ export { FORBIDDEN_PROTO_KEYS, assertNever, assertNotPrivateHost, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, computeTaskItemProgress, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expandIPv6, expectDefined, formatTaskList, formatTaskProgress, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, projectHash, projectSlug, recordActualUsage, repairToolUseAdjacency, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut, wstackGlobalRoot };
1665
1757
  //# sourceMappingURL=index.js.map
1666
1758
  //# sourceMappingURL=index.js.map