capacitor-mobile-claw 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,276 @@
1
+ /**
2
+ * fetch() polyfill — replaces the broken undici-based built-in fetch.
3
+ *
4
+ * Capacitor-NodeJS ships Node.js v18.20.4 with --expose_wasm disabled.
5
+ * The built-in fetch() uses undici whose HTTP parser (llhttp) requires WASM.
6
+ * This module provides a native https/http implementation.
7
+ *
8
+ * MUST be imported before ANY other module that might call fetch().
9
+ */
10
+
11
+ // ── WebAssembly stub ────────────────────────────────────────────────────
12
+ // Provide a minimal WebAssembly global so undici's lazyllhttp doesn't crash
13
+ // with "ReferenceError: WebAssembly is not defined". The stub lets the
14
+ // internal module graph load without error, but we replace fetch anyway.
15
+ if (typeof globalThis.WebAssembly === 'undefined') {
16
+ class _WasmError extends Error { constructor(msg) { super(msg); this.name = this.constructor.name; } }
17
+ class RuntimeError extends _WasmError {}
18
+ class CompileError extends _WasmError {}
19
+ class LinkError extends _WasmError {}
20
+
21
+ const _noWasm = (name) => { throw new CompileError(`WebAssembly.${name}() is not available (WASM disabled)`); };
22
+
23
+ globalThis.WebAssembly = {
24
+ // All compilation/instantiation methods throw CompileError so callers
25
+ // (sql.js, undici, pyodide) fall into their error/catch handlers cleanly.
26
+ compile: async () => _noWasm('compile'),
27
+ instantiate: async () => _noWasm('instantiate'),
28
+ instantiateStreaming: async () => _noWasm('instantiateStreaming'),
29
+ compileStreaming: async () => _noWasm('compileStreaming'),
30
+ validate: () => false,
31
+ Module: class Module { constructor() { _noWasm('Module'); } },
32
+ Instance: class Instance { constructor() { _noWasm('Instance'); } },
33
+ Memory: class Memory { constructor() { _noWasm('Memory'); } },
34
+ Table: class Table { constructor() { _noWasm('Table'); } },
35
+ Global: class Global { constructor() { _noWasm('Global'); } },
36
+ RuntimeError,
37
+ CompileError,
38
+ LinkError,
39
+ };
40
+ // Mark as stub so other modules can detect it
41
+ globalThis.WebAssembly._isStub = true;
42
+ // Catch unhandled rejections from lazy undici WASM compilation attempts.
43
+ // Node.js v18's built-in fetch triggers undici's lazyllhttp which calls
44
+ // WebAssembly.compile() — our stub rejects it, but the error may propagate
45
+ // as an unhandled rejection and crash the process. Suppress these errors.
46
+ process.on('unhandledRejection', (reason) => {
47
+ const msg = reason?.message || String(reason);
48
+ if (msg.includes('WASM disabled') || msg.includes('WebAssembly') ||
49
+ reason instanceof CompileError || reason instanceof _WasmError) {
50
+ // Silently swallow — we replace fetch with our native polyfill anyway
51
+ return;
52
+ }
53
+ // Let other unhandled rejections through to default handler
54
+ console.error('[unhandledRejection]', reason);
55
+ });
56
+
57
+ console.log('[fetch-polyfill] Installed WebAssembly stub (WASM disabled in this runtime)');
58
+ }
59
+
60
+ import * as _https from 'node:https';
61
+ import * as _http from 'node:http';
62
+ import { Readable } from 'node:stream';
63
+
64
+ class _Headers {
65
+ #map = new Map();
66
+ constructor(init) {
67
+ if (init instanceof _Headers) {
68
+ init.forEach((v, k) => this.set(k, v));
69
+ } else if (Array.isArray(init)) {
70
+ for (const [k, v] of init) this.set(k, v);
71
+ } else if (init && typeof init === 'object') {
72
+ for (const [k, v] of Object.entries(init)) this.set(k, v);
73
+ }
74
+ }
75
+ get(k) { return this.#map.get(k.toLowerCase()) ?? null; }
76
+ set(k, v) { this.#map.set(k.toLowerCase(), String(v)); }
77
+ has(k) { return this.#map.has(k.toLowerCase()); }
78
+ delete(k) { this.#map.delete(k.toLowerCase()); }
79
+ forEach(cb) { this.#map.forEach((v, k) => cb(v, k, this)); }
80
+ *entries() { yield* this.#map.entries(); }
81
+ *keys() { yield* this.#map.keys(); }
82
+ *values() { yield* this.#map.values(); }
83
+ [Symbol.iterator]() { return this.entries(); }
84
+ }
85
+
86
+ class _Response {
87
+ #body; #bodyUsed = false; #status; #statusText; #headers; #url;
88
+ constructor(body, { status = 200, statusText = '', headers = {}, url = '' } = {}) {
89
+ this.#body = body;
90
+ this.#status = status;
91
+ this.#statusText = statusText;
92
+ this.#headers = new _Headers(headers);
93
+ this.#url = url;
94
+ }
95
+ get ok() { return this.#status >= 200 && this.#status < 300; }
96
+ get status() { return this.#status; }
97
+ get statusText() { return this.#statusText; }
98
+ get headers() { return this.#headers; }
99
+ get url() { return this.#url; }
100
+ get bodyUsed() { return this.#bodyUsed; }
101
+ get body() {
102
+ if (this.#body instanceof Readable) {
103
+ if (typeof Readable.toWeb === 'function') {
104
+ return Readable.toWeb(this.#body);
105
+ }
106
+ const nodeStream = this.#body;
107
+ return new ReadableStream({
108
+ start(controller) {
109
+ nodeStream.on('data', (chunk) => controller.enqueue(typeof chunk === 'string' ? new TextEncoder().encode(chunk) : chunk));
110
+ nodeStream.on('end', () => controller.close());
111
+ nodeStream.on('error', (err) => controller.error(err));
112
+ },
113
+ cancel() { nodeStream.destroy(); },
114
+ });
115
+ }
116
+ return null;
117
+ }
118
+ async text() {
119
+ this.#bodyUsed = true;
120
+ if (this.#body instanceof Readable) {
121
+ const chunks = [];
122
+ for await (const chunk of this.#body) chunks.push(chunk);
123
+ return Buffer.concat(chunks).toString('utf-8');
124
+ }
125
+ if (Buffer.isBuffer(this.#body)) return this.#body.toString('utf-8');
126
+ return String(this.#body ?? '');
127
+ }
128
+ async json() { return JSON.parse(await this.text()); }
129
+ async arrayBuffer() {
130
+ this.#bodyUsed = true;
131
+ if (this.#body instanceof Readable) {
132
+ const chunks = [];
133
+ for await (const chunk of this.#body) chunks.push(chunk);
134
+ const buf = Buffer.concat(chunks);
135
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
136
+ }
137
+ if (Buffer.isBuffer(this.#body)) {
138
+ return this.#body.buffer.slice(this.#body.byteOffset, this.#body.byteOffset + this.#body.byteLength);
139
+ }
140
+ const buf = Buffer.from(String(this.#body ?? ''), 'utf-8');
141
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
142
+ }
143
+ async blob() {
144
+ const ab = await this.arrayBuffer();
145
+ const { Blob: B } = await import('node:buffer');
146
+ return new B([ab], { type: this.#headers.get('content-type') || '' });
147
+ }
148
+ }
149
+
150
+ function _nativeFetch(input, init = {}) {
151
+ return new Promise((resolve, reject) => {
152
+ const urlStr = typeof input === 'string' ? input : (input?.url ?? String(input));
153
+ const parsed = new URL(urlStr);
154
+ const transport = parsed.protocol === 'https:' ? _https : _http;
155
+ const method = (init.method || 'GET').toUpperCase();
156
+ const headers = {};
157
+
158
+ if (init.headers) {
159
+ if (init.headers instanceof _Headers || (init.headers.forEach && init.headers.entries)) {
160
+ init.headers.forEach((v, k) => { headers[k] = v; });
161
+ } else if (Array.isArray(init.headers)) {
162
+ for (const [k, v] of init.headers) headers[k.toLowerCase()] = v;
163
+ } else {
164
+ for (const [k, v] of Object.entries(init.headers)) headers[k.toLowerCase()] = v;
165
+ }
166
+ }
167
+
168
+ let bodyData = null;
169
+ if (init.body != null) {
170
+ if (typeof init.body === 'string') {
171
+ bodyData = Buffer.from(init.body, 'utf-8');
172
+ } else if (Buffer.isBuffer(init.body)) {
173
+ bodyData = init.body;
174
+ } else if (init.body instanceof Uint8Array) {
175
+ bodyData = Buffer.from(init.body);
176
+ } else {
177
+ bodyData = Buffer.from(String(init.body), 'utf-8');
178
+ }
179
+ if (!headers['content-length']) {
180
+ headers['content-length'] = String(bodyData.length);
181
+ }
182
+ }
183
+
184
+ const reqOpts = {
185
+ hostname: parsed.hostname,
186
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
187
+ path: parsed.pathname + parsed.search,
188
+ method,
189
+ headers,
190
+ };
191
+
192
+ const req = transport.request(reqOpts, (res) => {
193
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
194
+ const redirectUrl = new URL(res.headers.location, urlStr);
195
+ const redirectInit = { ...init };
196
+ if (res.statusCode === 303) {
197
+ redirectInit.method = 'GET';
198
+ delete redirectInit.body;
199
+ }
200
+ res.resume();
201
+ _nativeFetch(redirectUrl.href, redirectInit).then(resolve, reject);
202
+ return;
203
+ }
204
+
205
+ const responseHeaders = {};
206
+ for (const [k, v] of Object.entries(res.headers)) {
207
+ if (v != null) responseHeaders[k] = Array.isArray(v) ? v.join(', ') : v;
208
+ }
209
+
210
+ const response = new _Response(res, {
211
+ status: res.statusCode,
212
+ statusText: res.statusMessage || '',
213
+ headers: responseHeaders,
214
+ url: urlStr,
215
+ });
216
+ resolve(response);
217
+ });
218
+
219
+ if (init.signal) {
220
+ if (init.signal.aborted) {
221
+ req.destroy();
222
+ reject(new DOMException('The operation was aborted.', 'AbortError'));
223
+ return;
224
+ }
225
+ const onAbort = () => {
226
+ req.destroy();
227
+ reject(new DOMException('The operation was aborted.', 'AbortError'));
228
+ };
229
+ init.signal.addEventListener('abort', onAbort, { once: true });
230
+ req.on('close', () => init.signal.removeEventListener('abort', onAbort));
231
+ }
232
+
233
+ req.on('error', reject);
234
+ req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
235
+
236
+ if (bodyData) req.write(bodyData);
237
+ req.end();
238
+ });
239
+ }
240
+
241
+ // DOMException may not exist in Node.js v18
242
+ if (typeof globalThis.DOMException === 'undefined') {
243
+ globalThis.DOMException = class DOMException extends Error {
244
+ #name;
245
+ constructor(message, name = 'Error') { super(message); this.#name = name; }
246
+ get name() { return this.#name; }
247
+ };
248
+ }
249
+
250
+ // Replace the broken undici-based fetch with our native implementation.
251
+ // In Node.js v18, globalThis.fetch may be a lazy getter that triggers undici
252
+ // loading on first access. Use delete + defineProperty to fully replace it.
253
+ try { delete globalThis.fetch; } catch {}
254
+ Object.defineProperty(globalThis, 'fetch', {
255
+ value: _nativeFetch, writable: true, configurable: true, enumerable: true,
256
+ });
257
+ try { delete globalThis.Headers; } catch {}
258
+ Object.defineProperty(globalThis, 'Headers', {
259
+ value: _Headers, writable: true, configurable: true, enumerable: true,
260
+ });
261
+ try { delete globalThis.Response; } catch {}
262
+ Object.defineProperty(globalThis, 'Response', {
263
+ value: _Response, writable: true, configurable: true, enumerable: true,
264
+ });
265
+ if (!globalThis.Request) {
266
+ globalThis.Request = class Request {
267
+ constructor(input, init = {}) {
268
+ this.url = typeof input === 'string' ? input : input.url;
269
+ this.method = init.method || 'GET';
270
+ this.headers = new _Headers(init.headers);
271
+ this.body = init.body ?? null;
272
+ }
273
+ };
274
+ }
275
+
276
+ console.log('[fetch-polyfill] Replaced built-in fetch with native https implementation');
@@ -15,8 +15,28 @@
15
15
  * - Auth profile management (auth-profiles.json)
16
16
  */
17
17
 
18
+ // ── DEBUG: Global crash catchers (iOS only shows console.error) ──────────
19
+ // Must be BEFORE any imports so we catch errors during module evaluation.
20
+ process.on('uncaughtException', (err) => {
21
+ console.error(`[DEBUG] UNCAUGHT EXCEPTION: ${err.message}`);
22
+ console.error(err.stack || '(no stack)');
23
+ });
24
+ process.on('unhandledRejection', (reason) => {
25
+ const msg = reason?.message || String(reason);
26
+ // Don't log WASM-related rejections (handled by fetch-polyfill)
27
+ if (msg.includes('WASM disabled') || msg.includes('WebAssembly')) return;
28
+ console.error(`[DEBUG] UNHANDLED REJECTION: ${msg}`);
29
+ if (reason?.stack) console.error(reason.stack);
30
+ });
31
+
32
+ // ── fetch() polyfill — MUST be first import ─────────────────────────────
33
+ // Replaces the broken undici-based built-in fetch (requires WebAssembly)
34
+ // with a native https/http implementation. Being a separate module imported
35
+ // first ensures globalThis.fetch is replaced before any dependency evaluates.
36
+ import './fetch-polyfill.js';
37
+
18
38
  // ── Node.js v18 polyfills (Capacitor-NodeJS ships v18.20.4) ──────────────
19
- // undici and Anthropic SDK expect globals that only exist in Node.js >=20.
39
+ // Anthropic SDK expects globals that only exist in Node.js >=20.
20
40
  import { Blob } from 'node:buffer';
21
41
  if (typeof globalThis.File === 'undefined') {
22
42
  globalThis.File = class File extends Blob {
@@ -50,6 +70,7 @@ if (typeof globalThis.FormData === 'undefined') {
50
70
  import { createRequire } from 'node:module';
51
71
  const _require = createRequire(import.meta.url);
52
72
  const { channel } = _require('bridge');
73
+ console.error(`[DEBUG] Bridge channel loaded OK`);
53
74
 
54
75
  import { resolve, join } from 'node:path';
55
76
  import {
@@ -69,6 +90,7 @@ import { initMcpBridge } from './mcp-bridge-client.js';
69
90
  import { buildMcpAgentTools } from './mcp-agent-tools.js';
70
91
 
71
92
  const mcpBridge = initMcpBridge(channel);
93
+ console.error(`[DEBUG] MCP bridge initialized`);
72
94
 
73
95
  // Cache for discovered MCP tools — avoids re-discovery on every agent run
74
96
  let cachedMcpTools = null;
@@ -569,25 +591,13 @@ function executeJsTool(args) {
569
591
  }
570
592
 
571
593
  // ── Python execution (Phase 5) ───────────────────────────────────────────
594
+ // Pyodide requires WebAssembly which is disabled in Capacitor-NodeJS v18.
595
+ // Stub it out — execute_python returns a clear error message.
572
596
 
573
- import { loadPyodide } from 'pyodide';
574
-
575
- let pyodideInstance = null;
597
+ // import { loadPyodide } from 'pyodide'; // DISABLED: requires WebAssembly
576
598
 
577
599
  async function getPyodide() {
578
- if (!pyodideInstance) {
579
- pyodideInstance = await loadPyodide();
580
- // Block dangerous modules for sandbox security
581
- pyodideInstance.runPython(`
582
- import sys
583
- for _mod in ['subprocess', 'socket', 'http', 'urllib', 'ftplib', 'smtplib',
584
- 'webbrowser', 'ctypes', 'multiprocessing', 'shutil', 'tempfile',
585
- 'signal', 'resource']:
586
- sys.modules[_mod] = None
587
- del _mod
588
- `);
589
- }
590
- return pyodideInstance;
600
+ throw new Error('Python execution is unavailable: WebAssembly is disabled in this Node.js runtime.');
591
601
  }
592
602
 
593
603
  async function executePythonTool(args) {
@@ -1676,26 +1686,42 @@ function _legacySetupTools() {
1676
1686
  // ── Main agent run function ──────────────────────────────────────────────
1677
1687
 
1678
1688
  async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
1679
- await refreshOAuthTokenIfNeeded(agentId);
1689
+ console.error(`[DEBUG] runAgentLoop START: agentId=${agentId} sessionKey=${sessionKey} model=${requestedModel}`);
1690
+ try {
1691
+ await refreshOAuthTokenIfNeeded(agentId);
1692
+ } catch (oauthErr) {
1693
+ console.error(`[DEBUG] OAuth refresh failed: ${oauthErr.message}`);
1694
+ }
1680
1695
  const authProfiles = loadAuthProfiles(agentId);
1696
+ const profileKeys = Object.keys(authProfiles.profiles || {});
1697
+ const profileTypes = profileKeys.map(k => `${k}:${authProfiles.profiles[k]?.type}`);
1698
+ console.error(`[DEBUG] Auth profiles: ${profileKeys.length} profiles [${profileTypes.join(', ')}] lastGood=${JSON.stringify(authProfiles.lastGood)}`);
1681
1699
  const apiKey = resolveApiKey(authProfiles);
1682
1700
 
1683
1701
  if (!apiKey) {
1702
+ console.error(`[DEBUG] NO API KEY — sending agent.error`);
1684
1703
  channel.send('message', {
1685
1704
  type: 'agent.error',
1686
1705
  error: 'No Anthropic API key configured. Go to Settings to add one.',
1687
1706
  });
1688
1707
  return;
1689
1708
  }
1709
+ console.error(`[DEBUG] API key resolved: type=${apiKey.startsWith('sk-ant-oat') ? 'oauth' : apiKey.startsWith('sk-ant-') ? 'api_key' : 'unknown'} len=${apiKey.length} prefix=${apiKey.slice(0,12)}...`);
1690
1710
 
1691
1711
  const systemPrompt = loadSystemPrompt();
1712
+ console.error(`[DEBUG] System prompt loaded: ${systemPrompt.length} chars`);
1692
1713
  const modelId = requestedModel || 'claude-sonnet-4-5';
1714
+ console.error(`[DEBUG] Getting model: provider=anthropic modelId=${modelId}`);
1693
1715
  const model = getModel('anthropic', modelId);
1716
+ console.error(`[DEBUG] Model resolved: ${JSON.stringify({id: model?.id, provider: model?.provider, api: model?.api}).slice(0,200)}`);
1694
1717
 
1695
1718
  // Merge local tools with MCP device tools (if bridge is available)
1696
1719
  const localTools = buildAgentTools();
1720
+ console.error(`[DEBUG] Local tools: ${localTools.length}`);
1697
1721
  const mcpTools = await discoverMcpTools();
1722
+ console.error(`[DEBUG] MCP tools: ${mcpTools.length}`);
1698
1723
  const tools = [...localTools, ...mcpTools];
1724
+ console.error(`[DEBUG] Total tools: ${tools.length}`);
1699
1725
 
1700
1726
  // Create Agent instance
1701
1727
  const agent = new Agent({
@@ -1766,9 +1792,11 @@ async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
1766
1792
 
1767
1793
  channel.addListener('message', async (event) => {
1768
1794
  const msg = event;
1795
+ console.error(`[DEBUG] << RECV msg type=${msg?.type} keys=${Object.keys(msg||{}).join(',')}`);
1769
1796
 
1770
1797
  switch (msg.type) {
1771
1798
  case 'agent.start': {
1799
+ console.error(`[DEBUG] agent.start: prompt=${(msg.prompt||'').slice(0,80)} model=${msg.model} agentId=${msg.agentId} sessionKey=${msg.sessionKey}`);
1772
1800
  // Idempotency: silently drop duplicate messages
1773
1801
  if (msg.idempotencyKey) {
1774
1802
  if (recentIdempotencyKeys.has(msg.idempotencyKey)) break;
@@ -2350,7 +2378,11 @@ channel.addListener('message', async (event) => {
2350
2378
 
2351
2379
  // ── Init ──────────────────────────────────────────────────────────────────
2352
2380
 
2381
+ console.error(`[DEBUG] Starting init sequence...`);
2382
+ console.error(`[DEBUG] OPENCLAW_ROOT=${OPENCLAW_ROOT}`);
2383
+ console.error(`[DEBUG] DATADIR=${process.env.DATADIR || '(unset)'}, HOME=${process.env.HOME || '(unset)'}`);
2353
2384
  ensureOpenClawDirs();
2385
+ console.error(`[DEBUG] Dirs ensured. Sending worker.loading_phase=engine`);
2354
2386
  channel.send('message', { type: 'worker.loading_phase', phase: 'engine' });
2355
2387
 
2356
2388
  // Initialize SQLite, migrate legacy JSONL data, then signal ready and warm MCP discovery
@@ -2363,30 +2395,36 @@ initWorkerDb(OPENCLAW_ROOT).then(() => {
2363
2395
  console.warn(`[init] JSONL migration failed (non-fatal): ${err.message}`);
2364
2396
  }
2365
2397
  }).catch(err => {
2366
- console.warn(`[init] SQLite init failed, using JSONL fallback: ${err.message}`);
2398
+ console.error(`[DEBUG] SQLite init failed, using JSONL fallback: ${err.message}`);
2367
2399
  }).finally(() => {
2368
- // Send ready as soon as DB init/migration settles — do not block on MCP discovery.
2369
- channel.send('message', {
2370
- type: 'worker.ready',
2371
- nodeVersion: process.version,
2372
- openclawRoot: OPENCLAW_ROOT,
2373
- mcpToolCount: 0,
2374
- });
2375
- console.log(`[mobile-claw worker] Ready. Node ${process.version}, root=${OPENCLAW_ROOT}, mcpTools=0 (discovering...)`);
2400
+ try {
2401
+ console.error(`[DEBUG] .finally() entered — sending worker.ready`);
2402
+ channel.send('message', {
2403
+ type: 'worker.ready',
2404
+ nodeVersion: process.version,
2405
+ openclawRoot: OPENCLAW_ROOT,
2406
+ mcpToolCount: 0,
2407
+ });
2408
+ console.error(`[DEBUG] worker.ready SENT. Node=${process.version} root=${OPENCLAW_ROOT}`);
2376
2409
 
2377
- // Pre-discover MCP device tools at startup (non-blocking, best-effort).
2378
- // Results are cached so the first agent run doesn't wait for discovery.
2379
- channel.send('message', { type: 'worker.loading_phase', phase: 'tools' });
2380
- discoverMcpTools().then(tools => {
2381
- if (tools.length > 0) {
2382
- channel.send('message', {
2383
- type: 'worker.tools_updated',
2384
- mcpToolCount: tools.length,
2385
- });
2386
- console.log(`[mobile-claw worker] MCP tools ready: ${tools.length}`);
2387
- }
2388
- channel.send('message', { type: 'worker.loading_phase', phase: 'ready' });
2389
- }).catch(() => {
2390
- channel.send('message', { type: 'worker.loading_phase', phase: 'ready' });
2391
- });
2410
+ // Pre-discover MCP device tools at startup (non-blocking, best-effort).
2411
+ channel.send('message', { type: 'worker.loading_phase', phase: 'tools' });
2412
+ discoverMcpTools().then(tools => {
2413
+ console.error(`[DEBUG] MCP discovery resolved: ${tools.length} tools`);
2414
+ if (tools.length > 0) {
2415
+ channel.send('message', {
2416
+ type: 'worker.tools_updated',
2417
+ mcpToolCount: tools.length,
2418
+ });
2419
+ }
2420
+ channel.send('message', { type: 'worker.loading_phase', phase: 'ready' });
2421
+ console.error(`[DEBUG] All loading phases complete — worker fully ready`);
2422
+ }).catch((mcpErr) => {
2423
+ console.error(`[DEBUG] MCP discovery FAILED: ${mcpErr?.message || mcpErr}`);
2424
+ channel.send('message', { type: 'worker.loading_phase', phase: 'ready' });
2425
+ });
2426
+ } catch (finallyErr) {
2427
+ console.error(`[DEBUG] FATAL in .finally(): ${finallyErr?.message || finallyErr}`);
2428
+ console.error(finallyErr?.stack || '(no stack)');
2429
+ }
2392
2430
  });
@@ -113,7 +113,14 @@ export async function initWorkerDb(openclawRoot) {
113
113
  dbPath = join(openclawRoot, 'mobile-claw.db');
114
114
  mkdirSync(openclawRoot, { recursive: true });
115
115
 
116
- // Load sql.js — try WASM first, fall back to asm.js (pure JS)
116
+ // Load sql.js — skip entirely if WebAssembly is unavailable (Capacitor-NodeJS v18)
117
+ // sql.js's WASM and asm.js builds both use Emscripten which references WebAssembly
118
+ // globals at load time, so we can't even require() it without a real WASM runtime.
119
+ // The caller (main.js) falls back to JSONL persistence when initWorkerDb throws.
120
+ if (typeof WebAssembly === 'undefined' || globalThis.WebAssembly?._isStub) {
121
+ throw new Error('WebAssembly is not available — using JSONL fallback');
122
+ }
123
+
117
124
  let SQL;
118
125
  try {
119
126
  const initSqlJs = _require('sql.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-mobile-claw",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "On-device AI agent engine for Capacitor apps — embedded Node.js worker with LLM, file tools, code execution, git, and extensible MCP server",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/esm/index.d.ts",