@totalreclaw/totalreclaw 3.3.1-rc.20 → 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.
- package/CHANGELOG.md +47 -0
- package/api-client.ts +18 -11
- package/config.ts +60 -3
- package/crypto.ts +10 -2
- package/dist/api-client.js +17 -11
- package/dist/config.js +60 -3
- package/dist/crypto.js +10 -2
- package/dist/fs-helpers.js +82 -0
- package/dist/index.js +149 -34
- package/dist/lsh.js +7 -2
- package/dist/relay-headers.js +44 -0
- package/dist/subgraph-search.js +4 -3
- package/dist/subgraph-store.js +15 -10
- package/fs-helpers.ts +92 -0
- package/index.ts +166 -39
- package/lsh.ts +7 -2
- package/package.json +3 -2
- package/relay-headers.ts +50 -0
- package/subgraph-search.ts +4 -3
- package/subgraph-store.ts +15 -10
package/index.ts
CHANGED
|
@@ -135,6 +135,7 @@ import {
|
|
|
135
135
|
} from './onboarding-cli.js';
|
|
136
136
|
import { PluginHotCache, type HotFact } from './hot-cache-wrapper.js';
|
|
137
137
|
import { CONFIG, setRecoveryPhraseOverride } from './config.js';
|
|
138
|
+
import { buildRelayHeaders } from './relay-headers.js';
|
|
138
139
|
import {
|
|
139
140
|
readBillingCache,
|
|
140
141
|
writeBillingCache,
|
|
@@ -151,6 +152,7 @@ import {
|
|
|
151
152
|
resolveOnboardingState,
|
|
152
153
|
writeOnboardingState,
|
|
153
154
|
readPluginVersion,
|
|
155
|
+
cleanupInstallStagingDirs,
|
|
154
156
|
type OnboardingState,
|
|
155
157
|
} from './fs-helpers.js';
|
|
156
158
|
import { isRcBuild } from './qa-bug-report.js';
|
|
@@ -161,6 +163,19 @@ import { detectGatewayHost } from './gateway-url.js';
|
|
|
161
163
|
import { validateMnemonic } from '@scure/bip39';
|
|
162
164
|
import { wordlist } from '@scure/bip39/wordlists/english.js';
|
|
163
165
|
import crypto from 'node:crypto';
|
|
166
|
+
import { createRequire } from 'node:module';
|
|
167
|
+
import { fileURLToPath } from 'node:url';
|
|
168
|
+
import * as nodePath from 'node:path';
|
|
169
|
+
|
|
170
|
+
// CJS-style require for the @totalreclaw/core WASM module. We keep this
|
|
171
|
+
// load path lazy (only inside getSmartImportWasm() below) so a partial
|
|
172
|
+
// install of the dependency tree doesn't crash module init. Bare
|
|
173
|
+
// `require()` is a CommonJS global and is undefined under bare Node ESM —
|
|
174
|
+
// the shipped `dist/index.js` declares `"type":"module"`, so calling the
|
|
175
|
+
// global directly emits "require is not defined" at runtime (issue #124).
|
|
176
|
+
// createRequire bridges the gap. Same shape as crypto.ts / lsh.ts /
|
|
177
|
+
// subgraph-store.ts / claims-helper.ts.
|
|
178
|
+
const __cjsRequire = createRequire(import.meta.url);
|
|
164
179
|
|
|
165
180
|
// ---------------------------------------------------------------------------
|
|
166
181
|
// OpenClaw Plugin API type (defined locally to avoid SDK dependency)
|
|
@@ -876,11 +891,10 @@ async function initialize(logger: OpenClawPluginApi['logger']): Promise<void> {
|
|
|
876
891
|
const billingUrl = CONFIG.serverUrl;
|
|
877
892
|
const resp = await fetch(`${billingUrl}/v1/billing/status?wallet_address=${encodeURIComponent(walletAddr)}`, {
|
|
878
893
|
method: 'GET',
|
|
879
|
-
headers: {
|
|
894
|
+
headers: buildRelayHeaders({
|
|
880
895
|
'Authorization': `Bearer ${authKeyHex}`,
|
|
881
896
|
'Accept': 'application/json',
|
|
882
|
-
|
|
883
|
-
},
|
|
897
|
+
}),
|
|
884
898
|
});
|
|
885
899
|
if (resp.ok) {
|
|
886
900
|
const billingData = await resp.json() as Record<string, unknown>;
|
|
@@ -1453,11 +1467,11 @@ async function migrationGqlQuery<T>(
|
|
|
1453
1467
|
authKey?: string,
|
|
1454
1468
|
): Promise<T | null> {
|
|
1455
1469
|
try {
|
|
1456
|
-
const
|
|
1470
|
+
const overrides: Record<string, string> = {
|
|
1457
1471
|
'Content-Type': 'application/json',
|
|
1458
|
-
'X-TotalReclaw-Client': 'openclaw-plugin',
|
|
1459
1472
|
};
|
|
1460
|
-
if (authKey)
|
|
1473
|
+
if (authKey) overrides['Authorization'] = `Bearer ${authKey}`;
|
|
1474
|
+
const headers = buildRelayHeaders(overrides);
|
|
1461
1475
|
const response = await fetch(endpoint, {
|
|
1462
1476
|
method: 'POST',
|
|
1463
1477
|
headers,
|
|
@@ -2298,10 +2312,13 @@ async function handlePluginImportFrom(
|
|
|
2298
2312
|
// Smart Import — Two-Pass Pipeline (Profile + Triage)
|
|
2299
2313
|
// ---------------------------------------------------------------------------
|
|
2300
2314
|
|
|
2301
|
-
// Lazy-load WASM for smart import functions (same pattern as crypto.ts /
|
|
2315
|
+
// Lazy-load WASM for smart import functions (same pattern as crypto.ts /
|
|
2316
|
+
// subgraph-store.ts). Goes through __cjsRequire (createRequire(import.meta.url))
|
|
2317
|
+
// declared at the top of the file — bare `require()` is undefined under
|
|
2318
|
+
// pure-ESM Node, see issue #124.
|
|
2302
2319
|
let _smartImportWasm: typeof import('@totalreclaw/core') | null = null;
|
|
2303
2320
|
function getSmartImportWasm() {
|
|
2304
|
-
if (!_smartImportWasm) _smartImportWasm =
|
|
2321
|
+
if (!_smartImportWasm) _smartImportWasm = __cjsRequire('@totalreclaw/core');
|
|
2305
2322
|
return _smartImportWasm;
|
|
2306
2323
|
}
|
|
2307
2324
|
|
|
@@ -2840,17 +2857,37 @@ const plugin = {
|
|
|
2840
2857
|
// the loopback callsite if package.json read fails.
|
|
2841
2858
|
let pluginVersion: string | null = null;
|
|
2842
2859
|
try {
|
|
2843
|
-
//
|
|
2844
|
-
//
|
|
2845
|
-
//
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2860
|
+
// Resolve our own dist/ directory so `readPluginVersion` can locate
|
|
2861
|
+
// package.json. We use `import.meta.url` + ESM-static stdlib imports
|
|
2862
|
+
// (`fileURLToPath` from `node:url`, `nodePath.dirname` from `node:path`,
|
|
2863
|
+
// both imported at the top of this file). Earlier shape used inline
|
|
2864
|
+
// `require('node:url')` — undefined under bare-ESM Node, broke the
|
|
2865
|
+
// before_agent_start hook in the published rc.20 bundle (issue #124).
|
|
2866
|
+
const pluginDir = nodePath.dirname(fileURLToPath(import.meta.url));
|
|
2849
2867
|
pluginVersion = readPluginVersion(pluginDir);
|
|
2850
2868
|
rcMode = isRcBuild(pluginVersion);
|
|
2851
2869
|
if (rcMode) {
|
|
2852
2870
|
api.logger.info(`TotalReclaw: RC build detected (version=${pluginVersion}). RC-gated tools will be registered.`);
|
|
2853
2871
|
}
|
|
2872
|
+
|
|
2873
|
+
// 3.3.1-rc.21 (issue #126 — rc.20 finding F3): clean up
|
|
2874
|
+
// `.openclaw-install-stage-*` siblings left behind by an interrupted
|
|
2875
|
+
// `openclaw plugins install` run. Without cleanup, OpenClaw's plugin
|
|
2876
|
+
// loader auto-discovers the orphan directory on the next gateway
|
|
2877
|
+
// start and registers a duplicate `totalreclaw` plugin (duplicate
|
|
2878
|
+
// hooks, duplicate tools, "duplicate-plugin-id" warning every cycle).
|
|
2879
|
+
// Best-effort — never throws on permission / race failures.
|
|
2880
|
+
try {
|
|
2881
|
+
const removed = cleanupInstallStagingDirs(pluginDir);
|
|
2882
|
+
if (removed.length > 0) {
|
|
2883
|
+
api.logger.info(
|
|
2884
|
+
`TotalReclaw: removed ${removed.length} stale install-staging dir(s) from prior interrupted install: ${removed.join(', ')}`,
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
} catch {
|
|
2888
|
+
// Best-effort — already swallowed inside the helper, but keep this
|
|
2889
|
+
// outer try as belt-and-braces against future helper changes.
|
|
2890
|
+
}
|
|
2854
2891
|
} catch {
|
|
2855
2892
|
rcMode = false;
|
|
2856
2893
|
}
|
|
@@ -3014,8 +3051,30 @@ const plugin = {
|
|
|
3014
3051
|
// Write credentials.json + flip state to 'active' via
|
|
3015
3052
|
// fs-helpers. This centralizes disk I/O off the
|
|
3016
3053
|
// pair-http surface (scanner isolation).
|
|
3054
|
+
//
|
|
3055
|
+
// 3.3.1 (internal#130) — derive + persist the Smart Account
|
|
3056
|
+
// address right here so the user can see their scope address
|
|
3057
|
+
// immediately after pair, without waiting for a first chain
|
|
3058
|
+
// write. SA derivation runs locally (WASM deriveEoa + factory
|
|
3059
|
+
// getAddress eth_call); the mnemonic NEVER crosses any new
|
|
3060
|
+
// boundary — it's already on disk in credentials.json and is
|
|
3061
|
+
// consumed by the same `deriveSmartAccountAddress` call the
|
|
3062
|
+
// store/search paths use. Only the derived public address is
|
|
3063
|
+
// persisted to credentials.json (`scope_address`).
|
|
3064
|
+
let scopeAddress: string | undefined;
|
|
3065
|
+
try {
|
|
3066
|
+
scopeAddress = await deriveSmartAccountAddress(mnemonic, CONFIG.chainId);
|
|
3067
|
+
} catch (err) {
|
|
3068
|
+
// Best-effort. If chain RPC is unreachable at pair time, the
|
|
3069
|
+
// status tool re-tries derivation lazily on next call —
|
|
3070
|
+
// fall through and write credentials.json without it.
|
|
3071
|
+
api.logger.warn(
|
|
3072
|
+
`pair: scope_address derivation failed (will retry lazily): ${err instanceof Error ? err.message : String(err)}`,
|
|
3073
|
+
);
|
|
3074
|
+
}
|
|
3017
3075
|
const creds = loadCredentialsJson(CREDENTIALS_PATH) ?? {};
|
|
3018
|
-
const next = { ...creds, mnemonic };
|
|
3076
|
+
const next: typeof creds = { ...creds, mnemonic };
|
|
3077
|
+
if (scopeAddress) next.scope_address = scopeAddress;
|
|
3019
3078
|
if (!writeCredentialsJson(CREDENTIALS_PATH, next)) {
|
|
3020
3079
|
return { state: 'error', error: 'credentials_write_failed' };
|
|
3021
3080
|
}
|
|
@@ -3803,11 +3862,10 @@ const plugin = {
|
|
|
3803
3862
|
|
|
3804
3863
|
const res = await fetch(`${relayUrl}/v1/subgraph`, {
|
|
3805
3864
|
method: 'POST',
|
|
3806
|
-
headers: {
|
|
3865
|
+
headers: buildRelayHeaders({
|
|
3807
3866
|
'Content-Type': 'application/json',
|
|
3808
|
-
'X-TotalReclaw-Client': 'openclaw-plugin',
|
|
3809
3867
|
...(authKeyHex ? { Authorization: `Bearer ${authKeyHex}` } : {}),
|
|
3810
|
-
},
|
|
3868
|
+
}),
|
|
3811
3869
|
body: JSON.stringify({ query, variables }),
|
|
3812
3870
|
});
|
|
3813
3871
|
|
|
@@ -3943,11 +4001,10 @@ const plugin = {
|
|
|
3943
4001
|
const walletAddr = subgraphOwner || userId || '';
|
|
3944
4002
|
const response = await fetch(`${serverUrl}/v1/billing/status?wallet_address=${encodeURIComponent(walletAddr)}`, {
|
|
3945
4003
|
method: 'GET',
|
|
3946
|
-
headers: {
|
|
4004
|
+
headers: buildRelayHeaders({
|
|
3947
4005
|
'Authorization': `Bearer ${authKeyHex}`,
|
|
3948
4006
|
'Accept': 'application/json',
|
|
3949
|
-
|
|
3950
|
-
},
|
|
4007
|
+
}),
|
|
3951
4008
|
});
|
|
3952
4009
|
|
|
3953
4010
|
if (!response.ok) {
|
|
@@ -3972,6 +4029,37 @@ const plugin = {
|
|
|
3972
4029
|
checked_at: Date.now(),
|
|
3973
4030
|
});
|
|
3974
4031
|
|
|
4032
|
+
// 3.3.1 (internal#130) — surface the Smart Account / scope
|
|
4033
|
+
// address so the user can do subgraph queries, BaseScan
|
|
4034
|
+
// lookups, and cross-client portability checks BEFORE any
|
|
4035
|
+
// chain write completes. Resolution priority:
|
|
4036
|
+
// 1. In-memory `subgraphOwner` (already derived earlier).
|
|
4037
|
+
// 2. credentials.json `scope_address` (persisted at pair).
|
|
4038
|
+
// 3. Lazy derive now from the loaded mnemonic + cache it
|
|
4039
|
+
// back to credentials.json so the next call is free.
|
|
4040
|
+
let scopeAddress: string | undefined =
|
|
4041
|
+
subgraphOwner ?? undefined;
|
|
4042
|
+
if (!scopeAddress) {
|
|
4043
|
+
try {
|
|
4044
|
+
const credsCache = loadCredentialsJson(CREDENTIALS_PATH);
|
|
4045
|
+
if (credsCache?.scope_address && typeof credsCache.scope_address === 'string') {
|
|
4046
|
+
scopeAddress = credsCache.scope_address;
|
|
4047
|
+
} else if (credsCache?.mnemonic && typeof credsCache.mnemonic === 'string') {
|
|
4048
|
+
scopeAddress = await deriveSmartAccountAddress(
|
|
4049
|
+
credsCache.mnemonic,
|
|
4050
|
+
CONFIG.chainId,
|
|
4051
|
+
);
|
|
4052
|
+
if (scopeAddress) {
|
|
4053
|
+
writeCredentialsJson(CREDENTIALS_PATH, { ...credsCache, scope_address: scopeAddress });
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
} catch (deriveErr) {
|
|
4057
|
+
api.logger.warn(
|
|
4058
|
+
`totalreclaw_status: scope_address lookup failed: ${deriveErr instanceof Error ? deriveErr.message : String(deriveErr)}`,
|
|
4059
|
+
);
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
|
|
3975
4063
|
const tierLabel = tier === 'pro' ? 'Pro' : 'Free';
|
|
3976
4064
|
const lines: string[] = [
|
|
3977
4065
|
`Tier: ${tierLabel}`,
|
|
@@ -3980,13 +4068,21 @@ const plugin = {
|
|
|
3980
4068
|
if (freeWritesResetAt) {
|
|
3981
4069
|
lines.push(`Resets: ${new Date(freeWritesResetAt).toLocaleDateString()}`);
|
|
3982
4070
|
}
|
|
4071
|
+
if (scopeAddress) {
|
|
4072
|
+
lines.push(`Smart Account: ${scopeAddress}`);
|
|
4073
|
+
}
|
|
3983
4074
|
if (tier !== 'pro') {
|
|
3984
4075
|
lines.push(`Pricing: https://totalreclaw.xyz/pricing`);
|
|
3985
4076
|
}
|
|
3986
4077
|
|
|
3987
4078
|
return {
|
|
3988
4079
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
3989
|
-
details: {
|
|
4080
|
+
details: {
|
|
4081
|
+
tier,
|
|
4082
|
+
free_writes_used: freeWritesUsed,
|
|
4083
|
+
free_writes_limit: freeWritesLimit,
|
|
4084
|
+
scope_address: scopeAddress,
|
|
4085
|
+
},
|
|
3990
4086
|
};
|
|
3991
4087
|
} catch (err: unknown) {
|
|
3992
4088
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4709,11 +4805,10 @@ const plugin = {
|
|
|
4709
4805
|
|
|
4710
4806
|
const response = await fetch(`${serverUrl}/v1/billing/checkout`, {
|
|
4711
4807
|
method: 'POST',
|
|
4712
|
-
headers: {
|
|
4808
|
+
headers: buildRelayHeaders({
|
|
4713
4809
|
'Authorization': `Bearer ${authKeyHex}`,
|
|
4714
4810
|
'Content-Type': 'application/json',
|
|
4715
|
-
|
|
4716
|
-
},
|
|
4811
|
+
}),
|
|
4717
4812
|
body: JSON.stringify({
|
|
4718
4813
|
wallet_address: walletAddr,
|
|
4719
4814
|
tier: 'pro',
|
|
@@ -4797,11 +4892,10 @@ const plugin = {
|
|
|
4797
4892
|
`${serverUrl}/v1/billing/status?wallet_address=${encodeURIComponent(subgraphOwner)}`,
|
|
4798
4893
|
{
|
|
4799
4894
|
method: 'GET',
|
|
4800
|
-
headers: {
|
|
4895
|
+
headers: buildRelayHeaders({
|
|
4801
4896
|
'Authorization': `Bearer ${authKeyHex}`,
|
|
4802
4897
|
'Content-Type': 'application/json',
|
|
4803
|
-
|
|
4804
|
-
},
|
|
4898
|
+
}),
|
|
4805
4899
|
},
|
|
4806
4900
|
);
|
|
4807
4901
|
if (!billingResp.ok) {
|
|
@@ -5080,9 +5174,27 @@ const plugin = {
|
|
|
5080
5174
|
validateMnemonic(p, wordlist),
|
|
5081
5175
|
completePairing: async ({ mnemonic }) => {
|
|
5082
5176
|
try {
|
|
5177
|
+
// 3.3.1 (internal#130) — derive + persist the
|
|
5178
|
+
// Smart Account address now so the user can see
|
|
5179
|
+
// it immediately after pair, before any chain
|
|
5180
|
+
// write. Mnemonic stays in this scope (already
|
|
5181
|
+
// on disk in credentials.json); only the
|
|
5182
|
+
// derived public scope_address is added.
|
|
5183
|
+
let scopeAddress: string | undefined;
|
|
5184
|
+
try {
|
|
5185
|
+
scopeAddress = await deriveSmartAccountAddress(
|
|
5186
|
+
mnemonic,
|
|
5187
|
+
CONFIG.chainId,
|
|
5188
|
+
);
|
|
5189
|
+
} catch (deriveErr) {
|
|
5190
|
+
api.logger.warn(
|
|
5191
|
+
`totalreclaw_pair(relay): scope_address derivation failed (will retry lazily): ${deriveErr instanceof Error ? deriveErr.message : String(deriveErr)}`,
|
|
5192
|
+
);
|
|
5193
|
+
}
|
|
5083
5194
|
const creds =
|
|
5084
5195
|
loadCredentialsJson(CREDENTIALS_PATH) ?? {};
|
|
5085
|
-
const next = { ...creds, mnemonic };
|
|
5196
|
+
const next: typeof creds = { ...creds, mnemonic };
|
|
5197
|
+
if (scopeAddress) next.scope_address = scopeAddress;
|
|
5086
5198
|
if (!writeCredentialsJson(CREDENTIALS_PATH, next)) {
|
|
5087
5199
|
return { state: 'error', error: 'credentials_write_failed' };
|
|
5088
5200
|
}
|
|
@@ -5094,7 +5206,7 @@ const plugin = {
|
|
|
5094
5206
|
version: pluginVersion ?? '3.3.0',
|
|
5095
5207
|
});
|
|
5096
5208
|
api.logger.info(
|
|
5097
|
-
`totalreclaw_pair(relay): session ${remoteSession.token.slice(0, 8)}… completed; credentials written`,
|
|
5209
|
+
`totalreclaw_pair(relay): session ${remoteSession.token.slice(0, 8)}… completed; credentials written${scopeAddress ? ` (scope_address=${scopeAddress})` : ''}`,
|
|
5098
5210
|
);
|
|
5099
5211
|
return { state: 'active' };
|
|
5100
5212
|
} catch (err: unknown) {
|
|
@@ -5234,11 +5346,20 @@ const plugin = {
|
|
|
5234
5346
|
// declared. If the agent then reports the tool is missing from its
|
|
5235
5347
|
// tool list, the gap is upstream OpenClaw tool propagation, not our
|
|
5236
5348
|
// plugin — see issue #110 fix 3 + PR #102 (CLI fallback).
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5349
|
+
//
|
|
5350
|
+
// 3.3.1-rc.21 (issue #128): the breadcrumb is debug-grade — it was
|
|
5351
|
+
// bleeding into `openclaw agent --json` stdout, breaking programmatic
|
|
5352
|
+
// parsers that expect the JSON-RPC body to be the only thing on the
|
|
5353
|
+
// wire. Gated behind `TOTALRECLAW_VERBOSE_REGISTER` (or the general
|
|
5354
|
+
// `TOTALRECLAW_DEBUG` toggle) so ops can opt back in when chasing
|
|
5355
|
+
// a tool-injection regression. Default OFF — clean stdout for users.
|
|
5356
|
+
if (CONFIG.verboseRegister) {
|
|
5357
|
+
api.logger.info(
|
|
5358
|
+
'TotalReclaw: registerTool(totalreclaw_pair) returned. If the agent does not see it in its tool list ' +
|
|
5359
|
+
'after gateway restart, the issue is upstream tool injection (containerized agents) — fall back to ' +
|
|
5360
|
+
'`openclaw totalreclaw pair generate --url-pin-only` (PR #102) or `openclaw totalreclaw onboard --pair-only`.',
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5242
5363
|
|
|
5243
5364
|
// ---------------------------------------------------------------
|
|
5244
5365
|
// Tool: totalreclaw_report_qa_bug (3.3.1-rc.3 — RC-gated)
|
|
@@ -5370,9 +5491,15 @@ const plugin = {
|
|
|
5370
5491
|
},
|
|
5371
5492
|
{ name: 'totalreclaw_report_qa_bug' },
|
|
5372
5493
|
);
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5494
|
+
// 3.3.1-rc.21 (issue #128): demote the registration-confirmation
|
|
5495
|
+
// breadcrumb to verbose-only. Same `--json` stdout pollution risk
|
|
5496
|
+
// as the totalreclaw_pair breadcrumb above; ops can opt back in
|
|
5497
|
+
// via TOTALRECLAW_VERBOSE_REGISTER / TOTALRECLAW_DEBUG.
|
|
5498
|
+
if (CONFIG.verboseRegister) {
|
|
5499
|
+
api.logger.info(
|
|
5500
|
+
'totalreclaw_report_qa_bug registered (RC build — this tool is hidden in stable releases).',
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5376
5503
|
}
|
|
5377
5504
|
|
|
5378
5505
|
// ---------------------------------------------------------------
|
|
@@ -5508,7 +5635,7 @@ const plugin = {
|
|
|
5508
5635
|
const walletParam = encodeURIComponent(subgraphOwner || userId || '');
|
|
5509
5636
|
const billingResp = await fetch(`${billingUrl}/v1/billing/status?wallet_address=${walletParam}`, {
|
|
5510
5637
|
method: 'GET',
|
|
5511
|
-
headers: { 'Authorization': `Bearer ${authKeyHex}`, 'Accept': 'application/json'
|
|
5638
|
+
headers: buildRelayHeaders({ 'Authorization': `Bearer ${authKeyHex}`, 'Accept': 'application/json' }),
|
|
5512
5639
|
});
|
|
5513
5640
|
if (billingResp.ok) {
|
|
5514
5641
|
const billingData = await billingResp.json() as Record<string, unknown>;
|
package/lsh.ts
CHANGED
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
* Default parameters: 32 bits per table, 20 tables.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
// Lazy-load WASM
|
|
10
|
+
// Lazy-load WASM via createRequire. The shipped `dist/index.js` is ESM-only
|
|
11
|
+
// (`"type":"module"`) so the bare `require` global is undefined at runtime.
|
|
12
|
+
// See issue #124 for the bug this avoids; matches the pattern in
|
|
13
|
+
// claims-helper / consolidation / digest-sync / pin / retype-setscope.
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
const requireWasm = createRequire(import.meta.url);
|
|
11
16
|
let _WasmLshHasher: typeof import('@totalreclaw/core')['WasmLshHasher'] | null = null;
|
|
12
17
|
function getWasmLshHasher() {
|
|
13
|
-
if (!_WasmLshHasher) _WasmLshHasher =
|
|
18
|
+
if (!_WasmLshHasher) _WasmLshHasher = requireWasm('@totalreclaw/core').WasmLshHasher;
|
|
14
19
|
return _WasmLshHasher!;
|
|
15
20
|
}
|
|
16
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totalreclaw/totalreclaw",
|
|
3
|
-
"version": "3.3.1-rc.
|
|
3
|
+
"version": "3.3.1-rc.21",
|
|
4
4
|
"description": "End-to-end encrypted, agent-portable memory for OpenClaw and any LLM-agent runtime. XChaCha20-Poly1305 with protobuf v4 + on-chain Memory Taxonomy v1 (claim / preference / directive / commitment / episode / summary).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "rm -rf dist && tsc -p tsconfig.json --noCheck",
|
|
64
64
|
"verify-tarball": "node ../scripts/verify-tarball.mjs",
|
|
65
|
-
"test": "npx tsx manifest-shape.test.ts && npx tsx config-schema.test.ts && npx tsx llm-profile-reader.test.ts && npx tsx llm-client.test.ts && npx tsx llm-client-retry.test.ts && npx tsx gateway-url.test.ts && npx tsx retype-setscope.test.ts && npx tsx tool-gating.test.ts && npx tsx onboarding-noninteractive.test.ts && npx tsx pair-cli-json.test.ts && npx tsx pair-qr.test.ts && npx tsx pair-remote-client.test.ts && npx tsx qa-bug-report.test.ts && npx tsx nonce-serialization.test.ts && npx tsx phrase-safety-registry.test.ts && npx tsx test_issue_92_onnx_download_ux.test.ts && npx tsx onboard-pair-only.test.ts && npx tsx import-time-smoke.test.ts && npx tsx recall-relevance-gate.test.ts",
|
|
65
|
+
"test": "npx tsx manifest-shape.test.ts && npx tsx config-schema.test.ts && npx tsx config.test.ts && npx tsx relay-headers.test.ts && npx tsx scope-address-visible.test.ts && npx tsx llm-profile-reader.test.ts && npx tsx llm-client.test.ts && npx tsx llm-client-retry.test.ts && npx tsx gateway-url.test.ts && npx tsx retype-setscope.test.ts && npx tsx tool-gating.test.ts && npx tsx onboarding-noninteractive.test.ts && npx tsx pair-cli-json.test.ts && npx tsx pair-qr.test.ts && npx tsx pair-remote-client.test.ts && npx tsx qa-bug-report.test.ts && npx tsx nonce-serialization.test.ts && npx tsx phrase-safety-registry.test.ts && npx tsx test_issue_92_onnx_download_ux.test.ts && npx tsx onboard-pair-only.test.ts && npx tsx import-time-smoke.test.ts && npx tsx recall-relevance-gate.test.ts && npx tsx install-staging-cleanup.test.ts && npx tsx json-stdout-cleanliness.test.ts",
|
|
66
|
+
"smoke:dist": "npx tsx dist-esm-smoke.test.ts",
|
|
66
67
|
"check-scanner": "node ../scripts/check-scanner.mjs",
|
|
67
68
|
"prepack": "npm run build",
|
|
68
69
|
"prepublishOnly": "node ../scripts/check-scanner.mjs"
|
package/relay-headers.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared outbound-header helper for relay calls.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the common `X-TotalReclaw-*` headers so every fetch site
|
|
5
|
+
* consistently tags requests with:
|
|
6
|
+
* - `X-TotalReclaw-Client` — caller identity (defaults to `openclaw-plugin`).
|
|
7
|
+
* - `X-TotalReclaw-Session` — optional QA / observability tag from
|
|
8
|
+
* `TOTALRECLAW_SESSION_ID`. Used by Axiom log filters and the
|
|
9
|
+
* `qa-totalreclaw` skill to scope log searches per QA run.
|
|
10
|
+
*
|
|
11
|
+
* Pure function — no I/O, no network. Reads `getSessionId()` (which reads the
|
|
12
|
+
* env var via getter so harnesses that flip the env between calls pick up
|
|
13
|
+
* the new value).
|
|
14
|
+
*
|
|
15
|
+
* The session-id env var was accidentally placed in the v1 REMOVED_ENV_VARS
|
|
16
|
+
* list and silently warned-and-dropped, breaking Axiom traceability for QA
|
|
17
|
+
* runs (see internal#127). This helper is the canonical re-entry point for
|
|
18
|
+
* the variable.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { getSessionId } from './config.js';
|
|
22
|
+
|
|
23
|
+
/** Default `X-TotalReclaw-Client` value. */
|
|
24
|
+
export const DEFAULT_CLIENT_ID = 'openclaw-plugin';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the standard outbound header set.
|
|
28
|
+
*
|
|
29
|
+
* @param overrides - merge-in additional headers (`Authorization`,
|
|
30
|
+
* `Content-Type`, etc.); these win over the defaults.
|
|
31
|
+
* @param clientId - override the `X-TotalReclaw-Client` value.
|
|
32
|
+
*
|
|
33
|
+
* Always includes `X-TotalReclaw-Client`. Includes `X-TotalReclaw-Session`
|
|
34
|
+
* only when `TOTALRECLAW_SESSION_ID` is set + non-empty.
|
|
35
|
+
*/
|
|
36
|
+
export function buildRelayHeaders(
|
|
37
|
+
overrides: Record<string, string> = {},
|
|
38
|
+
clientId: string = DEFAULT_CLIENT_ID,
|
|
39
|
+
): Record<string, string> {
|
|
40
|
+
const headers: Record<string, string> = {
|
|
41
|
+
'X-TotalReclaw-Client': clientId,
|
|
42
|
+
};
|
|
43
|
+
const sessionId = getSessionId();
|
|
44
|
+
if (sessionId) {
|
|
45
|
+
headers['X-TotalReclaw-Session'] = sessionId;
|
|
46
|
+
}
|
|
47
|
+
// Caller-supplied headers (Authorization, Content-Type, Accept, etc.) take
|
|
48
|
+
// precedence over the defaults but should generally not stomp the X-* tags.
|
|
49
|
+
return { ...headers, ...overrides };
|
|
50
|
+
}
|
package/subgraph-search.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { getSubgraphConfig } from './subgraph-store.js';
|
|
24
24
|
import { CONFIG } from './config.js';
|
|
25
|
+
import { buildRelayHeaders } from './relay-headers.js';
|
|
25
26
|
|
|
26
27
|
export interface SubgraphSearchFact {
|
|
27
28
|
id: string;
|
|
@@ -48,13 +49,13 @@ async function gqlQuery<T>(
|
|
|
48
49
|
authKeyHex?: string,
|
|
49
50
|
): Promise<T | null> {
|
|
50
51
|
try {
|
|
51
|
-
const
|
|
52
|
+
const overrides: Record<string, string> = {
|
|
52
53
|
'Content-Type': 'application/json',
|
|
53
|
-
'X-TotalReclaw-Client': 'openclaw-plugin',
|
|
54
54
|
};
|
|
55
55
|
if (authKeyHex) {
|
|
56
|
-
|
|
56
|
+
overrides['Authorization'] = `Bearer ${authKeyHex}`;
|
|
57
57
|
}
|
|
58
|
+
const headers = buildRelayHeaders(overrides);
|
|
58
59
|
const response = await fetch(endpoint, {
|
|
59
60
|
method: 'POST',
|
|
60
61
|
headers,
|
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
|
|
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 =
|
|
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
|
|
@@ -383,12 +388,12 @@ async function submitFactOnChainLocked(
|
|
|
383
388
|
sender: string,
|
|
384
389
|
): Promise<{ txHash: string; userOpHash: string; success: boolean }> {
|
|
385
390
|
const bundlerUrl = `${config.relayUrl}/v1/bundler`;
|
|
386
|
-
const
|
|
391
|
+
const overrides: Record<string, string> = {
|
|
387
392
|
'Content-Type': 'application/json',
|
|
388
|
-
'X-TotalReclaw-Client': 'openclaw-plugin',
|
|
389
393
|
};
|
|
390
|
-
if (config.authKeyHex)
|
|
391
|
-
if (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);
|
|
392
397
|
|
|
393
398
|
// Helper for JSON-RPC calls to relay bundler (with 429 retry)
|
|
394
399
|
async function rpc(method: string, params: unknown[]): Promise<any> {
|
|
@@ -600,12 +605,12 @@ async function submitFactBatchOnChainLocked(
|
|
|
600
605
|
sender: string,
|
|
601
606
|
): Promise<{ txHash: string; userOpHash: string; success: boolean; batchSize: number }> {
|
|
602
607
|
const bundlerUrl = `${config.relayUrl}/v1/bundler`;
|
|
603
|
-
const
|
|
608
|
+
const overrides: Record<string, string> = {
|
|
604
609
|
'Content-Type': 'application/json',
|
|
605
|
-
'X-TotalReclaw-Client': 'openclaw-plugin',
|
|
606
610
|
};
|
|
607
|
-
if (config.authKeyHex)
|
|
608
|
-
if (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);
|
|
609
614
|
|
|
610
615
|
// Helper for JSON-RPC calls to relay bundler (with 429 retry)
|
|
611
616
|
async function rpc(method: string, params: unknown[]): Promise<any> {
|