From 52133e2148ba8a285da042771ba25aecc7acdd14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Mon, 12 Oct 2026 17:52:07 +0200 Subject: [PATCH] chore(remote-control): update OpenTunnel to 0.5.1 (#922) Adopt the upstream fix for the hosted API's intermittent Cloudflare 525s (anomalyco/opentunnel#45): transient API failures are retried with capped backoff, errors name the HTTP status, and an unexpired saved certificate keeps serving when the check still fails. The PROXY protocol stays disabled. The adapter declares the workspace's single stable Effect 4.0.1, and the opt-in live test resolves the SDK wherever npm places it. Validated typechecks, build, 38 Remote Control tests, packaging checks, packaged Node imports and the live hosted-relay run; merged without waiting for CI at the user's request. --- dev-docs/REMOTE_CONTROL.md | 16 +++-- package-lock.json | 66 ++++++++----------- packages/remote-tunnel/package.json | 4 +- .../scripts/remote-control-live.test.ts | 10 ++- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/dev-docs/REMOTE_CONTROL.md b/dev-docs/REMOTE_CONTROL.md index a5eae7564..b1745f012 100644 --- a/dev-docs/REMOTE_CONTROL.md +++ b/dev-docs/REMOTE_CONTROL.md @@ -12,9 +12,10 @@ browser ─TLS─▶ OpenTunnel relay (forwards ciphertext) ─▶ OpenTunnel SD - `packages/remote-tunnel` wraps `@opentunnel/client` behind CodeNomad-owned types. The server build bundles it into `dist/remote-control/tunnel-runtime.js` - with its own Effect 4.0.x: the SDK peers `effect ^4.0.0` and imports - `effect/http`, while `@opencode/client` pins `4.0.0-rc.112`. A strict npm - install of both side by side fails with `ERESOLVE`, so the published server keeps no runtime + with the Effect copy it resolves (the SDK peers `effect ^4.0.0`; since + OpenCode 2.0.27 the workspace shares one stable 4.0.x). It was introduced + while `@opencode/client` pinned `4.0.0-rc.112`, where a strict npm install of + both side by side failed with `ERESOLVE`; the published server still keeps no runtime dependency on the private workspace package. - CodeNomad claims one random route (`codenomad-`, persisted) on the device's shared `default` identity. OpenCode's `opencode pair --remote` and @@ -102,6 +103,11 @@ upstream affects Bun only. - [anomalyco/opentunnel#34](https://github.com/anomalyco/opentunnel/pull/34): both clients enforce the `max_conns` they advertise. +- [anomalyco/opentunnel#45](https://github.com/anomalyco/opentunnel/issues/45) + (0.5.1): API calls retry transient failures (5xx, Cloudflare 52x) with capped + backoff, errors name the HTTP status, and `connect` keeps an unexpired saved + certificate when the check still fails. CodeNomad does not enable the 0.5.0 + per-route PROXY protocol: the ingress expects plain HTTP. - Open topics: atomic identity writes with a provisioning lock, an in-process - connection target (no loopback hop, preserved peer address), per-connection - flow control, and relay-side `max_conns` enforcement. + connection target (no loopback hop, preserved peer address) and + per-connection flow control. The repository now accepts issues only. diff --git a/package-lock.json b/package-lock.json index 0ec0cbd41..0fd75a566 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5192,6 +5192,33 @@ "node": ">=14" } }, + "node_modules/@opentunnel/client": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@opentunnel/client/-/client-0.5.1.tgz", + "integrity": "sha512-TdrdD0RCgX5vGRjLEKAsPD0vshUdjJEIUr7KEmJA3wmgbmRj5TQSHO66AMWF6REhd7UJ2ZnYQ3XNjqgnT+ghfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opentunnel/protocol": "0.5.1" + }, + "engines": { + "bun": ">=1.4.0", + "node": ">=22" + }, + "peerDependencies": { + "effect": "^4.0.0" + } + }, + "node_modules/@opentunnel/protocol": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@opentunnel/protocol/-/protocol-0.5.1.tgz", + "integrity": "sha512-8DIZNyDxEFyzl6WzVB/M7hZauCU+zFABo0iviywj6kX3yO9jFSPzqWCBBaarpjWJEOXYXaSILllJI62fSlEdeA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "effect": "^4.0.0" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -18283,46 +18310,11 @@ "version": "0.1.0", "license": "MIT", "devDependencies": { - "@opentunnel/client": "0.4.1", - "effect": "4.0.2", + "@opentunnel/client": "0.5.1", + "effect": "4.0.1", "typescript": "^5.6.3" } }, - "packages/remote-tunnel/node_modules/@opentunnel/client": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@opentunnel/client/-/client-0.4.1.tgz", - "integrity": "sha512-TLHCko/cSoF6LY/IBvex9s/u9vh4CB3K/S06q7Zwu0HUISikMC+j/4437Y39slF7hmx1oSpTo7AySELu7j5DQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@opentunnel/protocol": "0.4.1" - }, - "engines": { - "bun": ">=1.4.0", - "node": ">=22" - }, - "peerDependencies": { - "effect": "^4.0.0" - } - }, - "packages/remote-tunnel/node_modules/@opentunnel/protocol": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@opentunnel/protocol/-/protocol-0.4.1.tgz", - "integrity": "sha512-ebAO6/H5VdeAtweqWhMBqTthTMqQfsmZRsbHJULpDLK3HGJa02L1VO4e4LnigLbIPhP/PULqPjFe5nmcMXUvNQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "effect": "^4.0.0" - } - }, - "packages/remote-tunnel/node_modules/effect": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.2.tgz", - "integrity": "sha512-YeYnLqSFrL4+tCOXRrfNP03w8FILoF/wDl+ukraJAGzXrjcS383W7LAAvGBbLatEyktmLxRq+9xJinx7QzXW5A==", - "dev": true, - "license": "MIT", - "peer": true - }, "packages/server": { "name": "@neuralnomads/codenomad", "version": "0.20.1", diff --git a/packages/remote-tunnel/package.json b/packages/remote-tunnel/package.json index e67b5c972..574478500 100644 --- a/packages/remote-tunnel/package.json +++ b/packages/remote-tunnel/package.json @@ -12,8 +12,8 @@ "typecheck": "tsc --noEmit -p tsconfig.json" }, "devDependencies": { - "@opentunnel/client": "0.4.1", - "effect": "4.0.2", + "@opentunnel/client": "0.5.1", + "effect": "4.0.1", "typescript": "^5.6.3" } } diff --git a/packages/server/scripts/remote-control-live.test.ts b/packages/server/scripts/remote-control-live.test.ts index 0b5bc192a..a7193bc43 100644 --- a/packages/server/scripts/remote-control-live.test.ts +++ b/packages/server/scripts/remote-control-live.test.ts @@ -10,7 +10,8 @@ import https from "node:https" import os from "node:os" import path from "node:path" import test from "node:test" -import { fileURLToPath, pathToFileURL } from "node:url" +import { createRequire } from "node:module" +import { pathToFileURL } from "node:url" import { openRemoteTunnel } from "@codenomad/remote-tunnel" import { startRemoteSurface } from "../src/remote-control/test-support/surface" @@ -39,8 +40,11 @@ function request(url: string, options: { method?: string; headers?: Record { // The facade never deletes identities; the qualification uses the SDK directly. - const sdk = path.resolve(fileURLToPath(import.meta.url), "../../../remote-tunnel/node_modules/@opentunnel/client/dist/promise/index.js") - const { create, OpenTunnelStorage } = await import(pathToFileURL(sdk).href) + // Resolved from the adapter package, wherever npm placed the SDK (nested or hoisted). + const lookup = createRequire(new URL("../../remote-tunnel/package.json", import.meta.url)).resolve.paths("@opentunnel/client") ?? [] + const root = lookup.map((directory) => path.join(directory, "@opentunnel", "client")).find((directory) => fs.existsSync(directory)) + if (!root) throw new Error("OpenTunnel SDK not found for throwaway tunnel removal") + const { create, OpenTunnelStorage } = await import(pathToFileURL(path.join(root, "dist", "promise", "index.js")).href) const client = create({ store: OpenTunnelStorage.xdg({ env: { ...process.env, XDG_DATA_HOME: dataHome } }) }) try { await client.tunnel.remove({ profile: PROFILE })