mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(browser): preserve request lifetime on Node 24
This commit is contained in:
+3
-2
@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Browser actions on Node 24:** keep browser request cancellation bound to the client and response lifetime instead of Node 24.16+'s prematurely aborted body-stream signal, preventing valid POST actions from failing after JSON parsing. Thanks @obviyus and @vincentkoc.
|
||||
- **SecretRef model credentials:** keep resolved provider secrets behind process-local sentinels through auth storage, stream setup, SDK configuration, and managed local-provider probing, then inject plaintext only at the final network or provider-plugin boundary while retaining exact-value log redaction. (#102008, #102009)
|
||||
- **Lean local model shell access:** keep `exec` directly visible beside the default structured Tool Search controls so coding-tuned local models can use their shell fallback instead of searching for missing domain tools. (#87587) Thanks @vincentkoc and @maweibin.
|
||||
- **OAuth refresh contention diagnostics:** keep local lock paths out of user-facing refresh failures and avoid duplicate failure prefixes while preserving structured provider and profile classification. (#83383) Thanks @vincentkoc.
|
||||
@@ -75,7 +76,7 @@ Docs: https://docs.openclaw.ai
|
||||
- **Microsoft Teams attachment metadata:** bound Bot Framework `attachmentInfo` JSON reads and cancel oversized streams before they can exhaust Gateway memory. (#99125) Thanks @ly85206559.
|
||||
- **Agent auth copy order:** preserve the source agent's portable auth-profile precedence when copying credentials to a new agent while excluding skipped profiles and transient auth state. (#100833) Thanks @machine3at.
|
||||
- **Memory session repair:** keep daily dreaming ingestion bookkeeping outside session-corpus audit and repair so `memory status --fix` preserves healthy daily state. (#93389) Thanks @Alix-007 and @vincentkoc.
|
||||
- **Remote browser CDP policy:** allow the configured CDP control host through an existing hostname allowlist without widening page navigation policy, while keeping strict-policy discovery bound to the configured control authority. (#100986, #100819) Thanks @NianJiuZst and @SarinV.
|
||||
- **Remote browser CDP policy:** allow the configured CDP control host through an existing hostname allowlist without widening page navigation policy, while keeping strict-policy discovery bound to the configured control authority. (#100986, #100819) Thanks @NianJiuZst, @SarinV, and @vincentkoc.
|
||||
- **Config unset diagnostics:** explain when an inherited or default configuration value cannot be unset instead of reporting a misleading successful deletion. (#96557) Thanks @moeghashim.
|
||||
- **Crestodian command probes:** contain stdout and stderr stream failures while keeping child-process close and spawn errors authoritative, preventing unhandled probe crashes. (#100741) Thanks @lsr911.
|
||||
- **Feishu mention forwarding:** fail closed when the bot Open ID is unavailable so group messages cannot be misclassified as explicit bot mentions. (#100891) Thanks @zhangguiping-xydt.
|
||||
@@ -202,7 +203,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Complete contribution record
|
||||
|
||||
This audited record covers the complete v2026.6.11..b81666ca6af25c86cc099983a4358cdc5ea9ced8 history: 1974 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
|
||||
This audited record covers the complete v2026.6.11..HEAD history: 1974 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
|
||||
|
||||
#### Pull requests
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { Express, NextFunction, Request, Response } from "express";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { installBrowserCommonMiddleware } from "./server-middleware.js";
|
||||
|
||||
type Middleware = (req: Request, res: Response, next: NextFunction) => void;
|
||||
|
||||
describe("installBrowserCommonMiddleware", () => {
|
||||
it("shadows native request signals with the browser response-lifetime signal", () => {
|
||||
const middleware: Middleware[] = [];
|
||||
const app = {
|
||||
use: vi.fn((...handlers: unknown[]) => {
|
||||
for (const handler of handlers) {
|
||||
if (typeof handler === "function") {
|
||||
middleware.push(handler as Middleware);
|
||||
}
|
||||
}
|
||||
return app;
|
||||
}),
|
||||
} as unknown as Express;
|
||||
installBrowserCommonMiddleware(app);
|
||||
|
||||
const nativeController = new AbortController();
|
||||
const req = new EventEmitter() as EventEmitter & Request;
|
||||
const requestPrototype = Object.create(Object.getPrototypeOf(req)) as object;
|
||||
Object.defineProperty(requestPrototype, "signal", {
|
||||
configurable: true,
|
||||
get: () => nativeController.signal,
|
||||
});
|
||||
Object.setPrototypeOf(req, requestPrototype);
|
||||
|
||||
const res = new EventEmitter() as EventEmitter & Response;
|
||||
Object.defineProperty(res, "writableEnded", { value: false, writable: true });
|
||||
const next = vi.fn();
|
||||
const commonMiddleware = middleware[0];
|
||||
if (!commonMiddleware) {
|
||||
throw new Error("browser common middleware was not installed");
|
||||
}
|
||||
|
||||
commonMiddleware(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalledOnce();
|
||||
expect(Object.hasOwn(req, "signal")).toBe(true);
|
||||
expect(req.signal).not.toBe(nativeController.signal);
|
||||
expect(req.signal.aborted).toBe(false);
|
||||
|
||||
req.emit("aborted");
|
||||
expect(req.signal.aborted).toBe(true);
|
||||
expect(req.signal.reason).toEqual(new Error("request aborted"));
|
||||
});
|
||||
});
|
||||
@@ -33,15 +33,12 @@ export function installBrowserCommonMiddleware(app: Express) {
|
||||
abort();
|
||||
}
|
||||
});
|
||||
// Make the signal available to browser route handlers on Node versions
|
||||
// whose IncomingMessage does not already expose a native read-only signal.
|
||||
const requestWithSignal = req as Request & { signal?: AbortSignal };
|
||||
if (!(requestWithSignal.signal instanceof AbortSignal)) {
|
||||
Object.defineProperty(req, "signal", {
|
||||
value: ctrl.signal,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
// Node 24.16+'s native request signal aborts when a POST body finishes.
|
||||
// Browser work follows the client/response lifetime instead.
|
||||
Object.defineProperty(req, "signal", {
|
||||
value: ctrl.signal,
|
||||
configurable: true,
|
||||
});
|
||||
next();
|
||||
});
|
||||
app.use(express.json({ limit: "1mb" }));
|
||||
|
||||
Reference in New Issue
Block a user