@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
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: totalreclaw
|
|
3
3
|
description: "End-to-end encrypted, decentralized memory for OpenClaw. A native kind:memory provider — recall is automatic via memory_search/memory_get, and facts are captured in the background. Trigger on 'install TotalReclaw', 'set up TotalReclaw', 'restore my recovery phrase', any recall request ('what do you remember about me', 'what's my X'), AND any explicit remember request ('remember X', 'save X')."
|
|
4
|
-
version: 3.3.
|
|
4
|
+
version: 3.3.13
|
|
5
5
|
author: TotalReclaw Team
|
|
6
6
|
license: MIT
|
|
7
7
|
homepage: https://totalreclaw.xyz
|
|
@@ -45,24 +45,41 @@ The 12-word recovery phrase MUST NEVER enter chat or the agent's LLM context. Th
|
|
|
45
45
|
```
|
|
46
46
|
openclaw plugins install @totalreclaw/totalreclaw
|
|
47
47
|
openclaw skills install totalreclaw
|
|
48
|
+
openclaw plugins enable totalreclaw
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
Published on npm + ClawHub (`p-diogo`, GitHub-verified). For a release candidate, pin with `@rc`; stable resolves from `latest`. The plugin self-applies
|
|
51
|
+
Published on npm + ClawHub (`p-diogo`, GitHub-verified). For a release candidate, pin with `@rc`; stable resolves from `latest`. Run `openclaw plugins enable totalreclaw` right after install, always — it binds the memory slot. On a clean install that's a no-op; on a **reinstall over a previous version** it's what stops the plugin from landing `Status: disabled` with the slot stuck at `memory-core` (a disabled plugin never registers its memory tools or pair routes, so pairing later fails with a 502). The plugin self-applies the rest of the gateway config it needs on load and auto-reloads (see Restart below) — no manual gateway restart.
|
|
52
|
+
|
|
53
|
+
**Reinstall recovery.** If install reports `already exists` / `delete it first`, do **not** add `--force` (it writes a broken `version=None` record). Clean the leftover package dir and reinstall — this never touches your credentials, which live under `~/.totalreclaw/`:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
openclaw plugins uninstall totalreclaw
|
|
57
|
+
rm -rf ~/.openclaw/npm/projects/*totalreclaw-totalreclaw*
|
|
58
|
+
openclaw plugins install @totalreclaw/totalreclaw
|
|
59
|
+
openclaw plugins enable totalreclaw
|
|
60
|
+
```
|
|
51
61
|
|
|
52
62
|
## Setup (pair — user-initiated QR)
|
|
53
63
|
|
|
54
|
-
Pairing is a deliberate, user-initiated
|
|
64
|
+
Pairing is a deliberate, user-initiated flow (the plugin does **not** auto-pair on load). When credentials don't exist:
|
|
65
|
+
|
|
66
|
+
1. Mint a pair session via the plugin's **in-process HTTP route** — the gateway process owns the relay pair WebSocket, so it survives until the user completes:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
curl -s http://localhost:18789/plugin/totalreclaw/pair/init
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
→ returns `{"url":"https://…/pair/p/<id>#pk=…","pin":"123456","expires_at_ms":…}`.
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
2. Surface the **url** and **pin** to the user verbatim (read from the JSON; never invent values): "Open `<url>` in your browser, enter PIN `<pin>`, and generate or paste your 12-word recovery phrase. Reply done once it's sealed."
|
|
58
|
-
3. The user completes in the browser (phrase is generated/imported browser-side, encrypted,
|
|
74
|
+
**Do NOT use `tr pair --json` for this.** The CLI holds the pair WebSocket in a subprocess that OpenClaw's ~30s shell-tool timeout kills — the WS then drops, and when the user submits their phrase the relay returns a **502** (`gateway_disconnected`). The in-process route above avoids that entirely. Only fall back to the CLI if the route is unreachable (no `curl`, or a pre-3.3.14 plugin), and then it MUST be detached so the WS outlives the shell exec: `setsid -f node "$TR_CLI" pair --json < /dev/null` — never run `tr pair` in the foreground.
|
|
75
|
+
2. Surface the **url** and **pin** to the user verbatim (read from the JSON; never invent values): "Open `<url>` in your browser, enter PIN `<pin>`, and generate or paste your 12-word recovery phrase. Reply done once it's sealed." Emit the URL as plain text on its own line — the `#pk=` fragment breaks if wrapped in backticks/markdown.
|
|
76
|
+
3. The user completes in the browser (phrase is generated/imported browser-side, encrypted, uploaded to the relay — never touches this chat). On completion `~/.totalreclaw/credentials.json` is written.
|
|
59
77
|
4. Confirm: `✓ TotalReclaw set up.` (If credentials already exist when first asked: `✓ TotalReclaw is already set up.`)
|
|
60
78
|
|
|
61
79
|
Pair is UNCONDITIONAL when no credentials — the user installing/requesting setup IS the consent; do not add a second consent gate. `tr --help` lists every subcommand.
|
|
62
80
|
|
|
63
81
|
```bash
|
|
64
|
-
TR_CLI="$(ls -d ~/.openclaw/npm/node_modules/@totalreclaw/totalreclaw/dist/tr-cli.js ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js 2>/dev/null | head -1)"
|
|
65
|
-
node "$TR_CLI" pair --json # start/re-pair
|
|
82
|
+
TR_CLI="$(ls -d ~/.openclaw/npm/node_modules/@totalreclaw/totalreclaw/dist/cli/tr-cli.js ~/.openclaw/extensions/totalreclaw/dist/cli/tr-cli.js 2>/dev/null | head -1)"
|
|
66
83
|
node "$TR_CLI" status --json # {"version":"…","paired":true,…}
|
|
67
84
|
```
|
|
68
85
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import fs from 'node:fs';
|
|
29
29
|
import path from 'node:path';
|
|
30
|
-
import { CONFIG, setChainIdOverride, setDataEdgeAddressOverride } from '
|
|
30
|
+
import { CONFIG, setChainIdOverride, setDataEdgeAddressOverride } from '../config.js';
|
|
31
31
|
|
|
32
32
|
/** A plausible EVM address — anything else from billing is ignored (#460). */
|
|
33
33
|
const DATA_EDGE_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* pre-derived auth-key + wallet-address + encryption-key from the caller.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { CONFIG } from '
|
|
17
|
-
import { buildRelayHeaders } from '
|
|
18
|
-
import { decrypt } from '
|
|
16
|
+
import { CONFIG } from '../config.js';
|
|
17
|
+
import { buildRelayHeaders } from '../billing/relay-headers.js';
|
|
18
|
+
import { decrypt } from '../crypto/crypto.js';
|
|
19
19
|
|
|
20
20
|
/** Decode a hex blob written by submitFactBatchOnChain back to plaintext. */
|
|
21
21
|
function fromHexBlob(hexBlob: string, encryptionKey: Buffer): string {
|
|
@@ -104,8 +104,8 @@ export async function exportAllFacts(
|
|
|
104
104
|
? { owner: walletAddress, first: PAGE_SIZE, lastId }
|
|
105
105
|
: { owner: walletAddress, first: PAGE_SIZE };
|
|
106
106
|
|
|
107
|
-
const
|
|
108
|
-
const facts =
|
|
107
|
+
const subgraphResponse = await gql<{ facts?: FactRow[] }>(query, variables);
|
|
108
|
+
const facts = subgraphResponse?.facts ?? [];
|
|
109
109
|
if (facts.length === 0) break;
|
|
110
110
|
|
|
111
111
|
for (const f of facts) {
|
|
@@ -31,23 +31,23 @@
|
|
|
31
31
|
* --json flag: all agent-facing CLI calls MUST use --json for clean machine-parseable output.
|
|
32
32
|
* Plain text mode is for direct user CLI use only.
|
|
33
33
|
*
|
|
34
|
-
* Install: wired via package.json `bin.tr` → dist/tr-cli.js
|
|
35
|
-
* Usage from container: `docker exec tr-openclaw node ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js status --json`
|
|
34
|
+
* Install: wired via package.json `bin.tr` → dist/cli/tr-cli.js
|
|
35
|
+
* Usage from container: `docker exec tr-openclaw node ~/.openclaw/extensions/totalreclaw/dist/cli/tr-cli.js status --json`
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
import { randomUUID } from 'node:crypto';
|
|
39
39
|
|
|
40
|
-
import { CONFIG, setRecoveryPhraseOverride } from '
|
|
41
|
-
import { loadCredentialsJson } from '
|
|
42
|
-
import { printStatus } from '
|
|
40
|
+
import { CONFIG, setRecoveryPhraseOverride } from '../config.js';
|
|
41
|
+
import { loadCredentialsJson } from '../fs-helpers.js';
|
|
42
|
+
import { printStatus } from '../pairing/onboarding-cli.js';
|
|
43
43
|
import {
|
|
44
44
|
deriveKeys,
|
|
45
45
|
computeAuthKeyHash,
|
|
46
46
|
encrypt,
|
|
47
47
|
generateBlindIndices,
|
|
48
48
|
generateContentFingerprint,
|
|
49
|
-
} from '
|
|
50
|
-
import { createApiClient } from '
|
|
49
|
+
} from '../crypto/crypto.js';
|
|
50
|
+
import { createApiClient } from '../billing/api-client.js';
|
|
51
51
|
import {
|
|
52
52
|
encodeFactProtobuf,
|
|
53
53
|
submitFactBatchOnChain,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
getSubgraphConfig,
|
|
56
56
|
PROTOBUF_VERSION_V4,
|
|
57
57
|
type FactPayload,
|
|
58
|
-
} from '
|
|
58
|
+
} from '../subgraph/subgraph-store.js';
|
|
59
59
|
import { exportAllFacts } from './tr-cli-export-helper.js';
|
|
60
60
|
|
|
61
61
|
// ---------------------------------------------------------------------------
|
|
@@ -68,7 +68,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
|
|
|
68
68
|
// Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
|
|
69
69
|
// Do not edit by hand — running tests will catch drift but the publish workflow
|
|
70
70
|
// rewrites this constant at the start of every npm/ClawHub publish.
|
|
71
|
-
const PLUGIN_VERSION = '3.3.
|
|
71
|
+
const PLUGIN_VERSION = '3.3.13';
|
|
72
72
|
|
|
73
73
|
function die(msg: string, code = 1): never {
|
|
74
74
|
process.stderr.write(`tr: ${msg}\n`);
|
|
@@ -124,7 +124,7 @@ interface CliContext {
|
|
|
124
124
|
async function buildContext(): Promise<CliContext> {
|
|
125
125
|
const creds = loadCredentialsJson(CREDENTIALS_PATH);
|
|
126
126
|
if (!creds) {
|
|
127
|
-
die('TotalReclaw is not set up. Run: node ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js pair --json');
|
|
127
|
+
die('TotalReclaw is not set up. Run: node ~/.openclaw/extensions/totalreclaw/dist/cli/tr-cli.js pair --json');
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const mnemonic =
|
|
@@ -244,8 +244,8 @@ async function cmdPair(args: string[]): Promise<void> {
|
|
|
244
244
|
// Phrase-safety: pair-cli-relay.ts is x25519-only; mnemonic never appears.
|
|
245
245
|
const outputMode = args.includes('--json') ? 'json' : args.includes('--url-pin') ? 'url-pin' : 'human';
|
|
246
246
|
|
|
247
|
-
const { runRelayPairCli } = await import('
|
|
248
|
-
const { defaultRenderQr, buildDefaultPairCliIo } = await import('
|
|
247
|
+
const { runRelayPairCli } = await import('../pairing/pair-cli-relay.js');
|
|
248
|
+
const { defaultRenderQr, buildDefaultPairCliIo } = await import('../pairing/pair-cli.js');
|
|
249
249
|
|
|
250
250
|
const io = buildDefaultPairCliIo();
|
|
251
251
|
const outcome = await runRelayPairCli('generate', {
|
|
@@ -261,7 +261,7 @@ async function cmdPair(args: string[]): Promise<void> {
|
|
|
261
261
|
deriveScopeAddress: undefined,
|
|
262
262
|
renderQr: defaultRenderQr,
|
|
263
263
|
io,
|
|
264
|
-
outputMode: outputMode as import('
|
|
264
|
+
outputMode: outputMode as import('../pairing/pair-cli.js').PairCliOutputMode,
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
if (outcome.status !== 'completed' && outcome.status !== 'canceled') {
|
|
@@ -333,12 +333,12 @@ async function cmdRemember(rawArgs: string[]): Promise<void> {
|
|
|
333
333
|
authKeyHex: ctx.authKeyHex,
|
|
334
334
|
walletAddress: ctx.walletAddress,
|
|
335
335
|
};
|
|
336
|
-
const
|
|
336
|
+
const submitResult = await submitFactBatchOnChain([protobuf], config);
|
|
337
337
|
|
|
338
|
-
if (!
|
|
338
|
+
if (!submitResult.success) {
|
|
339
339
|
die(
|
|
340
340
|
`remember failed: on-chain UserOp did not succeed (userOpHash=${
|
|
341
|
-
|
|
341
|
+
submitResult.userOpHash || 'none'
|
|
342
342
|
})`,
|
|
343
343
|
);
|
|
344
344
|
}
|
|
@@ -404,18 +404,18 @@ async function cmdForget(rawArgs: string[]): Promise<void> {
|
|
|
404
404
|
authKeyHex: ctx.authKeyHex,
|
|
405
405
|
walletAddress: ctx.walletAddress,
|
|
406
406
|
};
|
|
407
|
-
const
|
|
407
|
+
const submitResult = await submitFactBatchOnChain([protobuf], config);
|
|
408
408
|
|
|
409
|
-
if (!
|
|
409
|
+
if (!submitResult.success) {
|
|
410
410
|
die(
|
|
411
411
|
`forget failed: on-chain tombstone did not succeed (userOpHash=${
|
|
412
|
-
|
|
412
|
+
submitResult.userOpHash || 'none'
|
|
413
413
|
})`,
|
|
414
414
|
);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
if (jsonMode) {
|
|
418
|
-
log(JSON.stringify({ ok: true, id: factId, tx_hash:
|
|
418
|
+
log(JSON.stringify({ ok: true, id: factId, tx_hash: submitResult.txHash }));
|
|
419
419
|
} else {
|
|
420
420
|
log(`ok — tombstoned ${factId} (tx=${result.txHash || 'pending'})`);
|
|
421
421
|
}
|
|
@@ -24,12 +24,12 @@ import {
|
|
|
24
24
|
envNumber,
|
|
25
25
|
envString,
|
|
26
26
|
envStringLower,
|
|
27
|
-
} from '
|
|
27
|
+
} from '../entry.js';
|
|
28
28
|
import {
|
|
29
29
|
computeEntityTrapdoor,
|
|
30
30
|
isDigestBlob,
|
|
31
31
|
type AutoResolveMode,
|
|
32
|
-
} from '
|
|
32
|
+
} from '../extraction/claims-helper.js';
|
|
33
33
|
|
|
34
34
|
const requireWasm = createRequire(import.meta.url);
|
|
35
35
|
let _wasm: typeof import('@totalreclaw/core') | null = null;
|
|
@@ -306,17 +306,17 @@ export interface CandidateClaim {
|
|
|
306
306
|
*/
|
|
307
307
|
export function parseCandidateClaim(decryptedJson: string): CanonicalClaim | null {
|
|
308
308
|
if (isDigestBlob(decryptedJson)) return null;
|
|
309
|
-
let
|
|
309
|
+
let parsed: Record<string, unknown>;
|
|
310
310
|
try {
|
|
311
|
-
|
|
311
|
+
parsed = JSON.parse(decryptedJson) as Record<string, unknown>;
|
|
312
312
|
} catch {
|
|
313
313
|
return null;
|
|
314
314
|
}
|
|
315
|
-
if (typeof
|
|
315
|
+
if (typeof parsed.t !== 'string' || typeof parsed.c !== 'string') return null;
|
|
316
316
|
// Filter out infra categories — digests and entity rollup claims are not
|
|
317
317
|
// user-facing knowledge and must never be considered contradictions.
|
|
318
|
-
if (
|
|
319
|
-
return
|
|
318
|
+
if (parsed.c === 'dig' || parsed.c === 'ent') return null;
|
|
319
|
+
return parsed as CanonicalClaim;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
/** Is this candidate claim pinned (status `p`)? Delegates to WASM core. */
|
|
@@ -1052,9 +1052,9 @@ export function findDecisionForPin(
|
|
|
1052
1052
|
): DecisionLogEntry | null {
|
|
1053
1053
|
if (!logContent || logContent.length === 0) return null;
|
|
1054
1054
|
try {
|
|
1055
|
-
const
|
|
1056
|
-
if (
|
|
1057
|
-
return JSON.parse(
|
|
1055
|
+
const decisionJson = getWasm().findDecisionForPin(factId, role, logContent);
|
|
1056
|
+
if (decisionJson === 'null') return null;
|
|
1057
|
+
return JSON.parse(decisionJson) as DecisionLogEntry;
|
|
1058
1058
|
} catch {
|
|
1059
1059
|
// Fallback: local implementation if WASM fails.
|
|
1060
1060
|
const lines = logContent.split('\n').filter((l) => l.length > 0);
|
|
@@ -1141,13 +1141,13 @@ export function buildFeedbackFromDecision(
|
|
|
1141
1141
|
): FeedbackEntry | null {
|
|
1142
1142
|
if (!decision.winner_components || !decision.loser_components) return null;
|
|
1143
1143
|
try {
|
|
1144
|
-
const
|
|
1144
|
+
const feedbackJson = getWasm().buildFeedbackFromDecision(
|
|
1145
1145
|
JSON.stringify(decision),
|
|
1146
1146
|
action,
|
|
1147
1147
|
Math.floor(nowUnixSeconds),
|
|
1148
1148
|
);
|
|
1149
|
-
if (
|
|
1150
|
-
return JSON.parse(
|
|
1149
|
+
if (feedbackJson === 'null') return null;
|
|
1150
|
+
return JSON.parse(feedbackJson) as FeedbackEntry;
|
|
1151
1151
|
} catch {
|
|
1152
1152
|
// Fallback: local implementation if WASM fails.
|
|
1153
1153
|
if (action === 'pin_loser') {
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
isDigestStale,
|
|
22
22
|
shouldRecompile,
|
|
23
23
|
type DigestMode,
|
|
24
|
-
} from '
|
|
24
|
+
} from '../extraction/claims-helper.js';
|
|
25
25
|
|
|
26
26
|
const requireWasm = createRequire(import.meta.url);
|
|
27
27
|
let _wasm: typeof import('@totalreclaw/core') | null = null;
|
|
@@ -134,9 +134,30 @@ export function endRecompile(): void {
|
|
|
134
134
|
_recompileInProgress = false;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Signature of the claim set that drove the last *scheduled* recompile —
|
|
139
|
+
* the recency probe's `maxCreatedAt` at the moment we called `recompileFn`.
|
|
140
|
+
* `null` means "no attempt yet", which is distinct from any real timestamp
|
|
141
|
+
* (including 0 for an empty vault), so the very first attempt is never
|
|
142
|
+
* mistaken for a repeat.
|
|
143
|
+
*
|
|
144
|
+
* Issue #455: a recompile's on-chain write (digest store + previous-digest
|
|
145
|
+
* tombstone) isn't visible to `loadLatestDigest` until the subgraph indexes
|
|
146
|
+
* it (can take ~20s). Every `before_agent_start` call in that window still
|
|
147
|
+
* sees the OLD digest against the SAME unchanged set of active claims, so
|
|
148
|
+
* `evaluateDigestState` keeps reporting stale+recompile and would fire a
|
|
149
|
+
* second (third, fourth…) UserOp for content that produces an identical
|
|
150
|
+
* digest. Tracking the signature we already acted on lets us skip those
|
|
151
|
+
* repeats while still recompiling the instant a genuinely newer claim
|
|
152
|
+
* (or a tombstone/supersession, which also bumps the recency probe's
|
|
153
|
+
* `maxCreatedAt`) shows up.
|
|
154
|
+
*/
|
|
155
|
+
let _lastRecompileSignature: number | null = null;
|
|
156
|
+
|
|
137
157
|
/** Test-only helper to reset module state between cases. */
|
|
138
158
|
export function __resetDigestSyncState(): void {
|
|
139
159
|
_recompileInProgress = false;
|
|
160
|
+
_lastRecompileSignature = null;
|
|
140
161
|
}
|
|
141
162
|
|
|
142
163
|
// ---------------------------------------------------------------------------
|
|
@@ -562,7 +583,16 @@ export async function maybeInjectDigest(
|
|
|
562
583
|
});
|
|
563
584
|
|
|
564
585
|
if (state.stale && state.recompile && !isRecompileInProgress()) {
|
|
565
|
-
|
|
586
|
+
// Coalesce: only fire if the claim set feeding the digest has actually
|
|
587
|
+
// moved since the last recompile we scheduled. Otherwise we're re-firing
|
|
588
|
+
// for the same content while the previous write is still propagating
|
|
589
|
+
// through subgraph indexing — see `_lastRecompileSignature` above.
|
|
590
|
+
if (probe.maxCreatedAt !== _lastRecompileSignature) {
|
|
591
|
+
_lastRecompileSignature = probe.maxCreatedAt;
|
|
592
|
+
recompileFn(loaded.claimId);
|
|
593
|
+
} else {
|
|
594
|
+
logger.info('Digest: skip recompile — claim set unchanged');
|
|
595
|
+
}
|
|
566
596
|
}
|
|
567
597
|
|
|
568
598
|
const promptText = typeof loaded.digest.prompt_text === 'string' ? loaded.digest.prompt_text : null;
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import fs from 'node:fs';
|
|
28
28
|
import path from 'node:path';
|
|
29
|
-
import { CONFIG, setChainIdOverride, setDataEdgeAddressOverride } from '
|
|
29
|
+
import { CONFIG, setChainIdOverride, setDataEdgeAddressOverride } from '../config.js';
|
|
30
30
|
/** A plausible EVM address — anything else from billing is ignored (#460). */
|
|
31
31
|
const DATA_EDGE_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* runs (see internal#127). This helper is the canonical re-entry point for
|
|
18
18
|
* the variable.
|
|
19
19
|
*/
|
|
20
|
-
import { getSessionId } from '
|
|
20
|
+
import { getSessionId } from '../config.js';
|
|
21
21
|
/** Default `X-TotalReclaw-Client` value. */
|
|
22
22
|
export const DEFAULT_CLIENT_ID = 'openclaw-plugin';
|
|
23
23
|
/**
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* Phrase-safety: this module never touches the recovery phrase. It receives
|
|
13
13
|
* pre-derived auth-key + wallet-address + encryption-key from the caller.
|
|
14
14
|
*/
|
|
15
|
-
import { CONFIG } from '
|
|
16
|
-
import { buildRelayHeaders } from '
|
|
17
|
-
import { decrypt } from '
|
|
15
|
+
import { CONFIG } from '../config.js';
|
|
16
|
+
import { buildRelayHeaders } from '../billing/relay-headers.js';
|
|
17
|
+
import { decrypt } from '../crypto/crypto.js';
|
|
18
18
|
/** Decode a hex blob written by submitFactBatchOnChain back to plaintext. */
|
|
19
19
|
function fromHexBlob(hexBlob, encryptionKey) {
|
|
20
20
|
const hex = hexBlob.startsWith('0x') ? hexBlob.slice(2) : hexBlob;
|
|
@@ -71,8 +71,8 @@ export async function exportAllFacts(walletAddress, authKeyHex, encryptionKey) {
|
|
|
71
71
|
const variables = hasLastId
|
|
72
72
|
? { owner: walletAddress, first: PAGE_SIZE, lastId }
|
|
73
73
|
: { owner: walletAddress, first: PAGE_SIZE };
|
|
74
|
-
const
|
|
75
|
-
const facts =
|
|
74
|
+
const subgraphResponse = await gql(query, variables);
|
|
75
|
+
const facts = subgraphResponse?.facts ?? [];
|
|
76
76
|
if (facts.length === 0)
|
|
77
77
|
break;
|
|
78
78
|
for (const f of facts) {
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
* --json flag: all agent-facing CLI calls MUST use --json for clean machine-parseable output.
|
|
32
32
|
* Plain text mode is for direct user CLI use only.
|
|
33
33
|
*
|
|
34
|
-
* Install: wired via package.json `bin.tr` → dist/tr-cli.js
|
|
35
|
-
* Usage from container: `docker exec tr-openclaw node ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js status --json`
|
|
34
|
+
* Install: wired via package.json `bin.tr` → dist/cli/tr-cli.js
|
|
35
|
+
* Usage from container: `docker exec tr-openclaw node ~/.openclaw/extensions/totalreclaw/dist/cli/tr-cli.js status --json`
|
|
36
36
|
*/
|
|
37
37
|
import { randomUUID } from 'node:crypto';
|
|
38
|
-
import { CONFIG, setRecoveryPhraseOverride } from '
|
|
39
|
-
import { loadCredentialsJson } from '
|
|
40
|
-
import { printStatus } from '
|
|
41
|
-
import { deriveKeys, computeAuthKeyHash, encrypt, generateBlindIndices, generateContentFingerprint, } from '
|
|
42
|
-
import { createApiClient } from '
|
|
43
|
-
import { encodeFactProtobuf, submitFactBatchOnChain, deriveSmartAccountAddress, getSubgraphConfig, PROTOBUF_VERSION_V4, } from '
|
|
38
|
+
import { CONFIG, setRecoveryPhraseOverride } from '../config.js';
|
|
39
|
+
import { loadCredentialsJson } from '../fs-helpers.js';
|
|
40
|
+
import { printStatus } from '../pairing/onboarding-cli.js';
|
|
41
|
+
import { deriveKeys, computeAuthKeyHash, encrypt, generateBlindIndices, generateContentFingerprint, } from '../crypto/crypto.js';
|
|
42
|
+
import { createApiClient } from '../billing/api-client.js';
|
|
43
|
+
import { encodeFactProtobuf, submitFactBatchOnChain, deriveSmartAccountAddress, getSubgraphConfig, PROTOBUF_VERSION_V4, } from '../subgraph/subgraph-store.js';
|
|
44
44
|
import { exportAllFacts } from './tr-cli-export-helper.js';
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
46
|
// Helpers
|
|
@@ -51,7 +51,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
|
|
|
51
51
|
// Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
|
|
52
52
|
// Do not edit by hand — running tests will catch drift but the publish workflow
|
|
53
53
|
// rewrites this constant at the start of every npm/ClawHub publish.
|
|
54
|
-
const PLUGIN_VERSION = '3.3.
|
|
54
|
+
const PLUGIN_VERSION = '3.3.13';
|
|
55
55
|
function die(msg, code = 1) {
|
|
56
56
|
process.stderr.write(`tr: ${msg}\n`);
|
|
57
57
|
process.exit(code);
|
|
@@ -85,7 +85,7 @@ function toHexBlob(plaintext, encryptionKey) {
|
|
|
85
85
|
async function buildContext() {
|
|
86
86
|
const creds = loadCredentialsJson(CREDENTIALS_PATH);
|
|
87
87
|
if (!creds) {
|
|
88
|
-
die('TotalReclaw is not set up. Run: node ~/.openclaw/extensions/totalreclaw/dist/tr-cli.js pair --json');
|
|
88
|
+
die('TotalReclaw is not set up. Run: node ~/.openclaw/extensions/totalreclaw/dist/cli/tr-cli.js pair --json');
|
|
89
89
|
}
|
|
90
90
|
const mnemonic = (typeof creds.mnemonic === 'string' && creds.mnemonic.trim()) ||
|
|
91
91
|
(typeof creds.recovery_phrase === 'string' && creds.recovery_phrase.trim()) ||
|
|
@@ -192,8 +192,8 @@ async function cmdPair(args) {
|
|
|
192
192
|
// The pair flow is relay-brokered (works through Docker NAT).
|
|
193
193
|
// Phrase-safety: pair-cli-relay.ts is x25519-only; mnemonic never appears.
|
|
194
194
|
const outputMode = args.includes('--json') ? 'json' : args.includes('--url-pin') ? 'url-pin' : 'human';
|
|
195
|
-
const { runRelayPairCli } = await import('
|
|
196
|
-
const { defaultRenderQr, buildDefaultPairCliIo } = await import('
|
|
195
|
+
const { runRelayPairCli } = await import('../pairing/pair-cli-relay.js');
|
|
196
|
+
const { defaultRenderQr, buildDefaultPairCliIo } = await import('../pairing/pair-cli.js');
|
|
197
197
|
const io = buildDefaultPairCliIo();
|
|
198
198
|
const outcome = await runRelayPairCli('generate', {
|
|
199
199
|
relayBaseUrl: CONFIG.pairRelayUrl,
|
|
@@ -271,9 +271,9 @@ async function cmdRemember(rawArgs) {
|
|
|
271
271
|
authKeyHex: ctx.authKeyHex,
|
|
272
272
|
walletAddress: ctx.walletAddress,
|
|
273
273
|
};
|
|
274
|
-
const
|
|
275
|
-
if (!
|
|
276
|
-
die(`remember failed: on-chain UserOp did not succeed (userOpHash=${
|
|
274
|
+
const submitResult = await submitFactBatchOnChain([protobuf], config);
|
|
275
|
+
if (!submitResult.success) {
|
|
276
|
+
die(`remember failed: on-chain UserOp did not succeed (userOpHash=${submitResult.userOpHash || 'none'})`);
|
|
277
277
|
}
|
|
278
278
|
if (jsonMode) {
|
|
279
279
|
// JSON-first output for agent parsing.
|
|
@@ -331,12 +331,12 @@ async function cmdForget(rawArgs) {
|
|
|
331
331
|
authKeyHex: ctx.authKeyHex,
|
|
332
332
|
walletAddress: ctx.walletAddress,
|
|
333
333
|
};
|
|
334
|
-
const
|
|
335
|
-
if (!
|
|
336
|
-
die(`forget failed: on-chain tombstone did not succeed (userOpHash=${
|
|
334
|
+
const submitResult = await submitFactBatchOnChain([protobuf], config);
|
|
335
|
+
if (!submitResult.success) {
|
|
336
|
+
die(`forget failed: on-chain tombstone did not succeed (userOpHash=${submitResult.userOpHash || 'none'})`);
|
|
337
337
|
}
|
|
338
338
|
if (jsonMode) {
|
|
339
|
-
log(JSON.stringify({ ok: true, id: factId, tx_hash:
|
|
339
|
+
log(JSON.stringify({ ok: true, id: factId, tx_hash: submitResult.txHash }));
|
|
340
340
|
}
|
|
341
341
|
else {
|
|
342
342
|
log(`ok — tombstoned ${factId} (tx=${result.txHash || 'pending'})`);
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
|
-
import { envHomedir, envNumber, envString, envStringLower, } from '
|
|
21
|
-
import { computeEntityTrapdoor, isDigestBlob, } from '
|
|
20
|
+
import { envHomedir, envNumber, envString, envStringLower, } from '../entry.js';
|
|
21
|
+
import { computeEntityTrapdoor, isDigestBlob, } from '../extraction/claims-helper.js';
|
|
22
22
|
const requireWasm = createRequire(import.meta.url);
|
|
23
23
|
let _wasm = null;
|
|
24
24
|
function getWasm() {
|
|
@@ -153,20 +153,20 @@ export async function saveWeightsFile(file) {
|
|
|
153
153
|
export function parseCandidateClaim(decryptedJson) {
|
|
154
154
|
if (isDigestBlob(decryptedJson))
|
|
155
155
|
return null;
|
|
156
|
-
let
|
|
156
|
+
let parsed;
|
|
157
157
|
try {
|
|
158
|
-
|
|
158
|
+
parsed = JSON.parse(decryptedJson);
|
|
159
159
|
}
|
|
160
160
|
catch {
|
|
161
161
|
return null;
|
|
162
162
|
}
|
|
163
|
-
if (typeof
|
|
163
|
+
if (typeof parsed.t !== 'string' || typeof parsed.c !== 'string')
|
|
164
164
|
return null;
|
|
165
165
|
// Filter out infra categories — digests and entity rollup claims are not
|
|
166
166
|
// user-facing knowledge and must never be considered contradictions.
|
|
167
|
-
if (
|
|
167
|
+
if (parsed.c === 'dig' || parsed.c === 'ent')
|
|
168
168
|
return null;
|
|
169
|
-
return
|
|
169
|
+
return parsed;
|
|
170
170
|
}
|
|
171
171
|
/** Is this candidate claim pinned (status `p`)? Delegates to WASM core. */
|
|
172
172
|
export function isPinnedClaim(claim) {
|
|
@@ -715,10 +715,10 @@ export function findDecisionForPin(factId, role, logContent) {
|
|
|
715
715
|
if (!logContent || logContent.length === 0)
|
|
716
716
|
return null;
|
|
717
717
|
try {
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
718
|
+
const decisionJson = getWasm().findDecisionForPin(factId, role, logContent);
|
|
719
|
+
if (decisionJson === 'null')
|
|
720
720
|
return null;
|
|
721
|
-
return JSON.parse(
|
|
721
|
+
return JSON.parse(decisionJson);
|
|
722
722
|
}
|
|
723
723
|
catch {
|
|
724
724
|
// Fallback: local implementation if WASM fails.
|
|
@@ -812,10 +812,10 @@ export function buildFeedbackFromDecision(decision, action, nowUnixSeconds) {
|
|
|
812
812
|
if (!decision.winner_components || !decision.loser_components)
|
|
813
813
|
return null;
|
|
814
814
|
try {
|
|
815
|
-
const
|
|
816
|
-
if (
|
|
815
|
+
const feedbackJson = getWasm().buildFeedbackFromDecision(JSON.stringify(decision), action, Math.floor(nowUnixSeconds));
|
|
816
|
+
if (feedbackJson === 'null')
|
|
817
817
|
return null;
|
|
818
|
-
return JSON.parse(
|
|
818
|
+
return JSON.parse(feedbackJson);
|
|
819
819
|
}
|
|
820
820
|
catch {
|
|
821
821
|
// Fallback: local implementation if WASM fails.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* distinctive blind-index marker `DIGEST_TRAPDOOR`.
|
|
12
12
|
*/
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
|
-
import { DIGEST_CLAIM_CAP, DIGEST_TRAPDOOR, buildDigestClaim, extractDigestFromClaim, hoursSince, isDigestStale, shouldRecompile, } from '
|
|
14
|
+
import { DIGEST_CLAIM_CAP, DIGEST_TRAPDOOR, buildDigestClaim, extractDigestFromClaim, hoursSince, isDigestStale, shouldRecompile, } from '../extraction/claims-helper.js';
|
|
15
15
|
const requireWasm = createRequire(import.meta.url);
|
|
16
16
|
let _wasm = null;
|
|
17
17
|
function getWasm() {
|
|
@@ -76,9 +76,29 @@ export function tryBeginRecompile() {
|
|
|
76
76
|
export function endRecompile() {
|
|
77
77
|
_recompileInProgress = false;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Signature of the claim set that drove the last *scheduled* recompile —
|
|
81
|
+
* the recency probe's `maxCreatedAt` at the moment we called `recompileFn`.
|
|
82
|
+
* `null` means "no attempt yet", which is distinct from any real timestamp
|
|
83
|
+
* (including 0 for an empty vault), so the very first attempt is never
|
|
84
|
+
* mistaken for a repeat.
|
|
85
|
+
*
|
|
86
|
+
* Issue #455: a recompile's on-chain write (digest store + previous-digest
|
|
87
|
+
* tombstone) isn't visible to `loadLatestDigest` until the subgraph indexes
|
|
88
|
+
* it (can take ~20s). Every `before_agent_start` call in that window still
|
|
89
|
+
* sees the OLD digest against the SAME unchanged set of active claims, so
|
|
90
|
+
* `evaluateDigestState` keeps reporting stale+recompile and would fire a
|
|
91
|
+
* second (third, fourth…) UserOp for content that produces an identical
|
|
92
|
+
* digest. Tracking the signature we already acted on lets us skip those
|
|
93
|
+
* repeats while still recompiling the instant a genuinely newer claim
|
|
94
|
+
* (or a tombstone/supersession, which also bumps the recency probe's
|
|
95
|
+
* `maxCreatedAt`) shows up.
|
|
96
|
+
*/
|
|
97
|
+
let _lastRecompileSignature = null;
|
|
79
98
|
/** Test-only helper to reset module state between cases. */
|
|
80
99
|
export function __resetDigestSyncState() {
|
|
81
100
|
_recompileInProgress = false;
|
|
101
|
+
_lastRecompileSignature = null;
|
|
82
102
|
}
|
|
83
103
|
// ---------------------------------------------------------------------------
|
|
84
104
|
// Pure staleness + guard evaluation
|
|
@@ -351,7 +371,17 @@ export async function maybeInjectDigest(input) {
|
|
|
351
371
|
hoursSinceCompilation: hoursSince(compiledAt, nowMs),
|
|
352
372
|
});
|
|
353
373
|
if (state.stale && state.recompile && !isRecompileInProgress()) {
|
|
354
|
-
|
|
374
|
+
// Coalesce: only fire if the claim set feeding the digest has actually
|
|
375
|
+
// moved since the last recompile we scheduled. Otherwise we're re-firing
|
|
376
|
+
// for the same content while the previous write is still propagating
|
|
377
|
+
// through subgraph indexing — see `_lastRecompileSignature` above.
|
|
378
|
+
if (probe.maxCreatedAt !== _lastRecompileSignature) {
|
|
379
|
+
_lastRecompileSignature = probe.maxCreatedAt;
|
|
380
|
+
recompileFn(loaded.claimId);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
logger.info('Digest: skip recompile — claim set unchanged');
|
|
384
|
+
}
|
|
355
385
|
}
|
|
356
386
|
const promptText = typeof loaded.digest.prompt_text === 'string' ? loaded.digest.prompt_text : null;
|
|
357
387
|
return {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Env read is centralized in entry.ts (env-reading seam, Task 1.3 of the
|
|
14
14
|
* OpenClaw native integration plan, 2026-06-21).
|
|
15
15
|
*/
|
|
16
|
-
import { envNumber } from '
|
|
16
|
+
import { envNumber } from '../entry.js';
|
|
17
17
|
const DEFAULT_DOWNLOAD_TIMEOUT_MS = 600_000;
|
|
18
18
|
const KEEPALIVE_INTERVAL_MS = 60_000;
|
|
19
19
|
const MAX_DOWNLOAD_ATTEMPTS = 3;
|
|
@@ -21,12 +21,16 @@
|
|
|
21
21
|
* Compute cosine similarity between two vectors.
|
|
22
22
|
*
|
|
23
23
|
* Returns dot(a, b) / (||a|| * ||b||).
|
|
24
|
-
* Returns 0 if either vector has zero magnitude (avoids division by zero)
|
|
24
|
+
* Returns 0 if either vector has zero magnitude (avoids division by zero),
|
|
25
|
+
* or if the vectors differ in length — matching the authoritative core impl
|
|
26
|
+
* `cosine_similarity_f32` (rust/totalreclaw-core), which rejects mismatched
|
|
27
|
+
* dimensions rather than truncating to the shorter length. Keeping this local
|
|
28
|
+
* fallback byte-for-byte with core preserves cross-runtime rerank parity.
|
|
25
29
|
*/
|
|
26
30
|
export function cosineSimilarity(a, b) {
|
|
27
|
-
if (a.length
|
|
31
|
+
if (a.length !== b.length || a.length === 0)
|
|
28
32
|
return 0;
|
|
29
|
-
const len =
|
|
33
|
+
const len = a.length;
|
|
30
34
|
let dot = 0;
|
|
31
35
|
let normA = 0;
|
|
32
36
|
let normB = 0;
|
|
@@ -204,10 +208,15 @@ const SOURCE_WEIGHTS = {
|
|
|
204
208
|
};
|
|
205
209
|
const LEGACY_FALLBACK_WEIGHT = 0.85;
|
|
206
210
|
export function getSourceWeight(source) {
|
|
211
|
+
// Missing source → legacy fallback (0.85), matching core's None path.
|
|
207
212
|
if (!source)
|
|
208
213
|
return LEGACY_FALLBACK_WEIGHT;
|
|
209
214
|
const w = SOURCE_WEIGHTS[source.toLowerCase()];
|
|
210
|
-
|
|
215
|
+
// An unknown NON-EMPTY source string mirrors core's
|
|
216
|
+
// `MemorySource::from_str_lossy`, whose catch-all arm maps any unrecognized
|
|
217
|
+
// token to `user-inferred` (rust/totalreclaw-core/src/claims.rs) — so an
|
|
218
|
+
// unknown string weighs the same as user-inferred, NOT the legacy fallback.
|
|
219
|
+
return w ?? SOURCE_WEIGHTS['user-inferred'];
|
|
211
220
|
}
|
|
212
221
|
// ---------------------------------------------------------------------------
|
|
213
222
|
// Recency Scoring
|