@totalreclaw/totalreclaw 3.3.11-rc.4 → 3.3.11-rc.6
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 +38 -0
- package/SKILL.md +14 -4
- package/dist/tr-cli.js +1 -1
- package/dist/trajectory-poller.js +46 -1
- package/package.json +1 -1
- package/skill.json +1 -1
- package/tr-cli.ts +1 -1
- package/trajectory-poller.ts +47 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ 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.11-rc.6] — 2026-05-07
|
|
8
|
+
|
|
9
|
+
UX fix: mandatory ack-before-first-tool-call on install. Pedro's rc.5 user QA found the agent went silent for ~60 s while running `openclaw plugins install` before emitting the first user-visible line. From the user's POV the prompt looked unanswered. This RC strengthens SKILL.md + the quickstart guide to require line 1 BEFORE the first shell tool call, with reassuring wait-time copy.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **SKILL.md user-visible line 1 reworded** from `Installing TotalReclaw…` to `Setting up TotalReclaw — this takes about a minute…` — adds explicit wait-time signal so the user knows to wait rather than resend.
|
|
14
|
+
- **New "MANDATORY ACK BEFORE FIRST TOOL CALL" section** in SKILL.md top-level rules. Calls out forbidden vs required ordering: line 1 must precede any shell tool call (no `openclaw plugins install` → 60 s silent → emit line 1).
|
|
15
|
+
- **Silence rules updated**: skipping line 1 entirely is now explicitly forbidden (previously the rule only forbade extra narration between lines, not omission of line 1).
|
|
16
|
+
- **`docs/guides/openclaw-setup-quickstart.md` mirrors the change** with the same wait-time copy and an explicit "Line 1 fires BEFORE the first shell tool call — not after" sub-section.
|
|
17
|
+
|
|
18
|
+
### Implementation notes
|
|
19
|
+
|
|
20
|
+
- 4 new assertions in `skill-md-hybrid-primary.test.ts`: MANDATORY ACK section present, line 1 wait-time wording, Forbidden vs Required order documented, "Skipping line 1 entirely" listed as forbidden. 41/41 green.
|
|
21
|
+
- All other test suites unchanged: 92/92 fs-helpers + 21/21 register-command-name + 21/21 tr-cli-json + 44/44 trajectory-poller + 10/10 manifest-shape. check-scanner: 129 files, 0 flags.
|
|
22
|
+
- Pure documentation + spec change. No runtime code modified. Same poller behavior as rc.5.
|
|
23
|
+
|
|
24
|
+
## [3.3.11-rc.5] — 2026-05-07
|
|
25
|
+
|
|
26
|
+
Trajectory poller hardening: cap extractions per poll iteration + skip stale trajectory files. Pedro's 2026-05-07 zai 429 cascade was caused by ~5 old session files all crossing the extract threshold in the same poll → 5 back-to-back LLM calls in seconds → daily quota tripped. Today's chat memories were lost because every extraction call returned 0 facts (LLM rejected with rate-limit).
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Cap = 1 extraction per poll iteration.** When multiple session files cross the extract-interval threshold in the same 60 s poll, only the first fires the LLM call. The rest defer to subsequent polls. Their `turnsAccum` and `offset` state is preserved, so they don't lose progress — they just stagger. With the default 60 s poll interval, 5 backlogged files take 5 minutes to drain instead of 5 seconds. Free-tier LLM rate limits don't trip.
|
|
31
|
+
- **Stale-trajectory skip (>7 days mtime).** A user installing TotalReclaw on a host with months of OpenClaw session log history won't get a retroactive extraction backlog. Files with mtime older than 7 days are baseline-snapshotted (offset captured) but skip the extraction path entirely. If the user later resumes an old session, the offset is current and only net-new content extracts.
|
|
32
|
+
|
|
33
|
+
### Implementation notes
|
|
34
|
+
|
|
35
|
+
- `MAX_EXTRACTIONS_PER_POLL = 1` constant in `trajectory-poller.ts`. Sequential `for` loop already guarantees serialization within a poll; the cap just stops issuing additional LLM calls once the first one completes.
|
|
36
|
+
- `STALE_TRAJECTORY_AGE_MS = 7 * 24 * 60 * 60 * 1000`. Stale files get a one-time offset record (`state[file] = {offset: stat.size, turnsAccum: 0}`) so they're never re-stat'd repeatedly. If the user re-engages an old session (writes new content → mtime refreshes), the stale-skip check fails and normal extraction resumes from the recorded offset.
|
|
37
|
+
- 4 new test cases in `trajectory-poller.test.ts`: cap=1 multi-session deferral, second-poll-no-op-after-deferred-cap, stale-file skip (>7 days), recent-file (<7 days) NOT skipped. 44/44 green.
|
|
38
|
+
- 92/92 fs-helpers + 21/21 register-command-name + 37/37 skill-md + 21/21 tr-cli-json + 44/44 trajectory-poller + 10/10 manifest-shape. check-scanner: 129 files, 0 flags.
|
|
39
|
+
|
|
40
|
+
### Won't fix here
|
|
41
|
+
|
|
42
|
+
- Pop-os specific delayed-load anomaly (~58 min from rc.4 install to plugin first-load) — couldn't reproduce in fresh container; pop-os has accumulated state from many install/wipe cycles. Auto-QA confirmed clean fresh-install path.
|
|
43
|
+
- The retroactive-backlog scenario itself remains: if a user has ACTIVE sessions (mtime < 7 days) with high turn counts, they'll still drain over multiple polls. With cap=1 and 60 s interval, 5 active-but-backlogged files = 5 minutes to drain. Acceptable.
|
|
44
|
+
|
|
7
45
|
## [3.3.11-rc.4] — 2026-05-07
|
|
8
46
|
|
|
9
47
|
Fix #4 added to `patchOpenClawConfig()`: the **populated-allowlist plugin-skip bug** that broke every realistic install path.
|
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 tr CLI for remember / recall. Trigger on 'install TotalReclaw', 'set up TotalReclaw', 'restore my recovery phrase', or any remember / recall request."
|
|
4
|
-
version: 3.3.11-rc.
|
|
4
|
+
version: 3.3.11-rc.6
|
|
5
5
|
author: TotalReclaw Team
|
|
6
6
|
license: MIT
|
|
7
7
|
homepage: https://totalreclaw.xyz
|
|
@@ -61,6 +61,15 @@ The word "pair" is internal jargon — never use it in user-visible prose. The u
|
|
|
61
61
|
|
|
62
62
|
**Emit ONLY the user-visible lines listed below. Do not narrate tool calls. Do not describe what you're doing or about to do. Errors are the only allowed deviation, and only as a single tight line.** The expected user-facing transcript for a healthy install is **4–5 short lines TOTAL**. Anything more is a SKILL violation. Telegram and similar transports edit the bot's messages live as your tool calls progress — every interim "Let me check…" / "Plugin loaded. Let me verify…" line shows up to the user as awkward edit churn, even if you "delete" it. The fix is to never emit it in the first place.
|
|
63
63
|
|
|
64
|
+
### ⚡ MANDATORY ACK BEFORE FIRST TOOL CALL
|
|
65
|
+
|
|
66
|
+
**User-visible line 1 MUST be your FIRST output — emitted BEFORE you run any shell tool, plugin install, or `tr` command.** Install + setup takes 1–2 minutes of silent tool-execution; without an immediate ack the user assumes the request didn't register and may resend or abandon. The ack tells them work is happening.
|
|
67
|
+
|
|
68
|
+
The ack is unconditional. It fires the moment you decide to install / set up TotalReclaw — even before you've resolved `TR_CLI`, even before you've checked whether the plugin is already installed, even before the first `openclaw plugins install` shell call. Send line 1, THEN start the work.
|
|
69
|
+
|
|
70
|
+
Forbidden order (silent-then-narrate): ❌ run `openclaw plugins install` → wait 60s → emit line 1.
|
|
71
|
+
Required order (ack-then-work): ✅ emit line 1 → run `openclaw plugins install` silently → emit line 2 when `tr status` returns.
|
|
72
|
+
|
|
64
73
|
## ⛔ FORBIDDEN ACTIONS (4 hard rules from 2026-05-05 QA)
|
|
65
74
|
|
|
66
75
|
These specific actions break the pair flow. Everything else is governed by the "emit only the numbered user-visible lines" rule above.
|
|
@@ -93,8 +102,8 @@ Then invoke as `node "$TR_CLI" <cmd> [--json]`.
|
|
|
93
102
|
|
|
94
103
|
Speak only these lines, in order. Substitute the bracketed placeholders with the real values. No prefaces. No suffixes. No narration.
|
|
95
104
|
|
|
96
|
-
1. (
|
|
97
|
-
>
|
|
105
|
+
1. (FIRST output, emitted BEFORE any shell tool call — see "MANDATORY ACK" rule above)
|
|
106
|
+
> Setting up TotalReclaw — this takes about a minute…
|
|
98
107
|
2. (after install + CLI verify — `tr status` works)
|
|
99
108
|
> ✓ Installed (totalreclaw <version>, hybrid mode)
|
|
100
109
|
3. (account-setup URL + PIN, IMMEDIATELY after `tr pair --json` returns — no consent gate)
|
|
@@ -114,8 +123,9 @@ Do NOT emit transitional / narrative lines between any of the four user-visible
|
|
|
114
123
|
- ❌ "Need to pair. Let me kick that off:" → silent. Just run the `setsid -f` block. Emit line 3 when URL+PIN returns. ALSO never use the word "pair" in user-facing text — see vocabulary table above.
|
|
115
124
|
- ❌ "Standing by." → silent. Acknowledgements between lines are noise.
|
|
116
125
|
- ❌ Any "Let me X" / "Now I'll Y" / "Just give me a second" prelude.
|
|
126
|
+
- ❌ **Skipping line 1 entirely and going straight to shell tool calls.** Line 1 ack must precede the first tool call. Silent install = user thinks the prompt didn't register.
|
|
117
127
|
|
|
118
|
-
The transcript Pedro should see is exactly four messages — the four numbered lines, nothing in between.
|
|
128
|
+
The transcript Pedro should see is exactly four messages — the four numbered lines, nothing in between. Line 1 fires immediately. Lines 2–4 fire as work completes. No prose between.
|
|
119
129
|
|
|
120
130
|
## How does TotalReclaw work? (canonical answer for user questions)
|
|
121
131
|
|
package/dist/tr-cli.js
CHANGED
|
@@ -41,7 +41,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
|
|
|
41
41
|
// Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
|
|
42
42
|
// Do not edit by hand — running tests will catch drift but the publish workflow
|
|
43
43
|
// rewrites this constant at the start of every npm/ClawHub publish.
|
|
44
|
-
const PLUGIN_VERSION = '3.3.11-rc.
|
|
44
|
+
const PLUGIN_VERSION = '3.3.11-rc.6';
|
|
45
45
|
function die(msg, code = 1) {
|
|
46
46
|
process.stderr.write(`tr: ${msg}\n`);
|
|
47
47
|
process.exit(code);
|
|
@@ -41,6 +41,15 @@ import os from 'node:os';
|
|
|
41
41
|
import path from 'node:path';
|
|
42
42
|
const DEFAULT_POLL_INTERVAL_MS = 60_000;
|
|
43
43
|
const STATE_FILE = path.join(os.homedir(), '.totalreclaw', 'extract-state.json');
|
|
44
|
+
/**
|
|
45
|
+
* Skip trajectory files older than this. A user who installs
|
|
46
|
+
* TotalReclaw on a host with months of OpenClaw session log history
|
|
47
|
+
* shouldn't get a retroactive extraction backlog — we only care about
|
|
48
|
+
* ongoing chat from now forward (3.3.11-rc.5). Files with mtime older
|
|
49
|
+
* than this threshold get a one-time offset snapshot so they're never
|
|
50
|
+
* re-scanned, and skip the extraction path entirely.
|
|
51
|
+
*/
|
|
52
|
+
const STALE_TRAJECTORY_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
44
53
|
/**
|
|
45
54
|
* Start the trajectory poller. Runs an initial poll after 5 s, then
|
|
46
55
|
* every `pollIntervalMs` (default 60 s). Returns a handle the caller
|
|
@@ -62,15 +71,51 @@ export function startTrajectoryPoller(deps, opts = {}) {
|
|
|
62
71
|
return;
|
|
63
72
|
const extractInterval = deps.getExtractInterval();
|
|
64
73
|
let stateChanged = false;
|
|
74
|
+
// 3.3.11-rc.5: cap extractions per poll iteration. Pedro's
|
|
75
|
+
// 2026-05-07 zai 429 cascade was caused by N session files all
|
|
76
|
+
// crossing the extract threshold in the same poll → N back-to-back
|
|
77
|
+
// LLM calls trip the rate-limiter (especially on free tiers).
|
|
78
|
+
// With cap=1, extra files defer to the next poll iteration
|
|
79
|
+
// (60 s later by default). Their turnsAccum is preserved across
|
|
80
|
+
// polls so they don't lose progress.
|
|
81
|
+
let extractionsThisPoll = 0;
|
|
82
|
+
const MAX_EXTRACTIONS_PER_POLL = 1;
|
|
65
83
|
for (const file of files) {
|
|
84
|
+
// Stale-file skip: trajectory files untouched for STALE_TRAJECTORY_AGE_MS
|
|
85
|
+
// are likely abandoned sessions (old test runs, dead chats). Skip them
|
|
86
|
+
// entirely — don't burn LLM budget on extraction from stale content
|
|
87
|
+
// that the user has effectively forgotten about.
|
|
88
|
+
let mtimeMs = 0;
|
|
89
|
+
try {
|
|
90
|
+
mtimeMs = fs.statSync(file).mtimeMs;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (Date.now() - mtimeMs > STALE_TRAJECTORY_AGE_MS) {
|
|
96
|
+
// Lazy-record offset so we don't repeatedly re-scan stale files —
|
|
97
|
+
// if the user later resumes this session, the offset is already
|
|
98
|
+
// current and we'll only extract net-new content.
|
|
99
|
+
if (!state[file]) {
|
|
100
|
+
try {
|
|
101
|
+
state[file] = { offset: fs.statSync(file).size, turnsAccum: 0 };
|
|
102
|
+
stateChanged = true;
|
|
103
|
+
}
|
|
104
|
+
catch { /* ignore */ }
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
66
108
|
const lastEntry = state[file] ?? { offset: 0, turnsAccum: 0 };
|
|
67
109
|
const { messages, newOffset } = parseNewMessages(file, lastEntry.offset);
|
|
68
110
|
if (newOffset === lastEntry.offset)
|
|
69
111
|
continue; // nothing new
|
|
70
112
|
const turnsAdded = countTurns(messages);
|
|
71
113
|
const turnsAccum = lastEntry.turnsAccum + turnsAdded;
|
|
72
|
-
const shouldExtract = turnsAccum >= extractInterval &&
|
|
114
|
+
const shouldExtract = turnsAccum >= extractInterval &&
|
|
115
|
+
messages.length >= 2 &&
|
|
116
|
+
extractionsThisPoll < MAX_EXTRACTIONS_PER_POLL;
|
|
73
117
|
if (shouldExtract) {
|
|
118
|
+
extractionsThisPoll++;
|
|
74
119
|
deps.logger.info(`extractd: ${path.basename(file)} -> ${turnsAccum}/${extractInterval} turns; running extraction (${messages.length} messages)`);
|
|
75
120
|
const existing = deps.isDedupEnabled() ? await deps.getDedupCandidates(20, messages) : [];
|
|
76
121
|
const rawFacts = await deps.runExtraction(messages, 'turn', existing, undefined);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totalreclaw/totalreclaw",
|
|
3
|
-
"version": "3.3.11-rc.
|
|
3
|
+
"version": "3.3.11-rc.6",
|
|
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": [
|
package/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "totalreclaw",
|
|
3
|
-
"version": "3.3.11-rc.
|
|
3
|
+
"version": "3.3.11-rc.6",
|
|
4
4
|
"description": "End-to-end encrypted memory for AI agents — portable, yours forever. XChaCha20-Poly1305 E2EE: server never sees plaintext.",
|
|
5
5
|
"author": "TotalReclaw Team",
|
|
6
6
|
"license": "MIT",
|
package/tr-cli.ts
CHANGED
|
@@ -52,7 +52,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
|
|
|
52
52
|
// Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
|
|
53
53
|
// Do not edit by hand — running tests will catch drift but the publish workflow
|
|
54
54
|
// rewrites this constant at the start of every npm/ClawHub publish.
|
|
55
|
-
const PLUGIN_VERSION = '3.3.11-rc.
|
|
55
|
+
const PLUGIN_VERSION = '3.3.11-rc.6';
|
|
56
56
|
|
|
57
57
|
function die(msg: string, code = 1): never {
|
|
58
58
|
process.stderr.write(`tr: ${msg}\n`);
|
package/trajectory-poller.ts
CHANGED
|
@@ -127,6 +127,15 @@ export interface TrajectoryPollerHandle {
|
|
|
127
127
|
|
|
128
128
|
const DEFAULT_POLL_INTERVAL_MS = 60_000;
|
|
129
129
|
const STATE_FILE = path.join(os.homedir(), '.totalreclaw', 'extract-state.json');
|
|
130
|
+
/**
|
|
131
|
+
* Skip trajectory files older than this. A user who installs
|
|
132
|
+
* TotalReclaw on a host with months of OpenClaw session log history
|
|
133
|
+
* shouldn't get a retroactive extraction backlog — we only care about
|
|
134
|
+
* ongoing chat from now forward (3.3.11-rc.5). Files with mtime older
|
|
135
|
+
* than this threshold get a one-time offset snapshot so they're never
|
|
136
|
+
* re-scanned, and skip the extraction path entirely.
|
|
137
|
+
*/
|
|
138
|
+
const STALE_TRAJECTORY_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
130
139
|
|
|
131
140
|
/**
|
|
132
141
|
* Start the trajectory poller. Runs an initial poll after 5 s, then
|
|
@@ -152,17 +161,54 @@ export function startTrajectoryPoller(
|
|
|
152
161
|
|
|
153
162
|
const extractInterval = deps.getExtractInterval();
|
|
154
163
|
let stateChanged = false;
|
|
164
|
+
// 3.3.11-rc.5: cap extractions per poll iteration. Pedro's
|
|
165
|
+
// 2026-05-07 zai 429 cascade was caused by N session files all
|
|
166
|
+
// crossing the extract threshold in the same poll → N back-to-back
|
|
167
|
+
// LLM calls trip the rate-limiter (especially on free tiers).
|
|
168
|
+
// With cap=1, extra files defer to the next poll iteration
|
|
169
|
+
// (60 s later by default). Their turnsAccum is preserved across
|
|
170
|
+
// polls so they don't lose progress.
|
|
171
|
+
let extractionsThisPoll = 0;
|
|
172
|
+
const MAX_EXTRACTIONS_PER_POLL = 1;
|
|
155
173
|
|
|
156
174
|
for (const file of files) {
|
|
175
|
+
// Stale-file skip: trajectory files untouched for STALE_TRAJECTORY_AGE_MS
|
|
176
|
+
// are likely abandoned sessions (old test runs, dead chats). Skip them
|
|
177
|
+
// entirely — don't burn LLM budget on extraction from stale content
|
|
178
|
+
// that the user has effectively forgotten about.
|
|
179
|
+
let mtimeMs = 0;
|
|
180
|
+
try {
|
|
181
|
+
mtimeMs = fs.statSync(file).mtimeMs;
|
|
182
|
+
} catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (Date.now() - mtimeMs > STALE_TRAJECTORY_AGE_MS) {
|
|
186
|
+
// Lazy-record offset so we don't repeatedly re-scan stale files —
|
|
187
|
+
// if the user later resumes this session, the offset is already
|
|
188
|
+
// current and we'll only extract net-new content.
|
|
189
|
+
if (!state[file]) {
|
|
190
|
+
try {
|
|
191
|
+
state[file] = { offset: fs.statSync(file).size, turnsAccum: 0 };
|
|
192
|
+
stateChanged = true;
|
|
193
|
+
} catch { /* ignore */ }
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
157
198
|
const lastEntry = state[file] ?? { offset: 0, turnsAccum: 0 };
|
|
158
199
|
const { messages, newOffset } = parseNewMessages(file, lastEntry.offset);
|
|
159
200
|
if (newOffset === lastEntry.offset) continue; // nothing new
|
|
160
201
|
|
|
161
202
|
const turnsAdded = countTurns(messages);
|
|
162
203
|
const turnsAccum = lastEntry.turnsAccum + turnsAdded;
|
|
163
|
-
const shouldExtract =
|
|
204
|
+
const shouldExtract =
|
|
205
|
+
turnsAccum >= extractInterval &&
|
|
206
|
+
messages.length >= 2 &&
|
|
207
|
+
extractionsThisPoll < MAX_EXTRACTIONS_PER_POLL;
|
|
164
208
|
|
|
165
209
|
if (shouldExtract) {
|
|
210
|
+
extractionsThisPoll++;
|
|
211
|
+
|
|
166
212
|
deps.logger.info(
|
|
167
213
|
`extractd: ${path.basename(file)} -> ${turnsAccum}/${extractInterval} turns; running extraction (${messages.length} messages)`,
|
|
168
214
|
);
|