@totalreclaw/totalreclaw 3.3.7-rc.1 → 3.3.7-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,6 +4,31 @@ 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.7-rc.2] — 2026-05-04
8
+
9
+ Follow-up to 3.3.7-rc.1, caught in Pedro's manual integration testing on the OpenClaw side 2026-05-03. The 5-tier auth fix shipped DEAD-CODE in rc.1: gateway logs surfaced `[gateway] [plugins] command registration failed: Command name "restart" is reserved by a built-in command (plugin=totalreclaw, source=/home/pdiogo/.openclaw/extensions/totalreclaw/dist/index.js)`. The plugin's `/restart` registration was rejected because OpenClaw's plugin registry hard-rejects `restart` (and the rest of `RESERVED_COMMANDS` — see `node_modules/openclaw/dist/registry-*.js` — `help`, `commands`, `status`, `whoami`, `context`, `stop`, `restart`, `reset`, `new`, `compact`, `config`, `debug`, `allowlist`, `activation`, `skill`, `subagents`, `kill`, `steer`, `tell`, `model`, `models`, `queue`, `send`, `bash`, `exec`, `think`, `verbose`, `reasoning`, `elevated`, `usage`). Built-in `/restart` retains its allow-from-only semantics, which is what gave Pedro the original "You are not authorized" — and our 5-tier fallback never ran because plugin registration never succeeded.
10
+
11
+ ### Fixed — rename plugin slash command to `/totalreclaw-restart` (issue #215, follow-up)
12
+
13
+ **Fix:** plugin command name renamed from `restart` to `totalreclaw-restart`. The 5-tier auth resolver from rc.1 (`restart-auth.ts`, `inbound-user-tracker.ts`) is unchanged — it's the correct logic, just needed to attach to a non-reserved name. SKILL.md / setup-guide both tell the agent to issue `/totalreclaw-restart` so end-users never type `/restart` directly. The OpenClaw built-in `/restart` keeps its allow-from-only semantics — both commands coexist; the plugin's namespaced form is the recommended path for default-config users.
14
+
15
+ The SIGUSR1 emit path (`process.kill(process.pid, 'SIGUSR1')`) is unchanged — gateway accepts iff `commands.restart=true` (default), and the policy keys on the gateway-level config flag, NOT on the plugin command name. So `/totalreclaw-restart` triggers a real gateway restart end-to-end.
16
+
17
+ **Upstream FR (filed alongside this PR):** OpenClaw should allow plugins to override built-in command names with an explicit precedence flag (e.g. `registerCommand({ name: 'restart', overrideBuiltIn: true, ... })`). Until that lands, the namespaced workaround is canonical. Reference back to issue #215 architectural concerns.
18
+
19
+ Implementation:
20
+ - `skill/plugin/index.ts` — `api.registerCommand({ name: 'totalreclaw-restart', ... })` (was `'restart'`). Log lines updated to `/totalreclaw-restart` for cross-grep with the new SKILL.md instructions.
21
+ - `skill/plugin/restart-auth.ts` — docstring header reflects the rename + the rc.1 → rc.2 trail. The resolver matrix is byte-identical to rc.1.
22
+ - `skill/plugin/inbound-user-tracker.ts` — comment refers to the new command name.
23
+ - `skill/plugin/SKILL.md` — every user-facing instance of `/restart` (in agent-instructions: "issue `/restart` autonomously…") replaced with `/totalreclaw-restart`. Added a one-line note explaining why we renamed.
24
+ - `skill/SKILL.md` — same.
25
+ - `docs/guides/openclaw-setup.md` — same. The "If `/restart` returns unauthorized" section is now keyed on `/totalreclaw-restart`. The rare-but-real built-in `/restart` referenced as the OpenClaw-shipped command (which the user does NOT type for our path).
26
+
27
+ ### Tests
28
+
29
+ - Plugin: existing `restart-auth.test.ts` (29 assertions) + `inbound-user-tracker.test.ts` (14 assertions) all pass against the new command name (the resolver doesn't know its caller's name — it's pure). New regression test `register-command-name.test.ts` asserts the registered name is `totalreclaw-restart` (NOT `restart`) and asserts the plugin would not trip OpenClaw's `RESERVED_COMMANDS` check (parses the upstream `validateCommandName` rule directly from `node_modules/openclaw/dist/registry-*.js`). All pre-existing tests remain green.
30
+ - Hermes: `python/tests/test_restart_auth_5_tier_2_3_6.py` (21 assertions) all pass — the resolver is name-agnostic. CHANGELOG entry on the Hermes side notes the matching rename for the future Hermes `register_command` wiring.
31
+
7
32
  ## [3.3.7-rc.1] — 2026-05-03
8
33
 
9
34
  Patch wave from Pedro's QA on 3.3.6-rc.1 (real-user Telegram → Pop OS Docker container → OpenClaw 2026.4.22 + plugin 3.3.6-rc.1). Two ship-stoppers, both architectural rather than config-drift:
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.7-rc.1
4
+ version: 3.3.7-rc.2
5
5
  author: TotalReclaw Team
6
6
  license: MIT
7
7
  homepage: https://totalreclaw.xyz
@@ -38,7 +38,7 @@ Speak only these lines, in order. Substitute the bracketed placeholders with the
38
38
  > Installing TotalReclaw…
39
39
  2. (after install + auto-reload + `totalreclaw_pair` is bound)
40
40
  > ✓ Installed (totalreclaw <version>)
41
- 3. (optional — only if auto-reload didn't fire and you need to issue `/restart`)
41
+ 3. (optional — only if auto-reload didn't fire and you need to issue `/totalreclaw-restart`)
42
42
  > Restarting OpenClaw to load the plugin…
43
43
  4. (optional — only if you successfully disabled the built-in memory tool to prevent dual-write)
44
44
  > Disabled built-in memory tool.
@@ -63,15 +63,15 @@ If `totalreclaw_*` tools are already bound to your toolset, SKIP install and jum
63
63
 
64
64
  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.
65
65
 
66
- 2. **Wait for auto-reload — then issue `/restart` autonomously if needed (silent — emit user-visible line 2 on success, line 3 only if you had to restart).** 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. When bound, emit user-visible line 2 and proceed.
66
+ 2. **Wait for auto-reload — then issue `/totalreclaw-restart` autonomously if needed (silent — emit user-visible line 2 on success, line 3 only if you had to restart).** 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. When bound, emit user-visible line 2 and proceed.
67
67
 
68
68
  **If `totalreclaw_pair` is NOT visible after 10s — restart imperative (read carefully):**
69
69
 
70
- > Your IMMEDIATE next message must be the literal slash command `/restart` — nothing else. Do not narrate. Do not ask permission. Do not describe what `/restart` does. Do not propose alternatives. Just send `/restart`. You may emit user-visible line 3 (`Restarting OpenClaw to load the plugin…`) ONCE in the same turn, then issue `/restart`. Nothing else.
70
+ > Your IMMEDIATE next message must be the literal slash command `/totalreclaw-restart` — nothing else. Do not narrate. Do not ask permission. Do not describe what `/totalreclaw-restart` does. Do not propose alternatives. Just send `/totalreclaw-restart`. You may emit user-visible line 3 (`Restarting OpenClaw to load the plugin…`) ONCE in the same turn, then issue `/totalreclaw-restart`. Nothing else.
71
71
 
72
- `/restart` is in-process — it drains active runs cleanly; your shell is NOT shelling out to `docker restart` or `openclaw gateway restart` (those would suicide the host). After `/restart`, wait ~3s and re-run `openclaw plugins list`.
72
+ `/totalreclaw-restart` is in-process — it drains active runs cleanly; your shell is NOT shelling out to `docker restart` or `openclaw gateway restart` (those would suicide the host). After `/totalreclaw-restart`, wait ~3s and re-run `openclaw plugins list`. (3.3.7-rc.2 renamed this from `/restart` because OpenClaw reserves `/restart` for its built-in — the plugin-side fallback can only attach to a unique name. The 5-tier auth resolver is unchanged; only the command name moved.)
73
73
 
74
- **If `/restart` returns "You are not authorized to use this command":** surface this verbatim and stop. Do NOT loop `/restart` calls. Do NOT propose alternatives.
74
+ **If `/totalreclaw-restart` returns "You are not authorized to use this command":** surface this verbatim and stop. Do NOT loop `/totalreclaw-restart` calls. Do NOT propose alternatives.
75
75
 
76
76
  > Your Telegram channel needs `allowFrom` configured. Run on your host: `jq '.channels.<channel>.allowFrom = ["<your-id>"]' ~/.openclaw/openclaw.json | sponge ~/.openclaw/openclaw.json && docker restart <your-container-name>`. Substitute `<channel>` (e.g. `telegram`, `slack`), `<your-id>` (Telegram: `@username` or numeric `chat_id`; Slack: workspace member ID), and `<your-container-name>` (`docker ps` shows it). For non-Docker installs substitute `openclaw gateway restart` for the `docker restart` portion. On a managed service, ask the service's support to enable slash commands. Reply `done` after.
77
77
 
@@ -84,12 +84,12 @@ If `totalreclaw_*` tools are already bound to your toolset, SKIP install and jum
84
84
  - "Quick fix…"
85
85
  - "However, I don't see…"
86
86
  - "I need to use…"
87
- - "Should I /restart" / "Do you have a public URL" / "Want me to restart" / "Let me check if the tool is bound" / "I need permission to restart"
87
+ - "Should I /totalreclaw-restart" / "Do you have a public URL" / "Want me to restart" / "Let me check if the tool is bound" / "I need permission to restart"
88
88
  - "Now installing…" / "Got it" / "In parallel…" / "Step 1a/1b/1c"
89
89
 
90
90
  Substitute all of these with silence — the next user-visible line in the set, or nothing.
91
91
 
92
- 3. **Verify `totalreclaw_pair` is bound.** If still missing after `/restart` (or after the user fixed allowFrom and said `done`), tell them the restart didn't take effect and ask them to retry. Do not loop the install.
92
+ 3. **Verify `totalreclaw_pair` is bound.** If still missing after `/totalreclaw-restart` (or after the user fixed allowFrom and said `done`), tell them the restart didn't take effect and ask them to retry. Do not loop the install.
93
93
 
94
94
  4. **Check for existing credentials (silent unless found).** Run `test -s ~/.totalreclaw/credentials.json`. If it exists and is non-empty, emit *"✓ TotalReclaw is already set up."* and stop.
95
95
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Per-channel inbound-user tracker (issue #215, 3.3.7-rc.1).
3
3
  *
4
- * Tier 3 + Tier 5 of the `/restart` 5-tier auth fallback need to know
4
+ * Tier 3 + Tier 5 of the `/totalreclaw-restart` 5-tier auth fallback need to know
5
5
  * "how many distinct users have ever messaged this gateway on channel X".
6
6
  *
7
7
  * This module implements a simple disk-backed counter. Persistence
package/dist/index.js CHANGED
@@ -2833,7 +2833,7 @@ const plugin = {
2833
2833
  // <pluginDir>/.loaded.json` from the host shell. Both
2834
2834
  // surfaces should agree; if chat says boot=N but the
2835
2835
  // file says boot=N+1, the chat session is stale and a
2836
- // /restart is warranted.
2836
+ // /totalreclaw-restart is warranted.
2837
2837
  try {
2838
2838
  const m = _pluginDirForManifest
2839
2839
  ? readPluginLoadedManifest(_pluginDirForManifest)
@@ -2872,26 +2872,42 @@ const plugin = {
2872
2872
  },
2873
2873
  });
2874
2874
  // ---------------------------------------------------------------
2875
- // 3.3.7-rc.1 (issue #215) — `/restart` plugin command override
2875
+ // 3.3.7-rc.2 (issue #215, follow-up) — `/totalreclaw-restart`
2876
2876
  // ---------------------------------------------------------------
2877
2877
  //
2878
- // Replaces OpenClaw's built-in `/restart` so we can apply the
2879
- // 5-tier auth fallback. Plugin commands match BEFORE built-ins
2880
- // (see upstream `auto-reply/reply/commands-plugin.ts`), so this
2881
- // takes precedence whenever the plugin is loaded. We use
2882
- // `requireAuth: false` to bypass the channel-layer auth check
2883
- // the 5-tier fallback in `restart-auth.ts` decides allow / reject.
2878
+ // Originally rc.1 registered this as `/restart` to override the
2879
+ // OpenClaw built-in. That was wrong: OpenClaw's plugin registry
2880
+ // hard-rejects the name on the reserved list (see upstream
2881
+ // `RESERVED_COMMANDS` in `dist/registry-*.js`) registration
2882
+ // fails with `Command name "restart" is reserved by a built-in
2883
+ // command` and the 5-tier fallback never runs. Pedro caught this
2884
+ // in 3.3.7-rc.1 manual integration testing 2026-05-03; gateway
2885
+ // logs surfaced the rejection, so the rc.1 fix shipped DEAD-CODE.
2886
+ //
2887
+ // Workaround until upstream lands a plugin-override-precedence
2888
+ // flag (FR filed alongside this PR): use a unique, namespaced
2889
+ // command name. Plugin handles `/totalreclaw-restart`; the
2890
+ // built-in `/restart` keeps its allow-from-only semantics
2891
+ // unchanged. SKILL.md tells the agent to issue the namespaced
2892
+ // form, so end-users never type `restart` directly.
2893
+ //
2894
+ // We still use `requireAuth: false` to bypass the channel-layer
2895
+ // auth check — the 5-tier fallback in `restart-auth.ts` decides
2896
+ // allow / reject per the same matrix as rc.1.
2884
2897
  //
2885
2898
  // If allow → fire `process.kill(process.pid, 'SIGUSR1')`. The
2886
2899
  // gateway accepts SIGUSR1 iff `commands.restart=true` (the
2887
2900
  // default) — see upstream `setGatewaySigusr1RestartPolicy`.
2901
+ // (The SIGUSR1 policy still keys on `commands.restart`, NOT on
2902
+ // the plugin command name — gateways only honour one restart
2903
+ // signal.)
2888
2904
  //
2889
2905
  // If reject → return a short non-shaming message via
2890
2906
  // `rejectMessageFor` that points the user at the right config
2891
2907
  // key (no infinite loop — agent will follow the unauthorized
2892
2908
  // fallback path documented in SKILL.md instead).
2893
2909
  api.registerCommand({
2894
- name: 'restart',
2910
+ name: 'totalreclaw-restart',
2895
2911
  description: 'Restart OpenClaw gracefully (drains active runs first).',
2896
2912
  acceptsArgs: false,
2897
2913
  requireAuth: false,
@@ -2937,10 +2953,10 @@ const plugin = {
2937
2953
  getDistinctInboundUserCount: (ch) => getDistinctInboundUserCount(trackerPath, ch),
2938
2954
  });
2939
2955
  if (verdict.allow === false) {
2940
- api.logger.info(`TotalReclaw: /restart rejected (channel=${channel || '<none>'} sender=${senderId || '<none>'} reason=${verdict.reason})`);
2956
+ api.logger.info(`TotalReclaw: /totalreclaw-restart rejected (channel=${channel || '<none>'} sender=${senderId || '<none>'} reason=${verdict.reason})`);
2941
2957
  return { text: rejectMessageFor(verdict.reason) };
2942
2958
  }
2943
- api.logger.info(`TotalReclaw: /restart allowed (channel=${channel || '<none>'} sender=${senderId || '<none>'} tier=${verdict.reason})`);
2959
+ api.logger.info(`TotalReclaw: /totalreclaw-restart allowed (channel=${channel || '<none>'} sender=${senderId || '<none>'} tier=${verdict.reason})`);
2944
2960
  // Trigger the gateway's SIGUSR1 restart path. Wrap in
2945
2961
  // try/catch — `process.kill` can throw if the gateway is
2946
2962
  // already shutting down (rare but seen in the wild).
@@ -2949,7 +2965,7 @@ const plugin = {
2949
2965
  }
2950
2966
  catch (err) {
2951
2967
  const msg = err instanceof Error ? err.message : String(err);
2952
- api.logger.warn(`TotalReclaw: /restart SIGUSR1 emit failed: ${msg}`);
2968
+ api.logger.warn(`TotalReclaw: /totalreclaw-restart SIGUSR1 emit failed: ${msg}`);
2953
2969
  return {
2954
2970
  text: `Restart request acknowledged but the gateway didn't accept the signal (${msg}). Try \`docker restart <container>\` if running in Docker.`,
2955
2971
  };
@@ -2962,11 +2978,11 @@ const plugin = {
2962
2978
  // 3.3.7-rc.1 (issue #215) — track distinct inbound users per channel
2963
2979
  // ---------------------------------------------------------------
2964
2980
  //
2965
- // Tier 3 + tier 5 of the `/restart` 5-tier auth fallback need to
2966
- // know how many distinct users have messaged this gateway on
2967
- // each channel. We instrument `message_received` to record every
2968
- // (channel, senderId) pair to disk; the count survives gateway
2969
- // restarts (see `inbound-user-tracker.ts`).
2981
+ // Tier 3 + tier 5 of the `/totalreclaw-restart` 5-tier auth
2982
+ // fallback need to know how many distinct users have messaged this
2983
+ // gateway on each channel. We instrument `message_received` to
2984
+ // record every (channel, senderId) pair to disk; the count
2985
+ // survives gateway restarts (see `inbound-user-tracker.ts`).
2970
2986
  //
2971
2987
  // Best-effort: we never throw out of this hook even if the disk
2972
2988
  // write fails — the auth fallback degrades gracefully (a stale
@@ -1,5 +1,5 @@
1
1
  /**
2
- * /restart slash command — 5-tier auth fallback (issue #215)
2
+ * /totalreclaw-restart slash command — 5-tier auth fallback (issue #215)
3
3
  *
4
4
  * Architectural fix shipped 3.3.7-rc.1 after 3.3.6-rc.1 QA found that
5
5
  * default-config users (no `commands.ownerAllowFrom`, no
@@ -7,14 +7,24 @@
7
7
  * this command." when they typed `/restart` to recover from the plugin
8
8
  * tool-binding race (the dominant first-run install path).
9
9
  *
10
- * The plugin's `/restart` registration overrides OpenClaw's built-in
11
- * `/restart` (plugin commands are matched BEFORE built-ins; see
12
- * upstream `auto-reply/reply/commands-plugin.ts`). With
13
- * `requireAuth: false` the channel-layer auth check is skipped, and
14
- * this module's `resolveRestartAuth` decides allow-vs-reject using a
15
- * five-tier fallback. If the result is `allow`, the caller fires
10
+ * 3.3.7-rc.2 (2026-05-04) renamed the plugin command from `restart` to
11
+ * `totalreclaw-restart` after Pedro caught in rc.1 manual integration
12
+ * testing — that OpenClaw's plugin registry hard-rejects names on
13
+ * `RESERVED_COMMANDS` (gateway log: `Command name "restart" is reserved
14
+ * by a built-in command`). The 5-tier matrix below is unchanged from
15
+ * rc.1; only the command name attached to it changed. SKILL.md /
16
+ * setup-guide both tell the agent to issue the namespaced form, so
17
+ * end-users never type `restart` directly. An upstream FR is open
18
+ * asking for a plugin-override-precedence flag.
19
+ *
20
+ * Plugin handles `/totalreclaw-restart` (validation passes — hyphenated
21
+ * names are allowed and the name is unique). With `requireAuth: false`
22
+ * the channel-layer auth check is skipped, and this module's
23
+ * `resolveRestartAuth` decides allow-vs-reject using a five-tier
24
+ * fallback. If the result is `allow`, the caller fires
16
25
  * `process.kill(process.pid, 'SIGUSR1')` — which the gateway accepts
17
- * iff `commands.restart=true` (the default).
26
+ * iff `commands.restart=true` (the default; the SIGUSR1 policy keys on
27
+ * the gateway-level config flag, NOT on the plugin command name).
18
28
  *
19
29
  * Tier order (highest priority first):
20
30
  * 1. `commands.ownerAllowFrom` explicitly lists invoker → allow
@@ -69,7 +79,7 @@ function entryMatches(allowFrom, senderId) {
69
79
  return false;
70
80
  }
71
81
  /**
72
- * Resolve whether the given invoker may run `/restart`.
82
+ * Resolve whether the given invoker may run `/totalreclaw-restart`.
73
83
  *
74
84
  * Tier order: see file header.
75
85
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Per-channel inbound-user tracker (issue #215, 3.3.7-rc.1).
3
3
  *
4
- * Tier 3 + Tier 5 of the `/restart` 5-tier auth fallback need to know
4
+ * Tier 3 + Tier 5 of the `/totalreclaw-restart` 5-tier auth fallback need to know
5
5
  * "how many distinct users have ever messaged this gateway on channel X".
6
6
  *
7
7
  * This module implements a simple disk-backed counter. Persistence
package/index.ts CHANGED
@@ -3430,7 +3430,7 @@ const plugin = {
3430
3430
  // <pluginDir>/.loaded.json` from the host shell. Both
3431
3431
  // surfaces should agree; if chat says boot=N but the
3432
3432
  // file says boot=N+1, the chat session is stale and a
3433
- // /restart is warranted.
3433
+ // /totalreclaw-restart is warranted.
3434
3434
  try {
3435
3435
  const m = _pluginDirForManifest
3436
3436
  ? readPluginLoadedManifest(_pluginDirForManifest)
@@ -3472,26 +3472,42 @@ const plugin = {
3472
3472
  });
3473
3473
 
3474
3474
  // ---------------------------------------------------------------
3475
- // 3.3.7-rc.1 (issue #215) — `/restart` plugin command override
3475
+ // 3.3.7-rc.2 (issue #215, follow-up) — `/totalreclaw-restart`
3476
3476
  // ---------------------------------------------------------------
3477
3477
  //
3478
- // Replaces OpenClaw's built-in `/restart` so we can apply the
3479
- // 5-tier auth fallback. Plugin commands match BEFORE built-ins
3480
- // (see upstream `auto-reply/reply/commands-plugin.ts`), so this
3481
- // takes precedence whenever the plugin is loaded. We use
3482
- // `requireAuth: false` to bypass the channel-layer auth check
3483
- // the 5-tier fallback in `restart-auth.ts` decides allow / reject.
3478
+ // Originally rc.1 registered this as `/restart` to override the
3479
+ // OpenClaw built-in. That was wrong: OpenClaw's plugin registry
3480
+ // hard-rejects the name on the reserved list (see upstream
3481
+ // `RESERVED_COMMANDS` in `dist/registry-*.js`) registration
3482
+ // fails with `Command name "restart" is reserved by a built-in
3483
+ // command` and the 5-tier fallback never runs. Pedro caught this
3484
+ // in 3.3.7-rc.1 manual integration testing 2026-05-03; gateway
3485
+ // logs surfaced the rejection, so the rc.1 fix shipped DEAD-CODE.
3486
+ //
3487
+ // Workaround until upstream lands a plugin-override-precedence
3488
+ // flag (FR filed alongside this PR): use a unique, namespaced
3489
+ // command name. Plugin handles `/totalreclaw-restart`; the
3490
+ // built-in `/restart` keeps its allow-from-only semantics
3491
+ // unchanged. SKILL.md tells the agent to issue the namespaced
3492
+ // form, so end-users never type `restart` directly.
3493
+ //
3494
+ // We still use `requireAuth: false` to bypass the channel-layer
3495
+ // auth check — the 5-tier fallback in `restart-auth.ts` decides
3496
+ // allow / reject per the same matrix as rc.1.
3484
3497
  //
3485
3498
  // If allow → fire `process.kill(process.pid, 'SIGUSR1')`. The
3486
3499
  // gateway accepts SIGUSR1 iff `commands.restart=true` (the
3487
3500
  // default) — see upstream `setGatewaySigusr1RestartPolicy`.
3501
+ // (The SIGUSR1 policy still keys on `commands.restart`, NOT on
3502
+ // the plugin command name — gateways only honour one restart
3503
+ // signal.)
3488
3504
  //
3489
3505
  // If reject → return a short non-shaming message via
3490
3506
  // `rejectMessageFor` that points the user at the right config
3491
3507
  // key (no infinite loop — agent will follow the unauthorized
3492
3508
  // fallback path documented in SKILL.md instead).
3493
3509
  api.registerCommand({
3494
- name: 'restart',
3510
+ name: 'totalreclaw-restart',
3495
3511
  description: 'Restart OpenClaw gracefully (drains active runs first).',
3496
3512
  acceptsArgs: false,
3497
3513
  requireAuth: false,
@@ -3542,13 +3558,13 @@ const plugin = {
3542
3558
 
3543
3559
  if (verdict.allow === false) {
3544
3560
  api.logger.info(
3545
- `TotalReclaw: /restart rejected (channel=${channel || '<none>'} sender=${senderId || '<none>'} reason=${verdict.reason})`,
3561
+ `TotalReclaw: /totalreclaw-restart rejected (channel=${channel || '<none>'} sender=${senderId || '<none>'} reason=${verdict.reason})`,
3546
3562
  );
3547
3563
  return { text: rejectMessageFor(verdict.reason) };
3548
3564
  }
3549
3565
 
3550
3566
  api.logger.info(
3551
- `TotalReclaw: /restart allowed (channel=${channel || '<none>'} sender=${senderId || '<none>'} tier=${verdict.reason})`,
3567
+ `TotalReclaw: /totalreclaw-restart allowed (channel=${channel || '<none>'} sender=${senderId || '<none>'} tier=${verdict.reason})`,
3552
3568
  );
3553
3569
 
3554
3570
  // Trigger the gateway's SIGUSR1 restart path. Wrap in
@@ -3558,7 +3574,7 @@ const plugin = {
3558
3574
  process.kill(process.pid, 'SIGUSR1');
3559
3575
  } catch (err) {
3560
3576
  const msg = err instanceof Error ? err.message : String(err);
3561
- api.logger.warn(`TotalReclaw: /restart SIGUSR1 emit failed: ${msg}`);
3577
+ api.logger.warn(`TotalReclaw: /totalreclaw-restart SIGUSR1 emit failed: ${msg}`);
3562
3578
  return {
3563
3579
  text: `Restart request acknowledged but the gateway didn't accept the signal (${msg}). Try \`docker restart <container>\` if running in Docker.`,
3564
3580
  };
@@ -3572,11 +3588,11 @@ const plugin = {
3572
3588
  // 3.3.7-rc.1 (issue #215) — track distinct inbound users per channel
3573
3589
  // ---------------------------------------------------------------
3574
3590
  //
3575
- // Tier 3 + tier 5 of the `/restart` 5-tier auth fallback need to
3576
- // know how many distinct users have messaged this gateway on
3577
- // each channel. We instrument `message_received` to record every
3578
- // (channel, senderId) pair to disk; the count survives gateway
3579
- // restarts (see `inbound-user-tracker.ts`).
3591
+ // Tier 3 + tier 5 of the `/totalreclaw-restart` 5-tier auth
3592
+ // fallback need to know how many distinct users have messaged this
3593
+ // gateway on each channel. We instrument `message_received` to
3594
+ // record every (channel, senderId) pair to disk; the count
3595
+ // survives gateway restarts (see `inbound-user-tracker.ts`).
3580
3596
  //
3581
3597
  // Best-effort: we never throw out of this hook even if the disk
3582
3598
  // write fails — the auth fallback degrades gracefully (a stale
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totalreclaw/totalreclaw",
3
- "version": "3.3.7-rc.1",
3
+ "version": "3.3.7-rc.2",
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": [
@@ -64,7 +64,7 @@
64
64
  "scripts": {
65
65
  "build": "rm -rf dist && tsc -p tsconfig.json --noCheck",
66
66
  "verify-tarball": "node ../scripts/verify-tarball.mjs",
67
- "test": "npx tsx manifest-shape.test.ts && npx tsx config-schema.test.ts && npx tsx config.test.ts && npx tsx relay-headers.test.ts && npx tsx scope-address-visible.test.ts && npx tsx llm-profile-reader.test.ts && npx tsx llm-client.test.ts && npx tsx llm-client-retry.test.ts && npx tsx gateway-url.test.ts && npx tsx retype-setscope.test.ts && npx tsx tool-gating.test.ts && npx tsx onboarding-noninteractive.test.ts && npx tsx pair-cli-json.test.ts && npx tsx pair-qr.test.ts && npx tsx pair-remote-client.test.ts && npx tsx qa-bug-report.test.ts && npx tsx nonce-serialization.test.ts && npx tsx phrase-safety-registry.test.ts && npx tsx test_issue_92_onnx_download_ux.test.ts && npx tsx onboard-pair-only.test.ts && npx tsx import-time-smoke.test.ts && npx tsx install-staging-cleanup.test.ts && npx tsx partial-install-detection.test.ts && npx tsx install-reload-idempotency.test.ts && npx tsx json-stdout-cleanliness.test.ts && npx tsx load-manifest.test.ts && npx tsx url-binding.test.ts && npx tsx fs-helpers.test.ts && npx tsx pair-cli-default-mode.test.ts && npx tsx embedding-fallback-tag.test.ts && npx tsx staging-banner-gate.test.ts && npx tsx restart-auth.test.ts && npx tsx inbound-user-tracker.test.ts",
67
+ "test": "npx tsx manifest-shape.test.ts && npx tsx config-schema.test.ts && npx tsx config.test.ts && npx tsx relay-headers.test.ts && npx tsx scope-address-visible.test.ts && npx tsx llm-profile-reader.test.ts && npx tsx llm-client.test.ts && npx tsx llm-client-retry.test.ts && npx tsx gateway-url.test.ts && npx tsx retype-setscope.test.ts && npx tsx tool-gating.test.ts && npx tsx onboarding-noninteractive.test.ts && npx tsx pair-cli-json.test.ts && npx tsx pair-qr.test.ts && npx tsx pair-remote-client.test.ts && npx tsx qa-bug-report.test.ts && npx tsx nonce-serialization.test.ts && npx tsx phrase-safety-registry.test.ts && npx tsx test_issue_92_onnx_download_ux.test.ts && npx tsx onboard-pair-only.test.ts && npx tsx import-time-smoke.test.ts && npx tsx install-staging-cleanup.test.ts && npx tsx partial-install-detection.test.ts && npx tsx install-reload-idempotency.test.ts && npx tsx json-stdout-cleanliness.test.ts && npx tsx load-manifest.test.ts && npx tsx url-binding.test.ts && npx tsx fs-helpers.test.ts && npx tsx pair-cli-default-mode.test.ts && npx tsx embedding-fallback-tag.test.ts && npx tsx staging-banner-gate.test.ts && npx tsx restart-auth.test.ts && npx tsx inbound-user-tracker.test.ts && npx tsx register-command-name.test.ts",
68
68
  "smoke:dist": "npx tsx dist-esm-smoke.test.ts",
69
69
  "check-scanner": "node ../scripts/check-scanner.mjs",
70
70
  "check-version-drift": "node ../scripts/check-version-drift.mjs",
package/restart-auth.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * /restart slash command — 5-tier auth fallback (issue #215)
2
+ * /totalreclaw-restart slash command — 5-tier auth fallback (issue #215)
3
3
  *
4
4
  * Architectural fix shipped 3.3.7-rc.1 after 3.3.6-rc.1 QA found that
5
5
  * default-config users (no `commands.ownerAllowFrom`, no
@@ -7,14 +7,24 @@
7
7
  * this command." when they typed `/restart` to recover from the plugin
8
8
  * tool-binding race (the dominant first-run install path).
9
9
  *
10
- * The plugin's `/restart` registration overrides OpenClaw's built-in
11
- * `/restart` (plugin commands are matched BEFORE built-ins; see
12
- * upstream `auto-reply/reply/commands-plugin.ts`). With
13
- * `requireAuth: false` the channel-layer auth check is skipped, and
14
- * this module's `resolveRestartAuth` decides allow-vs-reject using a
15
- * five-tier fallback. If the result is `allow`, the caller fires
10
+ * 3.3.7-rc.2 (2026-05-04) renamed the plugin command from `restart` to
11
+ * `totalreclaw-restart` after Pedro caught in rc.1 manual integration
12
+ * testing — that OpenClaw's plugin registry hard-rejects names on
13
+ * `RESERVED_COMMANDS` (gateway log: `Command name "restart" is reserved
14
+ * by a built-in command`). The 5-tier matrix below is unchanged from
15
+ * rc.1; only the command name attached to it changed. SKILL.md /
16
+ * setup-guide both tell the agent to issue the namespaced form, so
17
+ * end-users never type `restart` directly. An upstream FR is open
18
+ * asking for a plugin-override-precedence flag.
19
+ *
20
+ * Plugin handles `/totalreclaw-restart` (validation passes — hyphenated
21
+ * names are allowed and the name is unique). With `requireAuth: false`
22
+ * the channel-layer auth check is skipped, and this module's
23
+ * `resolveRestartAuth` decides allow-vs-reject using a five-tier
24
+ * fallback. If the result is `allow`, the caller fires
16
25
  * `process.kill(process.pid, 'SIGUSR1')` — which the gateway accepts
17
- * iff `commands.restart=true` (the default).
26
+ * iff `commands.restart=true` (the default; the SIGUSR1 policy keys on
27
+ * the gateway-level config flag, NOT on the plugin command name).
18
28
  *
19
29
  * Tier order (highest priority first):
20
30
  * 1. `commands.ownerAllowFrom` explicitly lists invoker → allow
@@ -120,7 +130,7 @@ export interface RestartAuthInput {
120
130
  }
121
131
 
122
132
  /**
123
- * Resolve whether the given invoker may run `/restart`.
133
+ * Resolve whether the given invoker may run `/totalreclaw-restart`.
124
134
  *
125
135
  * Tier order: see file header.
126
136
  *
package/skill.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totalreclaw",
3
- "version": "3.3.7-rc.1",
3
+ "version": "3.3.7-rc.2",
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",