@drakon-systems/multi-clawd 1.8.1 → 1.8.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/README.md CHANGED
@@ -73,7 +73,10 @@ multi-clawd hermes sync --profile work --strategy least_used
73
73
  multi-clawd hermes doctor --config ~/configs/openclaw.json
74
74
  ```
75
75
 
76
- Validated against Hermes Agent **0.19.1**.
76
+ Validated against Hermes Agent **0.20.6**. The bridge/core/CLI integration
77
+ tests exercise that installed release's real Python APIs in isolated temporary
78
+ homes; on machines without a compatible Hermes install, those integration tests
79
+ skip while the no-Hermes CLI safety tests still run.
77
80
 
78
81
  ### Only stable setup tokens are imported
79
82
 
@@ -94,7 +97,7 @@ Everything else is refused, on purpose:
94
97
  on that source instead of duplicating the grant.
95
98
  - **`configDir` logins are not importable, and Hermes cannot be pointed at
96
99
  them either.** The same single-use-refresh-token problem applies, and unlike
97
- a native login there is no Hermes-side fallback: as of Hermes Agent 0.19.1,
100
+ a native login there is no Hermes-side fallback: as of Hermes Agent 0.20.6,
98
101
  its `claude_code` credential source reads only the native path above, never
99
102
  an arbitrary `configDir`. A `configDir` account can only reach Hermes' pool
100
103
  by getting its own `oauthTokenFile` (a `claude setup-token`, same as above),
@@ -364,7 +367,7 @@ openclaw plugins install (Get-Location).Path
364
367
  **Or let your agent install it.** Running an OpenClaw assistant or Claude
365
368
  Code on the target machine already? Paste it this and go make coffee:
366
369
 
367
- > Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v1.8.1/SETUP-AGENT.md
370
+ > Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v1.8.2/SETUP-AGENT.md
368
371
  > and follow it to set up multi-clawd on this machine. I own a second
369
372
  > Claude account — ask me when you need me to log in.
370
373
 
@@ -377,7 +380,11 @@ happens to say today. Swap the tag if you want a different release, but
377
380
  prefer a tag over a branch.
378
381
 
379
382
  **Requirements:** OpenClaw ≥ 2026.6, the `claude` CLI on `PATH`, and a
380
- second Claude subscription you own.
383
+ second Claude subscription you own. The current plugin SDK contract and runtime
384
+ registration path are tested against OpenClaw **2026.7.1**. The standalone
385
+ `multi-clawd` CLI and Hermes commands do not require the optional OpenClaw peer
386
+ to be resolvable; plugin loading and OpenClaw-backed commands still require the
387
+ host-provided peer.
381
388
 
382
389
  **Upgrading:**
383
390
 
package/SECURITY.md CHANGED
@@ -30,9 +30,10 @@ credit you (unless you'd rather we didn't), and note it in the CHANGELOG.
30
30
  `oauthTokenFile`, multi-clawd warns (once per process) when the file is
31
31
  readable beyond your own user account. It warns rather than refuses: the
32
32
  credential still works, and the fix is yours to make — `chmod 600`.
33
- - **Host credentials are stripped from child processes.** 22 Claude/Anthropic
34
- environment variables are cleared before each launch, so one account's
35
- credential cannot bleed into another account's session.
33
+ - **Host credentials are stripped from child processes.** 38 Claude/Anthropic
34
+ environment variables covering authentication, runtime, and telemetry are
35
+ cleared before each launch, so one account's credential cannot bleed into
36
+ another account's session.
36
37
  - **State files are written `0600`,** atomically (temp file + rename), and
37
38
  contain rate-limit telemetry only — never credentials.
38
39
 
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ const BASE_ARGS = [
34
34
  "--disallowedTools",
35
35
  "ScheduleWakeup,CronCreate,Bash(run_in_background:true),Monitor",
36
36
  ];
37
- const CLEAR_ENV = [
37
+ export const CLEAR_ENV = [
38
38
  "ANTHROPIC_API_KEY",
39
39
  "ANTHROPIC_API_KEY_OLD",
40
40
  "ANTHROPIC_API_TOKEN",
@@ -57,6 +57,22 @@ const CLEAR_ENV = [
57
57
  "CLAUDE_CODE_USE_BEDROCK",
58
58
  "CLAUDE_CODE_USE_FOUNDRY",
59
59
  "CLAUDE_CODE_USE_VERTEX",
60
+ "OTEL_EXPORTER_OTLP_ENDPOINT",
61
+ "OTEL_EXPORTER_OTLP_HEADERS",
62
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
63
+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
64
+ "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL",
65
+ "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
66
+ "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
67
+ "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL",
68
+ "OTEL_EXPORTER_OTLP_PROTOCOL",
69
+ "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
70
+ "OTEL_EXPORTER_OTLP_TRACES_HEADERS",
71
+ "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL",
72
+ "OTEL_LOGS_EXPORTER",
73
+ "OTEL_METRICS_EXPORTER",
74
+ "OTEL_SDK_DISABLED",
75
+ "OTEL_TRACES_EXPORTER",
60
76
  ];
61
77
  function expandHome(p) {
62
78
  if (p === "~")
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "multi-clawd",
3
3
  "name": "multi-clawd",
4
- "version": "1.8.1",
4
+ "version": "1.8.2",
5
5
  "description": "Register additional Claude Code logins (Max/Pro accounts) as first-class OpenClaw CLI backends for cross-account failover, keeping the full skills/MCP harness on every account.",
6
6
  "cliBackends": [
7
7
  "claw1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakon-systems/multi-clawd",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Multi-account Claude Code failover for OpenClaw — register additional Claude (Max/Pro) logins as first-class CLI backends and keep the full skills/MCP harness across every account. Also imports those accounts' setup tokens into Hermes Agent's Anthropic credential pool.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "openclaw": {
23
23
  "build": {
24
- "openclawVersion": "2026.6.11"
24
+ "openclawVersion": "2026.7.1"
25
25
  },
26
26
  "extensions": [
27
27
  "./src/index.ts"
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "main": "./dist/index.js",
38
38
  "bin": {
39
- "multi-clawd": "./scripts/cli.mjs"
39
+ "multi-clawd": "scripts/cli.mjs"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
package/scripts/cli.mjs CHANGED
File without changes
@@ -10,7 +10,7 @@ grant (a native or config-dir ``.credentials.json``) is single-use on refresh,
10
10
  so duplicating one into a second store guarantees that one of the copies dies.
11
11
  For a *native* login that copy is unnecessary anyway: Hermes' own
12
12
  ``claude_code`` credential source already reads that exact file directly. A
13
- *config-dir* login has no such fallback — as of Hermes Agent 0.19.1,
13
+ *config-dir* login has no such fallback — as of Hermes Agent 0.20.6,
14
14
  ``claude_code`` only reads the native path, never an arbitrary config dir — so
15
15
  it can only reach this bridge via its own setup token, never a duplicated
16
16
  grant. Requests carrying refresh tokens or expiries are refused outright.
@@ -60,6 +60,24 @@ ACCOUNT_ID_ALPHABET = frozenset("abcdefghijklmnopqrstuvwxyz0123456789_-")
60
60
  # Cleared whenever a managed row is written: a setup token never expires on a
61
61
  # schedule, so a stale expiry copied from an older row would quarantine it.
62
62
  STALE_EXPIRY_FIELDS = ("expires_at", "expires_at_ms", "last_refresh")
63
+ # Hermes' own runtime bookkeeping (agent/credential_pool.py PooledCredential).
64
+ # Cleared ONLY when the access token itself changes: Hermes' selection
65
+ # (CredentialPool._available_entries) skips an entry whose last_status is
66
+ # "exhausted" or "dead" until last_error_reset_at passes, which can be hours
67
+ # or days out. A rotated-in token proves the row is being actively managed
68
+ # again, so carrying the old verdict forward would leave a freshly-rotated
69
+ # credential unselectable until that window lapses on its own. A
70
+ # metadata-only update (label/priority) with the SAME token is not evidence
71
+ # of anything — Hermes' own telemetry for a healthy pool must survive it
72
+ # untouched.
73
+ STATUS_FIELDS = (
74
+ "last_status",
75
+ "last_status_at",
76
+ "last_error_code",
77
+ "last_error_reason",
78
+ "last_error_message",
79
+ "last_error_reset_at",
80
+ )
63
81
  # Mirrors src/hermes-core.ts's parseClaudeSetupToken: ASCII-only, and shaped
64
82
  # like the current `sk-ant-oat01-...` setup-token family. The version digits
65
83
  # are intentionally unconstrained beyond "two or more" so a future
@@ -443,7 +461,12 @@ def merge_credential(existing: dict[str, Any], credential: DesiredCredential) ->
443
461
  Expiry fields are cleared: a setup token carries no expiry, and a value left
444
462
  over from an older row would make Hermes treat a perfectly good credential
445
463
  as expired.
464
+
465
+ Status fields are cleared too, but only when the access token actually
466
+ changed — see STATUS_FIELDS. A metadata-only update (same token) preserves
467
+ them exactly.
446
468
  """
469
+ token_rotated = existing.get("access_token") != credential.access_token
447
470
  updated = copy.deepcopy(existing)
448
471
  updated.update(
449
472
  {
@@ -458,6 +481,9 @@ def merge_credential(existing: dict[str, Any], credential: DesiredCredential) ->
458
481
  updated.pop("refresh_token", None)
459
482
  for field in STALE_EXPIRY_FIELDS:
460
483
  updated.pop(field, None)
484
+ if token_rotated:
485
+ for field in STATUS_FIELDS:
486
+ updated.pop(field, None)
461
487
  return updated
462
488
 
463
489