capacitor-mobile-claw 1.1.0 → 1.3.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,291 @@
|
|
|
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
|
+
append(k, v) {
|
|
78
|
+
const lk = k.toLowerCase();
|
|
79
|
+
const existing = this.#map.get(lk);
|
|
80
|
+
this.#map.set(lk, existing != null ? `${existing}, ${String(v)}` : String(v));
|
|
81
|
+
}
|
|
82
|
+
has(k) { return this.#map.has(k.toLowerCase()); }
|
|
83
|
+
delete(k) { return this.#map.delete(k.toLowerCase()); }
|
|
84
|
+
forEach(cb) { this.#map.forEach((v, k) => cb(v, k, this)); }
|
|
85
|
+
*entries() { yield* this.#map.entries(); }
|
|
86
|
+
*keys() { yield* this.#map.keys(); }
|
|
87
|
+
*values() { yield* this.#map.values(); }
|
|
88
|
+
[Symbol.iterator]() { return this.entries(); }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class _Response {
|
|
92
|
+
#body; #bodyUsed = false; #status; #statusText; #headers; #url; #webStream;
|
|
93
|
+
constructor(body, { status = 200, statusText = '', headers = {}, url = '' } = {}) {
|
|
94
|
+
this.#body = body;
|
|
95
|
+
this.#status = status;
|
|
96
|
+
this.#statusText = statusText;
|
|
97
|
+
this.#headers = new _Headers(headers);
|
|
98
|
+
this.#url = url;
|
|
99
|
+
this.#webStream = undefined; // lazy-init, cached
|
|
100
|
+
}
|
|
101
|
+
get ok() { return this.#status >= 200 && this.#status < 300; }
|
|
102
|
+
get status() { return this.#status; }
|
|
103
|
+
get statusText() { return this.#statusText; }
|
|
104
|
+
get headers() { return this.#headers; }
|
|
105
|
+
get url() { return this.#url; }
|
|
106
|
+
get bodyUsed() { return this.#bodyUsed; }
|
|
107
|
+
get body() {
|
|
108
|
+
if (this.#webStream !== undefined) return this.#webStream;
|
|
109
|
+
if (this.#body instanceof Readable) {
|
|
110
|
+
if (typeof Readable.toWeb === 'function') {
|
|
111
|
+
this.#webStream = Readable.toWeb(this.#body);
|
|
112
|
+
} else {
|
|
113
|
+
const nodeStream = this.#body;
|
|
114
|
+
this.#webStream = new ReadableStream({
|
|
115
|
+
start(controller) {
|
|
116
|
+
nodeStream.on('data', (chunk) => controller.enqueue(typeof chunk === 'string' ? new TextEncoder().encode(chunk) : chunk));
|
|
117
|
+
nodeStream.on('end', () => controller.close());
|
|
118
|
+
nodeStream.on('error', (err) => controller.error(err));
|
|
119
|
+
},
|
|
120
|
+
cancel() { nodeStream.destroy(); },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return this.#webStream;
|
|
124
|
+
}
|
|
125
|
+
this.#webStream = null;
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
async text() {
|
|
129
|
+
this.#bodyUsed = true;
|
|
130
|
+
if (this.#body instanceof Readable) {
|
|
131
|
+
const chunks = [];
|
|
132
|
+
for await (const chunk of this.#body) chunks.push(chunk);
|
|
133
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
134
|
+
}
|
|
135
|
+
if (Buffer.isBuffer(this.#body)) return this.#body.toString('utf-8');
|
|
136
|
+
return String(this.#body ?? '');
|
|
137
|
+
}
|
|
138
|
+
async json() { return JSON.parse(await this.text()); }
|
|
139
|
+
async arrayBuffer() {
|
|
140
|
+
this.#bodyUsed = true;
|
|
141
|
+
if (this.#body instanceof Readable) {
|
|
142
|
+
const chunks = [];
|
|
143
|
+
for await (const chunk of this.#body) chunks.push(chunk);
|
|
144
|
+
const buf = Buffer.concat(chunks);
|
|
145
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
146
|
+
}
|
|
147
|
+
if (Buffer.isBuffer(this.#body)) {
|
|
148
|
+
return this.#body.buffer.slice(this.#body.byteOffset, this.#body.byteOffset + this.#body.byteLength);
|
|
149
|
+
}
|
|
150
|
+
const buf = Buffer.from(String(this.#body ?? ''), 'utf-8');
|
|
151
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
152
|
+
}
|
|
153
|
+
async blob() {
|
|
154
|
+
const ab = await this.arrayBuffer();
|
|
155
|
+
const { Blob: B } = await import('node:buffer');
|
|
156
|
+
return new B([ab], { type: this.#headers.get('content-type') || '' });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function _nativeFetch(input, init = {}) {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const urlStr = typeof input === 'string' ? input : (input?.url ?? String(input));
|
|
163
|
+
const method = (init.method || 'GET').toUpperCase();
|
|
164
|
+
console.error(`[DEBUG fetch] ${method} ${urlStr.slice(0,150)} body=${init.body ? init.body.length || '?' : 'none'}`);
|
|
165
|
+
const parsed = new URL(urlStr);
|
|
166
|
+
const transport = parsed.protocol === 'https:' ? _https : _http;
|
|
167
|
+
const headers = {};
|
|
168
|
+
|
|
169
|
+
if (init.headers) {
|
|
170
|
+
if (init.headers instanceof _Headers || (init.headers.forEach && init.headers.entries)) {
|
|
171
|
+
init.headers.forEach((v, k) => { headers[k] = v; });
|
|
172
|
+
} else if (Array.isArray(init.headers)) {
|
|
173
|
+
for (const [k, v] of init.headers) headers[k.toLowerCase()] = v;
|
|
174
|
+
} else {
|
|
175
|
+
for (const [k, v] of Object.entries(init.headers)) headers[k.toLowerCase()] = v;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let bodyData = null;
|
|
180
|
+
if (init.body != null) {
|
|
181
|
+
if (typeof init.body === 'string') {
|
|
182
|
+
bodyData = Buffer.from(init.body, 'utf-8');
|
|
183
|
+
} else if (Buffer.isBuffer(init.body)) {
|
|
184
|
+
bodyData = init.body;
|
|
185
|
+
} else if (init.body instanceof Uint8Array) {
|
|
186
|
+
bodyData = Buffer.from(init.body);
|
|
187
|
+
} else {
|
|
188
|
+
bodyData = Buffer.from(String(init.body), 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
if (!headers['content-length']) {
|
|
191
|
+
headers['content-length'] = String(bodyData.length);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const reqOpts = {
|
|
196
|
+
hostname: parsed.hostname,
|
|
197
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
198
|
+
path: parsed.pathname + parsed.search,
|
|
199
|
+
method,
|
|
200
|
+
headers,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const req = transport.request(reqOpts, (res) => {
|
|
204
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
205
|
+
const redirectUrl = new URL(res.headers.location, urlStr);
|
|
206
|
+
const redirectInit = { ...init };
|
|
207
|
+
if (res.statusCode === 303) {
|
|
208
|
+
redirectInit.method = 'GET';
|
|
209
|
+
delete redirectInit.body;
|
|
210
|
+
}
|
|
211
|
+
res.resume();
|
|
212
|
+
_nativeFetch(redirectUrl.href, redirectInit).then(resolve, reject);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const responseHeaders = {};
|
|
217
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
218
|
+
if (v != null) responseHeaders[k] = Array.isArray(v) ? v.join(', ') : v;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const response = new _Response(res, {
|
|
222
|
+
status: res.statusCode,
|
|
223
|
+
statusText: res.statusMessage || '',
|
|
224
|
+
headers: responseHeaders,
|
|
225
|
+
url: urlStr,
|
|
226
|
+
});
|
|
227
|
+
console.error(`[DEBUG fetch] ${method} ${urlStr.slice(0,80)} → ${res.statusCode} ${res.statusMessage || ''}`);
|
|
228
|
+
resolve(response);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (init.signal) {
|
|
232
|
+
if (init.signal.aborted) {
|
|
233
|
+
req.destroy();
|
|
234
|
+
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const onAbort = () => {
|
|
238
|
+
req.destroy();
|
|
239
|
+
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
|
240
|
+
};
|
|
241
|
+
init.signal.addEventListener('abort', onAbort, { once: true });
|
|
242
|
+
req.on('close', () => init.signal.removeEventListener('abort', onAbort));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
req.on('error', (err) => {
|
|
246
|
+
console.error(`[DEBUG fetch] ERROR ${method} ${urlStr.slice(0,80)}: ${err.message}`);
|
|
247
|
+
reject(err);
|
|
248
|
+
});
|
|
249
|
+
req.on('timeout', () => { req.destroy(); console.error(`[DEBUG fetch] TIMEOUT ${method} ${urlStr.slice(0,80)}`); reject(new Error('Request timed out')); });
|
|
250
|
+
|
|
251
|
+
if (bodyData) req.write(bodyData);
|
|
252
|
+
req.end();
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// DOMException may not exist in Node.js v18
|
|
257
|
+
if (typeof globalThis.DOMException === 'undefined') {
|
|
258
|
+
globalThis.DOMException = class DOMException extends Error {
|
|
259
|
+
#name;
|
|
260
|
+
constructor(message, name = 'Error') { super(message); this.#name = name; }
|
|
261
|
+
get name() { return this.#name; }
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Replace the broken undici-based fetch with our native implementation.
|
|
266
|
+
// In Node.js v18, globalThis.fetch may be a lazy getter that triggers undici
|
|
267
|
+
// loading on first access. Use delete + defineProperty to fully replace it.
|
|
268
|
+
try { delete globalThis.fetch; } catch {}
|
|
269
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
270
|
+
value: _nativeFetch, writable: true, configurable: true, enumerable: true,
|
|
271
|
+
});
|
|
272
|
+
try { delete globalThis.Headers; } catch {}
|
|
273
|
+
Object.defineProperty(globalThis, 'Headers', {
|
|
274
|
+
value: _Headers, writable: true, configurable: true, enumerable: true,
|
|
275
|
+
});
|
|
276
|
+
try { delete globalThis.Response; } catch {}
|
|
277
|
+
Object.defineProperty(globalThis, 'Response', {
|
|
278
|
+
value: _Response, writable: true, configurable: true, enumerable: true,
|
|
279
|
+
});
|
|
280
|
+
if (!globalThis.Request) {
|
|
281
|
+
globalThis.Request = class Request {
|
|
282
|
+
constructor(input, init = {}) {
|
|
283
|
+
this.url = typeof input === 'string' ? input : input.url;
|
|
284
|
+
this.method = init.method || 'GET';
|
|
285
|
+
this.headers = new _Headers(init.headers);
|
|
286
|
+
this.body = init.body ?? null;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
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) {
|
|
@@ -1110,6 +1120,15 @@ function buildAgentTools() {
|
|
|
1110
1120
|
|
|
1111
1121
|
// skillContext: if set, events are tagged as skill events and ignored by the main session view
|
|
1112
1122
|
function bridgeEvent(event, skillContext = null) {
|
|
1123
|
+
if (event.type === 'tool_execution_start' || event.type === 'tool_execution_end') {
|
|
1124
|
+
console.error(`[DEBUG event] ${event.type} tool=${event.toolName} id=${event.toolCallId}`);
|
|
1125
|
+
} else if (event.type === 'message_update') {
|
|
1126
|
+
// Only log first delta to avoid spam
|
|
1127
|
+
const e = event.assistantMessageEvent;
|
|
1128
|
+
if (e.type === 'text_delta' && (e.delta||'').length > 0) {
|
|
1129
|
+
console.error(`[DEBUG event] text_delta len=${e.delta.length} first20=${JSON.stringify(e.delta.slice(0,20))}`);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1113
1132
|
switch (event.type) {
|
|
1114
1133
|
case 'message_update': {
|
|
1115
1134
|
const e = event.assistantMessageEvent;
|
|
@@ -1540,20 +1559,27 @@ function buildSkillTools(injectedTools, milestones) {
|
|
|
1540
1559
|
}
|
|
1541
1560
|
|
|
1542
1561
|
async function runSkill(agentId, locale = 'en', injectedConfig = null) {
|
|
1543
|
-
|
|
1562
|
+
console.error(`[DEBUG runSkill] START agentId=${agentId} locale=${locale} hasConfig=${!!injectedConfig} configId=${injectedConfig?.id}`);
|
|
1563
|
+
try { await refreshOAuthTokenIfNeeded(agentId); } catch(e) { console.error(`[DEBUG runSkill] OAuth refresh err: ${e.message}`); }
|
|
1544
1564
|
const authProfiles = loadAuthProfiles(agentId);
|
|
1565
|
+
const profileKeys = Object.keys(authProfiles.profiles || {});
|
|
1566
|
+
console.error(`[DEBUG runSkill] Auth: ${profileKeys.length} profiles [${profileKeys.join(',')}] lastGood=${JSON.stringify(authProfiles.lastGood)}`);
|
|
1545
1567
|
const apiKey = resolveApiKey(authProfiles);
|
|
1546
1568
|
|
|
1547
1569
|
if (!apiKey) {
|
|
1570
|
+
console.error(`[DEBUG runSkill] NO API KEY — aborting`);
|
|
1548
1571
|
channel.send('message', {
|
|
1549
1572
|
type: 'agent.error',
|
|
1550
1573
|
error: 'No AI provider configured. Connect a provider first.',
|
|
1551
1574
|
});
|
|
1552
1575
|
return;
|
|
1553
1576
|
}
|
|
1577
|
+
console.error(`[DEBUG runSkill] API key: type=${apiKey.startsWith('sk-ant-oat') ? 'oauth' : 'api_key'} len=${apiKey.length} prefix=${apiKey.slice(0,15)}...`);
|
|
1554
1578
|
|
|
1555
1579
|
const modelId = 'claude-sonnet-4-5';
|
|
1580
|
+
console.error(`[DEBUG runSkill] Getting model anthropic/${modelId}`);
|
|
1556
1581
|
const model = getModel('anthropic', modelId);
|
|
1582
|
+
console.error(`[DEBUG runSkill] Model: ${JSON.stringify({id: model?.id, provider: model?.provider, api: model?.api}).slice(0,200)}`);
|
|
1557
1583
|
|
|
1558
1584
|
// Use injected config from client if available, fall back to hardcoded defaults
|
|
1559
1585
|
const milestones = injectedConfig?.milestones || SETUP_MILESTONES;
|
|
@@ -1599,12 +1625,27 @@ async function runSkill(agentId, locale = 'en', injectedConfig = null) {
|
|
|
1599
1625
|
});
|
|
1600
1626
|
|
|
1601
1627
|
// Notify client that the skill session has started (used to init isolated conversation view)
|
|
1628
|
+
console.error(`[DEBUG runSkill] Sending skill.session_started skillId=${skillId} sessionKey=${sessionKey}`);
|
|
1602
1629
|
channel.send('message', { type: 'skill.session_started', sessionKey, skillId });
|
|
1603
1630
|
|
|
1604
1631
|
const startTime = Date.now();
|
|
1632
|
+
console.error(`[DEBUG runSkill] Calling agent.prompt() with kickoff (${kickoff.length} chars): ${kickoff.slice(0,80)}`);
|
|
1605
1633
|
try {
|
|
1606
1634
|
await agent.prompt(kickoff);
|
|
1635
|
+
console.error(`[DEBUG runSkill] agent.prompt() resolved, waitForIdle()...`);
|
|
1607
1636
|
await agent.waitForIdle();
|
|
1637
|
+
console.error(`[DEBUG runSkill] Idle. Duration=${Date.now() - startTime}ms msgs=${agent.state.messages.length}`);
|
|
1638
|
+
// Check if the agent silently errored (prompt() resolves even on stream errors)
|
|
1639
|
+
if (agent.state.error) {
|
|
1640
|
+
console.error(`[DEBUG runSkill] AGENT STATE ERROR: ${agent.state.error}`);
|
|
1641
|
+
}
|
|
1642
|
+
const lastMsg = agent.state.messages[agent.state.messages.length - 1];
|
|
1643
|
+
if (lastMsg) {
|
|
1644
|
+
console.error(`[DEBUG runSkill] Last msg: role=${lastMsg.role} stopReason=${lastMsg.stopReason} errorMessage=${lastMsg.errorMessage || 'none'}`);
|
|
1645
|
+
if (lastMsg.errorMessage) {
|
|
1646
|
+
console.error(`[DEBUG runSkill] ERROR DETAIL: ${lastMsg.errorMessage}`);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1608
1649
|
|
|
1609
1650
|
saveSession(agent, agentId, sessionKey, startTime);
|
|
1610
1651
|
|
|
@@ -1618,7 +1659,11 @@ async function runSkill(agentId, locale = 'en', injectedConfig = null) {
|
|
|
1618
1659
|
durationMs: Date.now() - startTime,
|
|
1619
1660
|
});
|
|
1620
1661
|
channel.send('message', { type: 'skill.ended', skillId, sessionKey });
|
|
1662
|
+
console.error(`[DEBUG runSkill] skill.ended sent (success)`);
|
|
1621
1663
|
} catch (err) {
|
|
1664
|
+
console.error(`[DEBUG runSkill] CAUGHT ERROR: ${err.message}`);
|
|
1665
|
+
console.error(`[DEBUG runSkill] Error details: name=${err.name} status=${err.status} code=${err.code}`);
|
|
1666
|
+
console.error(err.stack || '(no stack)');
|
|
1622
1667
|
channel.send('message', {
|
|
1623
1668
|
type: 'agent.error',
|
|
1624
1669
|
skill: sessionKey,
|
|
@@ -1676,26 +1721,42 @@ function _legacySetupTools() {
|
|
|
1676
1721
|
// ── Main agent run function ──────────────────────────────────────────────
|
|
1677
1722
|
|
|
1678
1723
|
async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
|
|
1679
|
-
|
|
1724
|
+
console.error(`[DEBUG] runAgentLoop START: agentId=${agentId} sessionKey=${sessionKey} model=${requestedModel}`);
|
|
1725
|
+
try {
|
|
1726
|
+
await refreshOAuthTokenIfNeeded(agentId);
|
|
1727
|
+
} catch (oauthErr) {
|
|
1728
|
+
console.error(`[DEBUG] OAuth refresh failed: ${oauthErr.message}`);
|
|
1729
|
+
}
|
|
1680
1730
|
const authProfiles = loadAuthProfiles(agentId);
|
|
1731
|
+
const profileKeys = Object.keys(authProfiles.profiles || {});
|
|
1732
|
+
const profileTypes = profileKeys.map(k => `${k}:${authProfiles.profiles[k]?.type}`);
|
|
1733
|
+
console.error(`[DEBUG] Auth profiles: ${profileKeys.length} profiles [${profileTypes.join(', ')}] lastGood=${JSON.stringify(authProfiles.lastGood)}`);
|
|
1681
1734
|
const apiKey = resolveApiKey(authProfiles);
|
|
1682
1735
|
|
|
1683
1736
|
if (!apiKey) {
|
|
1737
|
+
console.error(`[DEBUG] NO API KEY — sending agent.error`);
|
|
1684
1738
|
channel.send('message', {
|
|
1685
1739
|
type: 'agent.error',
|
|
1686
1740
|
error: 'No Anthropic API key configured. Go to Settings to add one.',
|
|
1687
1741
|
});
|
|
1688
1742
|
return;
|
|
1689
1743
|
}
|
|
1744
|
+
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
1745
|
|
|
1691
1746
|
const systemPrompt = loadSystemPrompt();
|
|
1747
|
+
console.error(`[DEBUG] System prompt loaded: ${systemPrompt.length} chars`);
|
|
1692
1748
|
const modelId = requestedModel || 'claude-sonnet-4-5';
|
|
1749
|
+
console.error(`[DEBUG] Getting model: provider=anthropic modelId=${modelId}`);
|
|
1693
1750
|
const model = getModel('anthropic', modelId);
|
|
1751
|
+
console.error(`[DEBUG] Model resolved: ${JSON.stringify({id: model?.id, provider: model?.provider, api: model?.api}).slice(0,200)}`);
|
|
1694
1752
|
|
|
1695
1753
|
// Merge local tools with MCP device tools (if bridge is available)
|
|
1696
1754
|
const localTools = buildAgentTools();
|
|
1755
|
+
console.error(`[DEBUG] Local tools: ${localTools.length}`);
|
|
1697
1756
|
const mcpTools = await discoverMcpTools();
|
|
1757
|
+
console.error(`[DEBUG] MCP tools: ${mcpTools.length}`);
|
|
1698
1758
|
const tools = [...localTools, ...mcpTools];
|
|
1759
|
+
console.error(`[DEBUG] Total tools: ${tools.length}`);
|
|
1699
1760
|
|
|
1700
1761
|
// Create Agent instance
|
|
1701
1762
|
const agent = new Agent({
|
|
@@ -1724,9 +1785,12 @@ async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
|
|
|
1724
1785
|
|
|
1725
1786
|
// Run
|
|
1726
1787
|
const startTime = Date.now();
|
|
1788
|
+
console.error(`[DEBUG] Calling agent.prompt()...`);
|
|
1727
1789
|
try {
|
|
1728
1790
|
await agent.prompt(prompt);
|
|
1791
|
+
console.error(`[DEBUG] agent.prompt() resolved, calling waitForIdle()...`);
|
|
1729
1792
|
await agent.waitForIdle();
|
|
1793
|
+
console.error(`[DEBUG] agent idle. Duration=${Date.now() - startTime}ms messages=${agent.state.messages.length}`);
|
|
1730
1794
|
|
|
1731
1795
|
// Save session + send completion
|
|
1732
1796
|
currentSessionKey = sessionKey;
|
|
@@ -1740,9 +1804,13 @@ async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
|
|
|
1740
1804
|
cumulativeUsage: usage,
|
|
1741
1805
|
durationMs: Date.now() - startTime,
|
|
1742
1806
|
});
|
|
1807
|
+
console.error(`[DEBUG] agent.completed sent. tokens=${JSON.stringify(usage)}`);
|
|
1743
1808
|
// Keep currentAgent alive for multi-turn follow-ups
|
|
1744
1809
|
currentAbortController = null;
|
|
1745
1810
|
} catch (err) {
|
|
1811
|
+
console.error(`[DEBUG] agent.prompt THREW: ${err.message}`);
|
|
1812
|
+
console.error(`[DEBUG] Error details: status=${err.status} code=${err.code} name=${err.name}`);
|
|
1813
|
+
console.error(err.stack || '(no stack)');
|
|
1746
1814
|
const retryable = isTransientError(err);
|
|
1747
1815
|
channel.send('message', {
|
|
1748
1816
|
type: 'agent.error',
|
|
@@ -1766,9 +1834,11 @@ async function runAgentLoop(agentId, sessionKey, prompt, requestedModel) {
|
|
|
1766
1834
|
|
|
1767
1835
|
channel.addListener('message', async (event) => {
|
|
1768
1836
|
const msg = event;
|
|
1837
|
+
console.error(`[DEBUG] << RECV msg type=${msg?.type} keys=${Object.keys(msg||{}).join(',')}`);
|
|
1769
1838
|
|
|
1770
1839
|
switch (msg.type) {
|
|
1771
1840
|
case 'agent.start': {
|
|
1841
|
+
console.error(`[DEBUG] agent.start: prompt=${(msg.prompt||'').slice(0,80)} model=${msg.model} agentId=${msg.agentId} sessionKey=${msg.sessionKey}`);
|
|
1772
1842
|
// Idempotency: silently drop duplicate messages
|
|
1773
1843
|
if (msg.idempotencyKey) {
|
|
1774
1844
|
if (recentIdempotencyKeys.has(msg.idempotencyKey)) break;
|
|
@@ -1926,9 +1996,11 @@ channel.addListener('message', async (event) => {
|
|
|
1926
1996
|
}
|
|
1927
1997
|
|
|
1928
1998
|
case 'skill.start': {
|
|
1999
|
+
console.error(`[DEBUG] skill.start: skill=${msg.skill} agentId=${msg.agentId} locale=${msg.locale}`);
|
|
1929
2000
|
const agentId = msg.agentId || 'main';
|
|
1930
2001
|
const locale = msg.locale || 'en';
|
|
1931
2002
|
await runSkill(agentId, locale, msg.config || null);
|
|
2003
|
+
console.error(`[DEBUG] skill.start completed`);
|
|
1932
2004
|
break;
|
|
1933
2005
|
}
|
|
1934
2006
|
|
|
@@ -2344,13 +2416,17 @@ channel.addListener('message', async (event) => {
|
|
|
2344
2416
|
}
|
|
2345
2417
|
|
|
2346
2418
|
default:
|
|
2347
|
-
console.
|
|
2419
|
+
console.error(`[DEBUG] Unknown message type: ${msg.type}`);
|
|
2348
2420
|
}
|
|
2349
2421
|
});
|
|
2350
2422
|
|
|
2351
2423
|
// ── Init ──────────────────────────────────────────────────────────────────
|
|
2352
2424
|
|
|
2425
|
+
console.error(`[DEBUG] Starting init sequence...`);
|
|
2426
|
+
console.error(`[DEBUG] OPENCLAW_ROOT=${OPENCLAW_ROOT}`);
|
|
2427
|
+
console.error(`[DEBUG] DATADIR=${process.env.DATADIR || '(unset)'}, HOME=${process.env.HOME || '(unset)'}`);
|
|
2353
2428
|
ensureOpenClawDirs();
|
|
2429
|
+
console.error(`[DEBUG] Dirs ensured. Sending worker.loading_phase=engine`);
|
|
2354
2430
|
channel.send('message', { type: 'worker.loading_phase', phase: 'engine' });
|
|
2355
2431
|
|
|
2356
2432
|
// Initialize SQLite, migrate legacy JSONL data, then signal ready and warm MCP discovery
|
|
@@ -2363,30 +2439,36 @@ initWorkerDb(OPENCLAW_ROOT).then(() => {
|
|
|
2363
2439
|
console.warn(`[init] JSONL migration failed (non-fatal): ${err.message}`);
|
|
2364
2440
|
}
|
|
2365
2441
|
}).catch(err => {
|
|
2366
|
-
console.
|
|
2442
|
+
console.error(`[DEBUG] SQLite init failed, using JSONL fallback: ${err.message}`);
|
|
2367
2443
|
}).finally(() => {
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2444
|
+
try {
|
|
2445
|
+
console.error(`[DEBUG] .finally() entered — sending worker.ready`);
|
|
2446
|
+
channel.send('message', {
|
|
2447
|
+
type: 'worker.ready',
|
|
2448
|
+
nodeVersion: process.version,
|
|
2449
|
+
openclawRoot: OPENCLAW_ROOT,
|
|
2450
|
+
mcpToolCount: 0,
|
|
2451
|
+
});
|
|
2452
|
+
console.error(`[DEBUG] worker.ready SENT. Node=${process.version} root=${OPENCLAW_ROOT}`);
|
|
2376
2453
|
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2454
|
+
// Pre-discover MCP device tools at startup (non-blocking, best-effort).
|
|
2455
|
+
channel.send('message', { type: 'worker.loading_phase', phase: 'tools' });
|
|
2456
|
+
discoverMcpTools().then(tools => {
|
|
2457
|
+
console.error(`[DEBUG] MCP discovery resolved: ${tools.length} tools`);
|
|
2458
|
+
if (tools.length > 0) {
|
|
2459
|
+
channel.send('message', {
|
|
2460
|
+
type: 'worker.tools_updated',
|
|
2461
|
+
mcpToolCount: tools.length,
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
channel.send('message', { type: 'worker.loading_phase', phase: 'ready' });
|
|
2465
|
+
console.error(`[DEBUG] All loading phases complete — worker fully ready`);
|
|
2466
|
+
}).catch((mcpErr) => {
|
|
2467
|
+
console.error(`[DEBUG] MCP discovery FAILED: ${mcpErr?.message || mcpErr}`);
|
|
2468
|
+
channel.send('message', { type: 'worker.loading_phase', phase: 'ready' });
|
|
2469
|
+
});
|
|
2470
|
+
} catch (finallyErr) {
|
|
2471
|
+
console.error(`[DEBUG] FATAL in .finally(): ${finallyErr?.message || finallyErr}`);
|
|
2472
|
+
console.error(finallyErr?.stack || '(no stack)');
|
|
2473
|
+
}
|
|
2392
2474
|
});
|
|
@@ -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.3.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",
|