@totalreclaw/totalreclaw 3.3.1-rc.2 → 3.3.1-rc.21

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/SKILL.md +50 -83
  3. package/api-client.ts +18 -11
  4. package/config.ts +117 -3
  5. package/crypto.ts +10 -2
  6. package/dist/api-client.js +226 -0
  7. package/dist/billing-cache.js +100 -0
  8. package/dist/claims-helper.js +606 -0
  9. package/dist/config.js +280 -0
  10. package/dist/consolidation.js +258 -0
  11. package/dist/contradiction-sync.js +1034 -0
  12. package/dist/crypto.js +138 -0
  13. package/dist/digest-sync.js +361 -0
  14. package/dist/download-ux.js +63 -0
  15. package/dist/embedding.js +86 -0
  16. package/dist/extractor.js +1225 -0
  17. package/dist/first-run.js +103 -0
  18. package/dist/fs-helpers.js +563 -0
  19. package/dist/gateway-url.js +197 -0
  20. package/dist/generate-mnemonic.js +13 -0
  21. package/dist/hot-cache-wrapper.js +101 -0
  22. package/dist/import-adapters/base-adapter.js +64 -0
  23. package/dist/import-adapters/chatgpt-adapter.js +238 -0
  24. package/dist/import-adapters/claude-adapter.js +114 -0
  25. package/dist/import-adapters/gemini-adapter.js +201 -0
  26. package/dist/import-adapters/index.js +26 -0
  27. package/dist/import-adapters/mcp-memory-adapter.js +219 -0
  28. package/dist/import-adapters/mem0-adapter.js +158 -0
  29. package/dist/import-adapters/types.js +1 -0
  30. package/dist/index.js +5348 -0
  31. package/dist/llm-client.js +686 -0
  32. package/dist/llm-profile-reader.js +346 -0
  33. package/dist/lsh.js +62 -0
  34. package/dist/onboarding-cli.js +750 -0
  35. package/dist/pair-cli.js +344 -0
  36. package/dist/pair-crypto.js +359 -0
  37. package/dist/pair-http.js +404 -0
  38. package/dist/pair-page.js +826 -0
  39. package/dist/pair-qr.js +107 -0
  40. package/dist/pair-remote-client.js +410 -0
  41. package/dist/pair-session-store.js +566 -0
  42. package/dist/pin.js +542 -0
  43. package/dist/qa-bug-report.js +301 -0
  44. package/dist/relay-headers.js +44 -0
  45. package/dist/reranker.js +442 -0
  46. package/dist/retype-setscope.js +348 -0
  47. package/dist/semantic-dedup.js +75 -0
  48. package/dist/subgraph-search.js +289 -0
  49. package/dist/subgraph-store.js +694 -0
  50. package/dist/tool-gating.js +58 -0
  51. package/download-ux.ts +91 -0
  52. package/embedding.ts +32 -9
  53. package/fs-helpers.ts +124 -0
  54. package/gateway-url.ts +57 -9
  55. package/index.ts +586 -357
  56. package/llm-client.ts +211 -23
  57. package/lsh.ts +7 -2
  58. package/onboarding-cli.ts +114 -1
  59. package/package.json +19 -5
  60. package/pair-cli.ts +76 -8
  61. package/pair-crypto.ts +34 -24
  62. package/pair-page.ts +28 -17
  63. package/pair-qr.ts +152 -0
  64. package/pair-remote-client.ts +540 -0
  65. package/qa-bug-report.ts +381 -0
  66. package/relay-headers.ts +50 -0
  67. package/reranker.ts +73 -0
  68. package/retype-setscope.ts +12 -0
  69. package/subgraph-search.ts +4 -3
  70. package/subgraph-store.ts +109 -16
package/subgraph-store.ts CHANGED
@@ -10,13 +10,18 @@
10
10
  * and chain RPCs. No viem, no permissionless.
11
11
  */
12
12
 
13
- // Lazy-load WASM to avoid crash when npm install hasn't finished yet.
13
+ // Lazy-load WASM via createRequire the shipped bundle is ESM-only and
14
+ // the bare `require` global is undefined there (issue #124). Same pattern
15
+ // as crypto / lsh / claims-helper / consolidation / digest-sync.
16
+ import { createRequire } from 'node:module';
17
+ const requireWasm = createRequire(import.meta.url);
14
18
  let _wasm: typeof import('@totalreclaw/core') | null = null;
15
19
  function getWasm() {
16
- if (!_wasm) _wasm = require('@totalreclaw/core');
20
+ if (!_wasm) _wasm = requireWasm('@totalreclaw/core');
17
21
  return _wasm;
18
22
  }
19
23
  import { CONFIG } from './config.js';
24
+ import { buildRelayHeaders } from './relay-headers.js';
20
25
 
21
26
  // ---------------------------------------------------------------------------
22
27
  // Pimlico 429 retry helper
@@ -231,6 +236,68 @@ export async function deriveSmartAccountAddress(mnemonic: string, chainId?: numb
231
236
  */
232
237
  const deployedAccounts = new Set<string>();
233
238
 
239
+ // ---------------------------------------------------------------------------
240
+ // Per-account submission mutex — 3.3.1-rc.3 AA25 serialization
241
+ // ---------------------------------------------------------------------------
242
+ //
243
+ // Concurrent `submitFactOnChain` / `submitFactBatchOnChain` calls for the
244
+ // SAME Smart Account used to race at the nonce-fetch step:
245
+ // - Call A: getNonce()=5, build UserOp, submit, wait for receipt.
246
+ // - Call B: getNonce()=5 (A not mined yet), build UserOp, submit → AA25.
247
+ //
248
+ // The fix: chain submissions per `sender` address through a single promise.
249
+ // Each call awaits the previous in-flight submission before starting its
250
+ // own nonce fetch. Fallback to public RPC for getNonce continues to work
251
+ // because by the time B fetches, A's UserOp has been bundled AND mined.
252
+ //
253
+ // 16 AA25 occurrences were logged in rc.2 QA; this lock eliminates the
254
+ // race condition at the plugin layer. Subsequent AA25s would indicate
255
+ // nonce rot from another process (e.g. relay retrying the same UserOp)
256
+ // and are handled by the existing single-retry with fresh-nonce path.
257
+ const _senderSubmissionLocks = new Map<string, Promise<unknown>>();
258
+
259
+ async function withSenderLock<T>(sender: string, fn: () => Promise<T>): Promise<T> {
260
+ const key = sender.toLowerCase();
261
+ const prev = _senderSubmissionLocks.get(key) ?? Promise.resolve();
262
+ let release: () => void = () => {};
263
+ const thisCallGate = new Promise<void>((resolve) => { release = resolve; });
264
+ _senderSubmissionLocks.set(key, prev.then(() => thisCallGate));
265
+ try {
266
+ await prev; // wait for previous submission to settle (success OR failure)
267
+ } catch {
268
+ // Prior submission threw — that's the caller's problem, not ours.
269
+ // The lock is still released below; we re-enter the chain.
270
+ }
271
+ try {
272
+ return await fn();
273
+ } finally {
274
+ release();
275
+ // If we're the tail of the chain, clean up to avoid unbounded memory.
276
+ // Use `===` to ensure we don't clobber a newer lock that joined while
277
+ // we were running.
278
+ const current = _senderSubmissionLocks.get(key);
279
+ // The lock we set above was `prev.then(() => thisCallGate)` — when
280
+ // `thisCallGate` resolves, the whole promise resolves. If nothing
281
+ // queued behind us, remove the entry.
282
+ if (current) {
283
+ current.then(() => {
284
+ if (_senderSubmissionLocks.get(key) === current) {
285
+ _senderSubmissionLocks.delete(key);
286
+ }
287
+ }).catch(() => {
288
+ if (_senderSubmissionLocks.get(key) === current) {
289
+ _senderSubmissionLocks.delete(key);
290
+ }
291
+ });
292
+ }
293
+ }
294
+ }
295
+
296
+ /** Exposed for tests — reset the per-account lock map. */
297
+ export function __resetSenderLocksForTests(): void {
298
+ _senderSubmissionLocks.clear();
299
+ }
300
+
234
301
  /**
235
302
  * Check if a Smart Account is deployed and return factory/factoryData if not.
236
303
  *
@@ -303,22 +370,36 @@ export async function submitFactOnChain(
303
370
  throw new Error('Recovery phrase (TOTALRECLAW_RECOVERY_PHRASE) is required for on-chain submission');
304
371
  }
305
372
 
373
+ // Resolve sender up-front so we can serialize concurrent submissions for
374
+ // the SAME Smart Account (rc.3 AA25 fix). Derivation is CREATE2, so we
375
+ // don't need to hit the chain — WASM does it.
376
+ const eoa = getWasm().deriveEoa(config.mnemonic) as { private_key: string; address: string };
377
+ const sender = config.walletAddress || await deriveSmartAccountAddress(config.mnemonic, config.chainId);
378
+
379
+ return withSenderLock(sender, () => submitFactOnChainLocked(
380
+ protobufPayload, config, eoa, sender,
381
+ ));
382
+ }
383
+
384
+ async function submitFactOnChainLocked(
385
+ protobufPayload: Buffer,
386
+ config: SubgraphStoreConfig,
387
+ eoa: { private_key: string; address: string },
388
+ sender: string,
389
+ ): Promise<{ txHash: string; userOpHash: string; success: boolean }> {
306
390
  const bundlerUrl = `${config.relayUrl}/v1/bundler`;
307
- const headers: Record<string, string> = {
391
+ const overrides: Record<string, string> = {
308
392
  'Content-Type': 'application/json',
309
- 'X-TotalReclaw-Client': 'openclaw-plugin',
310
393
  };
311
- if (config.authKeyHex) headers['Authorization'] = `Bearer ${config.authKeyHex}`;
312
- if (config.walletAddress) headers['X-Wallet-Address'] = config.walletAddress;
394
+ if (config.authKeyHex) overrides['Authorization'] = `Bearer ${config.authKeyHex}`;
395
+ if (config.walletAddress) overrides['X-Wallet-Address'] = config.walletAddress;
396
+ const headers = buildRelayHeaders(overrides);
313
397
 
314
398
  // Helper for JSON-RPC calls to relay bundler (with 429 retry)
315
399
  async function rpc(method: string, params: unknown[]): Promise<any> {
316
400
  return rpcWithRetry(bundlerUrl, headers, method, params);
317
401
  }
318
402
 
319
- // 1. Derive EOA from mnemonic
320
- const eoa = getWasm().deriveEoa(config.mnemonic) as { private_key: string; address: string };
321
- const sender = config.walletAddress || await deriveSmartAccountAddress(config.mnemonic, config.chainId);
322
403
  const entryPoint = config.entryPointAddress || getWasm().getEntryPointAddress();
323
404
 
324
405
  // 2. Encode calldata (SimpleAccount.execute → DataEdge fallback)
@@ -508,21 +589,33 @@ export async function submitFactBatchOnChain(
508
589
  throw new Error('Recovery phrase (TOTALRECLAW_RECOVERY_PHRASE) is required for on-chain submission');
509
590
  }
510
591
 
592
+ // Resolve sender up-front for the per-account mutex (rc.3 AA25 fix).
593
+ const eoa = getWasm().deriveEoa(config.mnemonic) as { private_key: string; address: string };
594
+ const sender = config.walletAddress || await deriveSmartAccountAddress(config.mnemonic, config.chainId);
595
+
596
+ return withSenderLock(sender, () => submitFactBatchOnChainLocked(
597
+ protobufPayloads, config, eoa, sender,
598
+ ));
599
+ }
600
+
601
+ async function submitFactBatchOnChainLocked(
602
+ protobufPayloads: Buffer[],
603
+ config: SubgraphStoreConfig,
604
+ eoa: { private_key: string; address: string },
605
+ sender: string,
606
+ ): Promise<{ txHash: string; userOpHash: string; success: boolean; batchSize: number }> {
511
607
  const bundlerUrl = `${config.relayUrl}/v1/bundler`;
512
- const headers: Record<string, string> = {
608
+ const overrides: Record<string, string> = {
513
609
  'Content-Type': 'application/json',
514
- 'X-TotalReclaw-Client': 'openclaw-plugin',
515
610
  };
516
- if (config.authKeyHex) headers['Authorization'] = `Bearer ${config.authKeyHex}`;
517
- if (config.walletAddress) headers['X-Wallet-Address'] = config.walletAddress;
611
+ if (config.authKeyHex) overrides['Authorization'] = `Bearer ${config.authKeyHex}`;
612
+ if (config.walletAddress) overrides['X-Wallet-Address'] = config.walletAddress;
613
+ const headers = buildRelayHeaders(overrides);
518
614
 
519
615
  // Helper for JSON-RPC calls to relay bundler (with 429 retry)
520
616
  async function rpc(method: string, params: unknown[]): Promise<any> {
521
617
  return rpcWithRetry(bundlerUrl, headers, method, params);
522
618
  }
523
-
524
- const eoa = getWasm().deriveEoa(config.mnemonic) as { private_key: string; address: string };
525
- const sender = config.walletAddress || await deriveSmartAccountAddress(config.mnemonic, config.chainId);
526
619
  const entryPoint = config.entryPointAddress || getWasm().getEntryPointAddress();
527
620
 
528
621
  // Encode batch calldata (SimpleAccount.executeBatch)