@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 +25 -0
- package/SKILL.md +8 -8
- package/dist/inbound-user-tracker.js +1 -1
- package/dist/index.js +33 -17
- package/dist/restart-auth.js +19 -9
- package/inbound-user-tracker.ts +1 -1
- package/index.ts +33 -17
- package/package.json +2 -2
- package/restart-auth.ts +19 -9
- package/skill.json +1 -1
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.
|
|
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.
|
|
2875
|
+
// 3.3.7-rc.2 (issue #215, follow-up) — `/totalreclaw-restart`
|
|
2876
2876
|
// ---------------------------------------------------------------
|
|
2877
2877
|
//
|
|
2878
|
-
//
|
|
2879
|
-
//
|
|
2880
|
-
// (see upstream
|
|
2881
|
-
//
|
|
2882
|
-
//
|
|
2883
|
-
// the 5-tier fallback
|
|
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
|
|
2966
|
-
// know how many distinct users have messaged this
|
|
2967
|
-
// each channel. We instrument `message_received` to
|
|
2968
|
-
// (channel, senderId) pair to disk; the count
|
|
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
|
package/dist/restart-auth.js
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
*
|
package/inbound-user-tracker.ts
CHANGED
|
@@ -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.
|
|
3475
|
+
// 3.3.7-rc.2 (issue #215, follow-up) — `/totalreclaw-restart`
|
|
3476
3476
|
// ---------------------------------------------------------------
|
|
3477
3477
|
//
|
|
3478
|
-
//
|
|
3479
|
-
//
|
|
3480
|
-
// (see upstream
|
|
3481
|
-
//
|
|
3482
|
-
//
|
|
3483
|
-
// the 5-tier fallback
|
|
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
|
|
3576
|
-
// know how many distinct users have messaged this
|
|
3577
|
-
// each channel. We instrument `message_received` to
|
|
3578
|
-
// (channel, senderId) pair to disk; the count
|
|
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.
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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.
|
|
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",
|