JavaScript Tool Migration & Library System
JavaScript Tool Migration & Library System
Feature Information
- Feature ID: FEAT-015
- Created: 2026-02-28
- Last Updated: 2026-02-28
- Status: Draft
- Priority: P1 (Should Have)
- Owner: TBD
- Related RFC: RFC-015 (pending)
User Story
As a developer or power user of OneClaw,
I want built-in tools to be implemented in JavaScript (backed by native bridges) and third-party JavaScript libraries to be available to all JS tools,
so that the tool ecosystem is easy to extend with community JS libraries, and new tools like webfetch can leverage libraries like Turndown for rich processing without requiring app recompilation.
Typical Scenarios
- The agent calls
webfetchwith a URL. The tool fetches the HTML via the native HTTP bridge, passes it through the bundled Turndown library, and returns clean Markdown – ready for the model to read. - A developer wants to add a new tool that parses RSS feeds. They find a pure-JS RSS parser library, place it in the shared library directory, and write a JS tool that imports it. No Kotlin changes needed.
- The existing
http_requestbuilt-in tool is now a JS file shipped with the app. A user inspects it as a reference when writing their own HTTP-based tool. - The agent uses
read_fileexactly as before – the migration is fully transparent to the AI model and the user.
Feature Description
Overview
FEAT-015 has two tightly coupled goals:
- JS Library Bundle System: Allow shared JavaScript libraries (e.g., Turndown) to be bundled with the app as assets and made available to all JS tools running in the QuickJS runtime.
- Built-in Tool JS Migration: Re-implement the existing Kotlin built-in tools (
get_current_time,read_file,write_file,http_request) as JavaScript tools that ship as app assets, backed by the same native bridges introduced in FEAT-012. Add a newwebfetchbuilt-in JS tool that uses Turndown.
After this migration, the app ships zero Kotlin tool implementations. All tools are JS files – the Kotlin layer only provides bridges to native Android capabilities.
Architecture Overview
AI Model
↓ tool call
ToolExecutionEngine (Kotlin, unchanged)
↓
ToolRegistry (Kotlin, unchanged)
├── Built-in JS Tools [NEW - shipped as assets/js/tools/]
│ get_current_time.js
│ read_file.js
│ write_file.js
│ http_request.js
│ webfetch.js ← new tool
└── User JS Tools (FEAT-012 - from /sdcard/OneClaw/tools/)
QuickJS Runtime (FEAT-012 bridge)
├── Native Bridges: _time(), _readFile(), _writeFile(), _httpRequest()
└── Shared Library Loader: lib('turndown'), lib('...')
↑
assets/js/lib/
turndown.min.js
(future libraries)
Shared JS Library System
Library Storage
Shared libraries are bundled as app assets:
assets/js/lib/
turndown.min.js
User-provided libraries (for advanced use) can also be placed at:
{app_internal}/js/lib/
App-bundled libraries take precedence on name conflict with user-provided ones.
Library Loading API
Inside any JS tool (both built-in and user-defined), a library is loaded via a lib() function injected into the QuickJS global scope:
const TurndownService = lib('turndown');
const td = new TurndownService();
const markdown = td.turndown(htmlString);
The lib() function:
- Looks up the library name in
assets/js/lib/(then{app_internal}/js/lib/) - Evaluates the library script in a sandboxed scope
- Returns the library’s exported value (the last assigned
module.exports,exports, or the last evaluated expression) - Caches the result – subsequent
lib('turndown')calls within the same runtime session return the cached instance
Library Compatibility
Libraries must be:
- Self-contained: No Node.js built-ins (
require('fs'),require('path'), etc.) - QuickJS-compatible: Standard ES5/ES6+ JS; no browser-only APIs
- Pure-logic libraries: Libraries that process data, not libraries that require a DOM or native Node APIs
Turndown meets all three criteria.
Built-in Tool JS Migration
The following Kotlin tools are removed and replaced with JS equivalents:
| Tool | Kotlin Class (removed) | JS Asset (new) |
|---|---|---|
get_current_time |
GetCurrentTimeTool.kt |
assets/js/tools/get_current_time.js |
read_file |
ReadFileTool.kt |
assets/js/tools/read_file.js |
write_file |
WriteFileTool.kt |
assets/js/tools/write_file.js |
http_request |
HttpRequestTool.kt |
assets/js/tools/http_request.js |
Each JS tool calls through to native bridges already established in FEAT-012:
// get_current_time.js
function execute(params) {
return { time: _time() };
}
// http_request.js
function execute(params) {
const response = _httpRequest(params.method, params.url, params.headers, params.body);
return response;
}
The tool definitions (name, description, parameter schema) move from hardcoded Kotlin ToolDefinition objects to the same .json metadata format used by user JS tools (FEAT-012), stored alongside the .js files in assets/js/tools/.
New Tool: webfetch
webfetch is a new built-in JS tool that fetches a URL and returns the page content as clean Markdown.
Tool Definition
| Field | Value |
|---|---|
| Name | webfetch |
| Description | Fetch a web page and return its content as Markdown |
| Parameters | url (string, required): The URL to fetch |
| Required Permissions | INTERNET (already required by http_request) |
| Timeout | 30 seconds |
| Returns | Markdown string of the page content |
Implementation
// webfetch.js
const TurndownService = lib('turndown');
function execute(params) {
const response = _httpRequest('GET', params.url, {}, null);
if (response.error) {
return { error: response.error };
}
const contentType = (response.headers['content-type'] || '').toLowerCase();
if (!contentType.includes('text/html')) {
return { content: response.body };
}
const td = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
// Remove nav, header, footer, script, style before conversion
const cleanedHtml = response.body
.replace(/<(script|style|nav|header|footer)[^>]*>[\s\S]*?<\/\1>/gi, '');
const markdown = td.turndown(cleanedHtml);
return { content: markdown };
}
Relationship to http_request
webfetch and http_request are separate tools with distinct responsibilities:
http_request |
webfetch |
|
|---|---|---|
| Purpose | General-purpose HTTP calls | Human-readable web page content |
| Response | Raw HTTP response (body, status, headers) | Markdown string |
| Use case | APIs, JSON endpoints, custom headers | Documentation, articles, web pages |
| Turndown | No | Yes |
Built-in JS Tool Loading
Built-in JS tools (from assets/js/tools/) are loaded at app startup alongside FEAT-012 user JS tools:
- At startup,
JsToolLoaderscansassets/js/tools/for*.json+*.jspairs - Each pair is registered in
ToolRegistryas aJsTool(same class as FEAT-012 user tools) - App-internal user tools and external user tools are loaded afterward (FEAT-012 behavior unchanged)
- On name conflict: user tools override built-in tools (power users can replace any built-in tool)
Native Bridge Requirements
FEAT-012 introduced QuickJS bridges. FEAT-015 requires the following bridges to be available (some may already exist from FEAT-012):
| Bridge Function | Description | Already in FEAT-012? |
|---|---|---|
_time() |
Returns ISO 8601 current time string | Likely yes |
_readFile(path) |
Reads file content as string | Likely yes |
_writeFile(path, content) |
Writes string to file | Likely yes |
_httpRequest(method, url, headers, body) |
Makes HTTP request, returns {status, body, headers, error} |
Likely yes |
If any bridge is missing from FEAT-012, it must be added as part of FEAT-015 implementation.
User Interaction Flows
Using webfetch in a Conversation
1. User: "Summarize the content at https://example.com/article"
2. AI calls webfetch(url="https://example.com/article")
3. Tool fetches HTML, runs Turndown, returns Markdown
4. AI summarizes the Markdown content and responds to the user
5. Chat shows the webfetch tool call (URL → Markdown)
User JS Tool Using a Shared Library
1. User asks AI: "Create a tool that converts text to title case"
2. AI writes:
- {tools}/title_case.js (uses lib('some-case-library') or plain JS)
- {tools}/title_case.json
3. Tool is immediately available in the registry
4. User calls the tool in the next message
Acceptance Criteria
Must pass (all required):
- All four existing Kotlin built-in tools (
get_current_time,read_file,write_file,http_request) are removed from Kotlin and replaced with JS equivalents - Migrated tools behave identically to their Kotlin predecessors (same tool names, same parameter schemas, same return formats)
- Built-in JS tools are loaded from
assets/js/tools/at app startup lib()function is available in the QuickJS global scope for all JS toolslib('turndown')correctly loads the bundled Turndown library and returns a usableTurndownServiceconstructorwebfetchtool is available and returns clean Markdown for a given URLwebfetchhandles non-HTML responses (returns raw body without Turndown processing)webfetchhandles HTTP errors gracefully (returns error message)- User-defined JS tools (FEAT-012) can also use
lib()to access shared libraries - User tools with the same name as a built-in tool override the built-in tool
- All existing Layer 1A tests for built-in tools continue to pass after migration
Optional (nice to have for V1):
- User can add custom libraries to
{app_internal}/js/lib/and use them vialib() - Library loading errors produce clear error messages naming the library that failed
- Built-in tool JS source files are viewable (as a reference) from Settings or file manager
UI/UX Requirements
This feature has no new UI. The migration is transparent to users:
- Tool names and behaviors are identical
webfetchappears in tool lists like any other tool- Tool call display in chat is unchanged
Feature Boundary
Included
- JS Library Bundle System (
lib()API,assets/js/lib/directory) - Bundled Turndown library (
turndown.min.js) - Migration of
get_current_time,read_file,write_file,http_requestfrom Kotlin to JS - New
webfetchbuilt-in JS tool - Built-in JS tool loading from
assets/js/tools/ - User tool override of built-in tools on name conflict
Not Included (V1)
- Library version management or update mechanism
- Library marketplace or discovery
- Tree-shaking or bundling optimization for libraries
- Libraries that require a DOM (jsdom, etc.)
- HTML truncation / token-limit awareness in
webfetch(deferred to a future feature) webfetchcaching (deferred)- PDF or non-HTML content extraction in
webfetch(returns raw body)
Business Rules
- Built-in JS tools are read-only – they cannot be edited or deleted by the user via in-app UI
- A user tool with the same name as a built-in tool silently overrides the built-in tool
lib()only resolves libraries by name (not by path) – arbitrary file access is not allowed- Libraries loaded via
lib()are sandboxed within the same QuickJS instance – they cannot access the Android API directly webfetchonly accepts HTTP and HTTPS URLswebfetchfollows redirects (up to 5 hops, consistent withhttp_requestbehavior)webfetchstrips<script>,<style>,<nav>,<header>,<footer>before Turndown conversion
Non-Functional Requirements
Performance
lib()first load (cold): < 100ms for Turndownlib()subsequent calls (cached): < 1mswebfetchtotal round-trip (network excluded): < 200ms for Turndown processing- App startup time increase from loading built-in JS tools: < 100ms
Compatibility
- All migrated tools must be fully backward-compatible: same tool names, same parameter schemas, same return value structures
Security
lib()cannot load files outsideassets/js/lib/and{app_internal}/js/lib/- Libraries run in the same QuickJS sandbox as other JS tools – no elevated access
webfetchdoes not follow cross-origin redirects tofile://orcontent://URIs
Dependencies
Depends On
- FEAT-004 (Tool System): The tool interface, registry, and execution engine being replaced
- FEAT-012 (JavaScript Tool Engine): QuickJS runtime, native bridges, JS tool loading infrastructure
Depended On By
- No other features currently depend on FEAT-015
External Dependencies
- Turndown (~20KB minified, no runtime dependencies): bundled as
assets/js/lib/turndown.min.js
Error Handling
Error Scenarios
- Library not found
- Cause:
lib('unknown-lib')called for a non-existent library - Handling: Throw
Error("Library 'unknown-lib' not found")inside QuickJS – caught byToolExecutionEngineand returned asToolResult.error()
- Cause:
- Library parse/evaluation error
- Cause: Corrupted library file
- Handling: Throw descriptive error; tool returns error result
webfetchnetwork error- Cause: URL unreachable, timeout, DNS failure
- Handling: Return
{ error: "Network error: <message>" }
webfetchnon-200 response- Cause: 404, 500, etc.
- Handling: Return
{ error: "HTTP <status>: <status text>", content: response.body }
- Turndown conversion failure
- Cause: Malformed HTML that Turndown cannot process
- Handling: Fall back to returning raw stripped text; log warning
- Built-in tool JS file missing from assets
- Cause: Build misconfiguration
- Handling: Log error at startup; continue without registering the affected tool; surface in a startup diagnostics log
Future Improvements
- HTML truncation in
webfetch: Limit Markdown output to a configurable token budget to avoid overwhelming the model context webfetchcaching: Cache fetched pages for a short TTL (e.g., 5 minutes) to avoid redundant network requests- More bundled libraries: e.g., a Markdown parser, a CSV parser, a date/time utility
- Library manifest: A
lib-manifest.jsonlisting bundled libraries with name, version, and description – surfaced in Settings - User library upload: UI for adding custom libraries without using the file manager
Test Points
Functional Tests
- Verify
get_current_timeJS tool returns correct ISO 8601 time - Verify
read_fileJS tool reads file content correctly - Verify
write_fileJS tool writes and overwrites file content correctly - Verify
http_requestJS tool makes GET/POST requests and returns status, body, headers - Verify all four migrated tools have identical parameter schemas to their Kotlin predecessors
- Verify
lib('turndown')returns a usable constructor - Verify
lib('nonexistent')returns a clear error - Verify
webfetchreturns Markdown for an HTML page - Verify
webfetchstrips<script>and<nav>tags before conversion - Verify
webfetchreturns raw body for non-HTML content types - Verify
webfetchreturns error for unreachable URLs - Verify user JS tools can call
lib()for shared libraries - Verify user tool with same name as built-in overrides the built-in
Edge Cases
webfetchon a page with no body content (empty HTML)webfetchon a very large HTML page (>1MB)lib()called concurrently from multiple tools in the same sessionhttp_requestwith custom headers and POST body (same as before migration)- Built-in tool directory exists but a
.jsonor.jsfile is missing for one tool webfetchredirect chain exceeding 5 hops
Change History
| Date | Version | Changes | Owner |
|---|---|---|---|
| 2026-02-28 | 0.1 | Initial version | - |