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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2398
|
+
console.error(`[DEBUG] SQLite init failed, using JSONL fallback: ${err.message}`);
|
|
2367
2399
|
}).finally(() => {
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
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
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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 —
|
|
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.
|
|
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",
|