@totalreclaw/totalreclaw 3.3.1-rc.9 → 3.3.2-rc.1
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 +274 -1
- package/SKILL.md +31 -25
- package/api-client.ts +18 -11
- package/claims-helper.ts +47 -1
- package/config.ts +108 -4
- package/confirm-indexed.ts +191 -0
- package/crypto.ts +10 -2
- package/dist/api-client.js +226 -0
- package/dist/billing-cache.js +100 -0
- package/dist/claims-helper.js +624 -0
- package/dist/config.js +297 -0
- package/dist/confirm-indexed.js +127 -0
- package/dist/consolidation.js +258 -0
- package/dist/contradiction-sync.js +1034 -0
- package/dist/crypto.js +138 -0
- package/dist/digest-sync.js +361 -0
- package/dist/download-ux.js +63 -0
- package/dist/embedder-cache.js +185 -0
- package/dist/embedder-loader.js +121 -0
- package/dist/embedder-network.js +301 -0
- package/dist/embedding.js +141 -0
- package/dist/extractor.js +1225 -0
- package/dist/first-run.js +103 -0
- package/dist/fs-helpers.js +725 -0
- package/dist/gateway-url.js +197 -0
- package/dist/generate-mnemonic.js +13 -0
- package/dist/hot-cache-wrapper.js +101 -0
- package/dist/import-adapters/base-adapter.js +64 -0
- package/dist/import-adapters/chatgpt-adapter.js +238 -0
- package/dist/import-adapters/claude-adapter.js +114 -0
- package/dist/import-adapters/gemini-adapter.js +201 -0
- package/dist/import-adapters/index.js +26 -0
- package/dist/import-adapters/mcp-memory-adapter.js +219 -0
- package/dist/import-adapters/mem0-adapter.js +158 -0
- package/dist/import-adapters/types.js +1 -0
- package/dist/index.js +5393 -0
- package/dist/llm-client.js +687 -0
- package/dist/llm-profile-reader.js +346 -0
- package/dist/lsh.js +62 -0
- package/dist/onboarding-cli.js +750 -0
- package/dist/pair-cli.js +344 -0
- package/dist/pair-crypto.js +359 -0
- package/dist/pair-http.js +404 -0
- package/dist/pair-page.js +826 -0
- package/dist/pair-qr.js +107 -0
- package/dist/pair-remote-client.js +410 -0
- package/dist/pair-session-store.js +566 -0
- package/dist/pin.js +556 -0
- package/dist/qa-bug-report.js +301 -0
- package/dist/relay-headers.js +44 -0
- package/dist/reranker.js +409 -0
- package/dist/retype-setscope.js +368 -0
- package/dist/semantic-dedup.js +75 -0
- package/dist/subgraph-search.js +289 -0
- package/dist/subgraph-store.js +694 -0
- package/dist/tool-gating.js +58 -0
- package/download-ux.ts +91 -0
- package/embedder-cache.ts +230 -0
- package/embedder-loader.ts +189 -0
- package/embedder-network.ts +350 -0
- package/embedding.ts +118 -27
- package/fs-helpers.ts +277 -0
- package/gateway-url.ts +57 -9
- package/index.ts +486 -262
- package/llm-client.ts +4 -3
- package/lsh.ts +7 -2
- package/onboarding-cli.ts +114 -1
- package/package.json +25 -5
- package/pair-cli.ts +76 -8
- package/pair-crypto.ts +34 -24
- package/pair-page.ts +28 -17
- package/pair-qr.ts +152 -0
- package/pair-remote-client.ts +540 -0
- package/pin.ts +31 -0
- package/preinstall.mjs +65 -0
- package/qa-bug-report.ts +84 -2
- package/relay-headers.ts +50 -0
- package/reranker.ts +40 -0
- package/retype-setscope.ts +69 -8
- package/skill.json +1 -1
- package/subgraph-search.ts +4 -3
- package/subgraph-store.ts +15 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,278 @@ All notable changes to `@totalreclaw/totalreclaw` (the OpenClaw plugin) are docu
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### 3.3.2-rc.1 — preinstall orphan-stage cleanup (umbrella #182 F6 / issue #190)
|
|
10
|
+
|
|
11
|
+
The rc.21 fix (#126) added `cleanupInstallStagingDirs` called at plugin
|
|
12
|
+
register time. That helper is too late for the re-install scenario: with
|
|
13
|
+
an orphan `.openclaw-install-stage-*` sibling already present in
|
|
14
|
+
`~/.openclaw/extensions/`, OpenClaw's config validator fires
|
|
15
|
+
`duplicate plugin id detected; global plugin will be overridden by global
|
|
16
|
+
plugin` BEFORE plugin register runs, so registration is blocked entirely
|
|
17
|
+
and the helper never gets a chance to clean up. The user observes the
|
|
18
|
+
warning and a plugin that silently fails to load.
|
|
19
|
+
|
|
20
|
+
Fix: a new `preinstall.mjs` script runs at npm preinstall time (before
|
|
21
|
+
the new install is renamed into place). It removes orphan
|
|
22
|
+
`.openclaw-install-stage-*` siblings from `~/.openclaw/extensions/`
|
|
23
|
+
(and `$OPENCLAW_STATE_DIR/extensions/` when set), skipping the script's
|
|
24
|
+
own cwd basename so older OpenClaw versions that stage inside extensions/
|
|
25
|
+
don't delete their own in-flight install. Also keeps the existing
|
|
26
|
+
`.tr-partial-install` marker drop. Best-effort throughout — never throws
|
|
27
|
+
or exits non-zero.
|
|
28
|
+
|
|
29
|
+
Regression: `test_issue_190_preinstall_orphan_cleanup.test.ts`
|
|
30
|
+
(19 assertions) covers newer-OpenClaw flow (cwd in /tmp), older-OpenClaw
|
|
31
|
+
flow (cwd inside extensions/), `OPENCLAW_STATE_DIR` env override,
|
|
32
|
+
no-extensions-dir fallback, and unrelated-sibling preservation.
|
|
33
|
+
|
|
34
|
+
### Install / runtime hygiene (issues #126, #128)
|
|
35
|
+
|
|
36
|
+
Two narrow fixes from the rc.20 user-QA findings — both around install /
|
|
37
|
+
boot-time output cleanliness, no behavior change to the steady-state plugin.
|
|
38
|
+
|
|
39
|
+
- **#126 — clean up `.openclaw-install-stage-*` siblings.** When
|
|
40
|
+
`openclaw plugins install @totalreclaw/totalreclaw` is interrupted mid-
|
|
41
|
+
extract (e.g. by an auto-gateway-restart triggered by the same install),
|
|
42
|
+
the npm staging directory `<extensionsDir>/.openclaw-install-stage-XXXXXX/`
|
|
43
|
+
survives. On the next gateway start, OpenClaw's plugin loader auto-
|
|
44
|
+
discovers BOTH `.../totalreclaw/` AND the orphan staging dir, registers
|
|
45
|
+
duplicate plugins, fires hooks twice, and prints a "duplicate-plugin-id"
|
|
46
|
+
warning every cycle. A user running `openclaw plugins list` sees two
|
|
47
|
+
`totalreclaw` rows.
|
|
48
|
+
|
|
49
|
+
Fix: `cleanupInstallStagingDirs(pluginDir)` runs at plugin register time
|
|
50
|
+
(one tick after the loader resolves our entrypoint). It scans the
|
|
51
|
+
extensions directory for `.openclaw-install-stage-*` siblings and
|
|
52
|
+
recursively removes each one. Best-effort — never crashes plugin init
|
|
53
|
+
on permission / race failures.
|
|
54
|
+
|
|
55
|
+
Regression: `install-staging-cleanup.test.ts` (16 assertions) covers
|
|
56
|
+
fresh install, idempotent re-run, package-root vs `dist/` invocation,
|
|
57
|
+
unrelated-dotfile preservation (`.git`, `.openclaw-cache`), and stray-
|
|
58
|
+
file (non-directory) skipping.
|
|
59
|
+
|
|
60
|
+
- **#128 — registerTool breadcrumbs no longer bleed into `--json` stdout.**
|
|
61
|
+
The rc.20 breadcrumb logs (`registerTool(totalreclaw_pair) returned. ...`
|
|
62
|
+
and the RC-only `totalreclaw_report_qa_bug registered ...`) were emitted
|
|
63
|
+
via `api.logger.info`, which OpenClaw routes to stdout decorated with
|
|
64
|
+
`[plugins] `. When a user invoked `openclaw agent --message "..." --json`
|
|
65
|
+
for programmatic parsing, the breadcrumb appeared on stdout alongside
|
|
66
|
+
the JSON-RPC body, breaking any naive `JSON.parse(stdout)`.
|
|
67
|
+
|
|
68
|
+
Fix: gate both breadcrumbs behind `CONFIG.verboseRegister`, OFF by
|
|
69
|
+
default. Ops can opt back in with `TOTALRECLAW_VERBOSE_REGISTER=1` (or
|
|
70
|
+
the general `TOTALRECLAW_DEBUG=1` toggle) when chasing a tool-injection
|
|
71
|
+
regression. Default-off keeps `openclaw agent --json` stdout clean.
|
|
72
|
+
|
|
73
|
+
Regression: `json-stdout-cleanliness.test.ts` (11 assertions) confirms
|
|
74
|
+
both breadcrumbs are wrapped in `if (CONFIG.verboseRegister)` blocks,
|
|
75
|
+
simulates the gated `--json` stdout path and `JSON.parse`s the result,
|
|
76
|
+
and exercises the env-var resolution (`TOTALRECLAW_VERBOSE_REGISTER`
|
|
77
|
+
-> `TOTALRECLAW_DEBUG` -> default false).
|
|
78
|
+
|
|
79
|
+
## [3.3.1-rc.16] — 2026-04-24
|
|
80
|
+
|
|
81
|
+
Fixes #92 — slow-host install times out during ONNX-runtime / embedding-model
|
|
82
|
+
download. ONNX stays mandatory (no opt-in flag); first-call download is now
|
|
83
|
+
wrapped with timeout, progress, and retry UX so slow connections succeed
|
|
84
|
+
instead of silently hanging until OpenClaw SIGTERMs.
|
|
85
|
+
|
|
86
|
+
### Embedding-model download UX
|
|
87
|
+
|
|
88
|
+
- New `download-ux.ts` module — pure stdlib, no third-party imports — exposes
|
|
89
|
+
`downloadWithUX(label, fn, opts)`. Wraps a download promise with:
|
|
90
|
+
- **Per-attempt timeout**, default 600s (covers ~290 KB/s for the 344 MB
|
|
91
|
+
Harrier model). Configurable via env `TOTALRECLAW_ONNX_INSTALL_TIMEOUT`
|
|
92
|
+
(in seconds). Per-attempt timeout grows 1x/2x/4x across retries.
|
|
93
|
+
- **60s keep-alive log** during long downloads so users on slow networks
|
|
94
|
+
see "still downloading… (Ns elapsed)" rather than a frozen prompt.
|
|
95
|
+
- **3-attempt exponential-backoff retry** (5s/10s backoff between attempts)
|
|
96
|
+
to absorb transient network blips.
|
|
97
|
+
- **Loud actionable error** on exhaustion: names the env var to extend the
|
|
98
|
+
timeout and the exact `openclaw plugins install totalreclaw` command to
|
|
99
|
+
rerun.
|
|
100
|
+
- `embedding.ts` now wraps `AutoTokenizer.from_pretrained`,
|
|
101
|
+
`AutoModel.from_pretrained`, and the `pipeline()` call with
|
|
102
|
+
`downloadWithUX`. Prints a user-visible "Downloading embedding model
|
|
103
|
+
(~344MB) — this may take a few minutes on slower connections. Please wait."
|
|
104
|
+
message before the first download starts.
|
|
105
|
+
- ONNX remains a mandatory hard `dependency` (no `[embedding]`-style opt-in
|
|
106
|
+
extra). Recall accuracy is unchanged.
|
|
107
|
+
- Regression: `test_issue_92_onnx_download_ux.test.ts` exercises happy path,
|
|
108
|
+
transient failure → retry, full exhaustion, per-attempt timeout, and
|
|
109
|
+
keep-alive cadence. Wired into the plugin `npm test` chain.
|
|
110
|
+
|
|
111
|
+
## [3.3.1-rc.14] — 2026-04-24
|
|
112
|
+
|
|
113
|
+
Coordinated version bump with Python `2.3.1rc14`. Two narrow bug fixes
|
|
114
|
+
found during rc.13 user QA on 2026-04-24:
|
|
115
|
+
|
|
116
|
+
### RC-gated QA bug tool — target-repo hardening
|
|
117
|
+
|
|
118
|
+
`totalreclaw_report_qa_bug` now refuses to file to any repo that isn't
|
|
119
|
+
internal. rc.13 user QA surfaced agent-filed bug reports leaking to the
|
|
120
|
+
public `p-diogo/totalreclaw` tracker despite the tool's default target
|
|
121
|
+
being `p-diogo/totalreclaw-internal`.
|
|
122
|
+
|
|
123
|
+
- New env var: `TOTALRECLAW_QA_REPO` lets operators point the tool at a
|
|
124
|
+
private fork. The default stays `p-diogo/totalreclaw-internal`.
|
|
125
|
+
- New `resolveQaRepo(...)` guard: rejects any slug that is on the
|
|
126
|
+
public-repo denylist (includes `p-diogo/totalreclaw`,
|
|
127
|
+
`...-website`, `...-relay`, `...-plugin`, `...-hermes`) OR does not
|
|
128
|
+
end in `-internal`. The check runs before the HTTP POST is
|
|
129
|
+
constructed, so rejection never leaves the client.
|
|
130
|
+
- `CONFIG.qaRepoOverride` surfaces the env var through `config.ts`
|
|
131
|
+
(keeps scanner-sensitive `process.env` reads centralized).
|
|
132
|
+
- Regression test in `qa-bug-report.test.ts` mocks the public slug
|
|
133
|
+
and asserts `fetch` is NEVER called.
|
|
134
|
+
|
|
135
|
+
Labels on filing unchanged — still emits `qa-bug`, `pending-triage`,
|
|
136
|
+
`severity:<...>`, `component:<...>`, `rc:<...>`.
|
|
137
|
+
|
|
138
|
+
### Relay pair page — PIN paste button UX
|
|
139
|
+
|
|
140
|
+
The paste button on the step-1 PIN screen was silently failing under
|
|
141
|
+
certain browser states. rc.14 rewrites the handler with a proper
|
|
142
|
+
error taxonomy:
|
|
143
|
+
|
|
144
|
+
- Capability probe up front — `navigator.clipboard.readText` missing →
|
|
145
|
+
clear "Paste unavailable on this browser" toast.
|
|
146
|
+
- `NotAllowedError` → "Clipboard access denied — type the 6 digits
|
|
147
|
+
manually" (covers iOS Safari permission denial).
|
|
148
|
+
- Empty clipboard → "Clipboard is empty — copy the PIN from your chat
|
|
149
|
+
first".
|
|
150
|
+
- Non-digit content → "Clipboard has no digits — copy the 6-digit PIN
|
|
151
|
+
first".
|
|
152
|
+
- Every failure path focuses the first PIN cell so the user can fall
|
|
153
|
+
through to manual typing without another click.
|
|
154
|
+
- Errors log to `console.warn` with name + message so future failures
|
|
155
|
+
are diagnosable from browser devtools.
|
|
156
|
+
|
|
157
|
+
The mockup at `docs/mockups/rc13-pair-wizard/wizard.js` gets the same
|
|
158
|
+
rewrite for parity — the relay's `scripts/sync-pair-preview.mjs`
|
|
159
|
+
regenerates `/pair-preview/` from this source.
|
|
160
|
+
|
|
161
|
+
Fix also applies to the "Paste all 12 words" import-grid button on the
|
|
162
|
+
relay production page (same taxonomy, same focus-fallback).
|
|
163
|
+
|
|
164
|
+
## [3.3.1-rc.13] — 2026-04-24
|
|
165
|
+
|
|
166
|
+
Coordinated version bump with Python `2.3.1rc13`. No substantive
|
|
167
|
+
changes to the plugin's own TypeScript — the rc.13 fix lands on the
|
|
168
|
+
Hermes-side (`python/src/totalreclaw/hermes/pair_tool.py`) where the
|
|
169
|
+
asyncio lifecycle regression lived. We keep plugin + Python RC
|
|
170
|
+
numbers in lockstep so the release-pipeline tracker and
|
|
171
|
+
`qa-totalreclaw` skill carry both artifacts through QA as one
|
|
172
|
+
bundle.
|
|
173
|
+
|
|
174
|
+
See the corresponding entry in `python/CHANGELOG.md` for the full
|
|
175
|
+
design: the relay-pair WebSocket is now owned by a dedicated worker
|
|
176
|
+
thread (with its own event loop) so it survives the Hermes
|
|
177
|
+
tool-invocation loop teardown that destroyed the rc.10–rc.12 waiter
|
|
178
|
+
mid-recv and caused every pair attempt to 502.
|
|
179
|
+
|
|
180
|
+
The relay-served production pair page is also replaced with the
|
|
181
|
+
rc.13 wizard UX — a typeform-style 3-step flow (PIN → phrase → done)
|
|
182
|
+
mirroring the `docs/mockups/rc13-pair-wizard/` design. This lands in
|
|
183
|
+
the `totalreclaw-relay` repo PR, not here, but surfaces to every
|
|
184
|
+
OpenClaw user via the default relay pair flow.
|
|
185
|
+
|
|
186
|
+
### Plugin local-mode pair page
|
|
187
|
+
|
|
188
|
+
`skill/plugin/pair-page.ts` (the local-mode fallback served when a
|
|
189
|
+
user sets `TOTALRECLAW_PAIR_MODE=local`) retains its rc.10–rc.12 UX
|
|
190
|
+
shape. The wizard UX port for this file is deferred to rc.14 pending
|
|
191
|
+
a design decision on whether to share a single CSS+JS asset across
|
|
192
|
+
all three pair pages (relay / Python local / plugin local) or keep
|
|
193
|
+
them independently inlined. Local-mode is rarely exercised — the
|
|
194
|
+
plugin defaults to the relay flow via the Hermes Python sidecar and
|
|
195
|
+
only falls back here for air-gapped setups.
|
|
196
|
+
|
|
197
|
+
## [3.3.1-rc.12] — 2026-04-23
|
|
198
|
+
|
|
199
|
+
**Ship-stopper fix for rc.11.** The relay-served pair page's submit
|
|
200
|
+
button threw `NotSupportedError: Failed to execute 'importKey' on
|
|
201
|
+
'SubtleCrypto': Algorithm: Unrecognized name` when the user clicked
|
|
202
|
+
"Seal key and finish". Root cause: `ChaCha20-Poly1305` is NOT
|
|
203
|
+
implemented in the Web Crypto API of Chrome / Safari / Edge — the
|
|
204
|
+
spec exposes `AES-GCM` as the only AEAD. rc.10/rc.11 never worked
|
|
205
|
+
end-to-end for any user; every pair attempt failed silently and the
|
|
206
|
+
token expired without logging a failure — GH issue #79.
|
|
207
|
+
|
|
208
|
+
rc.12 swaps the cipher suite from ChaCha20-Poly1305 to AES-256-GCM on
|
|
209
|
+
both sides (browser + gateway). Wire shape unchanged — still 12-byte
|
|
210
|
+
nonce, 16-byte tag, sid-bound AAD, base64url encoding. HKDF info bumped
|
|
211
|
+
from `totalreclaw-pair-v1` to `totalreclaw-pair-v2` so rc.11 ciphertexts
|
|
212
|
+
cannot collide with rc.12 keys (fail-closed on any version skew).
|
|
213
|
+
|
|
214
|
+
### Changed
|
|
215
|
+
- `skill/plugin/pair-crypto.ts`: `aeadDecrypt` / `aeadEncryptWithSessionKey`
|
|
216
|
+
switched from `chacha20-poly1305` to `aes-256-gcm`. `HKDF_INFO` bumped
|
|
217
|
+
to `totalreclaw-pair-v2`.
|
|
218
|
+
- `skill/plugin/pair-page.ts` (local-mode pair page): WebCrypto
|
|
219
|
+
`ChaCha20-Poly1305` calls swapped to `AES-GCM`. Capability probe
|
|
220
|
+
function renamed `chaChaSupported` → `aesGcmSupported`.
|
|
221
|
+
|
|
222
|
+
### Observability
|
|
223
|
+
- The relay's `pair-html.ts` (user-facing page) now reports phase-labelled
|
|
224
|
+
error messages so a network / encrypt / submit failure no longer masks
|
|
225
|
+
as a silent "stuck on acknowledge screen". Relay PR (fix/pair-aes-gcm-rc12)
|
|
226
|
+
is the canonical fix for the issue reported in #79.
|
|
227
|
+
|
|
228
|
+
## [3.3.1-rc.11] — 2026-04-23
|
|
229
|
+
|
|
230
|
+
OpenClaw-side universal pair reachability — the plugin's `totalreclaw_pair` tool now routes through the relay WebSocket by default, mirroring the Python `2.3.1rc10` pivot on the Hermes side. The URL returned to the user is `https://api-staging.totalreclaw.xyz/pair/p/<token>#pk=<gateway_pubkey>` instead of the previous `http://<gateway-host>:<port>/plugin/totalreclaw/pair/finish?sid=<sid>#pk=…`. Managed hosts, Docker-in-cloud setups, phone-scan-QR flows, and split-network operators can now complete pairing without the browser needing loopback or LAN access to the gateway.
|
|
231
|
+
|
|
232
|
+
Paired with Hermes Python `2.3.1rc11` — both clients now reach for the relay by default, and `TOTALRECLAW_PAIR_MODE=local` on either side restores the rc.4–rc.10 loopback flow for air-gapped / self-hosted deployments.
|
|
233
|
+
|
|
234
|
+
### Added
|
|
235
|
+
|
|
236
|
+
- **`skill/plugin/pair-remote-client.ts`** — new. TypeScript mirror of `python/src/totalreclaw/pair/remote_client.py` (rc.10 Hermes):
|
|
237
|
+
- `openRemotePairSession({ relayBaseUrl?, pin?, clientId?, mode? })` — generates an ephemeral x25519 keypair via the existing `pair-crypto.ts` module, opens a WebSocket to `/pair/session/open`, sends `{type:"open", gateway_pubkey, pin, client_id, mode}`, and returns a `RemotePairSession` handle containing the user-facing URL (with `#pk=` fragment), PIN, token, expiry, and the live WebSocket.
|
|
238
|
+
- `awaitPhraseUpload(session, { completePairing, phraseValidator?, timeoutMs? })` — blocks on the kept-open WebSocket until the relay pushes `{type:"forward", client_pubkey, nonce, ciphertext}`. Decrypts locally via `decryptPairingPayload` using the gateway's private key (same ECDH + HKDF + ChaCha20-Poly1305 primitives as rc.10's loopback flow — byte-compatible with Python's `pair.crypto`). Runs the caller-supplied `completePairing` handler and sends `{type:"ack"}` back on success or `{type:"nack", error}` on validator / decrypt / completion failure.
|
|
239
|
+
- `pairViaRelay(...)` — one-shot convenience wrapper for tests and simple callers.
|
|
240
|
+
- **`ws` runtime dep** (`^8.18.3`) + **`@types/ws`** — pure-JS WebSocket client. Transitive already via `@totalreclaw/core`; rc.11 promotes it to a direct dep so the plugin's own import graph is explicit.
|
|
241
|
+
- **`TOTALRECLAW_PAIR_MODE`** env (plugin side) — mirrors the Python env. Unset or any non-`local` value routes through the relay; `local` preserves the rc.4–rc.10 loopback HTTP server served by `pair-http.ts` (`/plugin/totalreclaw/pair/{finish,start,respond,status}`).
|
|
242
|
+
- **`TOTALRECLAW_PAIR_RELAY_URL`** env (plugin side) — self-hosters can point at their own relay. Defaults to `wss://api-staging.totalreclaw.xyz`.
|
|
243
|
+
- **`skill/plugin/pair-remote-client.test.ts`** — 20 assertions across 5 scenarios: happy-path round-trip, invalid-phrase nack, relay open error, decrypt failure, https-to-wss scheme conversion. Runs against a local `ws` server stub — no network dependency.
|
|
244
|
+
|
|
245
|
+
### Changed
|
|
246
|
+
|
|
247
|
+
- **`totalreclaw_pair` tool** now branches on `CONFIG.pairMode`. In relay mode it returns the URL + PIN immediately and schedules a background task that blocks on the WebSocket until the browser completes (or the TTL lapses). Credentials-write happens in that background task via the same `loadCredentialsJson` / `writeCredentialsJson` / `setRecoveryPhraseOverride` / `writeOnboardingState` side-effect chain that the loopback `pair-http.respond` handler uses — so the onboarding-state flip remains identical. Tool payload shape unchanged (`{url, pin, expires_at_ms, qr_ascii, qr_png_b64, qr_unicode, mode}`) except for a new `transport: 'relay' | 'local'` field that tooling (QA harness, telemetry) can use to confirm which path served a given URL.
|
|
248
|
+
|
|
249
|
+
### Phrase-safety invariants (preserved)
|
|
250
|
+
|
|
251
|
+
- Relay is blind: the gateway's ephemeral x25519 private key never leaves the plugin host. The relay forwards opaque ciphertext; it cannot derive the symmetric key.
|
|
252
|
+
- PIN is out-of-band: the user reads the PIN from agent chat and types it into the browser. The relay stores the PIN in memory only; logs carry no PIN, no ciphertext, no pubkey, no phrase.
|
|
253
|
+
- Session state is in-memory on the relay with a 5-minute TTL. Redis deferred to Phase 2 per the design blueprint.
|
|
254
|
+
- Backwards-compat: `TOTALRECLAW_PAIR_MODE=local` preserves every bit of the rc.4–rc.10 flow — same loopback HTTP server, same session store, same browser page, same decrypt handler.
|
|
255
|
+
|
|
256
|
+
### Mechanism / byte-compat
|
|
257
|
+
|
|
258
|
+
The crypto is a literal TypeScript binding against the same `pair-crypto.ts` module `pair-http.ts` already imports. No new cipher suite, no new wire format — only the transport (WebSocket to relay + relay-served HTML page) differs from the loopback path. A ciphertext produced by the relay-served `pair-html.ts` page decrypts under the same gateway private key using the same `decryptPairingPayload(...)` call path. This is deliberate: `pair-crypto.ts` is the byte-compat anchor shared with Python's `pair.crypto`, and rc.11 extends that anchor to the relay wire.
|
|
259
|
+
|
|
260
|
+
## [3.3.1-rc.10] — 2026-04-23
|
|
261
|
+
|
|
262
|
+
Coordinated version bump with Hermes Python `2.3.1rc10`. rc.10 ships the relay-brokered pair flow — see `python/CHANGELOG.md` (the `2.3.1rc10` entry) for the full design. The `totalreclaw_pair` pair URL on the OpenClaw plugin side still uses the gateway-loopback HTTP server (the OpenClaw plugin runs in-process alongside a browser on the same host for most deployments, so the loopback URL actually reaches the user). The relay-brokered path is currently Hermes-side only — the OpenClaw plugin can pick it up in a later RC if the same universal-reachability problem starts biting OpenClaw users.
|
|
263
|
+
|
|
264
|
+
Bundled into rc.10: the previously-parked rc.5 QR display layer from PR #76 (`pair-qr.ts` + `pair-qr.test.ts`, tool-payload `qr_png_b64` + `qr_unicode` fields, `totalreclaw_setup` / `totalreclaw_onboarding_start` stub removal). All rebased onto main via the chore/rc.10-qr-rebase-pr76 branch.
|
|
265
|
+
|
|
266
|
+
### Added (rebased from PR #76)
|
|
267
|
+
|
|
268
|
+
- **`skill/plugin/pair-qr.ts`** — new. QR encoder module wrapping `qrcode` (PNG) + `qrcode-terminal` (Unicode block). Same contract as the Python side (`totalreclaw.pair.qr`).
|
|
269
|
+
- **`totalreclaw_pair` tool payload** — the `details` block now carries `qr_png_b64` (base64 PNG for image transports) and `qr_unicode` (terminal block-char string) alongside the existing `qr_ascii`. URL + PIN unchanged.
|
|
270
|
+
- **SKILL.md "Rendering the QR on your transport" section** — per-transport agent rendering guidance (Telegram attachment, terminal inline, web chat `<img>` embed).
|
|
271
|
+
- **`qrcode` + `@types/qrcode`** runtime deps.
|
|
272
|
+
|
|
273
|
+
### Removed (rc.5 phrase-safety carve-out closure, rebased)
|
|
274
|
+
|
|
275
|
+
- **`totalreclaw_setup` + `totalreclaw_onboarding_start`** agent tools — both were neutered pointer stubs in rc.4; rc.5 auto-QA flagged them as future-regression surface and their mere presence signalled to agents that "phrase handling happens here". Deleted outright in rc.5, preserved through rc.10. `skill/plugin/phrase-safety-registry.test.ts` now asserts neither name is registered.
|
|
276
|
+
|
|
277
|
+
Version bump reason: rc cadence keeps Python + plugin aligned so the release-pipeline tracker carries them through QA as one artifact set.
|
|
278
|
+
|
|
7
279
|
## [3.3.1-rc.9] — 2026-04-23
|
|
8
280
|
|
|
9
281
|
Coordinated version bump with Hermes Python `2.3.1rc9`. Plugin code itself is unchanged from `3.3.1-rc.6` (the first-run banner fix lives entirely on the Python side — `totalreclaw.onboarding.maybe_emit_welcome`). The rc.9 bundle ships the Hermes-side banner suppression and keeps plugin + Python versions aligned so the release-pipeline tracker can carry them through QA as one artifact set.
|
|
@@ -246,7 +518,8 @@ See: `plans/2026-04-22-plugin-3.3.1-provider-agnostic-llm.md` (internal).
|
|
|
246
518
|
- `--json` — emits a structured payload (requires `--non-interactive`).
|
|
247
519
|
- `--mode <generate|restore>` — skip the menu prompt.
|
|
248
520
|
- `--phrase <12-or-24>` — required for `--mode restore`; `-` reads stdin.
|
|
249
|
-
- `--emit-phrase` — opt-in
|
|
521
|
+
- `--emit-phrase` — historic opt-in flag (do not invoke via agent shell:
|
|
522
|
+
forbidden by the phrase-safety rule); included plaintext phrase in the
|
|
250
523
|
JSON payload. Default omits the phrase; the agent should direct the
|
|
251
524
|
user to read `~/.totalreclaw/credentials.json` in their terminal.
|
|
252
525
|
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: totalreclaw
|
|
3
3
|
description: "Install + set up TotalReclaw encrypted memory for OpenClaw, then use totalreclaw_remember / totalreclaw_recall. Trigger on 'install TotalReclaw', 'set up TotalReclaw', 'restore my recovery phrase', or any remember / recall request."
|
|
4
|
-
version: 3.3.
|
|
4
|
+
version: 3.3.2-rc.1
|
|
5
5
|
author: TotalReclaw Team
|
|
6
6
|
license: MIT
|
|
7
7
|
homepage: https://totalreclaw.xyz
|
|
@@ -24,37 +24,43 @@ metadata:
|
|
|
24
24
|
|
|
25
25
|
# TotalReclaw — OpenClaw
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
> The canonical install flow lives at <https://github.com/p-diogo/totalreclaw/blob/main/docs/guides/openclaw-setup.md>. If a user pastes that URL in chat, fetch it and follow its agent-instructions section. This SKILL.md mirrors the same steps so you can act even if the URL is unreachable.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
## Setup flow
|
|
30
30
|
|
|
31
|
-
If
|
|
31
|
+
If `totalreclaw_*` tools are already bound to your toolset, SKIP install and jump to step 4. Otherwise run all steps in order.
|
|
32
32
|
|
|
33
|
-
1. Run via your shell tool
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
7. After user says done: verify `~/.totalreclaw/credentials.json`. If missing, call `totalreclaw_pair` again (PIN likely expired) and resend.
|
|
40
|
-
8. Confirm: *"TotalReclaw set up ✓. Ask me to remember or recall anything — try `totalreclaw_remember` or ask me to remember something."*
|
|
33
|
+
1. **Install skill + plugin.** Run via your shell tool, in this order:
|
|
34
|
+
```
|
|
35
|
+
openclaw skills install totalreclaw
|
|
36
|
+
openclaw plugins install @totalreclaw/totalreclaw
|
|
37
|
+
```
|
|
38
|
+
For RC: replace the second command with `openclaw plugins install @totalreclaw/totalreclaw@rc`. If either shell call fails with `command not found` / ENOENT / "not authorized" (managed-service or shell-restricted environment), tell the user verbatim *"Your environment doesn't expose the OpenClaw CLI to me. Please install `totalreclaw` from your service's plugins / skills UI, then reply `done`."* Wait for `done` and skip to step 3.
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
2. **Wait for auto-reload — do NOT restart the gateway yourself.** OpenClaw's default config (`gateway.reload.mode = "hybrid"`) auto-restarts the gateway via a file-watcher within 1-3 seconds. Wait ~5s, then run `openclaw plugins list | grep totalreclaw` and confirm `totalreclaw_pair` is in your toolset. If it does NOT appear within 10s, the user has `gateway.reload.mode = off` — fall back: tell the user verbatim *"Auto-reload didn't fire. Please restart your OpenClaw gateway: `openclaw gateway restart` (native), `docker restart <your-container-name>` (Docker self-host — substitute the actual name; `docker ps` shows it), or your managed service's restart control. Reply `done` once it's back up."* Do NOT invoke the restart yourself — your shell is running inside the gateway you'd be restarting (catch-22). Wait for the user's `done`.
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
3. **Verify `totalreclaw_pair` is bound.** If still missing after the user said `done`, tell them the restart didn't take effect and to retry. Do not loop the install.
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
4. **Check for existing credentials.** Run `test -s ~/.totalreclaw/credentials.json`. If it exists and is non-empty, confirm *"TotalReclaw is already set up."* and stop.
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
5. **Set up the user's TotalReclaw account.** Tell the user: *"I'll walk you through setting up your TotalReclaw account."* Then call `totalreclaw_pair` (the account-setup tool — name kept for backward compatibility). Returns `{url, pin, qr_ascii, qr_png_b64, qr_unicode, expires_at_ms}` (see "Rendering the QR" below). Relay verbatim:
|
|
47
|
+
> *Open <url> in your browser. Generate a new 12-word recovery phrase there or paste an existing one — the phrase stays in your browser, the relay only sees ciphertext. Confirm PIN <pin>. Reply `done` once the page says it's sealed.*
|
|
48
|
+
|
|
49
|
+
6. **Verify and confirm.** After user says `done`, re-run `test -s ~/.totalreclaw/credentials.json`. If missing, the PIN expired — call `totalreclaw_pair` again and resend. If present, confirm *"Your TotalReclaw account is set up. Ask me to remember or recall anything."*
|
|
50
|
+
|
|
51
|
+
## Rendering the QR on your transport (rc.5+)
|
|
52
|
+
|
|
53
|
+
When you receive the `totalreclaw_pair` payload, render the QR based on your chat transport:
|
|
54
|
+
|
|
55
|
+
- **Transport supports image attachments** (Telegram, Slack, web chat, any transport with file/image upload): decode `qr_png_b64` from base64 to bytes, save to a tempfile with a `.png` suffix, and attach the tempfile as an image. Also show the URL + PIN as text so users who prefer clicking can still do so.
|
|
56
|
+
- **Terminal-only transport** (OpenClaw native CLI chat, bare SSH shell): emit `qr_unicode` inline in your reply. It renders as block characters in most terminal fonts; the user scans from their phone.
|
|
57
|
+
- **Web chat with inline-image support**: embed as `<img src="data:image/png;base64,<qr_png_b64>" alt="TotalReclaw account-setup QR">` if the chat client renders HTML; otherwise fall back to attachment.
|
|
58
|
+
|
|
59
|
+
The QR encodes ONLY the account-setup URL. The 6-digit PIN is a separate out-of-band confirmation — deliberately NOT baked into the QR (dual-channel design: scan + type). Never log, print, or re-emit the PIN outside the user-facing reply. The PIN must NOT end up in any other tool call, log file, or memory store.
|
|
54
60
|
|
|
55
61
|
## Phrase safety (HARD — never break)
|
|
56
62
|
|
|
57
|
-
NEVER echo / generate / ask the user to paste a recovery phrase in chat. NEVER invoke `openclaw totalreclaw onboard
|
|
63
|
+
NEVER echo / generate / ask the user to paste a recovery phrase in chat. NEVER invoke `openclaw totalreclaw onboard` (bare interactive form), `totalreclaw setup`, or any phrase-touching CLI via your shell tool (stdout enters LLM context). The ONLY agent-facilitated account-setup paths are (in order of preference): (1) the `totalreclaw_pair` account-setup tool; (2) if the tool is missing from your tool list — a known gap for some container-based agents, issue #87 — shell out to EITHER `openclaw totalreclaw pair generate --url-pin-only` (single-line JSON `{v,url,pin,expires_at_ms}`) OR `openclaw totalreclaw onboard --pair-only` (single-line JSON `{v,pair_url,pin,expires_at_ms}`, issue #95 — same account-setup flow, `pair_url` key naming). Both have zero phrase material on stdout (account-setup crypto is x25519-only, not BIP-39). If the user pastes a phrase anyway: tell them it is compromised, call `totalreclaw_pair` with `mode=generate` for a fresh wallet.
|
|
58
64
|
|
|
59
65
|
## Usage (post-setup)
|
|
60
66
|
|
|
@@ -66,12 +72,12 @@ NEVER echo / generate / ask the user to paste a recovery phrase in chat. NEVER i
|
|
|
66
72
|
|
|
67
73
|
## Diagnostics
|
|
68
74
|
|
|
69
|
-
- `totalreclaw_*` tools not visible → plugin not installed or
|
|
75
|
+
- `totalreclaw_*` tools not visible → plugin not installed or auto-reload didn't fire. Verify via `openclaw plugins list | grep totalreclaw` (or, on a managed service that doesn't expose the CLI, ask the user to confirm via their service's plugins UI). If `totalreclaw` is listed but tools are still absent, the gateway probably hasn't completed its auto-restart yet (give it 5-10s) OR `gateway.reload.mode = off` is set — instruct manual restart as fallback: `openclaw gateway restart` (native), `docker restart <your-container-name>` (Docker self-host), or the managed service's restart control. If the plugin is listed and the gateway has been restarted but tools are still missing (container-agent gap, issue #87), shell out to `openclaw totalreclaw pair generate --url-pin-only` on the gateway host and hand the user the `url` + `pin` from the JSON payload — OR `openclaw totalreclaw onboard --pair-only` (same flow, returns `pair_url` instead of `url`, issue #95). If your shell doesn't reach the gateway host (managed service), tell the user to install/enable the plugin via their service's plugins UI and restart the gateway via the service's restart control. Do NOT suggest the bare `openclaw totalreclaw onboard` (leaks phrase on stdout — deprecated in rc.18, removed next RC).
|
|
70
76
|
- User says done but `credentials.json` missing → PIN expired or entered wrong phrase; call `totalreclaw_pair` again.
|
|
71
|
-
- `onboarding required` → credentials missing; redo from the
|
|
77
|
+
- `onboarding required` → credentials missing; redo from the account-setup step.
|
|
72
78
|
- `quota exceeded` → `totalreclaw_status`, then offer `totalreclaw_upgrade`.
|
|
73
79
|
- `No LLM available for auto-extraction` at startup → provider key unreachable; check `~/.openclaw/agents/<agent>/agent/auth-profiles.json` or plugin config `extraction.llm`.
|
|
74
80
|
|
|
75
81
|
## Tool surface
|
|
76
82
|
|
|
77
|
-
`totalreclaw_pair` (ONLY setup path) · `_remember` · `_recall` · `_forget` · `_pin` · `_unpin` · `_retype` · `_set_scope` · `_export` · `_status` · `_upgrade` · `_migrate` · `_import_from` · `_import_batch` · `_consolidate` · `_onboarding_start` (pointer to local-terminal wizard, for users explicitly rejecting the browser flow) · `_report_qa_bug` (RC only).
|
|
83
|
+
`totalreclaw_pair` (ONLY account-setup path) · `_remember` · `_recall` · `_forget` · `_pin` · `_unpin` · `_retype` · `_set_scope` · `_export` · `_status` · `_upgrade` · `_migrate` · `_import_from` · `_import_batch` · `_consolidate` · `_onboarding_start` (pointer to local-terminal wizard, for users explicitly rejecting the browser flow) · `_report_qa_bug` (RC only).
|
package/api-client.ts
CHANGED
|
@@ -8,8 +8,15 @@
|
|
|
8
8
|
* Authorization: Bearer <hex-encoded-auth-key>
|
|
9
9
|
*
|
|
10
10
|
* The server hashes the auth key with SHA-256 to look up the user.
|
|
11
|
+
*
|
|
12
|
+
* Every outbound request goes through `buildRelayHeaders()` so the
|
|
13
|
+
* `X-TotalReclaw-Client` tag is set + the optional QA-tracing
|
|
14
|
+
* `X-TotalReclaw-Session` tag is forwarded when `TOTALRECLAW_SESSION_ID`
|
|
15
|
+
* is set. See `relay-headers.ts` and internal#127.
|
|
11
16
|
*/
|
|
12
17
|
|
|
18
|
+
import { buildRelayHeaders } from './relay-headers.js';
|
|
19
|
+
|
|
13
20
|
// ---------------------------------------------------------------------------
|
|
14
21
|
// Request / Response Types
|
|
15
22
|
// ---------------------------------------------------------------------------
|
|
@@ -126,7 +133,7 @@ export function createApiClient(serverUrl: string) {
|
|
|
126
133
|
): Promise<{ user_id: string }> {
|
|
127
134
|
const res = await fetch(`${baseUrl}/v1/register`, {
|
|
128
135
|
method: 'POST',
|
|
129
|
-
headers: { 'Content-Type': 'application/json'
|
|
136
|
+
headers: buildRelayHeaders({ 'Content-Type': 'application/json' }),
|
|
130
137
|
body: JSON.stringify({ auth_key_hash: authKeyHash, salt: saltHex }),
|
|
131
138
|
});
|
|
132
139
|
await assertOk(res, 'register');
|
|
@@ -160,10 +167,10 @@ export function createApiClient(serverUrl: string) {
|
|
|
160
167
|
): Promise<{ ids: string[]; duplicate_ids?: string[] }> {
|
|
161
168
|
const res = await fetch(`${baseUrl}/v1/store`, {
|
|
162
169
|
method: 'POST',
|
|
163
|
-
headers: {
|
|
170
|
+
headers: buildRelayHeaders({
|
|
164
171
|
'Content-Type': 'application/json',
|
|
165
172
|
Authorization: `Bearer ${authKeyHex}`,
|
|
166
|
-
},
|
|
173
|
+
}),
|
|
167
174
|
body: JSON.stringify({ user_id: userId, facts }),
|
|
168
175
|
});
|
|
169
176
|
await assertOk(res, 'store');
|
|
@@ -198,10 +205,10 @@ export function createApiClient(serverUrl: string) {
|
|
|
198
205
|
): Promise<SearchCandidate[]> {
|
|
199
206
|
const res = await fetch(`${baseUrl}/v1/search`, {
|
|
200
207
|
method: 'POST',
|
|
201
|
-
headers: {
|
|
208
|
+
headers: buildRelayHeaders({
|
|
202
209
|
'Content-Type': 'application/json',
|
|
203
210
|
Authorization: `Bearer ${authKeyHex}`,
|
|
204
|
-
},
|
|
211
|
+
}),
|
|
205
212
|
body: JSON.stringify({
|
|
206
213
|
user_id: userId,
|
|
207
214
|
trapdoors,
|
|
@@ -229,9 +236,9 @@ export function createApiClient(serverUrl: string) {
|
|
|
229
236
|
async deleteFact(factId: string, authKeyHex: string): Promise<void> {
|
|
230
237
|
const res = await fetch(`${baseUrl}/v1/facts/${encodeURIComponent(factId)}`, {
|
|
231
238
|
method: 'DELETE',
|
|
232
|
-
headers: {
|
|
239
|
+
headers: buildRelayHeaders({
|
|
233
240
|
Authorization: `Bearer ${authKeyHex}`,
|
|
234
|
-
},
|
|
241
|
+
}),
|
|
235
242
|
});
|
|
236
243
|
await assertOk(res, 'deleteFact');
|
|
237
244
|
const json = (await res.json()) as Record<string, unknown>;
|
|
@@ -254,10 +261,10 @@ export function createApiClient(serverUrl: string) {
|
|
|
254
261
|
async batchDelete(factIds: string[], authKeyHex: string): Promise<number> {
|
|
255
262
|
const res = await fetch(`${baseUrl}/v1/facts/batch-delete`, {
|
|
256
263
|
method: 'POST',
|
|
257
|
-
headers: {
|
|
264
|
+
headers: buildRelayHeaders({
|
|
258
265
|
'Content-Type': 'application/json',
|
|
259
266
|
Authorization: `Bearer ${authKeyHex}`,
|
|
260
|
-
},
|
|
267
|
+
}),
|
|
261
268
|
body: JSON.stringify({ fact_ids: factIds }),
|
|
262
269
|
});
|
|
263
270
|
await assertOk(res, 'batchDelete');
|
|
@@ -290,9 +297,9 @@ export function createApiClient(serverUrl: string) {
|
|
|
290
297
|
|
|
291
298
|
const res = await fetch(`${baseUrl}/v1/export?${params.toString()}`, {
|
|
292
299
|
method: 'GET',
|
|
293
|
-
headers: {
|
|
300
|
+
headers: buildRelayHeaders({
|
|
294
301
|
Authorization: `Bearer ${authKeyHex}`,
|
|
295
|
-
},
|
|
302
|
+
}),
|
|
296
303
|
});
|
|
297
304
|
await assertOk(res, 'exportFacts');
|
|
298
305
|
const json = (await res.json()) as Record<string, unknown>;
|
package/claims-helper.ts
CHANGED
|
@@ -96,6 +96,14 @@ export interface BuildClaimInput {
|
|
|
96
96
|
sourceAgent: string;
|
|
97
97
|
/** Creation timestamp. Defaults to now. */
|
|
98
98
|
extractedAt?: string;
|
|
99
|
+
/**
|
|
100
|
+
* 3.3.1-rc.22 — optional embedding-model id stamped on the claim for
|
|
101
|
+
* forward-compat. Defaults to omitted (callers that already know the
|
|
102
|
+
* active embedder pass it in; legacy paths leave it unset). The field
|
|
103
|
+
* is plugin-scoped — it survives the core validator's strip pass via
|
|
104
|
+
* the same re-attach path used for `schema_version` / `volatility`.
|
|
105
|
+
*/
|
|
106
|
+
embeddingModelId?: string;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
/**
|
|
@@ -112,7 +120,7 @@ export interface BuildClaimInput {
|
|
|
112
120
|
* payload (see `subgraph-store.ts::encodeFactProtobuf`).
|
|
113
121
|
*/
|
|
114
122
|
export function buildCanonicalClaim(input: BuildClaimInput): string {
|
|
115
|
-
const { fact, importance, extractedAt } = input;
|
|
123
|
+
const { fact, importance, extractedAt, embeddingModelId } = input;
|
|
116
124
|
|
|
117
125
|
// Defensive: ensure fact.source is always populated before v1 validation.
|
|
118
126
|
// `applyProvenanceFilterLax` should have set this upstream; this is the
|
|
@@ -125,6 +133,7 @@ export function buildCanonicalClaim(input: BuildClaimInput): string {
|
|
|
125
133
|
fact: factWithSource,
|
|
126
134
|
importance,
|
|
127
135
|
createdAt: extractedAt,
|
|
136
|
+
embeddingModelId,
|
|
128
137
|
});
|
|
129
138
|
}
|
|
130
139
|
|
|
@@ -173,6 +182,14 @@ export interface BuildClaimV1Input {
|
|
|
173
182
|
* when provided.
|
|
174
183
|
*/
|
|
175
184
|
pinStatus?: PinStatus;
|
|
185
|
+
/**
|
|
186
|
+
* 3.3.1-rc.22 — optional embedding-model id stamped on the claim for
|
|
187
|
+
* distillation forward-compat. Survives the core validator strip pass
|
|
188
|
+
* via the same re-attach path used for `schema_version` / `volatility`.
|
|
189
|
+
* When omitted the field is not emitted (legacy claims remain untagged
|
|
190
|
+
* and are read back as "unspecified").
|
|
191
|
+
*/
|
|
192
|
+
embeddingModelId?: string;
|
|
176
193
|
}
|
|
177
194
|
|
|
178
195
|
/**
|
|
@@ -257,6 +274,13 @@ export function buildCanonicalClaimV1(input: BuildClaimV1Input): string {
|
|
|
257
274
|
if (fact.volatility && (VALID_MEMORY_VOLATILITIES as readonly string[]).includes(fact.volatility)) {
|
|
258
275
|
canonical.volatility = fact.volatility;
|
|
259
276
|
}
|
|
277
|
+
// 3.3.1-rc.22 — forward-compat embedder marker. Plugin-only field;
|
|
278
|
+
// survives the core validator via re-attach. Future distillation
|
|
279
|
+
// detects this on read to re-embed selectively without forcing a
|
|
280
|
+
// vault-wide rebuild.
|
|
281
|
+
if (typeof input.embeddingModelId === 'string' && input.embeddingModelId.length > 0) {
|
|
282
|
+
canonical.embedding_model_id = input.embeddingModelId;
|
|
283
|
+
}
|
|
260
284
|
|
|
261
285
|
return JSON.stringify(canonical);
|
|
262
286
|
}
|
|
@@ -313,6 +337,11 @@ export interface BuildV1ClaimBlobInput {
|
|
|
313
337
|
* non-pin write.
|
|
314
338
|
*/
|
|
315
339
|
pinStatus?: PinStatus;
|
|
340
|
+
/**
|
|
341
|
+
* 3.3.1-rc.22 — optional embedding-model id stamped on the claim for
|
|
342
|
+
* distillation forward-compat. See `BuildClaimV1Input.embeddingModelId`.
|
|
343
|
+
*/
|
|
344
|
+
embeddingModelId?: string;
|
|
316
345
|
}
|
|
317
346
|
|
|
318
347
|
/**
|
|
@@ -374,6 +403,10 @@ export function buildV1ClaimBlob(input: BuildV1ClaimBlobInput): string {
|
|
|
374
403
|
if (input.volatility && (VALID_MEMORY_VOLATILITIES as readonly string[]).includes(input.volatility)) {
|
|
375
404
|
canonical.volatility = input.volatility;
|
|
376
405
|
}
|
|
406
|
+
// 3.3.1-rc.22 — see `buildCanonicalClaimV1` comment.
|
|
407
|
+
if (typeof input.embeddingModelId === 'string' && input.embeddingModelId.length > 0) {
|
|
408
|
+
canonical.embedding_model_id = input.embeddingModelId;
|
|
409
|
+
}
|
|
377
410
|
return JSON.stringify(canonical);
|
|
378
411
|
}
|
|
379
412
|
|
|
@@ -431,6 +464,13 @@ export interface V1BlobReadResult {
|
|
|
431
464
|
* when the writer explicitly omitted the field (treated as `"unpinned"`).
|
|
432
465
|
*/
|
|
433
466
|
pinStatus?: PinStatus;
|
|
467
|
+
/**
|
|
468
|
+
* 3.3.1-rc.22 — embedder identity tag. Absent on claims written by
|
|
469
|
+
* older plugin versions. Forward-compat marker; consumers MAY use it
|
|
470
|
+
* to decide whether a claim's stored embedding matches the active
|
|
471
|
+
* embedder before letting cosine similarity make a relevance call.
|
|
472
|
+
*/
|
|
473
|
+
embeddingModelId?: string;
|
|
434
474
|
}
|
|
435
475
|
|
|
436
476
|
export function readV1Blob(decrypted: string): V1BlobReadResult | null {
|
|
@@ -497,6 +537,12 @@ export function readV1Blob(decrypted: string): V1BlobReadResult | null {
|
|
|
497
537
|
result.pinStatus = ps;
|
|
498
538
|
}
|
|
499
539
|
}
|
|
540
|
+
// 3.3.1-rc.22 — pull the embedder identity tag through. Plugin-only
|
|
541
|
+
// field added by `buildCanonicalClaimV1` / `buildV1ClaimBlob` after
|
|
542
|
+
// core validation.
|
|
543
|
+
if (typeof obj.embedding_model_id === 'string' && obj.embedding_model_id.length > 0) {
|
|
544
|
+
result.embeddingModelId = obj.embedding_model_id;
|
|
545
|
+
}
|
|
500
546
|
|
|
501
547
|
return result;
|
|
502
548
|
} catch {
|