Skip to content

feat(openclaw): add native dependency stubs for WebContainer#8

Open
Dhi13man wants to merge 3 commits intoopen-gitagent:mainfrom
Dhi13man:feat/native-stubs
Open

feat(openclaw): add native dependency stubs for WebContainer#8
Dhi13man wants to merge 3 commits intoopen-gitagent:mainfrom
Dhi13man:feat/native-stubs

Conversation

@Dhi13man
Copy link

@Dhi13man Dhi13man commented Mar 21, 2026

Why

OpenClaw depends on 5 native Node.js modules (sharp, @lydell/node-pty, playwright-core, sqlite-vec, node-llama-cpp) that use C/C++ bindings. WebContainer runs a WASM-based Node.js sandbox that cannot load native addons — any require('sharp') crashes the process immediately. This blocks #4.

This PR provides stub packages that export the expected API surface with no-op implementations, allowing OpenClaw to import these modules without crashing.

Design Context

flowchart LR
    A[npm install openclaw] --> B[node_modules/ contains real native packages]
    B --> C[INSTALL_STUBS_SCRIPT runs]
    C --> D[cp stubs over real packages in node_modules/]
    D --> E["require('sharp') → stub builder, no crash"]
    D --> F["require('node-pty') → stub, spawn throws at call time"]
    D --> G["require('playwright-core') → stub, launch rejects"]
Loading

Each stub is a directory with 3 files:

  • package.json — dual exports field (import.mjs, require.cjs)
  • index.cjs — explicit module.exports.X = pattern (compatible with cjs-module-lexer)
  • index.mjs — re-exports from CJS

Why explicit module.exports.X instead of Proxy or spread?

Node's cjs-module-lexer (used by the ESM loader to detect CJS named exports) only parses static patterns. module.exports = new Proxy(...) or module.exports = { ...obj } won't be detected. Every stub uses module.exports.X = X on separate lines so ESM import { X } from 'pkg' resolves correctly.

Trade-offs

  • Stubs are no-ops, not simulators. sharp().resize().toBuffer() returns an empty Buffer. OpenClaw code that checks buffer size (e.g., optimizeImageToPng) will get 0 bytes and short-circuit. Accepted — image processing is disabled in WebContainer, not simulated.
  • Stubs export the API surface OpenClaw actually uses, not the full package API. Additional methods/exports may be needed during D3 integration — the stubs are designed to be extended when import-time crashes reveal gaps.
  • INSTALL_STUBS_SCRIPT copies stubs over real packages after npm install. This is a post-install override, not a pre-install interception. If npm install itself crashes on native build steps, the existing --ignore-scripts flag in container.ts:203 prevents that.

How to review

Order File What to check
1 src/openclaw/stubs/sharp/index.cjs Chainable builder pattern, metadata() returns hasAlpha/channels
2 src/openclaw/stubs/node-llama-cpp/index.cjs resolveModelFile and LlamaLogLevel exports (OpenClaw destructures these)
3 Other stubs Same dual-export pattern, throws at call time
4 src/openclaw/stubs.ts STUB_FILES map (15 entries), INSTALL_STUBS_SCRIPT with @lydell scope handling
5 src/openclaw/stubs-behavior.test.ts Each stub loaded via require() and exercised

Test plan

  • npx vitest run — 42 tests covering:
    • Registry integrity (parameterized across all 5 packages): STUB_FILES has 15 entries, each package has package.json/.cjs/.mjs, dual exports configured, module.exports pattern used, ESM re-exports present
    • Installer script: copies all 5 packages, creates @lydell scope dir, guards against missing node_modules
    • sharp behavior: callable function, chainable builder, toBuffer() → empty Buffer, metadata() includes hasAlpha/channels
    • node-pty behavior: spawn() throws with descriptive error
    • playwright behavior: chromium.launch() rejects, devices exported, TimeoutError class exists
    • sqlite-vec behavior: load() is no-op, getLoadablePath() returns empty string
    • node-llama-cpp behavior: getLlama() rejects, resolveModelFile() rejects, LlamaLogLevel has numeric levels

Closes #4 (partial — native dependency stubs)

Generated with Dhiman's Agentic Suite

Dhi13man and others added 3 commits March 21, 2026 13:58
Stub sharp, @lydell/node-pty, playwright-core, sqlite-vec, and
node-llama-cpp with dual ESM/CJS exports. Includes installer script
that copies stubs into node_modules/ after npm install, replacing
native packages that crash in WebContainer's WASM sandbox.

Co-Authored-By: Dhiman's Agentic Suite <dhiman.seal@hotmail.com>
…, installer guard

- Add resolveModelFile and LlamaLogLevel to node-llama-cpp stub
  (OpenClaw destructures these in embeddings.ts)
- Add hasAlpha and channels to sharp metadata() return
  (OpenClaw reads these in image-ops.ts:377)
- Add node_modules existence check to installer script

Co-Authored-By: Dhiman's Agentic Suite <dhiman.seal@hotmail.com>
Two test files:
- stubs.test.ts: STUB_FILES map integrity, package.json dual exports,
  installer script correctness
- stubs-behavior.test.ts: require() each stub and verify API surface —
  sharp chainable builder + metadata, node-pty spawn throws, playwright
  browser launchers reject, sqlite-vec load no-op, node-llama-cpp
  getLlama/resolveModelFile/LlamaLogLevel exports

Co-Authored-By: Dhiman's Agentic Suite <dhiman.seal@hotmail.com>
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.

Support OpenClaw as an agent template

1 participant