@totalreclaw/totalreclaw 3.3.12 → 3.3.13
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/SKILL.md +25 -8
- package/{billing-cache.ts → billing/billing-cache.ts} +1 -1
- package/{relay-headers.ts → billing/relay-headers.ts} +1 -1
- package/{tr-cli-export-helper.ts → cli/tr-cli-export-helper.ts} +5 -5
- package/{tr-cli.ts → cli/tr-cli.ts} +20 -20
- package/{contradiction-sync.ts → contradiction/contradiction-sync.ts} +13 -13
- package/{digest-sync.ts → digest/digest-sync.ts} +32 -2
- package/dist/{billing-cache.js → billing/billing-cache.js} +1 -1
- package/dist/{relay-headers.js → billing/relay-headers.js} +1 -1
- package/dist/{tr-cli-export-helper.js → cli/tr-cli-export-helper.js} +5 -5
- package/dist/{tr-cli.js → cli/tr-cli.js} +19 -19
- package/dist/{contradiction-sync.js → contradiction/contradiction-sync.js} +13 -13
- package/dist/{digest-sync.js → digest/digest-sync.js} +32 -2
- package/dist/{download-ux.js → embedding/download-ux.js} +1 -1
- package/dist/{reranker.js → embedding/reranker.js} +13 -4
- package/dist/{claims-helper.js → extraction/claims-helper.js} +54 -58
- package/dist/extraction/consolidation.js +170 -0
- package/dist/{extractor.js → extraction/extractor.js} +17 -17
- package/dist/extraction/importance-filter.js +38 -0
- package/dist/{semantic-dedup.js → extraction/semantic-dedup.js} +2 -2
- package/dist/{batch-gate.js → import/batch-gate.js} +1 -1
- package/dist/import/import-runtime.js +692 -0
- package/dist/import-adapters/chatgpt-adapter.js +7 -7
- package/dist/import-adapters/gemini-adapter.js +13 -13
- package/dist/import-adapters/mem0-adapter.js +10 -10
- package/dist/index.js +111 -1094
- package/dist/{llm-client.js → llm/llm-client.js} +2 -2
- package/dist/{hot-cache-wrapper.js → memory/hot-cache-wrapper.js} +5 -5
- package/dist/{memory-runtime.js → memory/memory-runtime.js} +1 -1
- package/dist/{pin.js → memory/pin.js} +18 -18
- package/dist/{retype-setscope.js → memory/retype-setscope.js} +19 -19
- package/dist/{credential-provider.js → pairing/credential-provider.js} +2 -2
- package/dist/{first-run.js → pairing/first-run.js} +1 -1
- package/dist/{onboarding-cli.js → pairing/onboarding-cli.js} +20 -20
- package/dist/{pair-cli-relay.js → pairing/pair-cli-relay.js} +10 -10
- package/dist/{pair-crypto.js → pairing/pair-crypto.js} +1 -1
- package/dist/{pair-http.js → pairing/pair-http.js} +3 -3
- package/dist/{pair-remote-client.js → pairing/pair-remote-client.js} +10 -10
- package/dist/{pair-session-store.js → pairing/pair-session-store.js} +4 -4
- package/dist/runtime/config-schema.js +59 -0
- package/dist/runtime/format-helpers.js +257 -0
- package/dist/runtime/types.js +11 -0
- package/dist/{confirm-indexed.js → subgraph/confirm-indexed.js} +1 -1
- package/dist/{subgraph-search.js → subgraph/subgraph-search.js} +14 -14
- package/dist/{subgraph-store.js → subgraph/subgraph-store.js} +6 -6
- package/{download-ux.ts → embedding/download-ux.ts} +1 -1
- package/{reranker.ts → embedding/reranker.ts} +13 -4
- package/{claims-helper.ts → extraction/claims-helper.ts} +52 -60
- package/{consolidation.ts → extraction/consolidation.ts} +63 -154
- package/{extractor.ts → extraction/extractor.ts} +18 -17
- package/extraction/importance-filter.ts +50 -0
- package/{semantic-dedup.ts → extraction/semantic-dedup.ts} +2 -2
- package/{batch-gate.ts → import/batch-gate.ts} +1 -1
- package/import/import-runtime.ts +853 -0
- package/import-adapters/chatgpt-adapter.ts +7 -7
- package/import-adapters/gemini-adapter.ts +13 -13
- package/import-adapters/mem0-adapter.ts +10 -10
- package/index.ts +129 -1377
- package/{llm-client.ts → llm/llm-client.ts} +2 -2
- package/{hot-cache-wrapper.ts → memory/hot-cache-wrapper.ts} +5 -5
- package/{memory-runtime.ts → memory/memory-runtime.ts} +1 -1
- package/{pin.ts → memory/pin.ts} +20 -20
- package/{retype-setscope.ts → memory/retype-setscope.ts} +21 -21
- package/{tool-gating.ts → memory/tool-gating.ts} +1 -1
- package/package.json +6 -6
- package/{credential-provider.ts → pairing/credential-provider.ts} +2 -2
- package/{first-run.ts → pairing/first-run.ts} +1 -1
- package/{onboarding-cli.ts → pairing/onboarding-cli.ts} +20 -20
- package/{pair-cli-relay.ts → pairing/pair-cli-relay.ts} +10 -10
- package/{pair-crypto.ts → pairing/pair-crypto.ts} +1 -1
- package/{pair-http.ts → pairing/pair-http.ts} +3 -3
- package/{pair-remote-client.ts → pairing/pair-remote-client.ts} +10 -10
- package/{pair-session-store.ts → pairing/pair-session-store.ts} +4 -4
- package/runtime/config-schema.ts +64 -0
- package/runtime/format-helpers.ts +293 -0
- package/runtime/types.ts +118 -0
- package/skill.json +44 -10
- package/{confirm-indexed.ts → subgraph/confirm-indexed.ts} +1 -1
- package/{subgraph-search.ts → subgraph/subgraph-search.ts} +14 -14
- package/{subgraph-store.ts → subgraph/subgraph-store.ts} +10 -10
- package/dist/consolidation.js +0 -249
- /package/{api-client.ts → billing/api-client.ts} +0 -0
- /package/{gateway-url.ts → billing/gateway-url.ts} +0 -0
- /package/{inbound-user-tracker.ts → billing/inbound-user-tracker.ts} +0 -0
- /package/{relay.ts → billing/relay.ts} +0 -0
- /package/{crypto.ts → crypto/crypto.ts} +0 -0
- /package/{vault-crypto.ts → crypto/vault-crypto.ts} +0 -0
- /package/dist/{api-client.js → billing/api-client.js} +0 -0
- /package/dist/{gateway-url.js → billing/gateway-url.js} +0 -0
- /package/dist/{inbound-user-tracker.js → billing/inbound-user-tracker.js} +0 -0
- /package/dist/{relay.js → billing/relay.js} +0 -0
- /package/dist/{crypto.js → crypto/crypto.js} +0 -0
- /package/dist/{vault-crypto.js → crypto/vault-crypto.js} +0 -0
- /package/dist/{embedder-cache.js → embedding/embedder-cache.js} +0 -0
- /package/dist/{embedder-loader.js → embedding/embedder-loader.js} +0 -0
- /package/dist/{embedder-network.js → embedding/embedder-network.js} +0 -0
- /package/dist/{embedding.js → embedding/embedding.js} +0 -0
- /package/dist/{lsh.js → embedding/lsh.js} +0 -0
- /package/dist/{import-state-manager.js → import/import-state-manager.js} +0 -0
- /package/dist/{llm-profile-reader.js → llm/llm-profile-reader.js} +0 -0
- /package/dist/{native-memory.js → memory/native-memory.js} +0 -0
- /package/dist/{tool-gating.js → memory/tool-gating.js} +0 -0
- /package/dist/{tools.js → memory/tools.js} +0 -0
- /package/dist/{generate-mnemonic.js → pairing/generate-mnemonic.js} +0 -0
- /package/dist/{pair-cli.js → pairing/pair-cli.js} +0 -0
- /package/dist/{pair-page.js → pairing/pair-page.js} +0 -0
- /package/dist/{pair-qr.js → pairing/pair-qr.js} +0 -0
- /package/dist/{restart-auth.js → pairing/restart-auth.js} +0 -0
- /package/dist/{qa-bug-report.js → setup/qa-bug-report.js} +0 -0
- /package/dist/{skill-register.js → setup/skill-register.js} +0 -0
- /package/dist/{trajectory-poller.js → subgraph/trajectory-poller.js} +0 -0
- /package/{embedder-cache.ts → embedding/embedder-cache.ts} +0 -0
- /package/{embedder-loader.ts → embedding/embedder-loader.ts} +0 -0
- /package/{embedder-network.ts → embedding/embedder-network.ts} +0 -0
- /package/{embedding.ts → embedding/embedding.ts} +0 -0
- /package/{lsh.ts → embedding/lsh.ts} +0 -0
- /package/{import-state-manager.ts → import/import-state-manager.ts} +0 -0
- /package/{llm-profile-reader.ts → llm/llm-profile-reader.ts} +0 -0
- /package/{native-memory.ts → memory/native-memory.ts} +0 -0
- /package/{tools.ts → memory/tools.ts} +0 -0
- /package/{generate-mnemonic.ts → pairing/generate-mnemonic.ts} +0 -0
- /package/{pair-cli.ts → pairing/pair-cli.ts} +0 -0
- /package/{pair-page.ts → pairing/pair-page.ts} +0 -0
- /package/{pair-qr.ts → pairing/pair-qr.ts} +0 -0
- /package/{restart-auth.ts → pairing/restart-auth.ts} +0 -0
- /package/{qa-bug-report.ts → setup/qa-bug-report.ts} +0 -0
- /package/{skill-register.ts → setup/skill-register.ts} +0 -0
- /package/{trajectory-poller.ts → subgraph/trajectory-poller.ts} +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Pure formatting / resolution helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// Extracted from index.ts. Every function here is a pure computation over its
|
|
6
|
+
// arguments (plus imported constants / stateless helpers) — none reads the
|
|
7
|
+
// plugin's mutable session state, so they carve cleanly out of the composing
|
|
8
|
+
// entry point. No environment-variable reads live here (they stay in config.ts /
|
|
9
|
+
// entry.ts per the OpenClaw env-harvesting scanner rule).
|
|
10
|
+
import { encrypt, decrypt } from '../crypto/crypto.js';
|
|
11
|
+
import { detectGatewayHost } from '../billing/gateway-url.js';
|
|
12
|
+
import { isRunningInDocker } from '../fs-helpers.js';
|
|
13
|
+
import { readBillingCache } from '../billing/billing-cache.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Human-friendly error messages
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Translate technical error messages from the on-chain submission pipeline
|
|
19
|
+
* into user-friendly messages. The original technical details are still
|
|
20
|
+
* logged via api.logger — this only affects what the agent sees.
|
|
21
|
+
*/
|
|
22
|
+
export function humanizeError(rawMessage) {
|
|
23
|
+
if (rawMessage.includes('AA23')) {
|
|
24
|
+
return 'Memory storage temporarily unavailable. Will retry next time.';
|
|
25
|
+
}
|
|
26
|
+
if (rawMessage.includes('AA10')) {
|
|
27
|
+
return 'Please wait a moment before storing more memories.';
|
|
28
|
+
}
|
|
29
|
+
if (rawMessage.includes('AA25')) {
|
|
30
|
+
return 'Memory storage busy. Will retry.';
|
|
31
|
+
}
|
|
32
|
+
if (rawMessage.includes('pm_sponsorUserOperation')) {
|
|
33
|
+
return 'Memory storage service temporarily unavailable.';
|
|
34
|
+
}
|
|
35
|
+
if (/Relay returned HTTP\s*404/.test(rawMessage)) {
|
|
36
|
+
return 'Memory service is temporarily offline.';
|
|
37
|
+
}
|
|
38
|
+
if (/Relay returned HTTP\s*5\d\d/.test(rawMessage)) {
|
|
39
|
+
return 'Memory service encountered a temporary error. Will retry next time.';
|
|
40
|
+
}
|
|
41
|
+
// Pass through non-technical messages as-is.
|
|
42
|
+
return rawMessage;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// 3.3.0 — pairing URL resolution
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Build the full pairing URL (including `#pk=` fragment) for a fresh
|
|
49
|
+
* pairing session. Pulls gateway config from `api.config.gateway`.
|
|
50
|
+
*
|
|
51
|
+
* Resolution order (3.3.1 — six-layer cascade):
|
|
52
|
+
* 1. `plugins.entries.totalreclaw.config.publicUrl` — explicit override
|
|
53
|
+
* 2. `gateway.remote.url` — OpenClaw's own remote-gateway URL
|
|
54
|
+
* 3. `gateway.bind === 'custom'` + `gateway.customBindHost` + port
|
|
55
|
+
* 4. Tailscale auto-detect — `tailscale status --json` → `https://<MagicDNS>`
|
|
56
|
+
* (assumes `tailscale serve` proxies to the gateway port on 443)
|
|
57
|
+
* 5. LAN auto-detect — first non-loopback, non-virtual IPv4 interface.
|
|
58
|
+
* Emits a warning: "only works on the same network".
|
|
59
|
+
* 6. Fallback `http://localhost:<port>` — warns with a pointer to
|
|
60
|
+
* configure `plugins.entries.totalreclaw.config.publicUrl`.
|
|
61
|
+
*
|
|
62
|
+
* Always returns a working URL string; never throws. The caller (CLI or
|
|
63
|
+
* JSON output) prints whatever we give it.
|
|
64
|
+
*/
|
|
65
|
+
export function buildPairingUrl(api, session) {
|
|
66
|
+
const cfg = api.config;
|
|
67
|
+
const pluginCfg = (api.pluginConfig ?? {});
|
|
68
|
+
const tlsEnabled = cfg?.gateway?.tls?.enabled === true;
|
|
69
|
+
const scheme = tlsEnabled ? 'https' : 'http';
|
|
70
|
+
const port = cfg?.gateway?.port ?? 18789;
|
|
71
|
+
let base;
|
|
72
|
+
// Layer 1 — explicit user override
|
|
73
|
+
if (typeof pluginCfg.publicUrl === 'string' && pluginCfg.publicUrl.trim()) {
|
|
74
|
+
base = pluginCfg.publicUrl.replace(/\/+$/, '');
|
|
75
|
+
base = base.replace(/^wss:\/\//i, 'https://').replace(/^ws:\/\//i, 'http://');
|
|
76
|
+
}
|
|
77
|
+
// Layer 2 — OpenClaw gateway remote URL
|
|
78
|
+
else if (typeof cfg?.gateway?.remote?.url === 'string' && cfg.gateway.remote.url.trim()) {
|
|
79
|
+
base = cfg.gateway.remote.url.trim().replace(/\/+$/, '');
|
|
80
|
+
base = base.replace(/^wss:\/\//i, 'https://').replace(/^ws:\/\//i, 'http://');
|
|
81
|
+
}
|
|
82
|
+
// Layer 3 — gateway.bind = custom + explicit customBindHost
|
|
83
|
+
else if (cfg?.gateway?.bind === 'custom' && cfg.gateway.customBindHost) {
|
|
84
|
+
base = `${scheme}://${cfg.gateway.customBindHost}:${port}`;
|
|
85
|
+
}
|
|
86
|
+
// Layers 4 + 5 — auto-detect via gateway-url helper (Tailscale CGNAT, then LAN)
|
|
87
|
+
else {
|
|
88
|
+
let detected = null;
|
|
89
|
+
// issue #110 fix 4 — pass `isDocker` so LAN detection skips
|
|
90
|
+
// 172.16/12 bridge IPs that no external browser can reach.
|
|
91
|
+
let isDocker = false;
|
|
92
|
+
try {
|
|
93
|
+
isDocker = isRunningInDocker();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Defensive: never block URL building on Docker sniff errors.
|
|
97
|
+
isDocker = false;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
detected = detectGatewayHost({ isDocker });
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
api.logger.warn(`TotalReclaw: host autodetect crashed: ${err instanceof Error ? err.message : String(err)} — falling back to localhost`);
|
|
104
|
+
}
|
|
105
|
+
if (detected?.kind === 'tailscale') {
|
|
106
|
+
// 3.3.1-rc.2: we surface the raw Tailscale CGNAT IP because passive
|
|
107
|
+
// NIC detection (no subprocess) cannot resolve the MagicDNS name.
|
|
108
|
+
// Caller can override via `publicUrl` for a proper https://<magicdns>.
|
|
109
|
+
// The IP + port URL still works inside the tailnet (peers can reach
|
|
110
|
+
// each other by CGNAT IP directly). TLS defaults to the gateway's
|
|
111
|
+
// own config because we no longer assume `tailscale serve`.
|
|
112
|
+
base = `${scheme}://${detected.host}:${port}`;
|
|
113
|
+
api.logger.warn(`TotalReclaw: pairing URL using Tailscale CGNAT IP ${detected.host}:${port} — ` +
|
|
114
|
+
detected.note);
|
|
115
|
+
}
|
|
116
|
+
else if (detected?.kind === 'lan') {
|
|
117
|
+
base = `${scheme}://${detected.host}:${port}`;
|
|
118
|
+
api.logger.warn(`TotalReclaw: pairing URL using LAN host ${detected.host}:${port} — ` +
|
|
119
|
+
`this URL only works from the same network. ` +
|
|
120
|
+
`Set plugins.entries.totalreclaw.config.publicUrl for remote access.`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Layer 6 — localhost fallback (or Docker-aware relay-pointer warning)
|
|
124
|
+
const bind = cfg?.gateway?.bind;
|
|
125
|
+
if (isDocker) {
|
|
126
|
+
// issue #110 fix 4: inside Docker the LAN IP is container-internal
|
|
127
|
+
// and useless. Loopback localhost only works for `docker exec`
|
|
128
|
+
// tests. The CORRECT pair URL for Docker is the relay-brokered
|
|
129
|
+
// path served by `tr pair` / the `/plugin/totalreclaw/pair/*` HTTP
|
|
130
|
+
// routes (CONFIG.pairMode === 'relay' since rc.11). The CLI-only
|
|
131
|
+
// path here cannot mint a relay session synchronously (the relay
|
|
132
|
+
// handshake needs a WS round-trip), so we emit the loopback URL
|
|
133
|
+
// with a LOUD warning pointing the operator at the pair CLI /
|
|
134
|
+
// publicUrl override.
|
|
135
|
+
api.logger.warn(`TotalReclaw: Docker container detected — pairing URL falling back to ` +
|
|
136
|
+
`http://localhost:${port}, which is unreachable from the host browser. ` +
|
|
137
|
+
`Run \`tr pair --url-pin\` (or \`openclaw totalreclaw pair generate --url-pin-only\`) ` +
|
|
138
|
+
`on the gateway host to mint a relay-brokered pair URL that reaches the host browser, ` +
|
|
139
|
+
`OR set plugins.entries.totalreclaw.config.publicUrl ` +
|
|
140
|
+
`to your gateway's host-reachable URL (e.g. http://<host-ip>:${port} when the ` +
|
|
141
|
+
`Docker port is published). Setting TOTALRECLAW_PAIR_MODE=relay is the default; ` +
|
|
142
|
+
`air-gapped operators on TOTALRECLAW_PAIR_MODE=local must publish a port + set publicUrl.`);
|
|
143
|
+
}
|
|
144
|
+
else if (bind === 'lan' || bind === 'tailnet') {
|
|
145
|
+
api.logger.warn(`TotalReclaw: pairing URL falling back to localhost because gateway.bind=${bind} could not be autodetected. ` +
|
|
146
|
+
'Set plugins.entries.totalreclaw.config.publicUrl to override.');
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
api.logger.warn(`TotalReclaw: pairing URL fell back to http://localhost:${port} — this URL only works on this machine. ` +
|
|
150
|
+
`Configure plugins.entries.totalreclaw.config.publicUrl for remote access.`);
|
|
151
|
+
}
|
|
152
|
+
base = `${scheme}://localhost:${port}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return `${base}/plugin/totalreclaw/pair/finish?sid=${encodeURIComponent(session.sid)}#pk=${encodeURIComponent(session.pkGatewayB64)}`;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolve whether this plugin is running on a `local` or `remote` gateway.
|
|
159
|
+
*
|
|
160
|
+
* Follows the same config surface `buildPairingUrl` uses:
|
|
161
|
+
* - `pluginConfig.publicUrl` set + non-localhost → remote
|
|
162
|
+
* - `gateway.remote.url` set + non-localhost → remote
|
|
163
|
+
* - `gateway.bind === 'lan' | 'tailnet' | 'custom'` → remote
|
|
164
|
+
* - anything else → local
|
|
165
|
+
*
|
|
166
|
+
* We treat a `publicUrl` or `remote.url` that points at `localhost` /
|
|
167
|
+
* `127.*` as local because that is what a dev-loopback override looks like;
|
|
168
|
+
* no one publishes a remote QR pairing for localhost.
|
|
169
|
+
*/
|
|
170
|
+
export function resolveGatewayMode(api) {
|
|
171
|
+
const cfg = api.config;
|
|
172
|
+
const pluginCfg = (api.pluginConfig ?? {});
|
|
173
|
+
const looksLocal = (url) => {
|
|
174
|
+
if (!url)
|
|
175
|
+
return true;
|
|
176
|
+
const u = url.trim().toLowerCase();
|
|
177
|
+
if (u === '')
|
|
178
|
+
return true;
|
|
179
|
+
return /^(?:wss?:\/\/|https?:\/\/)?(?:localhost|127\.|0\.0\.0\.0)/.test(u);
|
|
180
|
+
};
|
|
181
|
+
if (typeof pluginCfg.publicUrl === 'string' && !looksLocal(pluginCfg.publicUrl)) {
|
|
182
|
+
return 'remote';
|
|
183
|
+
}
|
|
184
|
+
const remoteUrl = cfg?.gateway?.remote?.url;
|
|
185
|
+
if (typeof remoteUrl === 'string' && !looksLocal(remoteUrl)) {
|
|
186
|
+
return 'remote';
|
|
187
|
+
}
|
|
188
|
+
const bind = cfg?.gateway?.bind;
|
|
189
|
+
if (bind === 'lan' || bind === 'tailnet' || bind === 'custom') {
|
|
190
|
+
return 'remote';
|
|
191
|
+
}
|
|
192
|
+
return 'local';
|
|
193
|
+
}
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Candidate-pool sizing
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
/**
|
|
198
|
+
* Compute the candidate pool size from a fact count.
|
|
199
|
+
*
|
|
200
|
+
* Server-side config takes priority (from billing cache), then local fallback.
|
|
201
|
+
* The server computes the optimal pool based on vault size and tier caps.
|
|
202
|
+
*
|
|
203
|
+
* Local fallback formula: pool = min(max(factCount * 3, 400), 5000)
|
|
204
|
+
* - At least 400 candidates (even for tiny vaults)
|
|
205
|
+
* - At most 5000 candidates (to bound decryption + reranking cost)
|
|
206
|
+
* - 3x fact count in between
|
|
207
|
+
*/
|
|
208
|
+
export function computeCandidatePool(factCount) {
|
|
209
|
+
const cache = readBillingCache();
|
|
210
|
+
if (cache?.features?.max_candidate_pool != null)
|
|
211
|
+
return cache.features.max_candidate_pool;
|
|
212
|
+
// Fallback to local formula if no server config
|
|
213
|
+
return Math.min(Math.max(factCount * 3, 400), 5000);
|
|
214
|
+
}
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Encrypt/decrypt hex helpers (on-chain blob encoding)
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
export function encryptToHex(plaintext, key) {
|
|
219
|
+
const b64 = encrypt(plaintext, key);
|
|
220
|
+
return Buffer.from(b64, 'base64').toString('hex');
|
|
221
|
+
}
|
|
222
|
+
export function decryptFromHex(hexBlob, key) {
|
|
223
|
+
const hex = hexBlob.startsWith('0x') ? hexBlob.slice(2) : hexBlob;
|
|
224
|
+
const b64 = Buffer.from(hex, 'hex').toString('base64');
|
|
225
|
+
return decrypt(b64, key);
|
|
226
|
+
}
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Lexical scoring / relative time
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
export function textScore(query, docText) {
|
|
231
|
+
const queryWords = new Set(query.toLowerCase().split(/\s+/).filter((w) => w.length >= 2));
|
|
232
|
+
const docWords = docText.toLowerCase().split(/\s+/);
|
|
233
|
+
let score = 0;
|
|
234
|
+
for (const word of docWords) {
|
|
235
|
+
if (queryWords.has(word))
|
|
236
|
+
score++;
|
|
237
|
+
}
|
|
238
|
+
return score;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Format a relative time string (e.g. "2 hours ago").
|
|
242
|
+
*/
|
|
243
|
+
export function relativeTime(isoOrMs) {
|
|
244
|
+
const ms = typeof isoOrMs === 'number' ? isoOrMs : new Date(isoOrMs).getTime();
|
|
245
|
+
const diffMs = Date.now() - ms;
|
|
246
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
247
|
+
if (seconds < 60)
|
|
248
|
+
return 'just now';
|
|
249
|
+
const minutes = Math.floor(seconds / 60);
|
|
250
|
+
if (minutes < 60)
|
|
251
|
+
return `${minutes}m ago`;
|
|
252
|
+
const hours = Math.floor(minutes / 60);
|
|
253
|
+
if (hours < 24)
|
|
254
|
+
return `${hours}h ago`;
|
|
255
|
+
const days = Math.floor(hours / 24);
|
|
256
|
+
return `${days}d ago`;
|
|
257
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Shared plugin runtime types
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// Extracted from index.ts so the composing entry point AND the domain modules
|
|
6
|
+
// carved out of it (runtime/format-helpers, import/import-runtime, …) can share
|
|
7
|
+
// one definition of the OpenClaw plugin-API surface and the internal row shapes
|
|
8
|
+
// without a circular import back through index.ts.
|
|
9
|
+
//
|
|
10
|
+
// Nothing here reads the environment or performs I/O — pure type declarations.
|
|
11
|
+
export {};
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { createRequire } from 'node:module';
|
|
29
29
|
import { getSubgraphConfig } from './subgraph-store.js';
|
|
30
|
-
import { buildRelayHeaders } from '
|
|
30
|
+
import { buildRelayHeaders } from '../billing/relay-headers.js';
|
|
31
31
|
const requireWasm = createRequire(import.meta.url);
|
|
32
32
|
let _wasm = null;
|
|
33
33
|
function getWasm() {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* - Large datasets: 1 + N batches + pagination queries
|
|
21
21
|
*/
|
|
22
22
|
import { getSubgraphConfig } from './subgraph-store.js';
|
|
23
|
-
import { CONFIG } from '
|
|
24
|
-
import { buildRelayHeaders } from '
|
|
25
|
-
import { relayFetch } from '
|
|
23
|
+
import { CONFIG } from '../config.js';
|
|
24
|
+
import { buildRelayHeaders } from '../billing/relay-headers.js';
|
|
25
|
+
import { relayFetch } from '../billing/relay.js';
|
|
26
26
|
/** Batch size for Phase 2 split queries. */
|
|
27
27
|
const TRAPDOOR_BATCH_SIZE = CONFIG.trapdoorBatchSize;
|
|
28
28
|
/** Graph Studio / Graph Network hard limit on `first` argument. */
|
|
@@ -126,8 +126,8 @@ function collectFacts(entries, allResults) {
|
|
|
126
126
|
async function paginateChunk(subgraphUrl, chunk, owner, allResults, maxCandidates, authKeyHex) {
|
|
127
127
|
let lastId = '';
|
|
128
128
|
while (allResults.size < maxCandidates) {
|
|
129
|
-
const
|
|
130
|
-
const entries =
|
|
129
|
+
const subgraphResponse = await gqlQuery(subgraphUrl, PAGINATE_QUERY, { trapdoors: chunk, owner, first: PAGE_SIZE, lastId }, authKeyHex);
|
|
130
|
+
const entries = subgraphResponse?.blindIndexes ?? [];
|
|
131
131
|
if (entries.length === 0)
|
|
132
132
|
break;
|
|
133
133
|
collectFacts(entries, allResults);
|
|
@@ -175,8 +175,8 @@ export async function searchSubgraph(owner, trapdoors, maxCandidates, authKeyHex
|
|
|
175
175
|
chunks.push(trapdoors.slice(i, i + TRAPDOOR_BATCH_SIZE));
|
|
176
176
|
}
|
|
177
177
|
const batchResults = await Promise.all(chunks.map(async (chunk) => {
|
|
178
|
-
const
|
|
179
|
-
return { chunk, entries:
|
|
178
|
+
const subgraphResponse = await gqlQuery(subgraphUrl, SEARCH_QUERY, { trapdoors: chunk, owner, first: PAGE_SIZE }, authKeyHex);
|
|
179
|
+
return { chunk, entries: subgraphResponse?.blindIndexes ?? [] };
|
|
180
180
|
}));
|
|
181
181
|
const saturatedChunks = [];
|
|
182
182
|
for (const { chunk, entries } of batchResults) {
|
|
@@ -224,8 +224,8 @@ export async function searchSubgraphBroadened(owner, maxCandidates, authKeyHex)
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
`;
|
|
227
|
-
const
|
|
228
|
-
return (
|
|
227
|
+
const subgraphResponse = await gqlQuery(subgraphUrl, query, { owner, first: Math.min(maxCandidates, 1000) }, authKeyHex);
|
|
228
|
+
return (subgraphResponse?.facts ?? []).filter(f => f.isActive !== false);
|
|
229
229
|
}
|
|
230
230
|
/**
|
|
231
231
|
* Fetch a single fact by its client-generated UUID.
|
|
@@ -256,8 +256,8 @@ export async function fetchFactById(owner, factId, authKeyHex) {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
`;
|
|
259
|
-
const
|
|
260
|
-
const fact =
|
|
259
|
+
const subgraphResponse = await gqlQuery(subgraphUrl, query, { id: factId }, authKeyHex);
|
|
260
|
+
const fact = subgraphResponse?.fact;
|
|
261
261
|
if (!fact)
|
|
262
262
|
return null;
|
|
263
263
|
if (fact.isActive === false)
|
|
@@ -282,9 +282,9 @@ export async function getSubgraphFactCount(owner, authKeyHex) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
`;
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
const count = parseInt(
|
|
285
|
+
const subgraphResponse = await gqlQuery(subgraphUrl, query, {}, authKeyHex);
|
|
286
|
+
if (subgraphResponse?.globalStates && subgraphResponse.globalStates.length > 0) {
|
|
287
|
+
const count = parseInt(subgraphResponse.globalStates[0].totalFacts, 10);
|
|
288
288
|
return isNaN(count) ? 0 : count;
|
|
289
289
|
}
|
|
290
290
|
return 0;
|
|
@@ -21,10 +21,10 @@ function getWasm() {
|
|
|
21
21
|
_wasm = requireWasm('@totalreclaw/core');
|
|
22
22
|
return _wasm;
|
|
23
23
|
}
|
|
24
|
-
import { CONFIG, getDataEdgeAddressOverride } from '
|
|
25
|
-
import { buildRelayHeaders } from '
|
|
26
|
-
import { rpcRequest, rpcWithRetry } from '
|
|
27
|
-
import { signUserOp } from '
|
|
24
|
+
import { CONFIG, getDataEdgeAddressOverride } from '../config.js';
|
|
25
|
+
import { buildRelayHeaders } from '../billing/relay-headers.js';
|
|
26
|
+
import { rpcRequest, rpcWithRetry } from '../billing/relay.js';
|
|
27
|
+
import { signUserOp } from '../crypto/vault-crypto.js';
|
|
28
28
|
/** Legacy protobuf wrapper schema version (v0/v1-binary inner blob). */
|
|
29
29
|
export const PROTOBUF_VERSION_LEGACY = 3;
|
|
30
30
|
/** Memory Taxonomy v1 protobuf wrapper schema version. */
|
|
@@ -457,7 +457,7 @@ async function submitFactOnChainLocked(protobufPayload, config, eoa, sender) {
|
|
|
457
457
|
break;
|
|
458
458
|
}
|
|
459
459
|
catch (err) {
|
|
460
|
-
const msg = err
|
|
460
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
461
461
|
// AA10 "sender already constructed" or AA25 invalid nonce → retry
|
|
462
462
|
if (/AA25|AA10|invalid account nonce|already being processed/i.test(msg)) {
|
|
463
463
|
lastErr = err;
|
|
@@ -658,7 +658,7 @@ async function submitFactBatchOnChainLocked(protobufPayloads, config, eoa, sende
|
|
|
658
658
|
break;
|
|
659
659
|
}
|
|
660
660
|
catch (err) {
|
|
661
|
-
const msg = err
|
|
661
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
662
662
|
// AA10 "sender already constructed" or AA25 invalid nonce → retry
|
|
663
663
|
if (/AA25|AA10|invalid account nonce|already being processed/i.test(msg)) {
|
|
664
664
|
lastErr = err;
|
|
@@ -23,12 +23,16 @@
|
|
|
23
23
|
* Compute cosine similarity between two vectors.
|
|
24
24
|
*
|
|
25
25
|
* Returns dot(a, b) / (||a|| * ||b||).
|
|
26
|
-
* Returns 0 if either vector has zero magnitude (avoids division by zero)
|
|
26
|
+
* Returns 0 if either vector has zero magnitude (avoids division by zero),
|
|
27
|
+
* or if the vectors differ in length — matching the authoritative core impl
|
|
28
|
+
* `cosine_similarity_f32` (rust/totalreclaw-core), which rejects mismatched
|
|
29
|
+
* dimensions rather than truncating to the shorter length. Keeping this local
|
|
30
|
+
* fallback byte-for-byte with core preserves cross-runtime rerank parity.
|
|
27
31
|
*/
|
|
28
32
|
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
29
|
-
if (a.length
|
|
33
|
+
if (a.length !== b.length || a.length === 0) return 0;
|
|
30
34
|
|
|
31
|
-
const len =
|
|
35
|
+
const len = a.length;
|
|
32
36
|
let dot = 0;
|
|
33
37
|
let normA = 0;
|
|
34
38
|
let normB = 0;
|
|
@@ -301,9 +305,14 @@ const SOURCE_WEIGHTS: Record<string, number> = {
|
|
|
301
305
|
const LEGACY_FALLBACK_WEIGHT = 0.85;
|
|
302
306
|
|
|
303
307
|
export function getSourceWeight(source: string | undefined): number {
|
|
308
|
+
// Missing source → legacy fallback (0.85), matching core's None path.
|
|
304
309
|
if (!source) return LEGACY_FALLBACK_WEIGHT;
|
|
305
310
|
const w = SOURCE_WEIGHTS[source.toLowerCase()];
|
|
306
|
-
|
|
311
|
+
// An unknown NON-EMPTY source string mirrors core's
|
|
312
|
+
// `MemorySource::from_str_lossy`, whose catch-all arm maps any unrecognized
|
|
313
|
+
// token to `user-inferred` (rust/totalreclaw-core/src/claims.rs) — so an
|
|
314
|
+
// unknown string weighs the same as user-inferred, NOT the legacy fallback.
|
|
315
|
+
return w ?? SOURCE_WEIGHTS['user-inferred'];
|
|
307
316
|
}
|
|
308
317
|
|
|
309
318
|
// ---------------------------------------------------------------------------
|