@totalreclaw/totalreclaw 3.3.0-rc.4 → 3.3.0-rc.5
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 +98 -0
- package/index.ts +61 -55
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,104 @@ 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
|
+
## [3.3.0-rc.5] — 2026-04-20
|
|
8
|
+
|
|
9
|
+
Fifth release candidate for 3.3.0. Single ship-stopper fix for rc.4's
|
|
10
|
+
QR-pairing flow, root-caused by the auto-QA run against rc.4 artifacts
|
|
11
|
+
(report: `docs/notes/QA-plugin-3.3.0-rc.4-20260420-1517.md` in
|
|
12
|
+
`totalreclaw-internal`, thread at `totalreclaw-internal#21` comment
|
|
13
|
+
4281568050). rc.2 (scanner), rc.3 (auth literal path), and rc.4 (auth
|
|
14
|
+
`'plugin'` literal + `ensureSessionsFileDir` mkdir before lock) fixes are
|
|
15
|
+
all preserved. No protocol / on-chain changes vs 3.3.0.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **`skill/plugin/index.ts` — register pair HTTP routes synchronously
|
|
20
|
+
(remove async IIFE)**. rc.2–rc.4 wrapped the 4 `api.registerHttpRoute`
|
|
21
|
+
calls in a fire-and-forget `(async () => { ... })()` block whose three
|
|
22
|
+
`await import(...)` calls (`./pair-http.js`, `@scure/bip39`, and
|
|
23
|
+
`@scure/bip39/wordlists/english.js`) settled one microtask AFTER the
|
|
24
|
+
SDK loader had already called `register()` and frozen the plugin's
|
|
25
|
+
HTTP-route registry. The 4 post-activation pushes landed on the
|
|
26
|
+
dispatcher's "inactive" copy and never reached the live router;
|
|
27
|
+
`openclaw plugins inspect totalreclaw --json | jq .httpRouteCount`
|
|
28
|
+
returned `0` on rc.4 despite both the `auth: 'plugin'` literal (rc.4)
|
|
29
|
+
and the `ensureSessionsFileDir` mkdir (rc.4) being correct. rc.5:
|
|
30
|
+
|
|
31
|
+
1. `buildPairRoutes`, `validateMnemonic`, and `wordlist` are now
|
|
32
|
+
**static top-of-file imports** (alongside the existing
|
|
33
|
+
`onboarding-cli.ts` / `generate-mnemonic.ts` static imports of the
|
|
34
|
+
same modules — no new deps, no circular-dep risk).
|
|
35
|
+
2. `writeOnboardingState` is added to the existing static
|
|
36
|
+
`./fs-helpers.js` import (it was the only dynamic import inside
|
|
37
|
+
the `completePairing` callback).
|
|
38
|
+
3. The async IIFE is deleted. `buildPairRoutes(...)` and the 4
|
|
39
|
+
`api.registerHttpRoute({...})` calls are now in the synchronous
|
|
40
|
+
body of `register(api)`, inside the existing
|
|
41
|
+
`if (typeof api.registerHttpRoute === 'function')` guard. The
|
|
42
|
+
`else` branch and warning are unchanged. The post-registration
|
|
43
|
+
info log now reads `'registered 4 QR-pairing HTTP routes
|
|
44
|
+
synchronously'` for clearer debug output.
|
|
45
|
+
4. `completePairing` remains `async` (it does disk I/O) — that is
|
|
46
|
+
fine because `registerHttpRoute` accepts async handlers. Only the
|
|
47
|
+
REGISTRATION had to be synchronous; the handler itself can
|
|
48
|
+
defer-to-microtask freely at runtime.
|
|
49
|
+
|
|
50
|
+
Scanner: static imports don't trigger any rule that dynamic imports
|
|
51
|
+
don't already trigger (verified via `node skill/scripts/check-scanner.mjs`,
|
|
52
|
+
0 flags, 72 files scanned).
|
|
53
|
+
|
|
54
|
+
**Before (rc.4):**
|
|
55
|
+
```ts
|
|
56
|
+
if (typeof api.registerHttpRoute === 'function') {
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const { buildPairRoutes } = await import('./pair-http.js');
|
|
60
|
+
const { validateMnemonic } = await import('@scure/bip39');
|
|
61
|
+
const { wordlist } = await import('@scure/bip39/wordlists/english.js');
|
|
62
|
+
const bundle = buildPairRoutes({ /* ... */ });
|
|
63
|
+
api.registerHttpRoute!({ path: bundle.finishPath, /*...*/, auth: 'plugin' });
|
|
64
|
+
api.registerHttpRoute!({ path: bundle.startPath, /*...*/, auth: 'plugin' });
|
|
65
|
+
api.registerHttpRoute!({ path: bundle.respondPath,/*...*/, auth: 'plugin' });
|
|
66
|
+
api.registerHttpRoute!({ path: bundle.statusPath, /*...*/, auth: 'plugin' });
|
|
67
|
+
// ^^ these 4 pushes happen AFTER register() has returned + the
|
|
68
|
+
// SDK loader has already activated the (empty) route registry.
|
|
69
|
+
} catch (err) { /* ... */ }
|
|
70
|
+
})();
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**After (rc.5):**
|
|
75
|
+
```ts
|
|
76
|
+
// top of file
|
|
77
|
+
import { buildPairRoutes } from './pair-http.js';
|
|
78
|
+
import { validateMnemonic } from '@scure/bip39';
|
|
79
|
+
import { wordlist } from '@scure/bip39/wordlists/english.js';
|
|
80
|
+
// ... fs-helpers import now also includes writeOnboardingState
|
|
81
|
+
|
|
82
|
+
// inside register(api)
|
|
83
|
+
if (typeof api.registerHttpRoute === 'function') {
|
|
84
|
+
const bundle = buildPairRoutes({ /* ... */ });
|
|
85
|
+
api.registerHttpRoute!({ path: bundle.finishPath, /*...*/, auth: 'plugin' });
|
|
86
|
+
api.registerHttpRoute!({ path: bundle.startPath, /*...*/, auth: 'plugin' });
|
|
87
|
+
api.registerHttpRoute!({ path: bundle.respondPath, /*...*/, auth: 'plugin' });
|
|
88
|
+
api.registerHttpRoute!({ path: bundle.statusPath, /*...*/, auth: 'plugin' });
|
|
89
|
+
// ^^ these 4 pushes happen synchronously BEFORE register() returns,
|
|
90
|
+
// i.e. BEFORE the SDK loader activates the registry.
|
|
91
|
+
api.logger.info('TotalReclaw: registered 4 QR-pairing HTTP routes synchronously');
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- **`skill/plugin/pair-http-route-registration.test.ts` — rc.5 regression
|
|
96
|
+
guard**. The existing SIMULATION suite (27 assertions covering the 4
|
|
97
|
+
routes' `auth` literal, path shape, handler type) is preserved. Added
|
|
98
|
+
a new SYNCHRONY suite (14 assertions) that invokes `plugin.register(mockApi)`
|
|
99
|
+
with a minimal mocked OpenClaw API and asserts `mockApi.registerHttpRoute`
|
|
100
|
+
has been called 4 times IMMEDIATELY after `register()` returns — no
|
|
101
|
+
`await`, no tick wait. This assertion would fail under the rc.4 async-IIFE
|
|
102
|
+
implementation and guards against any future refactor that re-introduces
|
|
103
|
+
an async boundary at the registration site. Total: 41/41 passing.
|
|
104
|
+
|
|
7
105
|
## [3.3.0-rc.4] — 2026-04-20
|
|
8
106
|
|
|
9
107
|
Fourth release candidate for 3.3.0. Two independent ship-stopper fixes for
|
package/index.ts
CHANGED
|
@@ -133,10 +133,14 @@ import {
|
|
|
133
133
|
isRunningInDocker,
|
|
134
134
|
deleteFileIfExists,
|
|
135
135
|
resolveOnboardingState,
|
|
136
|
+
writeOnboardingState,
|
|
136
137
|
type OnboardingState,
|
|
137
138
|
} from './fs-helpers.js';
|
|
138
139
|
import { decideToolGate, isGatedToolName } from './tool-gating.js';
|
|
139
140
|
import { detectFirstRun, buildWelcomePrepend, type GatewayMode } from './first-run.js';
|
|
141
|
+
import { buildPairRoutes } from './pair-http.js';
|
|
142
|
+
import { validateMnemonic } from '@scure/bip39';
|
|
143
|
+
import { wordlist } from '@scure/bip39/wordlists/english.js';
|
|
140
144
|
import crypto from 'node:crypto';
|
|
141
145
|
|
|
142
146
|
// ---------------------------------------------------------------------------
|
|
@@ -2711,62 +2715,64 @@ const plugin = {
|
|
|
2711
2715
|
// encrypted mnemonic payload, and expose a status polled by the
|
|
2712
2716
|
// CLI. See pair-http.ts and the 2026-04-20 design doc.
|
|
2713
2717
|
if (typeof api.registerHttpRoute === 'function') {
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2718
|
+
// rc.5 — the 4 `registerHttpRoute` calls MUST happen synchronously inside
|
|
2719
|
+
// `register(api)` because the SDK loader freezes the plugin's HTTP-route
|
|
2720
|
+
// registry as soon as `register()` returns. In rc.2–rc.4 this block was
|
|
2721
|
+
// wrapped in a fire-and-forget async IIFE whose `await import(...)`
|
|
2722
|
+
// settled one microtask AFTER the loader had already activated the
|
|
2723
|
+
// (empty) route list — the post-activation pushes landed on the
|
|
2724
|
+
// dispatcher's "inactive" copy and `openclaw plugins inspect
|
|
2725
|
+
// totalreclaw --json | jq .httpRouteCount` returned 0. See
|
|
2726
|
+
// `docs/notes/QA-plugin-3.3.0-rc.4-20260420-1517.md` (internal#21).
|
|
2727
|
+
// Moving `buildPairRoutes`, `@scure/bip39`, and `fs-helpers`
|
|
2728
|
+
// `writeOnboardingState` to static top-of-file imports keeps the
|
|
2729
|
+
// registration site synchronous and makes the call order deterministic.
|
|
2730
|
+
// `completePairing` remains async (it does disk I/O) — that is fine,
|
|
2731
|
+
// since `registerHttpRoute` accepts async handlers; only the
|
|
2732
|
+
// REGISTRATION must be synchronous.
|
|
2733
|
+
const bundle = buildPairRoutes({
|
|
2734
|
+
sessionsPath: CONFIG.pairSessionsPath,
|
|
2735
|
+
apiBase: '/plugin/totalreclaw/pair',
|
|
2736
|
+
logger: api.logger,
|
|
2737
|
+
validateMnemonic: (p) => validateMnemonic(p, wordlist),
|
|
2738
|
+
completePairing: async ({ mnemonic }) => {
|
|
2739
|
+
// Write credentials.json + flip state to 'active' via
|
|
2740
|
+
// fs-helpers. This centralizes disk I/O off the
|
|
2741
|
+
// pair-http surface (scanner isolation).
|
|
2742
|
+
const creds = loadCredentialsJson(CREDENTIALS_PATH) ?? {};
|
|
2743
|
+
const next = { ...creds, mnemonic };
|
|
2744
|
+
if (!writeCredentialsJson(CREDENTIALS_PATH, next)) {
|
|
2745
|
+
return { state: 'error', error: 'credentials_write_failed' };
|
|
2746
|
+
}
|
|
2747
|
+
// Hot-reload: update the runtime override so existing
|
|
2748
|
+
// in-memory state picks up the new phrase without a
|
|
2749
|
+
// process restart.
|
|
2750
|
+
setRecoveryPhraseOverride(mnemonic);
|
|
2751
|
+
// Flip onboarding state.
|
|
2752
|
+
writeOnboardingState(CONFIG.onboardingStatePath, {
|
|
2753
|
+
onboardingState: 'active',
|
|
2754
|
+
createdBy: 'generate',
|
|
2755
|
+
credentialsCreatedAt: new Date().toISOString(),
|
|
2756
|
+
version: '3.3.0',
|
|
2749
2757
|
});
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
}
|
|
2769
|
-
})();
|
|
2758
|
+
return { state: 'active' };
|
|
2759
|
+
},
|
|
2760
|
+
});
|
|
2761
|
+
// auth: 'plugin' — the 4 pair routes are reached from the operator's
|
|
2762
|
+
// phone/laptop browser, which has no gateway bearer token. The plugin
|
|
2763
|
+
// authenticates each request itself via (a) the in-memory pair session
|
|
2764
|
+
// (sid + secondaryCode + single-use consumption), (b) ECDH + AEAD for
|
|
2765
|
+
// the encrypted mnemonic payload. See gateway-cli dist
|
|
2766
|
+
// `matchedPluginRoutesRequireGatewayAuth` / `enforcePluginRouteGatewayAuth`
|
|
2767
|
+
// — routes with `auth: 'gateway'` require a bearer token and 401 any
|
|
2768
|
+
// browser caller, which is the wrong semantic for QR-pair. rc.3
|
|
2769
|
+
// shipped `auth: 'gateway'` and the QA agent confirmed the routes
|
|
2770
|
+
// were unreachable from a browser (QA-plugin-3.3.0-rc.3 report).
|
|
2771
|
+
api.registerHttpRoute!({ path: bundle.finishPath, handler: bundle.handlers.finish, auth: 'plugin' });
|
|
2772
|
+
api.registerHttpRoute!({ path: bundle.startPath, handler: bundle.handlers.start, auth: 'plugin' });
|
|
2773
|
+
api.registerHttpRoute!({ path: bundle.respondPath, handler: bundle.handlers.respond, auth: 'plugin' });
|
|
2774
|
+
api.registerHttpRoute!({ path: bundle.statusPath, handler: bundle.handlers.status, auth: 'plugin' });
|
|
2775
|
+
api.logger.info('TotalReclaw: registered 4 QR-pairing HTTP routes synchronously');
|
|
2770
2776
|
} else {
|
|
2771
2777
|
api.logger.warn(
|
|
2772
2778
|
'api.registerHttpRoute is unavailable on this OpenClaw version — /totalreclaw pair will not work. ' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totalreclaw/totalreclaw",
|
|
3
|
-
"version": "3.3.0-rc.
|
|
3
|
+
"version": "3.3.0-rc.5",
|
|
4
4
|
"description": "End-to-end encrypted, agent-portable memory for OpenClaw and any LLM-agent runtime. XChaCha20-Poly1305 with protobuf v4 + on-chain Memory Taxonomy v1 (claim / preference / directive / commitment / episode / summary).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|