From da49195ef1df2ab5b206ac852265cac58c843a46 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Wed, 8 Jul 2026 05:39:03 -0700 Subject: [PATCH] fix(registry): drop http-get package --- README.md | 1 - .../registry-flatten-colocation-spec.md | 2 +- docs-internal/registry-parity-worklist.md | 28 ++++-- packages/core/AGENTS.md | 2 +- packages/core/CLAUDE.md | 2 +- packages/core/package.json | 1 - packages/core/tests/duckdb-package.test.ts | 24 +++-- .../core/tests/helpers/registry-commands.ts | 6 +- packages/runtime-core/commands/http_get | Bin 35505 -> 0 bytes .../integration/cross-runtime-network.test.ts | 32 +++--- packages/shell/package.json | 1 - packages/shell/src/main.ts | 2 - pnpm-lock.yaml | 27 ----- software/duckdb/test/duckdb.test.ts | 21 ++-- software/http-get/agentos-package.json | 10 -- software/http-get/native/c/http_get.c | 95 ------------------ software/http-get/package.json | 33 ------ software/http-get/src/index.ts | 5 - software/http-get/test/manifest.test.ts | 19 ---- software/http-get/tsconfig.json | 8 -- toolchain/Makefile | 2 +- toolchain/c/Makefile | 4 +- toolchain/conformance/c-parity.test.ts | 11 +- website/src/generated/registry.json | 12 --- 24 files changed, 70 insertions(+), 278 deletions(-) delete mode 100644 packages/runtime-core/commands/http_get delete mode 100644 software/http-get/agentos-package.json delete mode 100644 software/http-get/native/c/http_get.c delete mode 100644 software/http-get/package.json delete mode 100644 software/http-get/src/index.ts delete mode 100644 software/http-get/test/manifest.test.ts delete mode 100644 software/http-get/tsconfig.json diff --git a/README.md b/README.md index bc241e805c..1cf5b93e0f 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,6 @@ Browse pre-built agents, tools, filesystems, and software packages at the [agent | `@agentos-software/git` | git | git version control | rust | - | - | | `@agentos-software/grep` | grep | GNU grep pattern matching (grep, egrep, fgrep) | rust | - | - | | `@agentos-software/gzip` | gzip | GNU gzip compression (gzip, gunzip, zcat) | rust | - | - | -| `@agentos-software/http-get` | http-get | Minimal HTTP GET fetch helper | c | - | - | | `@agentos-software/jq` | jq | jq JSON processor | rust | - | - | | `@agentos-software/ripgrep` | ripgrep | ripgrep fast recursive search | rust | - | - | | `@agentos-software/sed` | sed | GNU sed stream editor | rust | - | - | diff --git a/docs-internal/registry-flatten-colocation-spec.md b/docs-internal/registry-flatten-colocation-spec.md index 3cd23d7c06..f4298f4cc9 100644 --- a/docs-internal/registry-flatten-colocation-spec.md +++ b/docs-internal/registry-flatten-colocation-spec.md @@ -59,7 +59,7 @@ repo-root/ │ │ ├── sh/ cat/ ls/ cp/ mv/ sort/ … # ~80 command crates │ │ └── (du/ expr/ column/ rev/ strings/ — 1:1 libs that belong to coreutils) │ │ -│ ├── sqlite3/ duckdb/ wget/ zip/ unzip/ http-get/ # C-based, native/c/.c +│ ├── sqlite3/ duckdb/ wget/ zip/ unzip/ # C-based, native/c/.c │ ├── grep/ sed/ gawk/ jq/ yq/ fd/ ripgrep/ tree/ file/ tar/ gzip/ diffutils/ findutils/ # Rust, native/crates/ │ │ │ ├── claude/ codex/ opencode/ pi/ pi-cli/ # agent adapters — @agentos-software/*, JS only, no native/ diff --git a/docs-internal/registry-parity-worklist.md b/docs-internal/registry-parity-worklist.md index 2b9883875c..5742b46e40 100644 --- a/docs-internal/registry-parity-worklist.md +++ b/docs-internal/registry-parity-worklist.md @@ -102,7 +102,7 @@ actual backing: |---|---|---|---| | **curl** | DONE | our custom driver over a libcurl fork | real `curl` CLI (upstream `src/tool_*.c`) | | **wget** | DONE | our 174-line `wget.c` (dropped) | real GNU Wget vs our sysroot — stub `getrlimit`/`getgroups`, then build | -| **http-get** | TODO | our 95-line `http_get.c` | drop, or a real tool | +| **http-get** | DONE | our 95-line `http_get.c` | dropped; real curl covers HTTP fetches | | **git** | TODO | our hand-rolled git from `sha1`+`flate2` | **real git** (upstream C), patched for WASI — **NOT gitoxide** | | **fd** | TODO | our `secureexec-fd` on raw `regex` (not sharkdp/fd) | real **fd** (sharkdp) | | **findutils** (`find`,`xargs`) | TODO | our hand-rolled on `regex`/shims | real GNU findutils, or `uutils/findutils` | @@ -184,10 +184,26 @@ works (`wasi-spawn` broker), so `xargs` is not a blocker. aggregate C `programs` builds 57 commands in `2026-07-08T05-09-53-0700-sqlite3-make-programs-final.log`. Rev: `typytnkk` — `fix(sqlite3): build official SQLite shell`. -- **http-get — drop, don't port.** `http_get.c` is a 95-line raw-socket **loopback - test client**, not an HTTP fetcher; real curl covers GET. But it's imported by - `packages/shell` and is the client in cross-runtime network tests — migrate those - dependents first, then drop the command. +- **http-get — DONE.** Dropped the 95-line raw-socket loopback client instead of + porting it: real curl now covers HTTP fetch behavior in DuckDB remote CSV tests, + runtime cross-network loopback tests, and the C conformance loopback row. + Removed the `@agentos-software/http-get` package, fallback command, shell/core + dependencies, registry listing, C command install entries, and lockfile package + edges. The low-level `http_get_test` fixture remains because it is a socket + diagnostic test program, not a shipped registry command. Proof: pnpm lockfile + refresh succeeds in + `2026-07-08T05-39-44-0700-http-get-pnpm-lock-refresh.log`; core and shell + type checks pass in + `2026-07-08T05-40-57-0700-http-get-core-check-types-after-install.log` and + `2026-07-08T05-41-34-0700-http-get-shell-check-types-final.log`; DuckDB test + file typecheck passes in + `2026-07-08T05-40-57-0700-http-get-duckdb-test-typecheck-after-install.log`; + aggregate C `programs` builds 57 commands, with `http_get` absent after stale + generated cleanup/install, in + `2026-07-08T05-41-34-0700-http-get-make-c-programs-without-command.log` and + `2026-07-08T05-43-24-0700-http-get-clear-stale-generated-and-install.log`; + cross-runtime network tests pass 11/11 with the WASM curl rows in + `2026-07-08T05-47-43-0700-http-get-runtime-cross-network-test-pass.log`. - **tree — DONE.** Replaced the custom Rust `secureexec-tree`/`cmd-tree` crates with upstream Steve Baker `tree` 2.3.2 from `OldManProgrammer/unix-tree`. It builds as a C toolchain command from pinned source, stages into @@ -421,7 +437,7 @@ real e2e tests that prove Linux-parity behavior — not smoke tests. ### 12. No tests at all — 12 software + 5 agents - **Broken:** zero e2e coverage: `gawk, sed, grep, tar, gzip, jq, ripgrep, yq, - diffutils, file, http-get, vim`; agents `claude, codex, opencode, pi, pi-cli`. + diffutils, file, vim`; agents `claude, codex, opencode, pi, pi-cli`. - **Objective:** write real e2e tests proving each behaves like its Linux counterpart (jq processes real JSON, sed edits streams, tar round-trips archives, grep/rg search real trees, gzip round-trips, etc.); agents exercise the real ACP diff --git a/packages/core/AGENTS.md b/packages/core/AGENTS.md index 25e194d226..8540ca7f41 100644 --- a/packages/core/AGENTS.md +++ b/packages/core/AGENTS.md @@ -118,7 +118,7 @@ An agent's launch config lives entirely in its `/opt/agentos` package `agentos-p - Sandbox toolkit quickstarts/tests that depend on external Docker should use an explicit `SKIP_DOCKER=1` gate instead of `skipIf`, and the truthful host-tool path is to read `AGENTOS_TOOLS_PORT` inside the VM and `POST` `{ toolkit, tool, input }` to `http://127.0.0.1:$AGENTOS_TOOLS_PORT/call` from a guest Node script. - Shared Vitest helpers under `src/test/` should register optional capability coverage conditionally in code instead of with `describe.skipIf` / `test.skipIf`; `US-088` treats those markers as product-debt skips even when they only guard backend capability differences. - Pi bash-tool E2E coverage depends on registry WASM commands being built locally. Gate those tests with `tests/helpers/registry-commands.ts` `hasRegistryCommands` and include the `@agentos-software/common` software package only when the command artifacts exist. -- Software package tests for C-built commands such as `duckdb` and `http_get` should go through `tests/helpers/registry-commands.ts`: prefer copied `../secure-exec/software/*/wasm` artifacts, fall back to `../secure-exec/toolchain/c/build` when available, and let the helper build missing C-source artifacts on demand before declaring the command unavailable. When bootstrapping from secure-exec `toolchain/c`, build `make sysroot` first and then run a second `make` for the concrete `build/...` targets so `SYSROOT` resolves to the patched tree instead of the vanilla SDK sysroot chosen at parse time; in that second pass, treat `sysroot/lib/wasm32-wasi/libc.a` as already built so `make` does not loop back through the patch pipeline because of preserved sysroot timestamps. +- Software package tests for C-built commands such as `duckdb` and `curl` should go through `tests/helpers/registry-commands.ts`: prefer copied `../secure-exec/software/*/wasm` artifacts, fall back to `../secure-exec/toolchain/c/build` when available, and let the helper build missing C-source artifacts on demand before declaring the command unavailable. When bootstrapping from secure-exec `toolchain/c`, build `make sysroot` first and then run a second `make` for the concrete `build/...` targets so `SYSROOT` resolves to the patched tree instead of the vanilla SDK sysroot chosen at parse time; in that second pass, treat `sysroot/lib/wasm32-wasi/libc.a` as already built so `make` does not loop back through the patch pipeline because of preserved sysroot timestamps. - `tests/claude-session.test.ts` is the Claude SDK truth suite. It runs the real `@anthropic-ai/claude-agent-sdk` session path through llmock and covers PATH-backed `xu`, text-only replies, nested `node` `execSync` and `spawn`, metadata, lifecycle, and mode updates. Run it with `pnpm --dir packages/core exec vitest run tests/claude-session.test.ts --reporter=verbose` when verifying Claude regressions. - **Kernel permissions are declarative pass-through config.** `AgentOsOptions.permissions` should stay JSON-serializable and be forwarded to the native sidecar without host-side probing or callback evaluation; Rust owns glob matching and policy decisions. - ACP session events are live-only over `onSessionEvent()`. Do not reintroduce sequence numbers, local replay buffers, or event cursor recovery. diff --git a/packages/core/CLAUDE.md b/packages/core/CLAUDE.md index 25e194d226..8540ca7f41 100644 --- a/packages/core/CLAUDE.md +++ b/packages/core/CLAUDE.md @@ -118,7 +118,7 @@ An agent's launch config lives entirely in its `/opt/agentos` package `agentos-p - Sandbox toolkit quickstarts/tests that depend on external Docker should use an explicit `SKIP_DOCKER=1` gate instead of `skipIf`, and the truthful host-tool path is to read `AGENTOS_TOOLS_PORT` inside the VM and `POST` `{ toolkit, tool, input }` to `http://127.0.0.1:$AGENTOS_TOOLS_PORT/call` from a guest Node script. - Shared Vitest helpers under `src/test/` should register optional capability coverage conditionally in code instead of with `describe.skipIf` / `test.skipIf`; `US-088` treats those markers as product-debt skips even when they only guard backend capability differences. - Pi bash-tool E2E coverage depends on registry WASM commands being built locally. Gate those tests with `tests/helpers/registry-commands.ts` `hasRegistryCommands` and include the `@agentos-software/common` software package only when the command artifacts exist. -- Software package tests for C-built commands such as `duckdb` and `http_get` should go through `tests/helpers/registry-commands.ts`: prefer copied `../secure-exec/software/*/wasm` artifacts, fall back to `../secure-exec/toolchain/c/build` when available, and let the helper build missing C-source artifacts on demand before declaring the command unavailable. When bootstrapping from secure-exec `toolchain/c`, build `make sysroot` first and then run a second `make` for the concrete `build/...` targets so `SYSROOT` resolves to the patched tree instead of the vanilla SDK sysroot chosen at parse time; in that second pass, treat `sysroot/lib/wasm32-wasi/libc.a` as already built so `make` does not loop back through the patch pipeline because of preserved sysroot timestamps. +- Software package tests for C-built commands such as `duckdb` and `curl` should go through `tests/helpers/registry-commands.ts`: prefer copied `../secure-exec/software/*/wasm` artifacts, fall back to `../secure-exec/toolchain/c/build` when available, and let the helper build missing C-source artifacts on demand before declaring the command unavailable. When bootstrapping from secure-exec `toolchain/c`, build `make sysroot` first and then run a second `make` for the concrete `build/...` targets so `SYSROOT` resolves to the patched tree instead of the vanilla SDK sysroot chosen at parse time; in that second pass, treat `sysroot/lib/wasm32-wasi/libc.a` as already built so `make` does not loop back through the patch pipeline because of preserved sysroot timestamps. - `tests/claude-session.test.ts` is the Claude SDK truth suite. It runs the real `@anthropic-ai/claude-agent-sdk` session path through llmock and covers PATH-backed `xu`, text-only replies, nested `node` `execSync` and `spawn`, metadata, lifecycle, and mode updates. Run it with `pnpm --dir packages/core exec vitest run tests/claude-session.test.ts --reporter=verbose` when verifying Claude regressions. - **Kernel permissions are declarative pass-through config.** `AgentOsOptions.permissions` should stay JSON-serializable and be forwarded to the native sidecar without host-side probing or callback evaluation; Rust owns glob matching and policy decisions. - ACP session events are live-only over `onSessionEvent()`. Do not reintroduce sequence numbers, local replay buffers, or event cursor recovery. diff --git a/packages/core/package.json b/packages/core/package.json index 9b71bf2af0..e09daf4caf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -88,7 +88,6 @@ "@agentos-software/git": "workspace:*", "@agentos-software/grep": "workspace:*", "@agentos-software/gzip": "workspace:*", - "@agentos-software/http-get": "workspace:*", "@agentos-software/jq": "workspace:*", "@agentos-software/opencode": "workspace:*", "@agentos-software/pi": "workspace:*", diff --git a/packages/core/tests/duckdb-package.test.ts b/packages/core/tests/duckdb-package.test.ts index 9b6c0913e2..2c02753dd4 100644 --- a/packages/core/tests/duckdb-package.test.ts +++ b/packages/core/tests/duckdb-package.test.ts @@ -5,19 +5,19 @@ import { type ServerResponse, } from "node:http"; import coreutils from "@agentos-software/coreutils"; +import curl from "@agentos-software/curl"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import duckdb from "@agentos-software/duckdb"; -import httpGet from "@agentos-software/http-get"; import { AgentOs } from "../dist/index.js"; import { cSysrootPackageSkipReason } from "./helpers/registry-commands.js"; const DUCKDB_PACKAGE = duckdb; -const HTTP_GET_PACKAGE = httpGet; +const CURL_PACKAGE = curl; // C-sysroot packages are the ONE sanctioned skip: they need the patched wasi C // sysroot most checkouts don't build (see helpers/registry-commands.ts). const duckdbPackageSkipReason = cSysrootPackageSkipReason( { pkg: DUCKDB_PACKAGE, name: "duckdb" }, - { pkg: HTTP_GET_PACKAGE, name: "http-get" }, + { pkg: CURL_PACKAGE, name: "curl" }, ); function closeServer(server: Server) { @@ -47,7 +47,7 @@ describe("duckdb registry package", () => { await vm.dispose(); } vm = await AgentOs.create({ - software: options?.software ?? [coreutils, HTTP_GET_PACKAGE, DUCKDB_PACKAGE], + software: options?.software ?? [coreutils, CURL_PACKAGE, DUCKDB_PACKAGE], ...(options?.loopbackExemptPorts ? { loopbackExemptPorts: options.loopbackExemptPorts } : {}), @@ -106,7 +106,7 @@ describe("duckdb registry package", () => { await recreateVm({ loopbackExemptPorts: [address.port] }); let result = await vm.exec( - `http_get ${address.port} /remote.csv /tmp/remote.csv`, + `curl -fsS -o /tmp/remote.csv http://127.0.0.1:${address.port}/remote.csv`, ); expect(result.exitCode, result.stderr || result.stdout).toBe(0); @@ -166,11 +166,11 @@ describe("duckdb registry package", () => { async () => { await vm.dispose(); - const httpGetReadOnly = { - ...HTTP_GET_PACKAGE, - commands: [{ name: "http_get", permissionTier: "read-only" as const }], + const curlReadOnly = { + ...CURL_PACKAGE, + commands: [{ name: "curl", permissionTier: "read-only" as const }], }; - vm = await AgentOs.create({ software: [coreutils, httpGetReadOnly] }); + vm = await AgentOs.create({ software: [coreutils, curlReadOnly] }); const server = createServer((req: IncomingMessage, res: ServerResponse) => { res.writeHead(200, { "Content-Type": "text/plain" }); @@ -187,11 +187,13 @@ describe("duckdb registry package", () => { throw new Error("failed to bind test HTTP server"); } await recreateVm({ - software: [coreutils, httpGetReadOnly], + software: [coreutils, curlReadOnly], loopbackExemptPorts: [address.port], }); - const result = await vm.exec(`http_get ${address.port} /blocked`); + const result = await vm.exec( + `curl -fsS http://127.0.0.1:${address.port}/blocked`, + ); expect(result.exitCode).not.toBe(0); } finally { await closeServer(server); diff --git a/packages/core/tests/helpers/registry-commands.ts b/packages/core/tests/helpers/registry-commands.ts index 65cdcb9ed6..2a0b781c5e 100644 --- a/packages/core/tests/helpers/registry-commands.ts +++ b/packages/core/tests/helpers/registry-commands.ts @@ -12,8 +12,8 @@ * when it exists (local registry builds produce both), so a stale or empty * command set still fails loudly. * - * The only sanctioned exception is the C-sysroot package set (duckdb, - * http-get, sqlite3, zip, unzip): those need the patched wasi C sysroot + * The only sanctioned exception is the C-sysroot package set (curl, duckdb, + * sqlite3, zip, unzip): those need the patched wasi C sysroot * that most checkouts don't have, so `cSysrootPackageSkipReason` reports a * skip reason instead of throwing. Everything else is load-or-throw. */ @@ -160,7 +160,7 @@ export function requireBuilt( } /** - * Skip reason for the C-sysroot package set ONLY (duckdb, http-get, sqlite3, + * Skip reason for the C-sysroot package set ONLY (curl, duckdb, sqlite3, * zip, unzip). These need the patched wasi C sysroot * (`make -C toolchain/c`), which most checkouts don't build — a missing * artifact is an environment limitation, not a forgotten build, so suites may diff --git a/packages/runtime-core/commands/http_get b/packages/runtime-core/commands/http_get deleted file mode 100644 index 52a1fb8342fd98e8472e311de1456a871cf59301..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35505 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 diff --git a/packages/runtime-core/tests/integration/cross-runtime-network.test.ts b/packages/runtime-core/tests/integration/cross-runtime-network.test.ts index 5167ea3bad..9336b2320c 100644 --- a/packages/runtime-core/tests/integration/cross-runtime-network.test.ts +++ b/packages/runtime-core/tests/integration/cross-runtime-network.test.ts @@ -19,7 +19,7 @@ import { } from '@rivet-dev/agentos-vm-test-harness'; import type { IntegrationKernelResult, Kernel } from '@rivet-dev/agentos-vm-test-harness'; -const WASM_HTTP_GET = resolve(C_BUILD_DIR, 'http_get'); +const WASM_CURL = resolve(C_BUILD_DIR, 'curl'); const WASM_HTTP_SERVER = resolve(C_BUILD_DIR, 'http_server'); const WASM_TCP_ECHO = resolve(C_BUILD_DIR, 'tcp_echo'); const WASM_TCP_SERVER = resolve(C_BUILD_DIR, 'tcp_server'); @@ -28,7 +28,7 @@ function skipReasonWasmNetwork(): string | false { const wasmSkipReason = skipUnlessWasmBuilt(); if (wasmSkipReason) return wasmSkipReason; for (const [name, path] of [ - ['http_get', WASM_HTTP_GET], + ['curl', WASM_CURL], ['http_server', WASM_HTTP_SERVER], ['tcp_echo', WASM_TCP_ECHO], ['tcp_server', WASM_TCP_SERVER], @@ -231,7 +231,7 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { expect(client.stdout.trim()).toBe('js-pong:ping'); }); - itIf(!wasmNetworkSkipReason, 'W1 WASM http_get -> JS node:http server over VM loopback', async () => { + itIf(!wasmNetworkSkipReason, 'W1 WASM curl -> JS node:http server over VM loopback', async () => { ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'], commandDirs: [C_BUILD_DIR, COMMANDS_DIR], @@ -239,13 +239,13 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { const server = spawnGuestNodeProgram(ctx.kernel, guestJsHttpServer(3102)); await waitForOutput(server, 'js http listening 3102', 'JS HTTP server'); - const wasm = await ctx.kernel.exec('http_get 3102 /from-wasm'); + const wasm = await ctx.kernel.exec('curl -fsS http://127.0.0.1:3102/from-wasm'); server.process.kill(15); await server.process.wait().catch(() => {}); expect(wasm.exitCode).toBe(0); - expect(wasm.stderr).not.toContain('socket error'); - expect(wasm.stdout).toContain('body: js:GET:/from-wasm'); + expect(wasm.stderr).toBe(''); + expect(wasm.stdout.trim()).toBe('js:GET:/from-wasm'); }); itIf(!wasmNetworkSkipReason, 'J3 JS fetch -> WASM HTTP server over VM loopback', async () => { @@ -341,7 +341,7 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { expect(wasm.stdout).toContain('received: js-pong:hello'); }); - itIf(!wasmNetworkSkipReason, 'W3 WASM http_get -> WASM HTTP server over VM loopback', async () => { + itIf(!wasmNetworkSkipReason, 'W3 WASM curl -> WASM HTTP server over VM loopback', async () => { ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'], commandDirs: [C_BUILD_DIR, COMMANDS_DIR], @@ -349,12 +349,12 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { const server = spawnGuestProgram(ctx.kernel, 'http_server', ['3108']); await waitForListener(ctx.kernel, 3108, 'WASM HTTP server'); - const wasm = await ctx.kernel.exec('http_get 3108 /from-wasm'); + const wasm = await ctx.kernel.exec('curl -fsS http://127.0.0.1:3108/from-wasm'); const serverExit = await server.process.wait(); expect(wasm.exitCode).toBe(0); - expect(wasm.stderr).not.toContain('socket error'); - expect(wasm.stdout).toContain('body: wasm:GET:/from-wasm'); + expect(wasm.stderr).toBe(''); + expect(wasm.stdout.trim()).toBe('wasm:GET:/from-wasm'); expect(serverExit).toBe(0); }); @@ -426,7 +426,7 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { } }); - itIf(!wasmNetworkSkipReason, 'O2 WASM http_get -> host loopback requires loopback exemption', async () => { + itIf(!wasmNetworkSkipReason, 'O2 WASM curl -> host loopback requires loopback exemption', async () => { const seenRequests: string[] = []; const hostServer = createHttpServer((req, res) => { seenRequests.push(req.url ?? ''); @@ -443,9 +443,9 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { runtimes: ['wasmvm', 'node'], commandDirs: [C_BUILD_DIR, COMMANDS_DIR], }); - const noExemption = await ctx.kernel.exec(`http_get ${port} /blocked`); + const noExemption = await ctx.kernel.exec(`curl -fsS http://127.0.0.1:${port}/blocked`); expect(noExemption.exitCode).not.toBe(0); - expect(noExemption.stderr).toMatch(/EACCES|Bad address|Connection refused|connect/); + expect(noExemption.stderr).toMatch(/EACCES|Bad address|Connection refused|connect|Failed to connect|Invalid argument/); expect(seenRequests).toEqual([]); await ctx.dispose(); @@ -454,10 +454,10 @@ describe('cross-runtime network integration', { timeout: 90_000 }, () => { commandDirs: [C_BUILD_DIR, COMMANDS_DIR], loopbackExemptPorts: [port], }); - const allowed = await ctx.kernel.exec(`http_get ${port} /allowed`); + const allowed = await ctx.kernel.exec(`curl -fsS http://127.0.0.1:${port}/allowed`); expect(allowed.exitCode).toBe(0); - expect(allowed.stderr).not.toContain('socket error'); - expect(allowed.stdout).toContain('body: host:/allowed'); + expect(allowed.stderr).toBe(''); + expect(allowed.stdout.trim()).toBe('host:/allowed'); expect(seenRequests).toEqual(['/allowed']); } finally { await new Promise((resolveClose) => hostServer.close(() => resolveClose())); diff --git a/packages/shell/package.json b/packages/shell/package.json index 254004e6b4..38631b38c9 100644 --- a/packages/shell/package.json +++ b/packages/shell/package.json @@ -26,7 +26,6 @@ "@agentos-software/git": "workspace:*", "@agentos-software/grep": "workspace:*", "@agentos-software/gzip": "workspace:*", - "@agentos-software/http-get": "workspace:*", "@agentos-software/jq": "workspace:*", "@agentos-software/ripgrep": "workspace:*", "@agentos-software/sed": "workspace:*", diff --git a/packages/shell/src/main.ts b/packages/shell/src/main.ts index f3e6f6996b..d7abe702f4 100644 --- a/packages/shell/src/main.ts +++ b/packages/shell/src/main.ts @@ -35,7 +35,6 @@ import gawk from "@agentos-software/gawk"; import git from "@agentos-software/git"; import grep from "@agentos-software/grep"; import gzip from "@agentos-software/gzip"; -import httpGet from "@agentos-software/http-get"; import jq from "@agentos-software/jq"; import ripgrep from "@agentos-software/ripgrep"; import sed from "@agentos-software/sed"; @@ -153,7 +152,6 @@ const software: SoftwareInput[] = [ yq, codex, git, - httpGet, sqlite3, wget, ] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ab0cccb90..86a6acee1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2544,9 +2544,6 @@ importers: '@agentos-software/gzip': specifier: workspace:* version: link:../../software/gzip - '@agentos-software/http-get': - specifier: workspace:* - version: link:../../software/http-get '@agentos-software/jq': specifier: workspace:* version: link:../../software/jq @@ -2853,9 +2850,6 @@ importers: '@agentos-software/gzip': specifier: workspace:* version: link:../../software/gzip - '@agentos-software/http-get': - specifier: workspace:* - version: link:../../software/http-get '@agentos-software/jq': specifier: workspace:* version: link:../../software/jq @@ -3472,27 +3466,6 @@ importers: specifier: ^2.1.9 version: 2.1.9(@types/node@22.19.15) - software/http-get: - devDependencies: - '@agentos-software/manifest': - specifier: workspace:* - version: link:../../packages/manifest - '@agentos/test-harness': - specifier: workspace:* - version: link:../../test-harness - '@rivet-dev/agentos-toolchain': - specifier: workspace:* - version: link:../../packages/agentos-toolchain - '@types/node': - specifier: ^22.10.2 - version: 22.19.15 - typescript: - specifier: ^5.9.2 - version: 5.9.3 - vitest: - specifier: ^2.1.9 - version: 2.1.9(@types/node@22.19.15) - software/jq: devDependencies: '@agentos-software/manifest': diff --git a/software/duckdb/test/duckdb.test.ts b/software/duckdb/test/duckdb.test.ts index 178039b040..06349c73fb 100644 --- a/software/duckdb/test/duckdb.test.ts +++ b/software/duckdb/test/duckdb.test.ts @@ -33,7 +33,6 @@ import { setTimeout as sleep } from 'node:timers/promises'; const hasWasmDuckDB = existsSync(resolve(C_BUILD_DIR, 'duckdb')); const hasWasmCurl = (existsSync(resolve(COMMANDS_DIR, 'curl')) || existsSync(resolve(C_BUILD_DIR, 'curl'))); -const hasWasmHttpGet = existsSync(resolve(C_BUILD_DIR, 'http_get')); async function mountKernel( filesystem: ReturnType, @@ -52,7 +51,7 @@ async function mountKernel( commandDirs, permissions: { duckdb: 'read-write', - http_get: 'full', + curl: 'full', }, }) ); @@ -215,7 +214,7 @@ describeIf(hasWasmDuckDB, 'duckdb command', { timeout: 120_000 }, () => { }); itIf( - hasWasmCurl || hasWasmHttpGet, + hasWasmCurl, 'queries data fetched over the network through the shared VFS', async () => { const filesystem = createInMemoryFileSystem(); @@ -243,18 +242,10 @@ describeIf(hasWasmDuckDB, 'duckdb command', { timeout: 120_000 }, () => { loopbackExemptPorts: [address.port], }); - let result; - if (hasWasmHttpGet) { - result = await kernel.exec( - `http_get ${address.port} /remote.csv /tmp/remote.csv` - ); - expect(result.exitCode).toBe(0); - } else { - result = await kernel.exec( - `curl -fsS -o /tmp/remote.csv http://127.0.0.1:${address.port}/remote.csv` - ); - expect(result.exitCode).toBe(0); - } + let result = await kernel.exec( + `curl -fsS -o /tmp/remote.csv http://127.0.0.1:${address.port}/remote.csv` + ); + expect(result.exitCode).toBe(0); expect(await filesystem.readTextFile('/tmp/remote.csv')).toContain('city,value'); diff --git a/software/http-get/agentos-package.json b/software/http-get/agentos-package.json deleted file mode 100644 index 4f8fb816f9..0000000000 --- a/software/http-get/agentos-package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "commands": [ - "http_get" - ], - "registry": { - "title": "http-get", - "description": "Minimal HTTP GET fetch helper.", - "category": "networking" - } -} diff --git a/software/http-get/native/c/http_get.c b/software/http-get/native/c/http_get.c deleted file mode 100644 index 0e7dc466ea..0000000000 --- a/software/http-get/native/c/http_get.c +++ /dev/null @@ -1,95 +0,0 @@ -/* http_get.c — connect to an HTTP server, send GET request, print response body */ -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char *argv[]) { - if (argc < 2) { - fprintf(stderr, "usage: http_get [path] [output_file]\n"); - return 1; - } - - int port = atoi(argv[1]); - const char *path = argc >= 3 ? argv[2] : "/"; - const char *output_file = argc >= 4 ? argv[3] : NULL; - - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) { - perror("socket"); - return 1; - } - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons((uint16_t)port); - inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); - - if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - perror("connect"); - close(fd); - return 1; - } - - char request[1024]; - int request_len = snprintf( - request, - sizeof(request), - "GET %s HTTP/1.0\r\nHost: localhost\r\n\r\n", - path - ); - if (request_len < 0 || (size_t)request_len >= sizeof(request)) { - fprintf(stderr, "request path too long\n"); - close(fd); - return 1; - } - - ssize_t sent = send(fd, request, (size_t)request_len, 0); - if (sent < 0) { - perror("send"); - close(fd); - return 1; - } - - /* Read full response */ - char response[4096]; - size_t total = 0; - ssize_t n; - while ((n = recv(fd, response + total, sizeof(response) - total - 1, 0)) > 0) { - total += (size_t)n; - } - response[total] = '\0'; - - close(fd); - - /* Find body after \r\n\r\n */ - const char *body = strstr(response, "\r\n\r\n"); - if (body) { - body += 4; - if (output_file) { - FILE *out = fopen(output_file, "wb"); - if (!out) { - perror("fopen"); - return 1; - } - size_t body_len = total - (size_t)(body - response); - if (fwrite(body, 1, body_len, out) != body_len) { - perror("fwrite"); - fclose(out); - return 1; - } - fclose(out); - } else { - printf("body: %s\n", body); - } - } else { - printf("body: (no separator found)\n"); - return 1; - } - - return 0; -} diff --git a/software/http-get/package.json b/software/http-get/package.json deleted file mode 100644 index ef23733846..0000000000 --- a/software/http-get/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@agentos-software/http-get", - "version": "0.3.3", - "type": "module", - "license": "Apache-2.0", - "description": "Minimal HTTP GET fetch helper for secure-exec VMs", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "files": [ - "dist", - "!dist/package", - "!dist/package.tar" - ], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } - }, - "scripts": { - "build": "agentos-toolchain stage --commands-dir ../../toolchain/target/wasm32-wasip1/release/commands --if-missing skip && tsc && agentos-toolchain build", - "check-types": "tsc --noEmit", - "test": "vitest run test/ --passWithNoTests" - }, - "devDependencies": { - "@agentos-software/manifest": "workspace:*", - "@rivet-dev/agentos-toolchain": "workspace:*", - "@types/node": "^22.10.2", - "typescript": "^5.9.2", - "@agentos/test-harness": "workspace:*", - "vitest": "^2.1.9" - } -} diff --git a/software/http-get/src/index.ts b/software/http-get/src/index.ts deleted file mode 100644 index dba100e2e1..0000000000 --- a/software/http-get/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { SoftwarePackageRef } from "@agentos-software/manifest"; - -const packagePath = new URL("./package.aospkg", import.meta.url).pathname; - -export default { packagePath } satisfies SoftwarePackageRef; diff --git a/software/http-get/test/manifest.test.ts b/software/http-get/test/manifest.test.ts deleted file mode 100644 index 89139d33b8..0000000000 --- a/software/http-get/test/manifest.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { describe, expect, it } from "vitest"; - -const packageDir = new URL("..", import.meta.url).pathname; - -describe("package manifest", () => { - it("declares command binaries", () => { - const manifest = JSON.parse( - readFileSync(join(packageDir, "agentos-package.json"), "utf8"), - ); - - expect(manifest.commands?.length ?? 0).toBeGreaterThan(0); - for (const command of manifest.commands) { - expect(typeof command).toBe("string"); - expect(command.length).toBeGreaterThan(0); - } - }); -}); diff --git a/software/http-get/tsconfig.json b/software/http-get/tsconfig.json deleted file mode 100644 index 03ce790ab7..0000000000 --- a/software/http-get/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"] -} diff --git a/toolchain/Makefile b/toolchain/Makefile index 1761fd6dd2..06b41a7c08 100644 --- a/toolchain/Makefile +++ b/toolchain/Makefile @@ -213,7 +213,7 @@ wasm: vendor patch-vendor patch-std wasm-opt-check # codex the codex fork build (codex, codex-exec) # `just toolchain-cmd ` calls this. `make wasm` builds the fast # Rust set; `make -C c programs install` builds/installs the fast C set. -C_COMMANDS := zip unzip envsubst sqlite3 curl wget duckdb vim http_get +C_COMMANDS := zip unzip envsubst sqlite3 curl wget duckdb vim .PHONY: cmd/% cmd/%: diff --git a/toolchain/c/Makefile b/toolchain/c/Makefile index 1d23d461e2..d11cc666b8 100644 --- a/toolchain/c/Makefile +++ b/toolchain/c/Makefile @@ -68,10 +68,10 @@ COMMANDS_DIR ?= ../target/wasm32-wasip1/release/commands # Fast real commands installed by the default software gate. Slow/heavy commands # (duckdb, vim) remain available through explicit parent `make cmd/`. -COMMANDS := zip unzip envsubst sqlite3 curl wget tree http_get +COMMANDS := zip unzip envsubst sqlite3 curl wget tree # Programs requiring patched sysroot (Tier 2+ custom host imports) -PATCHED_PROGRAMS := http_get_test isatty_test getpid_test getppid_test getppid_verify userinfo pipe_test dup_test spawn_child spawn_exit_code pipeline kill_child waitpid_return waitpid_edge syscall_coverage getpwuid_test signal_tests sigaction_self sigaction_behavior delayed_tcp_echo delayed_kill pipe_edge tcp_accept_spawn tcp_echo tcp_server http_server udp_echo unix_socket signal_handler http_get dns_lookup sqlite3 curl wget tree fs_probe +PATCHED_PROGRAMS := http_get_test isatty_test getpid_test getppid_test getppid_verify userinfo pipe_test dup_test spawn_child spawn_exit_code pipeline kill_child waitpid_return waitpid_edge syscall_coverage getpwuid_test signal_tests sigaction_self sigaction_behavior delayed_tcp_echo delayed_kill pipe_edge tcp_accept_spawn tcp_echo tcp_server http_server udp_echo unix_socket signal_handler dns_lookup sqlite3 curl wget tree fs_probe # Discover all package command and test-program C source files. ALL_SOURCES := $(foreach dir,$(C_SOURCE_DIRS),$(wildcard $(dir)/*.c)) diff --git a/toolchain/conformance/c-parity.test.ts b/toolchain/conformance/c-parity.test.ts index a106b22530..17e3271bb3 100644 --- a/toolchain/conformance/c-parity.test.ts +++ b/toolchain/conformance/c-parity.test.ts @@ -942,7 +942,7 @@ describeIf(!skipReason(), 'C parity: native vs WASM', { timeout: 30_000 }, () => } }); - itIf(!netSkip, 'http_get: connect to HTTP server, receive response body', async () => { + itIf(!netSkip, 'curl: connect to HTTP server, receive response body', async () => { // Start a local HTTP server const server = createHttpServer((_req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); @@ -953,14 +953,11 @@ describeIf(!skipReason(), 'C parity: native vs WASM', { timeout: 30_000 }, () => try { await recreateKernel({ loopbackExemptPorts: [port] }); - const native = await runNative('http_get', [String(port)]); - const wasm = await kernel.exec(`http_get ${port}`); + const wasm = await kernel.exec(`curl -fsS http://127.0.0.1:${port}/`); - expect(wasm.exitCode).toBe(native.exitCode); expect(wasm.exitCode).toBe(0); - expect(wasm.stdout).toBe(native.stdout); - expect(normalizeStderr(wasm.stderr)).toBe(normalizeStderr(native.stderr)); - expect(wasm.stdout).toContain('body: hello from http'); + expect(wasm.stderr).toBe(''); + expect(wasm.stdout.trim()).toBe('hello from http'); } finally { server.close(); } diff --git a/website/src/generated/registry.json b/website/src/generated/registry.json index d65f37e211..c3c75c37fb 100644 --- a/website/src/generated/registry.json +++ b/website/src/generated/registry.json @@ -403,18 +403,6 @@ "package": "@agentos-software/codex-cli", "status": "available", "image": "/images/registry/codex.svg" - }, - { - "slug": "http-get", - "title": "http-get", - "description": "Minimal HTTP GET fetch helper.", - "types": [ - "software" - ], - "category": "networking", - "priority": 0, - "package": "@agentos-software/http-get", - "status": "available" } ] }