capacitor-mobile-claw 1.0.14 → 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;
|
|
@@ -219,6 +241,10 @@ You are a capable, resourceful assistant that helps users work with files, code,
|
|
|
219
241
|
|
|
220
242
|
Persistent knowledge base. Claw updates this file to remember important context across conversations.
|
|
221
243
|
|
|
244
|
+
## Long-Term Memory
|
|
245
|
+
You have persistent vector memory tools: memory_recall, memory_store, memory_forget, memory_search, memory_get.
|
|
246
|
+
Relevant memories are automatically injected into your context. Use memory/YYYY-MM-DD.md files for daily notes.
|
|
247
|
+
|
|
222
248
|
## Workspace
|
|
223
249
|
- Fresh workspace, no project loaded yet
|
|
224
250
|
|
|
@@ -565,25 +591,13 @@ function executeJsTool(args) {
|
|
|
565
591
|
}
|
|
566
592
|
|
|
567
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.
|
|
568
596
|
|
|
569
|
-
import { loadPyodide } from 'pyodide';
|
|
570
|
-
|
|
571
|
-
let pyodideInstance = null;
|
|
597
|
+
// import { loadPyodide } from 'pyodide'; // DISABLED: requires WebAssembly
|
|
572
598
|
|
|
573
599
|
async function getPyodide() {
|
|
574
|
-
|
|
575
|
-
pyodideInstance = await loadPyodide();
|
|
576
|
-
// Block dangerous modules for sandbox security
|
|
577
|
-
pyodideInstance.runPython(`
|
|
578
|
-
import sys
|
|
579
|
-
for _mod in ['subprocess', 'socket', 'http', 'urllib', 'ftplib', 'smtplib',
|
|
580
|
-
'webbrowser', 'ctypes', 'multiprocessing', 'shutil', 'tempfile',
|
|
581
|
-
'signal', 'resource']:
|
|
582
|
-
sys.modules[_mod] = None
|
|
583
|
-
del _mod
|
|
584
|
-
`);
|
|
585
|
-
}
|
|
586
|
-
return pyodideInstance;
|
|
600
|
+
throw new Error('Python execution is unavailable: WebAssembly is disabled in this Node.js runtime.');
|
|
587
601
|
}
|
|
588
602
|
|
|
589
603
|
async function executePythonTool(args) {
|
|
@@ -1672,26 +1686,42 @@ function _legacySetupTools() {
|
|
|
1672
1686
|
// ── Main agent run function ──────────────────────────────────────────────
|
|
1673
1687
|
|
|
1674
1688
|
async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
|
|
1675
|
-
|
|
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
|
+
}
|
|
1676
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)}`);
|
|
1677
1699
|
const apiKey = resolveApiKey(authProfiles);
|
|
1678
1700
|
|
|
1679
1701
|
if (!apiKey) {
|
|
1702
|
+
console.error(`[DEBUG] NO API KEY — sending agent.error`);
|
|
1680
1703
|
channel.send('message', {
|
|
1681
1704
|
type: 'agent.error',
|
|
1682
1705
|
error: 'No Anthropic API key configured. Go to Settings to add one.',
|
|
1683
1706
|
});
|
|
1684
1707
|
return;
|
|
1685
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)}...`);
|
|
1686
1710
|
|
|
1687
1711
|
const systemPrompt = loadSystemPrompt();
|
|
1712
|
+
console.error(`[DEBUG] System prompt loaded: ${systemPrompt.length} chars`);
|
|
1688
1713
|
const modelId = requestedModel || 'claude-sonnet-4-5';
|
|
1714
|
+
console.error(`[DEBUG] Getting model: provider=anthropic modelId=${modelId}`);
|
|
1689
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)}`);
|
|
1690
1717
|
|
|
1691
1718
|
// Merge local tools with MCP device tools (if bridge is available)
|
|
1692
1719
|
const localTools = buildAgentTools();
|
|
1720
|
+
console.error(`[DEBUG] Local tools: ${localTools.length}`);
|
|
1693
1721
|
const mcpTools = await discoverMcpTools();
|
|
1722
|
+
console.error(`[DEBUG] MCP tools: ${mcpTools.length}`);
|
|
1694
1723
|
const tools = [...localTools, ...mcpTools];
|
|
1724
|
+
console.error(`[DEBUG] Total tools: ${tools.length}`);
|
|
1695
1725
|
|
|
1696
1726
|
// Create Agent instance
|
|
1697
1727
|
const agent = new Agent({
|
|
@@ -1762,9 +1792,11 @@ async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
|
|
|
1762
1792
|
|
|
1763
1793
|
channel.addListener('message', async (event) => {
|
|
1764
1794
|
const msg = event;
|
|
1795
|
+
console.error(`[DEBUG] << RECV msg type=${msg?.type} keys=${Object.keys(msg||{}).join(',')}`);
|
|
1765
1796
|
|
|
1766
1797
|
switch (msg.type) {
|
|
1767
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}`);
|
|
1768
1800
|
// Idempotency: silently drop duplicate messages
|
|
1769
1801
|
if (msg.idempotencyKey) {
|
|
1770
1802
|
if (recentIdempotencyKeys.has(msg.idempotencyKey)) break;
|
|
@@ -2346,7 +2378,11 @@ channel.addListener('message', async (event) => {
|
|
|
2346
2378
|
|
|
2347
2379
|
// ── Init ──────────────────────────────────────────────────────────────────
|
|
2348
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)'}`);
|
|
2349
2384
|
ensureOpenClawDirs();
|
|
2385
|
+
console.error(`[DEBUG] Dirs ensured. Sending worker.loading_phase=engine`);
|
|
2350
2386
|
channel.send('message', { type: 'worker.loading_phase', phase: 'engine' });
|
|
2351
2387
|
|
|
2352
2388
|
// Initialize SQLite, migrate legacy JSONL data, then signal ready and warm MCP discovery
|
|
@@ -2359,30 +2395,36 @@ initWorkerDb(OPENCLAW_ROOT).then(() => {
|
|
|
2359
2395
|
console.warn(`[init] JSONL migration failed (non-fatal): ${err.message}`);
|
|
2360
2396
|
}
|
|
2361
2397
|
}).catch(err => {
|
|
2362
|
-
console.
|
|
2398
|
+
console.error(`[DEBUG] SQLite init failed, using JSONL fallback: ${err.message}`);
|
|
2363
2399
|
}).finally(() => {
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
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}`);
|
|
2372
2409
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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
|
+
}
|
|
2388
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.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",
|