@totalreclaw/totalreclaw 3.3.12-rc.19 → 3.3.12-rc.20
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 +3 -1
- package/batch-gate.ts +9 -6
- package/billing-cache.ts +25 -20
- package/config.ts +5 -4
- package/dist/batch-gate.js +9 -6
- package/dist/billing-cache.js +19 -21
- package/dist/config.js +5 -4
- package/dist/fs-helpers.js +55 -148
- package/dist/index.js +31 -36
- package/dist/subgraph-store.js +37 -3
- package/dist/tr-cli.js +1 -1
- package/dist/trajectory-poller.js +86 -10
- package/fs-helpers.ts +67 -160
- package/index.ts +31 -32
- package/package.json +2 -2
- package/skill.json +1 -1
- package/subgraph-store.ts +43 -7
- package/tr-cli.ts +1 -1
- package/trajectory-poller.ts +93 -23
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.12-rc.
|
|
4
|
+
version: 3.3.12-rc.20
|
|
5
5
|
author: TotalReclaw Team
|
|
6
6
|
license: MIT
|
|
7
7
|
homepage: https://totalreclaw.xyz
|
|
@@ -38,6 +38,8 @@ Do not describe storage as `"local"`, `"local-only"`, `"stored locally"`, `"on d
|
|
|
38
38
|
|
|
39
39
|
The 12-word recovery phrase MUST NEVER enter chat or the agent's LLM context. The plugin's browser-based pair flow handles phrase generation and import. Never echo it, never ask the user to paste it, never pass it to any tool. If the user pastes one anyway, tell them it's compromised and re-pair for a fresh wallet.
|
|
40
40
|
|
|
41
|
+
**HARD guardrail — install/reinstall/upgrade flows:** During install, reinstall, upgrade, or any verification steps, the agent MUST NOT read, list, cat, show, display, or inspect `credentials.json`, `~/.totalreclaw/credentials.json`, `~/.totalreclaw*/credentials.json`, `~/.openclaw/plugins/totalreclaw/credentials.json`, or ANY credentials/phrase/key/secrets file — for ANY reason, including "verifying the install." These files contain encryption key material and Smart Account addresses that MUST NOT enter the LLM context. Never run `ls`, `find`, `cat`, `show`, or similar commands against plugin directories to "check files."
|
|
42
|
+
|
|
41
43
|
## Install
|
|
42
44
|
|
|
43
45
|
```
|
package/batch-gate.ts
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* Boot-time chain-gate predicate for client-side `executeBatch` UserOp
|
|
3
3
|
* submission. Spec #281 §9 Phase 1 (item imp-16).
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* The gate batches via `executeBatch` on Gnosis (chain 100). After ops-1
|
|
6
|
+
* (2026-06-05) BOTH tiers run on Gnosis, so both now batch — the chain==100
|
|
7
|
+
* check is retained as a safety guard (a non-100 chain, which shouldn't occur
|
|
8
|
+
* after ops-1, falls back to single-fact). The legacy Free ⇒ Base Sepolia
|
|
9
|
+
* (84532) single-fact path is gone with the retired two-tier routing (#402).
|
|
10
|
+
* The `TOTALRECLAW_GNOSIS_BATCH_ENABLED` env var is a hard kill-switch —
|
|
11
|
+
* setting it to `false` disables batching regardless of chain, so ops can
|
|
12
|
+
* revert to single-fact submission without a client redeploy. Behaviour of
|
|
13
|
+
* `shouldBatchOnChain` is unchanged by #402.
|
|
11
14
|
*
|
|
12
15
|
* Read at boot only (module-load time). Per-write reads would re-parse the
|
|
13
16
|
* env on every submission — too expensive for the auto-extraction hot path
|
package/billing-cache.ts
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* This module:
|
|
12
12
|
* - reads/writes `~/.totalreclaw/billing-cache.json` (path from CONFIG)
|
|
13
13
|
* - exports `BillingCache`, `BILLING_CACHE_PATH`, `BILLING_CACHE_TTL`
|
|
14
|
-
* - keeps the chain-id override in sync with the
|
|
15
|
-
*
|
|
14
|
+
* - keeps the chain-id override in sync with the relay's authoritative
|
|
15
|
+
* `chain_id` (after ops-1 both tiers are on Gnosis 100; the client consumes
|
|
16
|
+
* the relay value verbatim — see `syncChainIdFromBilling`)
|
|
16
17
|
* - does NOT import anything that performs outbound I/O
|
|
17
18
|
*
|
|
18
19
|
* Do NOT add any outbound-request call to this file — a single match for
|
|
@@ -50,6 +51,12 @@ export interface BillingCache {
|
|
|
50
51
|
max_facts_per_extraction?: number;
|
|
51
52
|
max_candidate_pool?: number;
|
|
52
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Authoritative chain id from the relay `/v1/billing/status` response.
|
|
56
|
+
* After ops-1 (2026-06-05) both tiers are on Gnosis (100); the relay is the
|
|
57
|
+
* source of truth, so the client consumes this verbatim (#402).
|
|
58
|
+
*/
|
|
59
|
+
chain_id?: number;
|
|
53
60
|
checked_at: number;
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -58,24 +65,21 @@ export interface BillingCache {
|
|
|
58
65
|
// ---------------------------------------------------------------------------
|
|
59
66
|
|
|
60
67
|
/**
|
|
61
|
-
* Apply the
|
|
68
|
+
* Apply the relay's authoritative `chain_id` to the runtime chain override.
|
|
62
69
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
70
|
+
* After ops-1 (2026-06-05) both tiers run on Gnosis (chain 100) and the relay
|
|
71
|
+
* returns an authoritative `chain_id` in `/v1/billing/status`. The client MUST
|
|
72
|
+
* consume that verbatim — the old tier→chain derivation (Free ⇒ 84532 Base
|
|
73
|
+
* Sepolia) was retired two-tier logic that mis-signed FREE-tier UserOps
|
|
74
|
+
* against the wrong chain and queried a Base-Sepolia RPC for a Gnosis-deployed
|
|
75
|
+
* sender, producing deterministic AA10 (#402).
|
|
67
76
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
77
|
+
* A missing / non-finite `chain_id` (older relay, partial payload) defaults to
|
|
78
|
+
* 100 — never 84532. Called from `readBillingCache` and `writeBillingCache` so
|
|
79
|
+
* every cache read or write keeps the override in sync. Idempotent.
|
|
71
80
|
*/
|
|
72
|
-
export function
|
|
73
|
-
|
|
74
|
-
setChainIdOverride(100);
|
|
75
|
-
} else {
|
|
76
|
-
// Free or unknown → reset to the default free-tier chain.
|
|
77
|
-
setChainIdOverride(84532);
|
|
78
|
-
}
|
|
81
|
+
export function syncChainIdFromBilling(chainId: number | undefined): void {
|
|
82
|
+
setChainIdOverride(typeof chainId === 'number' && Number.isFinite(chainId) ? chainId : 100);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// ---------------------------------------------------------------------------
|
|
@@ -95,8 +99,9 @@ export function readBillingCache(): BillingCache | null {
|
|
|
95
99
|
if (!fs.existsSync(BILLING_CACHE_PATH)) return null;
|
|
96
100
|
const raw = JSON.parse(fs.readFileSync(BILLING_CACHE_PATH, 'utf-8')) as BillingCache;
|
|
97
101
|
if (!raw.checked_at || Date.now() - raw.checked_at > BILLING_CACHE_TTL) return null;
|
|
98
|
-
// Keep chain override in sync with persisted
|
|
99
|
-
|
|
102
|
+
// Keep chain override in sync with the persisted authoritative chain_id
|
|
103
|
+
// across process restarts.
|
|
104
|
+
syncChainIdFromBilling(raw.chain_id);
|
|
100
105
|
return raw;
|
|
101
106
|
} catch {
|
|
102
107
|
return null;
|
|
@@ -118,5 +123,5 @@ export function writeBillingCache(cache: BillingCache): void {
|
|
|
118
123
|
}
|
|
119
124
|
// Sync chain override AFTER the write so in-process UserOp signing picks
|
|
120
125
|
// up the correct chain immediately, even if the disk write failed.
|
|
121
|
-
|
|
126
|
+
syncChainIdFromBilling(cache.chain_id);
|
|
122
127
|
}
|
package/config.ts
CHANGED
|
@@ -227,10 +227,11 @@ export const CONFIG = {
|
|
|
227
227
|
// TOTALRECLAW_DATA_EDGE_ADDRESS / TOTALRECLAW_ENTRYPOINT_ADDRESS /
|
|
228
228
|
// TOTALRECLAW_RPC_URL (undocumented; internal knobs).
|
|
229
229
|
//
|
|
230
|
-
// Reads the runtime override set
|
|
231
|
-
// Falls back to
|
|
232
|
-
//
|
|
233
|
-
//
|
|
230
|
+
// Reads the runtime override set from the relay's authoritative chain_id
|
|
231
|
+
// (syncChainIdFromBilling). Falls back to 100 (Gnosis) pre-billing-lookup —
|
|
232
|
+
// after ops-1 both tiers are on Gnosis, so 84532 is never the default (#402).
|
|
233
|
+
// Must be a getter, not a literal — a literal would freeze UserOps to the
|
|
234
|
+
// wrong chainId and fail signature validation at the bundler.
|
|
234
235
|
get chainId(): number {
|
|
235
236
|
return _chainIdOverride ?? 100;
|
|
236
237
|
},
|
package/dist/batch-gate.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* Boot-time chain-gate predicate for client-side `executeBatch` UserOp
|
|
3
3
|
* submission. Spec #281 §9 Phase 1 (item imp-16).
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* The gate batches via `executeBatch` on Gnosis (chain 100). After ops-1
|
|
6
|
+
* (2026-06-05) BOTH tiers run on Gnosis, so both now batch — the chain==100
|
|
7
|
+
* check is retained as a safety guard (a non-100 chain, which shouldn't occur
|
|
8
|
+
* after ops-1, falls back to single-fact). The legacy Free ⇒ Base Sepolia
|
|
9
|
+
* (84532) single-fact path is gone with the retired two-tier routing (#402).
|
|
10
|
+
* The `TOTALRECLAW_GNOSIS_BATCH_ENABLED` env var is a hard kill-switch —
|
|
11
|
+
* setting it to `false` disables batching regardless of chain, so ops can
|
|
12
|
+
* revert to single-fact submission without a client redeploy. Behaviour of
|
|
13
|
+
* `shouldBatchOnChain` is unchanged by #402.
|
|
11
14
|
*
|
|
12
15
|
* Read at boot only (module-load time). Per-write reads would re-parse the
|
|
13
16
|
* env on every submission — too expensive for the auto-extraction hot path
|
package/dist/billing-cache.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* This module:
|
|
12
12
|
* - reads/writes `~/.totalreclaw/billing-cache.json` (path from CONFIG)
|
|
13
13
|
* - exports `BillingCache`, `BILLING_CACHE_PATH`, `BILLING_CACHE_TTL`
|
|
14
|
-
* - keeps the chain-id override in sync with the
|
|
15
|
-
*
|
|
14
|
+
* - keeps the chain-id override in sync with the relay's authoritative
|
|
15
|
+
* `chain_id` (after ops-1 both tiers are on Gnosis 100; the client consumes
|
|
16
|
+
* the relay value verbatim — see `syncChainIdFromBilling`)
|
|
16
17
|
* - does NOT import anything that performs outbound I/O
|
|
17
18
|
*
|
|
18
19
|
* Do NOT add any outbound-request call to this file — a single match for
|
|
@@ -33,25 +34,21 @@ export const BILLING_CACHE_TTL = 2 * 60 * 60 * 1000; // 2 hours
|
|
|
33
34
|
// Chain-id sync
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
/**
|
|
36
|
-
* Apply the
|
|
37
|
+
* Apply the relay's authoritative `chain_id` to the runtime chain override.
|
|
37
38
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* After ops-1 (2026-06-05) both tiers run on Gnosis (chain 100) and the relay
|
|
40
|
+
* returns an authoritative `chain_id` in `/v1/billing/status`. The client MUST
|
|
41
|
+
* consume that verbatim — the old tier→chain derivation (Free ⇒ 84532 Base
|
|
42
|
+
* Sepolia) was retired two-tier logic that mis-signed FREE-tier UserOps
|
|
43
|
+
* against the wrong chain and queried a Base-Sepolia RPC for a Gnosis-deployed
|
|
44
|
+
* sender, producing deterministic AA10 (#402).
|
|
42
45
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
+
* A missing / non-finite `chain_id` (older relay, partial payload) defaults to
|
|
47
|
+
* 100 — never 84532. Called from `readBillingCache` and `writeBillingCache` so
|
|
48
|
+
* every cache read or write keeps the override in sync. Idempotent.
|
|
46
49
|
*/
|
|
47
|
-
export function
|
|
48
|
-
|
|
49
|
-
setChainIdOverride(100);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// Free or unknown → reset to the default free-tier chain.
|
|
53
|
-
setChainIdOverride(84532);
|
|
54
|
-
}
|
|
50
|
+
export function syncChainIdFromBilling(chainId) {
|
|
51
|
+
setChainIdOverride(typeof chainId === 'number' && Number.isFinite(chainId) ? chainId : 100);
|
|
55
52
|
}
|
|
56
53
|
// ---------------------------------------------------------------------------
|
|
57
54
|
// Read / write
|
|
@@ -71,8 +68,9 @@ export function readBillingCache() {
|
|
|
71
68
|
const raw = JSON.parse(fs.readFileSync(BILLING_CACHE_PATH, 'utf-8'));
|
|
72
69
|
if (!raw.checked_at || Date.now() - raw.checked_at > BILLING_CACHE_TTL)
|
|
73
70
|
return null;
|
|
74
|
-
// Keep chain override in sync with persisted
|
|
75
|
-
|
|
71
|
+
// Keep chain override in sync with the persisted authoritative chain_id
|
|
72
|
+
// across process restarts.
|
|
73
|
+
syncChainIdFromBilling(raw.chain_id);
|
|
76
74
|
return raw;
|
|
77
75
|
}
|
|
78
76
|
catch {
|
|
@@ -96,5 +94,5 @@ export function writeBillingCache(cache) {
|
|
|
96
94
|
}
|
|
97
95
|
// Sync chain override AFTER the write so in-process UserOp signing picks
|
|
98
96
|
// up the correct chain immediately, even if the disk write failed.
|
|
99
|
-
|
|
97
|
+
syncChainIdFromBilling(cache.chain_id);
|
|
100
98
|
}
|
package/dist/config.js
CHANGED
|
@@ -208,10 +208,11 @@ export const CONFIG = {
|
|
|
208
208
|
// TOTALRECLAW_DATA_EDGE_ADDRESS / TOTALRECLAW_ENTRYPOINT_ADDRESS /
|
|
209
209
|
// TOTALRECLAW_RPC_URL (undocumented; internal knobs).
|
|
210
210
|
//
|
|
211
|
-
// Reads the runtime override set
|
|
212
|
-
// Falls back to
|
|
213
|
-
//
|
|
214
|
-
//
|
|
211
|
+
// Reads the runtime override set from the relay's authoritative chain_id
|
|
212
|
+
// (syncChainIdFromBilling). Falls back to 100 (Gnosis) pre-billing-lookup —
|
|
213
|
+
// after ops-1 both tiers are on Gnosis, so 84532 is never the default (#402).
|
|
214
|
+
// Must be a getter, not a literal — a literal would freeze UserOps to the
|
|
215
|
+
// wrong chainId and fail signature validation at the bundler.
|
|
215
216
|
get chainId() {
|
|
216
217
|
return _chainIdOverride ?? 100;
|
|
217
218
|
},
|
package/dist/fs-helpers.js
CHANGED
|
@@ -791,14 +791,11 @@ export function resolveOnboardingState(credentialsPath, statePath) {
|
|
|
791
791
|
* Auto-patch `~/.openclaw/openclaw.json` with the entries required by
|
|
792
792
|
* OpenClaw 2026.5.x for clean operation (issues #225 + #226 + verbosity):
|
|
793
793
|
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
* crash loop ("plugins.slots.memory: plugin not found: totalreclaw")
|
|
800
|
-
* that survives container restarts until `openclaw plugins install`
|
|
801
|
-
* repopulates the install record.
|
|
794
|
+
* NOTE (rc.20, #402): this helper no longer writes `plugins.slots.memory`
|
|
795
|
+
* (OpenClaw 2026.6.8 claims the memory slot natively on install/enable) nor
|
|
796
|
+
* `plugins.installs` (the former Fix #6 self-heal — its record reader
|
|
797
|
+
* discards the whole installs map if any entry fails schema validation).
|
|
798
|
+
* Pre-existing values of both are left untouched. The remaining fixes are:
|
|
802
799
|
*
|
|
803
800
|
* 2. `plugins.entries.totalreclaw.hooks.allowConversationAccess = true`
|
|
804
801
|
* Grant the plugin access to `agent_end` and `before_agent_start`
|
|
@@ -812,15 +809,11 @@ export function resolveOnboardingState(credentialsPath, statePath) {
|
|
|
812
809
|
* this to "off" on first run for a clean UX. Existing explicit values
|
|
813
810
|
* ("partial", "block", "progress") are preserved.
|
|
814
811
|
*
|
|
815
|
-
* 4
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
* `bundledDiscovery: "compat"` restores the looser behavior so allow-
|
|
821
|
-
* listed non-bundled plugins load. Without this fix, users with
|
|
822
|
-
* telegram or any model provider configured before TR install (which
|
|
823
|
-
* populates allow) get a silent plugin-skip on every gateway boot.
|
|
812
|
+
* Fix #4 (`plugins.bundledDiscovery="compat"`) and Fix #5 (appending
|
|
813
|
+
* "totalreclaw" to `plugins.allow`) were RETIRED together in rc.20 (#402):
|
|
814
|
+
* native install manages the allowlist, and TR growing it was the only
|
|
815
|
+
* thing that forced strict-allowlist mode (the sole reason the compat
|
|
816
|
+
* workaround existed). Pre-existing values of both keys are left untouched.
|
|
824
817
|
*
|
|
825
818
|
* Design constraints
|
|
826
819
|
* ------------------
|
|
@@ -843,8 +836,8 @@ export function resolveOnboardingState(credentialsPath, statePath) {
|
|
|
843
836
|
* Defaults to `<home>/.openclaw/openclaw.json`.
|
|
844
837
|
*/
|
|
845
838
|
export function patchOpenClawConfig(configPath,
|
|
846
|
-
//
|
|
847
|
-
//
|
|
839
|
+
// Retained on the signature (register() passes it) but no longer consumed:
|
|
840
|
+
// the Fix #6 install-record self-heal it fed was retired in rc.20 (#402).
|
|
848
841
|
pluginVersion) {
|
|
849
842
|
const home = envHomeDir();
|
|
850
843
|
const target = configPath ?? path.join(home, '.openclaw', 'openclaw.json');
|
|
@@ -861,109 +854,44 @@ pluginVersion) {
|
|
|
861
854
|
cfg.plugins = {};
|
|
862
855
|
}
|
|
863
856
|
let mutated = false;
|
|
864
|
-
// --- Fix #6 (
|
|
857
|
+
// --- Fix #6 (installs self-heal) RETIRED in rc.20 (#402) ---
|
|
865
858
|
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
// `
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
//
|
|
859
|
+
// patchOpenClawConfig used to fabricate a `plugins.installs.totalreclaw`
|
|
860
|
+
// record (version/spec/source:"self-heal"/installedAt) whenever a version
|
|
861
|
+
// was passed and no record existed. Removed because it was inert at best
|
|
862
|
+
// and harmful at worst on OpenClaw 2026.6.8:
|
|
863
|
+
// (a) the record reader validates the WHOLE installs map against a
|
|
864
|
+
// schema where `source` must be a valid PluginInstallSource — the
|
|
865
|
+
// fabricated `source:"self-heal"` (and any source-less record) fails
|
|
866
|
+
// safeParse, discarding the ENTIRE installs map at load; and
|
|
867
|
+
// (b) the native installer never persists install records into
|
|
868
|
+
// openclaw.json (it strips them via withoutPluginInstallRecords and
|
|
869
|
+
// commits to the SQLite index, which always wins on merge).
|
|
870
|
+
// So the plugin no longer writes `plugins.installs` at all; a pre-existing
|
|
871
|
+
// record is left byte-identical. `pluginVersion` is retained on the
|
|
872
|
+
// signature (register() passes it) but is no longer consumed here.
|
|
873
|
+
void pluginVersion;
|
|
874
|
+
// --- Fix #5 (plugins.allow self-append) RETIRED in rc.20 (#402) ---
|
|
875
875
|
//
|
|
876
|
-
//
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
//
|
|
880
|
-
//
|
|
876
|
+
// patchOpenClawConfig used to append "totalreclaw" to a non-empty
|
|
877
|
+
// `plugins.allow`. Removed as a pair with Fix #4 (below): OpenClaw's native
|
|
878
|
+
// `persistPluginInstall` already calls `addInstalledPluginToAllowlist`, and
|
|
879
|
+
// an empty/absent allowlist means allow-all (no write needed). TR
|
|
880
|
+
// unconditionally growing an allowlist was the very thing that flipped
|
|
881
|
+
// hosts into strict-allowlist enforcement — which then required the Fix #4
|
|
882
|
+
// `bundledDiscovery:"compat"` workaround. Dropping both lets native install
|
|
883
|
+
// own the allowlist; existing hosts keep whatever is already on disk.
|
|
884
|
+
// --- Fix #1 (memory-slot write) RETIRED in rc.20 (#402) ---
|
|
881
885
|
//
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
: null;
|
|
892
|
-
if (!existingVersion) {
|
|
893
|
-
cfg.plugins.installs.totalreclaw = {
|
|
894
|
-
...(typeof existing === 'object' && existing !== null ? existing : {}),
|
|
895
|
-
version: pluginVersion,
|
|
896
|
-
spec: '@totalreclaw/totalreclaw',
|
|
897
|
-
source: 'self-heal',
|
|
898
|
-
installedAt: new Date().toISOString(),
|
|
899
|
-
};
|
|
900
|
-
mutated = true;
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
// --- Fix #5 (3.3.12-rc.3): plugins.allow includes "totalreclaw" ---
|
|
904
|
-
//
|
|
905
|
-
// OpenClaw 2026.5.x: when `plugins.allow` is a non-empty array, the
|
|
906
|
-
// gateway switches into strict-allowlist mode. Plugins NOT in the
|
|
907
|
-
// allow list are silently rejected at load time — even bundled ones
|
|
908
|
-
// are gated. Pedro's pop-os 2026-05-08 QA had `plugins.allow` =
|
|
909
|
-
// ['device-pair', 'google', 'telegram', 'zai'] AFTER `openclaw
|
|
910
|
-
// plugins install @totalreclaw/totalreclaw@rc` ran. The install
|
|
911
|
-
// command did NOT add 'totalreclaw' to the allow list. Plugin
|
|
912
|
-
// shipped as `disabled`. Setup never proceeded.
|
|
913
|
-
//
|
|
914
|
-
// Defensive: when allow is a non-empty array and 'totalreclaw' is
|
|
915
|
-
// not in it, append. Don't touch null/undefined allow (means
|
|
916
|
-
// auto-discover mode — plugin is reachable without explicit allow).
|
|
917
|
-
if (Array.isArray(cfg.plugins.allow) && cfg.plugins.allow.length > 0) {
|
|
918
|
-
if (!cfg.plugins.allow.includes('totalreclaw')) {
|
|
919
|
-
cfg.plugins.allow.push('totalreclaw');
|
|
920
|
-
mutated = true;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
// --- Fix #1: plugins.slots.memory = "totalreclaw" (gated on install) ---
|
|
924
|
-
//
|
|
925
|
-
// DEFENSIVE GATE (3.3.9-rc.4 — 2026-05-05): only write the slot when
|
|
926
|
-
// the plugin is genuinely INSTALLED (`plugins.installs.totalreclaw`
|
|
927
|
-
// present with a `version`). Writing the slot unconditionally
|
|
928
|
-
// produced a startup crash loop on Pedro's pop-os QA host on
|
|
929
|
-
// 2026-05-05 — after a config reset, `plugins.installs.totalreclaw`
|
|
930
|
-
// was missing but a previously-written `slots.memory = "totalreclaw"`
|
|
931
|
-
// had survived. OpenClaw's startup validator refuses to start with
|
|
932
|
-
//
|
|
933
|
-
// Gateway failed to start: Error: Invalid config at openclaw.json.
|
|
934
|
-
// plugins.slots.memory: plugin not found: totalreclaw
|
|
935
|
-
// Run "openclaw doctor --fix" to repair, then retry.
|
|
936
|
-
//
|
|
937
|
-
// The container restart-loop drained ~13 attempts (12:10-12:23 UTC)
|
|
938
|
-
// until `openclaw plugins install` was re-run and re-populated
|
|
939
|
-
// `plugins.installs.totalreclaw`. With this gate, future installs
|
|
940
|
-
// that wipe `plugins.installs` (config reset, `doctor --fix`,
|
|
941
|
-
// migration tools) cannot regress into the same boot loop — slot is
|
|
942
|
-
// only ever written when the install record exists, and the install
|
|
943
|
-
// record is the install pipeline's authoritative signal that the
|
|
944
|
-
// plugin is on disk and registered with the gateway.
|
|
945
|
-
//
|
|
946
|
-
// The hooks patch (Fix #2) and Telegram streaming patch (Fix #3) are
|
|
947
|
-
// not gated this way — they write under `plugins.entries` and
|
|
948
|
-
// `channels` which are inert without an install record, so they can
|
|
949
|
-
// never trip the validator.
|
|
950
|
-
const installsRoot = cfg.plugins.installs;
|
|
951
|
-
const installEntry = typeof installsRoot === 'object' && installsRoot !== null
|
|
952
|
-
? installsRoot.totalreclaw
|
|
953
|
-
: undefined;
|
|
954
|
-
const pluginIsInstalled = typeof installEntry === 'object'
|
|
955
|
-
&& installEntry !== null
|
|
956
|
-
&& typeof installEntry.version === 'string'
|
|
957
|
-
&& installEntry.version.length > 0;
|
|
958
|
-
if (pluginIsInstalled) {
|
|
959
|
-
if (typeof cfg.plugins.slots !== 'object' || cfg.plugins.slots === null) {
|
|
960
|
-
cfg.plugins.slots = {};
|
|
961
|
-
}
|
|
962
|
-
if (cfg.plugins.slots.memory !== 'totalreclaw') {
|
|
963
|
-
cfg.plugins.slots.memory = 'totalreclaw';
|
|
964
|
-
mutated = true;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
886
|
+
// patchOpenClawConfig used to write `plugins.slots.memory = "totalreclaw"`
|
|
887
|
+
// (install-gated, to dodge a startup crash loop). OpenClaw 2026.6.8 now
|
|
888
|
+
// claims the memory slot NATIVELY during `plugins install`/`enable` (its
|
|
889
|
+
// persistPluginInstall "slot selection" phase), so the hand-written write
|
|
890
|
+
// was redundant on the good path and twice failed in production. The
|
|
891
|
+
// plugin no longer touches the memory slot — a pre-existing value is left
|
|
892
|
+
// byte-identical. Fix numbering below is preserved for continuity with the
|
|
893
|
+
// #225/#226 history. The Fix #6 install-record self-heal was retired
|
|
894
|
+
// above too (rc.20, #402).
|
|
967
895
|
// --- Fix #2: plugins.entries.totalreclaw.hooks.allowConversationAccess = true ---
|
|
968
896
|
if (typeof cfg.plugins.entries !== 'object' || cfg.plugins.entries === null) {
|
|
969
897
|
cfg.plugins.entries = {};
|
|
@@ -1006,36 +934,15 @@ pluginVersion) {
|
|
|
1006
934
|
}
|
|
1007
935
|
}
|
|
1008
936
|
}
|
|
1009
|
-
// --- Fix #4
|
|
937
|
+
// --- Fix #4 (plugins.bundledDiscovery="compat") RETIRED in rc.20 (#402) ---
|
|
1010
938
|
//
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
// despite it being in the allow list. `openclaw doctor --fix` cures it
|
|
1019
|
-
// by setting `bundledDiscovery: "compat"`, but users shouldn't need
|
|
1020
|
-
// to run doctor manually for the plugin to load.
|
|
1021
|
-
//
|
|
1022
|
-
// Fix: when `plugins.allow` is a non-empty array AND
|
|
1023
|
-
// `plugins.bundledDiscovery` is unset, set it to "compat". If the user
|
|
1024
|
-
// explicitly chose "allowlist" (the stricter mode), preserve their
|
|
1025
|
-
// choice — only first-run defaults are touched.
|
|
1026
|
-
//
|
|
1027
|
-
// This bug was missed by the auto-QA harness because the harness ran
|
|
1028
|
-
// on a fresh canonical container with `plugins.allow=null`, hitting
|
|
1029
|
-
// the auto-discover code path. Real users with telegram + a model
|
|
1030
|
-
// provider configured before TR install have a populated allow list,
|
|
1031
|
-
// hitting the strict-mode path. The auto-QA harness in 3.3.11-rc.4
|
|
1032
|
-
// adds a populated-allow scenario to catch future regressions.
|
|
1033
|
-
if (Array.isArray(cfg.plugins.allow) && cfg.plugins.allow.length > 0) {
|
|
1034
|
-
if (cfg.plugins.bundledDiscovery === undefined) {
|
|
1035
|
-
cfg.plugins.bundledDiscovery = 'compat';
|
|
1036
|
-
mutated = true;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
939
|
+
// patchOpenClawConfig used to set `plugins.bundledDiscovery = "compat"`
|
|
940
|
+
// whenever `plugins.allow` was populated, to keep non-bundled totalreclaw
|
|
941
|
+
// loadable under strict-allowlist mode. Retired as a pair with Fix #5: the
|
|
942
|
+
// ONLY reason a host was in strict-allowlist mode was that TR itself grew
|
|
943
|
+
// the allowlist (Fix #5). With Fix #5 gone, native install manages the
|
|
944
|
+
// allowlist and this compat workaround is unnecessary. An existing
|
|
945
|
+
// `bundledDiscovery` value on disk is left untouched.
|
|
1039
946
|
if (!mutated)
|
|
1040
947
|
return 'unchanged';
|
|
1041
948
|
// Write back with 2-space indent to match OpenClaw's own write style.
|