Skip to content

feat(ext): perry-ext-undici — undici served by the native fetch stack - #7032

Merged
proggeramlug merged 5 commits into
PerryTS:mainfrom
jdalton:feat/ext-undici
Jul 30, 2026
Merged

feat(ext): perry-ext-undici — undici served by the native fetch stack#7032
proggeramlug merged 5 commits into
PerryTS:mainfrom
jdalton:feat/ext-undici

Conversation

@jdalton

@jdalton jdalton commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Serves import { ProxyAgent, setGlobalDispatcher, fetch, Agent, getGlobalDispatcher } from 'undici' from perry's native fetch/HTTP stack instead of AOT-compiling undici's ~50k lines of JS. The wrapper is thin glue — no reqwest/tokio deps of its own.

Why this matters

undici is Node's own HTTP stack (the engine behind global fetch), so it lands in a huge share of dependency graphs — and its module-init paths were the source of two compiler bugs fixed in #7021 (the class extends DOMException probe and zstd content-decoding). Serving it natively removes that surface from every compiled binary that pulls it in. Socket Firewall uses exactly ProxyAgent + setGlobalDispatcher (src/lib/proxy/fetch-proxy.ts).

API coverage

Export Status
new ProxyAgent(uri | {uri, token?}) ✅ (bad uri → UND_ERR_INVALID_ARG)
setGlobalDispatcher(agent) ✅ applies/clears the proxy on the native fetch client
fetch ✅ reuses the native fetch lowering, proxy-aware
new Agent(opts?) ✅ direct-connection dispatcher
getGlobalDispatcher()
agent.close()/.destroy() resolve (pool is process-global)
request() rejects with "use fetch()"
Pool/Client/MockAgent/interceptors compile-time unimplemented-API error (#463 strict)

How the proxy reaches the client

setGlobalDispatcher(proxyAgent)js_undici_set_global_dispatcher → new js_fetch_set_global_proxy(uri, token) symbol added to both perry-stdlib's fetch/mod.rs and perry-ext-fetch (mirrors; the linked archive wins). It builds a proxied reqwest::Client (Proxy::all + auth) stored behind an RwLock; every fetch path selects fetch_client(). Blast radius is nil for non-undici programs: with no dispatcher set, fetch_client() returns the existing HTTP_CLIENT unchanged (one RwLock read → Arc clone per fetch). reqwest gives real CONNECT tunneling for https targets. Because Node's global fetch is undici, this correctly makes perry's global fetch honor the dispatcher.

Wiring

New crates/perry-ext-undici (staticlib+rlib, perry-ffi only); codegen row family native_table/undici.rs + a HIR new ProxyAgent/Agent arm; [bindings.undici] in well_known_bindings.toml with an upstream provenance pin (lock-step per #7031); undici added to binding_needs_shared_tokio so the driver rebuilds it in the shared-tokio invocation; module_to_features("undici") = [] with the flip loop re-asserting web-fetch (mapping it to http-client would strip the very fetch impl the glue calls); NATIVE_MODULES + manifest entries + sandbox network unlock + regenerated d.ts.

Validation

  • cargo test -p perry --bin perry: 758 passed, 0 failed; manifest/unimplemented-API/stub gates green; clippy + fmt clean.
  • Crate units: 10/10 (option parsing + dispatcher wiring via a recording shim).
  • E2E (no node_modules; Python mock proxy): direct via: nonesetGlobalDispatcher(new ProxyAgent({uri, token}))via: 1, proxyAuth: Basic …new Agent() restores direct → string-form proxies w/o auth → request() rejects clearly. PASS.

Known gaps: namespace form new undici.ProxyAgent() and import { fetch as f } unsupported (latter matches node-fetch today). Should rebase on #7031 at merge (shared well_known_bindings.toml / NATIVE_MODULES). Pre-existing perry-ext-fetch test-link failure on the base commit is unrelated (verified via git stash).

Summary by CodeRabbit

  • New Features

    • Added native undici support, including fetch, Agent, ProxyAgent, global dispatchers, and proxy authentication.
    • Added configurable proxy routing for fetch requests.
    • Added Zstandard compression and decompression support.
    • Improved DOMException subclassing and RegExp call behavior.
  • Security

    • Added pinned security-tool audits, supply-chain checks, and automated soak-window validation.
    • Introduced a seven-day dependency release-age policy across supported package and crate workflows.
  • Documentation

    • Updated API references and typings with the supported undici interfaces.

jdalton added a commit to jdalton/perry that referenced this pull request Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c823a61-d867-4bc9-8492-f07016b4c32a

📥 Commits

Reviewing files that changed from the base of the PR and between bc14d47 and 108a5cb.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • Cargo.toml
  • changelog.d/7032-ext-undici.md
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-api-manifest/src/entries/part_4.rs
  • crates/perry-codegen/src/lower_call/native_table/mod.rs
  • crates/perry-codegen/src/lower_call/native_table/undici.rs
  • crates/perry-ext-fetch/src/lib.rs
  • crates/perry-ext-undici/Cargo.toml
  • crates/perry-ext-undici/src/lib.rs
📝 Walkthrough

Walkthrough

This PR adds native undici support with proxy-aware fetch routing, runtime and compilation fixes for npm CLI compatibility, and a repository-wide seven-day dependency soak policy with SRI-pinned security tooling and CI enforcement.

Changes

Native undici integration

Layer / File(s) Summary
Undici contracts and registration
Cargo.toml, crates/perry-ext-undici/*, crates/perry-api-manifest/*, crates/perry/well_known_bindings.toml, docs/api/*, workspace-architecture.json
Registers undici as an externalized native binding and documents its dispatcher, fetch, agent, and proxy APIs.
Undici dispatch and proxy implementation
crates/perry-hir/..., crates/perry-codegen/..., crates/perry-ext-undici/*, crates/perry-ext-fetch/src/lib.rs, crates/perry-stdlib/src/fetch/*
Lowers undici constructors and methods to native handlers, stores global dispatcher handles, and routes fetch requests through the selected proxy-aware client.
Undici build and platform integration
crates/perry/src/commands/compile/*, crates/perry/src/commands/sandbox_profile.rs, crates/perry/src/commands/stdlib_features.rs
Adds undici to native extension resolution, shared Tokio handling, Web Fetch feature selection, network permissions, and compile-time tests.

Runtime and compilation fixes

Layer / File(s) Summary
Runtime constructors and compression
crates/perry-codegen/*, crates/perry-runtime/*, crates/perry-ext-zlib/*
Adds DOMException subclass initialization, callable RegExp construction, and Zstandard synchronous, callback, and streaming codecs.
Optimized build feature reconciliation
crates/perry/src/commands/compile/optimized_libs/*
Filters auto-selected features against workspace declarations, preserves required Web Fetch support, and expands diagnostics for feature-gated link failures.
Archive panic-runtime deduplication
crates/perry/src/commands/compile/strip_dedup.rs, changelog.d/7021-real-npm-cli-compile-gaps.md
Adjusts panic/unwind symbol localization and member nomination during bundled archive deduplication.

Supply-chain soak and security tooling

Layer / File(s) Summary
Soak policy surfaces
.cargo/config.toml, .npmrc, .github/dependabot.yml, tools/*, package.json, .claude/skills/soak/*, changelog.d/6912-security-tooling-soak.md
Defines the seven-day Cargo, npm, pnpm, Taze, and Dependabot release-age policy and related commands.
Soak validation and dependency updates
scripts/soak/constants.mts, scripts/soak/paths.mts, scripts/soak/soak.mts, scripts/soak/update-deps.mts, scripts/soak/*test.mts
Adds parity checks, date and annotation validation, catalog checks, supported autofixes, dependency update flows, and unit/end-to-end tests.
Pinned tools and security workflows
external-tools.json, scripts/soak/external-tools.mts, .github/workflows/*, .github/zizmor.yml
Adds SRI-verified tool installation, platform asset handling, firewall shims, automated soak autofix PRs, and CI audits for workflows and AI configuration.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5874 — Overlaps in constructor and super(...) lowering for Error-like built-ins.
  • PerryTS/perry#6912 — Overlaps in the seven-day soak policy, tooling, and CI surfaces.
  • PerryTS/perry#7021 — Overlaps in feature filtering, Zstandard, DOMException, and RegExp compilation/runtime fixes.

Suggested labels: tooling

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it doesn't follow the required template sections like Summary, Changes, Related issue, Test plan, and Checklist. Reformat it to the repo template and add the missing Summary, Changes, Related issue, Test plan, and Checklist sections.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed It clearly describes the new perry-ext-undici support and native fetch-stack serving, matching the main change.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
.github/zizmor.yml (1)

32-38: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Scope the cache-poisoning ratchet to the known workflows.

disable: true also exempts workflows added later, unlike the excessive-permissions carve-out right below which names its debt explicitly. An ignore: list of the current release workflows keeps the grandfathering auditable and makes new surface fail the gate as intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/zizmor.yml around lines 32 - 38, Update the cache-poisoning
configuration in zizmor.yml to replace the global disable: true exemption with
an ignore list containing only the currently affected release workflows.
Preserve the existing grandfathered workflows, document the scoped debt
similarly to excessive-permissions, and ensure newly added workflows remain
subject to the lint check.
.github/workflows/zizmor.yml (1)

6-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Include the tool-pin sources in the PR path filter.

The audit runs a rack-installed binary, so a PR touching external-tools.json or scripts/soak/external-tools.mts (e.g. bumping the zizmor pin) skips this workflow entirely and only breaks after merge.

♻️ Proposed change
   pull_request:
-    paths: ['.github/**']
+    paths:
+      - '.github/**'
+      - 'external-tools.json'
+      - 'scripts/soak/external-tools.mts'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/zizmor.yml around lines 6 - 7, Update the pull_request
paths filter in the workflow trigger to include external-tools.json and
scripts/soak/external-tools.mts alongside .github/**, ensuring changes to the
tool-pin sources also run the zizmor audit.
scripts/soak/soak.mts (1)

83-130: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

npm-side excludes have no staleness/prune path.

The yaml side gets both a warning (staleExcludes) and pruning (fixWorkspaceYaml), so its ledger converges; a version-pinned min-release-age-exclude[]= entry whose removable has passed is never warned about and never pruned, so cleared npm bypasses accumulate indefinitely. Worth extending the same pair of helpers over .npmrc lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/soak/soak.mts` around lines 83 - 130, The npmrc validation in
checkNpmrcExcludes only checks annotation presence and date consistency, so
expired version-pinned excludes are neither reported nor removable. Extend the
npm-side soak flow with the same stale-exclude warning and pruning behavior used
by staleExcludes and fixWorkspaceYaml, operating on .npmrc lines while
preserving bare-name exclusions and valid active pins.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/security-audit.yml:
- Around line 128-137: Update the “Scan each skill” workflow to run skillspector
through the locked uv project defined by external-tools.json, using uv sync
--locked and the configured exclude-newer constraint instead of resolving
dependencies via uv tool run. Derive the skillspector revision from
external-tools.json, or add a parity check that fails when the workflow revision
differs, so the job cannot use a stale SHA.

In `@crates/perry-ext-fetch/src/lib.rs`:
- Around line 260-267: Update both js_fetch_set_global_proxy implementations in
crates/perry-ext-fetch/src/lib.rs:260-267 and
crates/perry-stdlib/src/fetch/mod.rs:154-161 to check uri_ptr.is_null() before
decoding and clear GLOBAL_PROXY_CLIENT only for null pointers; return 0.0 for
decoded empty URIs, and revise the related documentation from “null/empty” to
null-only.

In `@crates/perry-runtime/src/event_target.rs`:
- Around line 329-360: Update js_dom_exception_subclass_init to root this_value
before any allocation-capable string conversion or field update, and avoid
retaining the raw exception pointer across those operations. Reload the rooted
object and any needed property-name handles after optional_string_from_value and
each set_event_field call so GC cannot invalidate them.

In `@crates/perry/src/commands/compile/optimized_libs/driver.rs`:
- Around line 328-339: Add an `undici` case to the `PERRY_DISABLE_WELL_KNOWN`
validation in the optimized-libraries driver, alongside the existing fastify
handling. When the environment flag is enabled and `module_normalized ==
"undici"`, emit the same clear unsupported-configuration error and stop before
codegen proceeds.

In `@external-tools.json`:
- Around line 48-57: Update the npm entry in external-tools.json, especially its
notes, to identify npm/cli 11.10.0 as the minimum version supporting
min-release-age. Do not label min-release-age as requiring npm 12; reserve the
npm 11.17 requirement for min-release-age-exclude if it is documented.

In `@scripts/soak/external-tools.mts`:
- Around line 341-375: Update linkHandle and its interaction with writeShims so
installing rack-pinned managers such as npm or pnpm cannot silently replace an
existing firewall shim at BIN_DIR/<cmd>. Detect an existing shim body before
creating the direct handle, then preserve or recreate the shim (or emit the
required warning) while maintaining idempotent behavior regardless of whether
--install or --shims runs first.

In `@scripts/soak/soak.mts`:
- Around line 494-516: Update fixCargoConfig and fixWorkspaceYaml to handle
missing configuration keys by appending valid entries, matching fixNpmrc’s
behavior; preserve existing-value rewrites. Ensure the check messages for
missing keys no longer advertise --fix unless both fixers can successfully
repair absent keys.
- Around line 48-60: Update checkCargoConfig so the global-min-publish-age regex
only matches a value within the [registry] table, mirroring the existing
[unstable] section-scoping behavior. Ensure an unsectioned or differently
sectioned setting is reported as missing while preserving the expected age
validation and finding output.

---

Nitpick comments:
In @.github/workflows/zizmor.yml:
- Around line 6-7: Update the pull_request paths filter in the workflow trigger
to include external-tools.json and scripts/soak/external-tools.mts alongside
.github/**, ensuring changes to the tool-pin sources also run the zizmor audit.

In @.github/zizmor.yml:
- Around line 32-38: Update the cache-poisoning configuration in zizmor.yml to
replace the global disable: true exemption with an ignore list containing only
the currently affected release workflows. Preserve the existing grandfathered
workflows, document the scoped debt similarly to excessive-permissions, and
ensure newly added workflows remain subject to the lint check.

In `@scripts/soak/soak.mts`:
- Around line 83-130: The npmrc validation in checkNpmrcExcludes only checks
annotation presence and date consistency, so expired version-pinned excludes are
neither reported nor removable. Extend the npm-side soak flow with the same
stale-exclude warning and pruning behavior used by staleExcludes and
fixWorkspaceYaml, operating on .npmrc lines while preserving bare-name
exclusions and valid active pins.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 42f0f3ab-9406-4dc5-bace-4824ca3d0a04

📥 Commits

Reviewing files that changed from the base of the PR and between 8504ca9 and bc14d47.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • tools/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (57)
  • .cargo/config.toml
  • .claude/skills/soak/SKILL.md
  • .github/dependabot.yml
  • .github/workflows/security-audit.yml
  • .github/workflows/soak-autofix.yml
  • .github/workflows/zizmor.yml
  • .github/zizmor.yml
  • .npmrc
  • Cargo.toml
  • changelog.d/6912-security-tooling-soak.md
  • changelog.d/7021-real-npm-cli-compile-gaps.md
  • changelog.d/7032-ext-undici.md
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-api-manifest/src/entries/part_4.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/lower_call/native_table/mod.rs
  • crates/perry-codegen/src/lower_call/native_table/undici.rs
  • crates/perry-codegen/src/lower_call/new_helpers.rs
  • crates/perry-codegen/src/runtime_decls/strings_part2.rs
  • crates/perry-ext-fetch/src/lib.rs
  • crates/perry-ext-undici/Cargo.toml
  • crates/perry-ext-undici/src/lib.rs
  • crates/perry-ext-zlib/Cargo.toml
  • crates/perry-ext-zlib/src/stream.rs
  • crates/perry-hir/src/lower/expr_new.rs
  • crates/perry-runtime/src/event_target.rs
  • crates/perry-runtime/src/object/global_this.rs
  • crates/perry-runtime/src/object/global_this/ctor_thunks.rs
  • crates/perry-runtime/src/object/global_this/populate.rs
  • crates/perry-stdlib/src/fetch/abort_bridge.rs
  • crates/perry-stdlib/src/fetch/mod.rs
  • crates/perry/src/commands/compile/optimized_libs.rs
  • crates/perry/src/commands/compile/optimized_libs/driver.rs
  • crates/perry/src/commands/compile/optimized_libs/freshness.rs
  • crates/perry/src/commands/compile/optimized_libs/tests.rs
  • crates/perry/src/commands/compile/resolve.rs
  • crates/perry/src/commands/compile/strip_dedup.rs
  • crates/perry/src/commands/compile/well_known.rs
  • crates/perry/src/commands/sandbox_profile.rs
  • crates/perry/src/commands/stdlib_features.rs
  • crates/perry/well_known_bindings.toml
  • docs/api/perry.d.ts
  • docs/src/api/reference.md
  • external-tools.json
  • package.json
  • scripts/soak/constants.mts
  • scripts/soak/external-tools.mts
  • scripts/soak/external-tools.test.mts
  • scripts/soak/paths.mts
  • scripts/soak/soak.mts
  • scripts/soak/soak.test.mts
  • scripts/soak/update-deps.mts
  • scripts/soak/update-deps.test.mts
  • tools/package.json
  • tools/pnpm-workspace.yaml
  • tools/taze.config.mts
  • workspace-architecture.json

Comment on lines +128 to +137
- name: Scan each skill
run: |
set -euo pipefail
for skill in .claude/skills/*/; do
echo "::group::skillspector ${skill}"
uv tool run --python 3.12 \
--from git+https://github.com/NVIDIA/skillspector@2eb84478 \
skillspector scan "${skill}" --no-llm
echo "::endgroup::"
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unlocked uv tool run resolution defeats the soak for this job, and the SHA is a second, unchecked copy of the pin.

external-tools.json documents skillspector as installed through a locked uv project (uv sync --locked plus exclude-newer) precisely so its dependency tree is reproducible and soaked; uv tool run --from git+...@2eb84478 resolves transitive Python deps fresh on every run, so freshly published packages execute in CI — the exact exposure SOAK_DAYS exists to prevent. Add --exclude-newer/a locked project here, and read the revision from external-tools.json (or add a parity assertion) so a manifest bump can't leave this job scanning with a stale SHA.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/security-audit.yml around lines 128 - 137, Update the
“Scan each skill” workflow to run skillspector through the locked uv project
defined by external-tools.json, using uv sync --locked and the configured
exclude-newer constraint instead of resolving dependencies via uv tool run.
Derive the skillspector revision from external-tools.json, or add a parity check
that fails when the workflow revision differs, so the job cannot use a stale
SHA.

Comment thread crates/perry-ext-fetch/src/lib.rs Outdated
Comment on lines +260 to +267
let uri = read_str(uri_ptr).unwrap_or_default();
if uri.is_empty() {
if let Ok(mut guard) = GLOBAL_PROXY_CLIENT.write() {
*guard = None;
return 1.0;
}
return 0.0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)perry-(ext-fetch|stdlib).*Fetch|fetch/mod\.rs$|lib\.rs$' || true

echo "== outline relevant dirs =="
for f in crates/perry-ext-fetch/src/lib.rs crates/perry-stdlib/src/fetch/mod.rs; do
  if [ -f "$f" ]; then
    echo "--- $f ($(wc -l < "$f") lines) ---"
    ast-grep outline "$f" --view condensed || true
  fi
done

echo "== relevant source snippets =="
sed -n '230,285p' crates/perry-ext-fetch/src/lib.rs
printf '\n---\n'
sed -n '120,180p' crates/perry-stdlib/src/fetch/mod.rs

echo "== search related symbols =="
rg -n "js_fetch_set_global_proxy|GLOBAL_PROXY_CLIENT|ProxyAgent|read_str|string_from_header|unwrap_or_default|clear" crates/perry-ext-fetch crates/perry-stdlib crates -g '*.{rs,md,js,ts}'

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== exact proxy function snippets =="
sed -n '245,275p' crates/perry-ext-fetch/src/lib.rs
printf '\n---\n'
sed -n '140,170p' crates/perry-stdlib/src/fetch/mod.rs

echo "== proxy helper definitions/imports =="
rg -n "unsafe fn (read_str|string_from_header)|fn (read_str|string_from_header)|read_string|JsString::from_raw|StringHeader|GLOBAL_PROXY_CLIENT|new ProxyAgent|Agent / ProxyAgent" \
  crates/perry-ext-fetch/src/lib.rs crates/perry-stdlib/src/fetch/mod.rs crates/perry-codegen/src/lower_call/native_table/undici.rs -C 4

echo "== focused docs/tests references =="
rg -n "ProxyAgent|AGENT |new ProxyAgent|set_global_proxy|GLOBAL_PROXY_CLIENT|clear proxy|null |empty uri|uri" crates/perry-codegen crates -g '*.rs' -g '*.md' | head -n 120

echo "== read-only code-flow summary with python =="
python3 - <<'PY'
from pathlib import Path
for path in ["crates/perry-ext-fetch/src/lib.rs", "crates/perry-stdlib/src/fetch/mod.rs"]:
    s = Path(path).read_text()
    needle = "js_fetch_set_global_proxy"
    idx = s.find(needle)
    print(f"\n--- {path} index={idx} ---")
    if idx == -1:
        print("missing")
        continue
    idx2 = s.find("->", idx)
    idx3 = s.find("{", idx2) if idx2 != -1 else -1
    idx4 = s.find("}", idx3)
    func = s[idx: idx4+1] if idx != -1 and idx4 != -1 else s[idx:]
    print(f"first 220 chars:\n{func[:220]!r}")
    print("contains before is_empty:", "uri_ptr.is_null()" in func, "read_str(uri_ptr)" in func or "string_from_header(uri_ptr)" in func)
    print("line counts in function:", func.count("\n")+1)
PY

Repository: PerryTS/perry

Length of output: 50369


Check for a null proxy URI before decoding. Both js_fetch_set_global_proxy copies decode the URI pointer first and then use is_empty() to decide whether to clear GLOBAL_PROXY_CLIENT, so a non-null empty proxy URI is treated the same as the documented null “clear proxy” signal. Distinguish uri_ptr.is_null() from a decoded empty string, and return 0.0 for the latter while changing the doc wording from “null/empty” if that signal is meant to be null-only.

📍 Affects 2 files
  • crates/perry-ext-fetch/src/lib.rs#L260-L267 (this comment)
  • crates/perry-stdlib/src/fetch/mod.rs#L154-L161
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-ext-fetch/src/lib.rs` around lines 260 - 267, Update both
js_fetch_set_global_proxy implementations in
crates/perry-ext-fetch/src/lib.rs:260-267 and
crates/perry-stdlib/src/fetch/mod.rs:154-161 to check uri_ptr.is_null() before
decoding and clear GLOBAL_PROXY_CLIENT only for null pointers; return 0.0 for
decoded empty URIs, and revise the related documentation from “null/empty” to
null-only.

Comment on lines +329 to +360
/// `class X extends DOMException` — `super(message, name)` initializer
/// (undici's `WebSocketError`, and its module-init `class Test extends
/// DOMException` capability probe). The subclass instance is a registry-class
/// object, not the ErrorHeader `new DOMException(...)` allocates, so stamp the
/// DOMException surface onto `this`: `message`, `name` (default `"Error"`,
/// matching `js_dom_exception_new`), and the legacy numeric `code` for that
/// name.
#[no_mangle]
pub extern "C" fn js_dom_exception_subclass_init(this_value: f64, message: f64, name: f64) -> f64 {
let Some(exception) = value_as_ptr::<ObjectHeader>(this_value) else {
return undefined_value();
};
let message_ptr = optional_string_from_value(message, b"");
let name_ptr = optional_string_from_value(name, b"Error");
let name_string = unsafe {
let len = (*name_ptr).byte_len as usize;
let data = (name_ptr as *const u8).add(std::mem::size_of::<StringHeader>());
String::from_utf8_lossy(std::slice::from_raw_parts(data, len)).into_owned()
};
set_event_field(
exception,
b"message",
crate::value::js_nanbox_string(message_ptr as i64),
);
set_event_field(
exception,
b"name",
crate::value::js_nanbox_string(name_ptr as i64),
);
set_event_field(exception, b"code", dom_exception_code(&name_string));
undefined_value()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether ordinary allocation paths can evacuate/move existing objects,
# and whether other *_subclass_init helpers already root `this` across
# allocating calls (to see if this is an established, accepted pattern).
rg -n "fn arena_alloc_gc|fn alloc_block|evacuat|RuntimeHandleScope" crates/perry-runtime/src -g '*.rs' -A3 -B3 | head -200
rg -n "fn js_string_from_bytes" crates/perry-runtime/src -g '*.rs' -A20
rg -n "_subclass_init" crates/perry-runtime/src/event_target.rs -B2 -A25

Repository: PerryTS/perry

Length of output: 30646


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== event_target exact lines =="
sed -n '300,360p' crates/perry-runtime/src/event_target.rs

echo "== event target earlier allocation pattern =="
sed -n '230,300p' crates/perry-runtime/src/event_target.rs

echo "== string_from_value / optional_string_from_value =="
rg -n "fn optional_string_from_value|fn string_from_value" crates/perry-runtime/src -g '*.rs' -A25 -B5

echo "== value_as_ptr and RuntimeHandleScope API =="
rg -n "fn value_as_ptr|struct RuntimeHandleScope|pub struct RuntimeHandleScope|fn root_nanbox_f64|fn root_raw_mut_ptr|fn root_nanbox_f64_slice|refreshed_nanbox_f64_slice" crates/perry-runtime/src -g '*.rs' -A12 -B8

echo "== gc files outline =="
fd . crates/perry-runtime/src -g 'gc.rs|gc*.rs' -x sh -c 'echo "--- $1"; ast-grep outline "$1" --view compact || true' sh {}

Repository: PerryTS/perry

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== event_target value helpers =="
sed -n '30,70p' crates/perry-runtime/src/event_target.rs

echo "== string_from_value in event_target =="
sed -n '80,96p' crates/perry-runtime/src/event_target.rs

echo "== string coercion path =="
rg -n "fn js_string_coerce|js_string_coerce" crates/perry-runtime/src -g '*.rs' -A20 -B5

echo "== set_event_field definitions =="
rg -n "fn set_event_field|unsafe fn set_event_field|pub fn set_event_field" crates/perry-runtime/src -g '*.rs' -A40 -B10

echo "== gc allocator allocation APIs =="
rg -n "string_storage_alloc|fn gc_alloc|fn allocate|RuntimeHandleScope|collect|move|evacuat|sweep|mark" crates/perry-runtime/src/gc crates/perry-runtime/src -g '*.rs' | head -300

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== event_target helper lines =="
sed -n '30,96p' crates/perry-runtime/src/event_target.rs

echo "== js_string_coerce direct definitions =="
rg -n "pub extern \"C\" fn js_string_coerce|pub fn js_string_coerce|fn js_string_coerce" crates/perry-runtime/src -g '*.rs' --glob '!**/string/*' --glob '!**/util_debuglog.rs' -A30 -B10 | head -220

echo "== set_event_field definitions in event_target =="
rg -n "fn set_event_field|unsafe fn set_event_field|pub unsafe fn set_event_field" crates/perry-runtime/src/event_target.rs -A45 -B15

echo "== focused gc allocator/root patterns =="
rg -n "string_storage_alloc|memory_order|Ordering::|RuntimeHandleScope::new|RootVisitor|gc_alloc|alloc_block|Collect|evacuate|evacuat|move.*object" crates/perry-runtime/src/gc crates/perry-runtime/src -g '*.rs' | head -260

Repository: PerryTS/perry

Length of output: 40526


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== set_builtin_property_attrs with gc/alloc context =="
rg -n "pub fn set_builtin_property_attrs|fn set_builtin_property_attrs|fn builtin_property_put" crates/perry-runtime/src -g '*.rs' -A80 -B20 | head -220

echo "== js_string_coerce remainder =="
sed -n '625,676p' crates/perry-runtime/src/builtins/numbers.rs

echo "== RuntimeHandleScope additional methods =="
rg -n "pub fn root_string_ptr|get_raw_const_ptr|get_raw_mut_ptr|get_nanbox_f64" crates/perry-runtime/src/gc/roots/runtime_handles.rs -A20 -B8

echo "== object allocation APIs that can collect =="
rg -n "fn js_object_set_field_by_name|fn js_object_put|fn js_object_put_by_name|ObjectHeader allocation|object_alloc_gc|alloc_block|maybe_collect|gc_collect" crates/perry-runtime/src/object crates/perry-runtime/src/value -g '*.rs' | head -220

Repository: PerryTS/perry

Length of output: 18069


Root the this object across string creations and field setting.

exception is dereferenced from a local NaN-boxed f64, then used after optional_string_from_value() may materialize strings and after each set_event_field() can allocate/collect. Root this_value up front and reload it/its property-name handles after each allocation-calling step, or perform the string conversions before rooting the object pointer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/event_target.rs` around lines 329 - 360, Update
js_dom_exception_subclass_init to root this_value before any allocation-capable
string conversion or field update, and avoid retaining the raw exception pointer
across those operations. Reload the rooted object and any needed property-name
handles after optional_string_from_value and each set_event_field call so GC
cannot invalidate them.

Source: Learnings

Comment on lines +328 to +339
// `undici` (#466): perry-ext-undici is thin glue over the
// native Web Fetch stack. Its `setGlobalDispatcher` writes
// the proxy config through `js_fetch_set_global_proxy`,
// defined in perry-stdlib's `web-fetch` module (and mirrored
// by perry-ext-fetch). A ProxyAgent-only program never calls
// `fetch()` in a way `uses_fetch` detects, so re-assert
// `web-fetch` here or the wrapper's extern reference dangles
// at link time. (`web-fetch` implies `async-runtime`, which
// the wrapper's JsPromise surface needs anyway.)
if module_normalized == "undici" {
features.insert("web-fetch");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate driver.rs and relevant references =="
fd -a 'driver\.rs$' . | sed 's#^\./##'
rg -n "imports_fastify|imports_undici|use_well_known|perry-ext-undici|js_undici|fastify|undici|web-fetch" -S --glob '!target/**' .

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file size =="
wc -l crates/perry/src/commands/compile/optimized_libs/driver.rs

echo "== relevant ranges in driver.rs =="
sed -n '1,110p' crates/perry/src/commands/compile/optimized_libs/driver.rs
echo "---"
sed -n '300,420p' crates/perry/src/commands/compile/optimized_libs/driver.rs
echo "---"
sed -n '540,610p' crates/perry/src/commands/compile/optimized_libs/driver.rs
echo "---"
sed -n '840,890p' crates/perry/src/commands/compile/optimized_libs/driver.rs

echo "== targeted occurrences (fastify/undici/use_well_known only) =="
rg -n "imports_fastify|imports_undici|use_well_known|fastify|undici|well_known" crates/perry/src/commands/compile/optimized_libs/driver.rs

Repository: PerryTS/perry

Length of output: 22971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== well_known_iteration_set and well_known lookup/bundled logic =="
rg -n "well_known_iteration_set|lookup_well_known|bundled_staticlib_path_for_target|well_known_bindings" crates/perry/src/commands/compile -S

echo "== well_known module files =="
fd -a 'well_known.*|bindings.*|toml$' crates/perry/src/commands/compile | sed 's#^\./##'
for f in $(fd 'well_known.*|well_known_bindings|bindings' crates/perry/src/commands/compile | sed 's#^\./##'); do
  echo "--- $f"
  wc -l "$f"
  rg -n "undici|fastify|node:" "$f" -S || true
done

echo "== parse well_known bindings for undici entries =="
python3 - <<'PY'
from pathlib import Path
p = Path("crates/perry/src/commands/compile/well_known_bindings.toml")
if p.exists():
    print(p.read_text(errors="replace"))
else:
    print("missing", p)
PY

echo "== stdlib feature/source references for undici =="
rg -n "undici|js_undici|web-fetch|fetch" crates/perry-stdlib -S --glob '*.rs' -g '!README.md' -g '!docs/**' | head -n 200

Repository: PerryTS/perry

Length of output: 27741


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate well_known_bindings.toml =="
git ls-files | grep -F 'well_known_bindings.toml' || true
find . -name 'well_known_bindings.toml' -print || true

echo "== all undici/js_undici/native undici references outside lock files =="
rg -n "undici|js_undici|perry-ext-undici|perry_ext_undici" -S --glob '!target/**' --glob '!**/Cargo.lock' --glob '!CHANGELOG.md' .

echo "== stdlib module list around fetch/fastify =="
sed -n '130,210p' crates/perry-stdlib/src/lib.rs
echo "---"
rg -n "export .*(fastify|fetch|client|server)|pub mod (fastify|http|fetch)|pub fn js_(fastify|fetch|undici)" crates/perry-stdlib/src -S

Repository: PerryTS/perry

Length of output: 23388


Add a PERRY_DISABLE_WELL_KNOWN guard for undici.

undici is wired through perry-ext-undici with no matching perry-stdlib feature/module to provide js_undici_*, just like fastify. If PERRY_DISABLE_WELL_KNOWN=1 is set while a program imports undici, the well-known routing path that links perry_ext_undici.a is skipped and codegen still emits native undici symbols, leading to an undefined-symbol link failure instead of a clear fastify-style error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/optimized_libs/driver.rs` around lines 328
- 339, Add an `undici` case to the `PERRY_DISABLE_WELL_KNOWN` validation in the
optimized-libraries driver, alongside the existing fastify handling. When the
environment flag is enabled and `module_normalized == "undici"`, emit the same
clear unsupported-configuration error and stop before codegen proceeds.

Comment thread external-tools.json
Comment on lines +48 to +57
"npm": {
"notes": [
"npm 12 (min-release-age support). ONE platform-agnostic registry tarball, single integrity",
"Installed from the pinned tarball only — never npm install -g npm, no self-update path"
],
"description": "npm — pinned, SRI-verified registry tarball; installed without self-update",
"repository": "npm:npm",
"version": "12.0.1",
"integrity": "sha512-L5T9i/YAQWQWqTS/xZxJkei/9zcu99hCeE4qi41IyBVV7mRQad3qc2JfuOktwmH+qwGI/V2rbCL+/UYxb1+RQA=="
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Which npm CLI version introduced the min-release-age and min-release-age-exclude config options?

💡 Result:

The npm CLI configuration options min-release-age and min-release-age-exclude were introduced in different versions of the npm CLI, both within the v11 release series [1]. 1. min-release-age: This configuration was introduced in npm CLI v11.10.0 [2][3]. It allows users to specify a relative time (in minutes) to filter out package versions published too recently, acting as a security policy to prevent the installation of newly published, potentially malicious packages [4][2]. 2. min-release-age-exclude: This configuration was introduced in npm CLI v11.17.0 [1]. It allows users to exempt specific packages (by exact name or glob pattern) from the restrictions imposed by the min-release-age policy [5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== external-tools.json npm entry =="
sed -n '44,58p' external-tools.json || true

echo
echo "== soak constants mentioning npm version =="
fd -a 'constants\.mts$' . | sed 's#^\./##' | while read -r f; do
  if rg -q 'npm|version' "$f"; then
    echo "--- $f ---"
    sed -n '1,40p' "$f"
  fi
done

echo
echo "== min-release-age references =="
rg -n "min-release-age|npm >= 11\.17|npm 12" -S .

Repository: PerryTS/perry

Length of output: 6798


Record npm’s minimum min-release-age support accurately.

min-release-age is supported starting npm npm/cli@v11.10.0, while the >= 11.17 requirement applies to min-release-age-exclude; avoid labeling the npm min-release-age floor as npm 12 in external-tools.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@external-tools.json` around lines 48 - 57, Update the npm entry in
external-tools.json, especially its notes, to identify npm/cli 11.10.0 as the
minimum version supporting min-release-age. Do not label min-release-age as
requiring npm 12; reserve the npm 11.17 requirement for min-release-age-exclude
if it is documented.

Comment on lines +341 to +375
export function linkHandle(target: string, name: string): void {
mkdirSync(BIN_DIR, { recursive: true })
const handle = path.join(BIN_DIR, name)
// force also removes a DANGLING handle (existsSync would report false
// for one and a bare symlink would then throw EEXIST).
rmSync(handle, { force: true })
if (process.platform === 'win32') {
// A symlinked/copied handle breaks Windows SEA binaries: pnpm.exe
// resolves its dist/ siblings from the handle's OWN directory, not the
// rack. Forward to the absolute rack target instead — a .cmd for
// cmd/pwsh and an extensionless bash shim for Git Bash. Non-.exe
// targets are node entry scripts (registry-tarball tools). The handle
// BASE must not keep a caller-supplied .exe suffix: pwsh resolves
// `pnpm` to `pnpm.exe` first, and a bash text file wearing that name
// is "not a valid application for this OS platform".
const base = path.join(BIN_DIR, name.replace(/\.exe$/, ''))
const viaExe = target.endsWith('.exe')
rmSync(base, { force: true })
rmSync(`${base}.cmd`, { force: true })
rmSync(`${base}.exe`, { force: true })
writeFileSync(
`${base}.cmd`,
viaExe ? `@echo off\r\n"${target}" %*\r\n` : `@echo off\r\nnode "${target}" %*\r\n`,
)
writeFileSync(
base,
viaExe
? `#!/usr/bin/env bash\nexec "${target}" "$@"\n`
: `#!/usr/bin/env bash\nexec node "${target}" "$@"\n`,
)
chmodSync(base, 0o755)
return
}
symlinkSync(target, handle)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

An install after --shims silently removes the firewall shim.

linkHandle and writeShims own the same BIN_DIR/<cmd> path for the rack-pinned managers (npm, pnpm). Within one main() call the order is safe (--install-all then --shims), but a later --install pnpm overwrites the shim with a direct rack symlink, so installs stop routing through sfw with no diagnostic — the opposite of the fail-open-but-loud contract the shim body implements. Detecting a shim body in linkHandle and either re-writing it or warning would keep the two idempotent in any order.

Also applies to: 580-629

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/soak/external-tools.mts` around lines 341 - 375, Update linkHandle
and its interaction with writeShims so installing rack-pinned managers such as
npm or pnpm cannot silently replace an existing firewall shim at BIN_DIR/<cmd>.
Detect an existing shim body before creating the direct handle, then preserve or
recreate the shim (or emit the required warning) while maintaining idempotent
behavior regardless of whether --install or --shims runs first.

Comment thread scripts/soak/soak.mts
Comment thread scripts/soak/soak.mts Outdated
Comment on lines +494 to +516
export function fixCargoConfig(body: string): string {
return body.replace(
/^(global-min-publish-age\s*=\s*)"[^"]*"/m,
`$1"${SOAK_DAYS} days"`,
)
}

export function fixNpmrc(body: string): string {
// [ \t] not \s: `\s` matches newlines, so `\s*$` under /m swallowed the
// blank lines that follow the key (silent reformatting of the file).
if (/^min-release-age=\d+[ \t]*$/m.test(body)) {
return body.replace(/^min-release-age=\d+[ \t]*$/m, `min-release-age=${SOAK_DAYS}`)
}
return `${body.trimEnd()}\nmin-release-age=${SOAK_DAYS}\n`
}

export function fixWorkspaceYaml(body: string): string {
// [ \t] not \s on the trailing match: `\s*$` under /m consumes the
// newlines after the value, deleting following blank lines.
let out = body.replace(
/^(minimumReleaseAge:[ \t]*)\d+[ \t]*$/m,
`$1${SOAK_MINUTES}`,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fixers only patch existing values, but the check text promises --fix for missing keys too.

fixCargoConfig and fixWorkspaceYaml are pure value-drift rewrites (unlike fixNpmrc, which appends). When the key is absent, the finding at Line 58 / Line 157 still advertises "(or run --fix)", so the soak-autofix workflow no-ops, re-raises, and opens the same failing run every day with guidance that cannot work. Either insert the key like fixNpmrc does, or drop the --fix hint from the (missing) path (as checkDependabotCooldown already does for a missing block).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/soak/soak.mts` around lines 494 - 516, Update fixCargoConfig and
fixWorkspaceYaml to handle missing configuration keys by appending valid
entries, matching fixNpmrc’s behavior; preserve existing-value rewrites. Ensure
the check messages for missing keys no longer advertise --fix unless both fixers
can successfully repair absent keys.

jdalton and others added 4 commits July 30, 2026 09:08
…tack (PerryTS#466)

Bare `import { ProxyAgent, setGlobalDispatcher, fetch, Agent,
getGlobalDispatcher } from 'undici'` now resolves through the
well-known bindings table to a thin native wrapper instead of
AOT-compiling undici's ~50k lines of JS.

- crates/perry-ext-undici: ProxyAgent(uri | {uri, token?}) / Agent
  dispatcher handles; setGlobalDispatcher pushes the proxy config into
  the shared fetch client via the new js_fetch_set_global_proxy symbol;
  getGlobalDispatcher returns the installed handle (lazily creating the
  implicit global Agent); request() rejects with a clear
  not-implemented error pointing at fetch.
- perry-stdlib fetch + perry-ext-fetch (mirror): global proxy override
  state + proxied reqwest client (CONNECT tunneling for https,
  absolute-form for http, token sent as Proxy-Authorization); every
  fetch path now routes through fetch_client().
- perry-hir: bare-ident `new ProxyAgent(...)` / `new Agent(...)`
  imported from undici lowers to receiver-less NativeMethodCall
  (mirrors the http/https Agent arm), so locals tag as
  (undici, ProxyAgent|Agent) instances for close()/destroy() dispatch.
- perry-codegen: UNDICI_ROWS dispatch family (constructors,
  set/getGlobalDispatcher, request, close/destroy). `fetch` from
  undici reuses the name-keyed Expr::FetchWithOptions lowering.
- perry CLI: well_known_bindings.toml row, shared-tokio set (+
  codegen-units pins), web-fetch re-assert in the flip loop,
  sandbox-profile network unlock, PERRY_NATIVE_EXTENSION_PACKAGES
  guard, manifest NATIVE_MODULES + API entries, regenerated API docs.

Targets undici's stable dispatcher API (unchanged across 6.x -> 7.x).
E2E-verified against a local mock proxy: proxied fetch carries the
ProxyAgent token as Proxy-Authorization, installing a plain Agent
restores direct connections.
@proggeramlug
proggeramlug merged commit 03908f6 into PerryTS:main Jul 30, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants