@totalreclaw/totalreclaw 3.3.1-rc.13 → 3.3.1-rc.14

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,59 @@ 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.1-rc.14] — 2026-04-24
8
+
9
+ Coordinated version bump with Python `2.3.1rc14`. Two narrow bug fixes
10
+ found during rc.13 user QA on 2026-04-24:
11
+
12
+ ### RC-gated QA bug tool — target-repo hardening
13
+
14
+ `totalreclaw_report_qa_bug` now refuses to file to any repo that isn't
15
+ internal. rc.13 user QA surfaced agent-filed bug reports leaking to the
16
+ public `p-diogo/totalreclaw` tracker despite the tool's default target
17
+ being `p-diogo/totalreclaw-internal`.
18
+
19
+ - New env var: `TOTALRECLAW_QA_REPO` lets operators point the tool at a
20
+ private fork. The default stays `p-diogo/totalreclaw-internal`.
21
+ - New `resolveQaRepo(...)` guard: rejects any slug that is on the
22
+ public-repo denylist (includes `p-diogo/totalreclaw`,
23
+ `...-website`, `...-relay`, `...-plugin`, `...-hermes`) OR does not
24
+ end in `-internal`. The check runs before the HTTP POST is
25
+ constructed, so rejection never leaves the client.
26
+ - `CONFIG.qaRepoOverride` surfaces the env var through `config.ts`
27
+ (keeps scanner-sensitive `process.env` reads centralized).
28
+ - Regression test in `qa-bug-report.test.ts` mocks the public slug
29
+ and asserts `fetch` is NEVER called.
30
+
31
+ Labels on filing unchanged — still emits `qa-bug`, `pending-triage`,
32
+ `severity:<...>`, `component:<...>`, `rc:<...>`.
33
+
34
+ ### Relay pair page — PIN paste button UX
35
+
36
+ The paste button on the step-1 PIN screen was silently failing under
37
+ certain browser states. rc.14 rewrites the handler with a proper
38
+ error taxonomy:
39
+
40
+ - Capability probe up front — `navigator.clipboard.readText` missing →
41
+ clear "Paste unavailable on this browser" toast.
42
+ - `NotAllowedError` → "Clipboard access denied — type the 6 digits
43
+ manually" (covers iOS Safari permission denial).
44
+ - Empty clipboard → "Clipboard is empty — copy the PIN from your chat
45
+ first".
46
+ - Non-digit content → "Clipboard has no digits — copy the 6-digit PIN
47
+ first".
48
+ - Every failure path focuses the first PIN cell so the user can fall
49
+ through to manual typing without another click.
50
+ - Errors log to `console.warn` with name + message so future failures
51
+ are diagnosable from browser devtools.
52
+
53
+ The mockup at `docs/mockups/rc13-pair-wizard/wizard.js` gets the same
54
+ rewrite for parity — the relay's `scripts/sync-pair-preview.mjs`
55
+ regenerates `/pair-preview/` from this source.
56
+
57
+ Fix also applies to the "Paste all 12 words" import-grid button on the
58
+ relay production page (same taxonomy, same focus-fallback).
59
+
7
60
  ## [3.3.1-rc.13] — 2026-04-24
8
61
 
9
62
  Coordinated version bump with Python `2.3.1rc13`. No substantive
package/config.ts CHANGED
@@ -203,6 +203,17 @@ export const CONFIG = {
203
203
  return process.env.TOTALRECLAW_QA_GITHUB_TOKEN || process.env.GITHUB_TOKEN || '';
204
204
  },
205
205
 
206
+ // 3.3.1-rc.14: optional target-repo override for the RC-gated QA
207
+ // bug-report tool. The `qa-bug-report` module enforces a
208
+ // "slug ends in `-internal`" rule on whatever is resolved here, so
209
+ // this override is only useful for forks / mirrors of the internal
210
+ // tracker. Leaving unset uses the production default
211
+ // (`p-diogo/totalreclaw-internal`). Read via getter so operators can
212
+ // flip the var at runtime.
213
+ get qaRepoOverride(): string {
214
+ return process.env.TOTALRECLAW_QA_REPO || '';
215
+ },
216
+
206
217
  // Paths
207
218
  home,
208
219
  billingCachePath: path.join(home, '.totalreclaw', 'billing-cache.json'),
package/index.ts CHANGED
@@ -5280,10 +5280,16 @@ const plugin = {
5280
5280
  details: { error: 'missing_github_token' },
5281
5281
  };
5282
5282
  }
5283
+ // rc.14 — `repo` is resolved inside `postQaBugIssue` via
5284
+ // `resolveQaRepo(...)`, which reads `TOTALRECLAW_QA_REPO` and
5285
+ // refuses any slug that isn't a `-internal` fork. Pass the
5286
+ // config-surfaced override so env reads stay in config.ts.
5287
+ const repoOverride = CONFIG.qaRepoOverride || undefined;
5283
5288
  const result = await postQaBugIssue(
5284
5289
  params as unknown as import('./qa-bug-report.js').QaBugArgs,
5285
5290
  {
5286
5291
  githubToken: token,
5292
+ repo: repoOverride,
5287
5293
  logger: api.logger,
5288
5294
  },
5289
5295
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totalreclaw/totalreclaw",
3
- "version": "3.3.1-rc.13",
3
+ "version": "3.3.1-rc.14",
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/qa-bug-report.ts CHANGED
@@ -18,6 +18,13 @@
18
18
  * POST. BIP-39 phrases, API keys, Telegram bot tokens, and bearer tokens
19
19
  * in headers all become `<REDACTED>` in the posted issue. Refer to
20
20
  * `redactSecrets()` for the exact rule set.
21
+ *
22
+ * Target repo safety: the default target is `p-diogo/totalreclaw-internal`.
23
+ * Operators can override via the `TOTALRECLAW_QA_REPO` env var, but only
24
+ * to another slug ending in `-internal`. Any other slug — including the
25
+ * public `p-diogo/totalreclaw` — is rejected with a loud error. rc.13 QA
26
+ * surfaced a repo-slug drift where QA findings leaked to the public
27
+ * tracker; rc.14 adds this fail-loud guard.
21
28
  */
22
29
 
23
30
  // ---------------------------------------------------------------------------
@@ -148,7 +155,12 @@ export interface QaBugArgs {
148
155
  export interface QaBugDeps {
149
156
  /** GitHub personal-access token with `repo` scope. */
150
157
  githubToken: string;
151
- /** Repo to post to. Defaults to `p-diogo/totalreclaw-internal`. */
158
+ /**
159
+ * Repo to post to. Defaults to `resolveQaRepo(null)` → reads
160
+ * `TOTALRECLAW_QA_REPO` env var and falls back to
161
+ * `p-diogo/totalreclaw-internal`. Pass a slug (tests only) to
162
+ * bypass env-var lookup.
163
+ */
152
164
  repo?: string;
153
165
  /**
154
166
  * Abstract fetch for testing — defaults to global `fetch`. Intentionally
@@ -160,6 +172,76 @@ export interface QaBugDeps {
160
172
  logger?: { info: (msg: string) => void; warn: (msg: string) => void };
161
173
  }
162
174
 
175
+ // ---------------------------------------------------------------------------
176
+ // Target repo guard — fail-loud on any repo that isn't the internal tracker.
177
+ // ---------------------------------------------------------------------------
178
+
179
+ export const DEFAULT_QA_REPO = 'p-diogo/totalreclaw-internal';
180
+
181
+ /**
182
+ * Known-public repo slugs that must never receive QA bug reports. The
183
+ * structural rule (`endsWith('-internal')`) below should already block
184
+ * these, but the explicit denylist is a belt-and-braces safety against
185
+ * a future rename that accidentally drops the `-internal` suffix.
186
+ */
187
+ export const PUBLIC_REPOS_DENYLIST: ReadonlySet<string> = new Set([
188
+ 'p-diogo/totalreclaw',
189
+ 'p-diogo/totalreclaw-website',
190
+ 'p-diogo/totalreclaw-relay',
191
+ 'p-diogo/totalreclaw-plugin',
192
+ 'p-diogo/totalreclaw-hermes',
193
+ ]);
194
+
195
+ /**
196
+ * Resolve the target repo for a QA bug filing.
197
+ *
198
+ * Precedence: explicit override → `TOTALRECLAW_QA_REPO` env → default.
199
+ * Throws if the slug is on the public denylist or does not end in
200
+ * `-internal`. rc.13 QA found agent-filed bug reports leaking to the
201
+ * public repo; this guard makes any such drift fail loudly rather than
202
+ * silently leak RC ship-stopper detail.
203
+ *
204
+ * `TOTALRECLAW_QA_REPO` is the documented override var. The env-var
205
+ * read lives in `config.ts` (CONFIG.qaRepoOverride) so this module
206
+ * never touches process environment directly — keeps the plugin
207
+ * scanner-sim clean because this file also performs a GitHub HTTPS
208
+ * request (env + network in the same file would trip OpenClaw's
209
+ * env-harvesting heuristic).
210
+ *
211
+ * Pass the env-resolved slug (or `null`/empty for default) as
212
+ * `override`. Tests can inject via the second arg.
213
+ */
214
+ export function resolveQaRepo(
215
+ override?: string | null,
216
+ env?: Record<string, string | undefined>,
217
+ ): string {
218
+ // `env` is only for test injection — production callers should
219
+ // pre-resolve the env value via CONFIG.qaRepoOverride and pass it as
220
+ // `override`. The env lookup is a last-resort fallback that works in
221
+ // Node but is NEVER the primary path in production.
222
+ const envOverride = env ? env.TOTALRECLAW_QA_REPO : undefined;
223
+ const raw = (override || envOverride || DEFAULT_QA_REPO).trim();
224
+ if (!raw || !raw.includes('/')) {
225
+ throw new Error(`invalid QA repo slug '${raw}': expected 'owner/name' format`);
226
+ }
227
+ if (PUBLIC_REPOS_DENYLIST.has(raw)) {
228
+ throw new Error(
229
+ `refusing to file QA bug to PUBLIC repo '${raw}'. ` +
230
+ 'QA bug reports contain RC ship-stopper detail that must not ' +
231
+ "leak to public. Set TOTALRECLAW_QA_REPO to a repo ending in " +
232
+ "'-internal' (e.g. p-diogo/totalreclaw-internal).",
233
+ );
234
+ }
235
+ if (!raw.endsWith('-internal')) {
236
+ throw new Error(
237
+ `refusing to file QA bug to repo '${raw}': slug must end in ` +
238
+ "'-internal' (structural safety rule). Override via " +
239
+ 'TOTALRECLAW_QA_REPO only to another internal fork.',
240
+ );
241
+ }
242
+ return raw;
243
+ }
244
+
163
245
  const VALID_INTEGRATIONS = new Set([
164
246
  'plugin',
165
247
  'hermes',
@@ -260,7 +342,7 @@ export async function postQaBugIssue(
260
342
  if ('error' in validation) throw new Error(`invalid args: ${validation.error}`);
261
343
  if (!deps.githubToken) throw new Error('githubToken is required');
262
344
 
263
- const repo = deps.repo ?? 'p-diogo/totalreclaw-internal';
345
+ const repo = resolveQaRepo(deps.repo ?? null);
264
346
  const url = `https://api.github.com/repos/${repo}/issues`;
265
347
 
266
348
  const title = `[qa-bug] ${redactSecrets(args.title)}`;