@totalreclaw/totalreclaw 3.3.1 → 3.3.2-rc.2

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 CHANGED
@@ -4,7 +4,83 @@ 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]
7
+ ## [3.3.2-rc.1] — 2026-04-27
8
+
9
+ Hotfix bundle for the inside-gateway agent-flow ship-stoppers caught by the
10
+ 2026-04-27 user QA against stable 3.3.1 (umbrella issue #182). The four fixes
11
+ combined unblock the agent-driven canonical install path.
12
+
13
+ ### Added — filesystem load manifest (issue #186)
14
+
15
+ The plugin now writes `.loaded.json` and `.error.json` into its own
16
+ extension directory at register-time. The agent has no working CLI inside
17
+ the gateway (issue #182 finding F1 — `openclaw plugins list` hangs in
18
+ some Docker setups), so the manifests are the canonical filesystem signal
19
+ that register() ran to completion AND which tools the SDK saw.
20
+
21
+ - `~/.openclaw/extensions/totalreclaw/.loaded.json` —
22
+ `{loadedAt: <ms>, tools: [<name>...], version: <semver>}`. Written
23
+ synchronously at the end of `register(api)`. Captures every tool name
24
+ passed to `api.registerTool` during the call.
25
+ - `~/.openclaw/extensions/totalreclaw/.error.json` —
26
+ `{loadedAt, error, stack?, version?}`. Written from the try/catch
27
+ surrounding the register() body when register() throws. Successful
28
+ boots clear any stale `.error.json`; failed boots preserve any prior
29
+ `.loaded.json` so the agent can compare timestamps.
30
+
31
+ Synchronous writes only (same constraint as `registerHttpRoute` — the SDK
32
+ freezes plugin registries the moment register() returns; an async write
33
+ would race that freeze and the manifest could miss late tool
34
+ registrations).
35
+
36
+ Regression: `load-manifest.test.ts` (22 assertions).
37
+
38
+ ### Added — `totalreclaw_pair` declared in skill manifest (issue #185)
39
+
40
+ The `totalreclaw_pair` tool is now advertised in `skill.json` alongside
41
+ `totalreclaw_remember`/`recall`/`forget`/`export`/`status`/`consolidate`/
42
+ `upgrade`/`import_from`. Previously plugin-only — if the plugin runtime
43
+ load failed silently (e.g. dep race in #188), the tool never appeared
44
+ in the agent's toolset and the canonical setup flow was unreachable.
45
+
46
+ The skill-side declaration ensures the tool name is visible in the
47
+ skill registry advertisement even when plugin runtime issues prevent
48
+ binding. The implementation remains in the plugin (browser-side
49
+ e2e-encrypted recovery-phrase flow); only the declaration moves up.
50
+
51
+ ### Added — atomic dependency validation in postinstall (issue #188)
52
+
53
+ `postinstall.mjs` is now a real lifecycle script (replacing the inline
54
+ `node -e` shim). After `npm install`, it require()s every critical dep
55
+ (`@scure/bip39`, `@scure/bip39/wordlists/english.js`, `@totalreclaw/core`,
56
+ `@totalreclaw/client`, `qrcode`, `ws`). On first-attempt failure: clears
57
+ `node_modules`, re-runs `npm install --ignore-scripts` once, re-validates.
58
+ If retry also fails, exits non-zero so the install surfaces the failure
59
+ instead of writing `enabled: true` over a broken half-state.
60
+
61
+ The retry loop can be skipped with `TOTALRECLAW_SKIP_POSTINSTALL_RETRY=1`
62
+ for sandboxed CI / restricted-network environments.
63
+
64
+ Phrase-safety: the script does NOT touch credentials.json, mnemonics,
65
+ or any phrase code path. Only validates module loading and cleans
66
+ staging directories.
67
+
68
+ ### Added — install-stage cleanup at install-time (issue #190)
69
+
70
+ `postinstall.mjs` extends the rc.21 staging-cleanup behavior (which ran
71
+ at register-time) to ALSO sweep `<extensions>/.openclaw-install-stage-*`
72
+ siblings during the post-install step itself. Goal: a re-install starts
73
+ from a clean parent dir, eliminating the
74
+ "duplicate plugin id detected; global plugin will be overridden by global
75
+ plugin" warning during the install. Safety: skipped when the plugin's
76
+ parent dir is not an `extensions/` directory (dev checkouts) so no random
77
+ siblings are deleted.
78
+
79
+ Regression: `postinstall-validation.test.ts` (17 assertions) covers
80
+ happy path, marker clearing, staging sweep, idempotent re-runs,
81
+ unrelated-dotfile preservation, and dev-checkout safety.
82
+
83
+
8
84
 
9
85
  ### Install / runtime hygiene (issues #126, #128)
10
86
 
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.1-rc.22
4
+ version: 3.3.2-rc.2
5
5
  author: TotalReclaw Team
6
6
  license: MIT
7
7
  homepage: https://totalreclaw.xyz
@@ -43,10 +43,10 @@ If `totalreclaw_*` tools are already bound to your toolset, SKIP install and jum
43
43
 
44
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.
45
45
 
46
- 5. **Pair.** Call `totalreclaw_pair`. Returns `{url, pin, qr_ascii, qr_png_b64, qr_unicode, expires_at_ms}` (see "Rendering the QR" below). Relay verbatim:
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
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
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 *"TotalReclaw is set up. Ask me to remember or recall anything."*
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
50
 
51
51
  ## Rendering the QR on your transport (rc.5+)
52
52
 
@@ -54,13 +54,13 @@ When you receive the `totalreclaw_pair` payload, render the QR based on your cha
54
54
 
55
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
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="Pair QR">` if the chat client renders HTML; otherwise fall back to attachment.
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
58
 
59
- The QR encodes ONLY the pair 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.
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.
60
60
 
61
61
  ## Phrase safety (HARD — never break)
62
62
 
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 setup paths are (in order of preference): (1) `totalreclaw_pair` 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 pair flow, `pair_url` key naming). Both have zero phrase material on stdout (pair-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.
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.
64
64
 
65
65
  ## Usage (post-setup)
66
66
 
@@ -74,10 +74,10 @@ NEVER echo / generate / ask the user to paste a recovery phrase in chat. NEVER i
74
74
 
75
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).
76
76
  - User says done but `credentials.json` missing → PIN expired or entered wrong phrase; call `totalreclaw_pair` again.
77
- - `onboarding required` → credentials missing; redo from the pair step.
77
+ - `onboarding required` → credentials missing; redo from the account-setup step.
78
78
  - `quota exceeded` → `totalreclaw_status`, then offer `totalreclaw_upgrade`.
79
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`.
80
80
 
81
81
  ## Tool surface
82
82
 
83
- `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).
@@ -420,6 +420,107 @@ export function wipePartialInstall(pluginRootDir) {
420
420
  return false;
421
421
  }
422
422
  }
423
+ // ---------------------------------------------------------------------------
424
+ // Plugin load manifest (.loaded.json / .error.json) — 3.3.2-rc.1 #186
425
+ // ---------------------------------------------------------------------------
426
+ /**
427
+ * Filenames written into the plugin root dir at the end of register() and
428
+ * (on failure) from the surrounding try/catch. The presence of `.loaded.json`
429
+ * is the canonical filesystem signal that the plugin's register() body ran
430
+ * to completion AND the SDK tool/route/hook registries received calls.
431
+ *
432
+ * Acceptance criteria (issue #186):
433
+ * - `cat ~/.openclaw/extensions/totalreclaw/.loaded.json` shows
434
+ * `{loadedAt, tools, version}` after every successful gateway start.
435
+ * - `cat ~/.openclaw/extensions/totalreclaw/.error.json` shows
436
+ * `{loadedAt, error, stack}` if register() threw.
437
+ * - Both are overwritten on each register() call so the agent can rely
438
+ * on the timestamp matching the most recent gateway start.
439
+ *
440
+ * Why these MUST be synchronous writes (same constraint as
441
+ * `registerHttpRoute` per the comment in index.ts around the route
442
+ * registration site): the SDK loader treats register() returning as the
443
+ * signal to freeze the registries. An async `fs.promises.writeFile` would
444
+ * settle one microtask AFTER the loader has moved on, so the manifest
445
+ * could miss tools that registered late OR drop entirely if the gateway
446
+ * exits before the microtask runs. `writeFileSync` is the only safe choice.
447
+ */
448
+ export const PLUGIN_LOADED_MANIFEST = '.loaded.json';
449
+ export const PLUGIN_ERROR_MANIFEST = '.error.json';
450
+ /**
451
+ * Resolve the plugin root dir from the loaded module's directory. The plugin
452
+ * is shipped with `dist/index.js` as the entry, so `import.meta.url` resolves
453
+ * to `<root>/dist/`. We walk up one level to put the manifests next to
454
+ * `package.json`. Defensive: if the caller already passed the root (no
455
+ * trailing `dist`), we still return a sensible path.
456
+ */
457
+ function resolvePluginRootForManifest(pluginDir) {
458
+ const base = path.basename(pluginDir);
459
+ return base === 'dist' ? path.resolve(pluginDir, '..') : pluginDir;
460
+ }
461
+ /**
462
+ * Write the success manifest. SYNCHRONOUS — the SDK freezes the plugin
463
+ * registries the moment register() returns; `fs.promises.writeFile` would
464
+ * race that freeze and the manifest could miss late tool registrations or
465
+ * never land at all if the process exits before the microtask runs.
466
+ *
467
+ * Best-effort: returns `true` on success, `false` on any I/O error. Never
468
+ * throws — failing to write the manifest is a diagnostic loss, not a
469
+ * correctness loss, so we don't propagate.
470
+ *
471
+ * The manifest is written at mode 0644 (world-readable). It contains no
472
+ * secrets — only a timestamp, the (publicly known) tool names, and the
473
+ * plugin version. Cleared first so a stale `.error.json` from a previous
474
+ * failed boot doesn't survive a successful boot.
475
+ */
476
+ export function writePluginManifest(pluginDir, manifest) {
477
+ try {
478
+ const root = resolvePluginRootForManifest(pluginDir);
479
+ if (!fs.existsSync(root))
480
+ return false;
481
+ const loadedPath = path.join(root, PLUGIN_LOADED_MANIFEST);
482
+ const errorPath = path.join(root, PLUGIN_ERROR_MANIFEST);
483
+ // Best-effort error-file cleanup — a successful boot supersedes any
484
+ // prior failure marker. If the unlink fails (e.g. permission), the
485
+ // .loaded.json timestamp still tells the agent which is current.
486
+ try {
487
+ if (fs.existsSync(errorPath))
488
+ fs.unlinkSync(errorPath);
489
+ }
490
+ catch {
491
+ // Swallow — best-effort.
492
+ }
493
+ fs.writeFileSync(loadedPath, JSON.stringify(manifest, null, 2));
494
+ return true;
495
+ }
496
+ catch {
497
+ return false;
498
+ }
499
+ }
500
+ /**
501
+ * Write the error manifest. SYNCHRONOUS for the same reason as
502
+ * `writePluginManifest`. Called from the try/catch surrounding the
503
+ * register() body so the agent has a filesystem signal that the plugin
504
+ * registered AT LEAST attempted to load and failed in a specific way.
505
+ *
506
+ * Does NOT clear `.loaded.json` from a prior successful boot — keeping
507
+ * the older success marker around lets the agent see "last good boot was
508
+ * X, current boot failed at Y" without spelunking logs. The newer
509
+ * `.error.json` timestamp wins as "current state".
510
+ */
511
+ export function writePluginError(pluginDir, error) {
512
+ try {
513
+ const root = resolvePluginRootForManifest(pluginDir);
514
+ if (!fs.existsSync(root))
515
+ return false;
516
+ const errorPath = path.join(root, PLUGIN_ERROR_MANIFEST);
517
+ fs.writeFileSync(errorPath, JSON.stringify(error, null, 2));
518
+ return true;
519
+ }
520
+ catch {
521
+ return false;
522
+ }
523
+ }
423
524
  /**
424
525
  * Drop the `.tr-partial-install` marker into `pluginRootDir`. Idempotent
425
526
  * (overwrites any existing marker) and best-effort — returns `true` on