arkclaw-webchat-cli 0.3.0__tar.gz → 0.5.0__tar.gz

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.
Files changed (25) hide show
  1. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/PKG-INFO +4 -4
  2. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/README.md +3 -3
  3. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/pyproject.toml +1 -1
  4. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/flows.py +68 -66
  5. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/sts.py +42 -5
  6. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/openclaw.py +3 -2
  7. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/.gitignore +0 -0
  8. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/__init__.py +0 -0
  9. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/attachments.py +0 -0
  10. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/cli.py +0 -0
  11. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/config.py +0 -0
  12. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/control.py +0 -0
  13. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/core.py +0 -0
  14. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/doctor.py +0 -0
  15. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/errors.py +0 -0
  16. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/identity.py +0 -0
  17. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/oauth.py +0 -0
  18. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/output.py +0 -0
  19. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/policy.py +0 -0
  20. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/providers.py +0 -0
  21. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/secrets_store.py +0 -0
  22. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/__init__.py +0 -0
  23. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/a2a.py +0 -0
  24. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/base.py +0 -0
  25. {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arkclaw-webchat-cli
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: CLI to chat with an ArkClaw EE space's Claw over enterprise SSO — zero permanent AK/SK.
5
5
  Author: ArkClaw Team
6
6
  Keywords: arkclaw,cli,ee,openclaw,sso,sts
@@ -40,7 +40,7 @@ arkclaw chat ci-xxxxxxxx
40
40
  ## What it can do
41
41
 
42
42
  - **Log in as you** — browser SSO (PKCE), short-lived token, auto-refresh. No AK/SK, no client secret, nothing permanent on disk but a token in your OS keychain.
43
- - **List your agents** — `arkclaw agents` shows the claws you can chat with.
43
+ - **List your agents** — `arkclaw agents` shows the claws you've chatted with (kept locally; add one with `arkclaw chat ci-...`).
44
44
  - **Chat** — interactive REPL or one-shot `-m`, streaming token-by-token, with a live "what's it doing" spinner and tool-event trace.
45
45
  - **Talk to a specific claw** — by id (`chat ci-...`) or by name (`chat 答疑助手`).
46
46
  - **Manage a claw's files** — read/write its managed workspace files (`AGENTS.md`, `SOUL.md`, `MEMORY.md`, …) with `ls` / `pull` / `push`.
@@ -77,7 +77,7 @@ arkclaw init
77
77
  # 1. log in (a browser opens; sign in with SSO)
78
78
  arkclaw login
79
79
 
80
- # 2. see which claws you can talk to
80
+ # 2. list the claws you've used (empty at first — you add them by chatting)
81
81
  arkclaw agents
82
82
 
83
83
  # 3. chat — interactive…
@@ -99,7 +99,7 @@ straight to `arkclaw login https://your-space...`. See **[Configuration](#config
99
99
  |---|---|
100
100
  | `arkclaw init [address]` | One-time interactive setup. Saves the address + (only what isn't auto-discovered) the CLI client and STS role to `~/.arkclaw/defaults.json`, so later commands need no env/flags. |
101
101
  | `arkclaw login [space-url]` | Browser SSO login. Uses `init` defaults if you omit the URL. `--transport a2a --endpoint <url>` for an agent endpoint. `--clawid ci-...` sets a default claw. Bare `arkclaw login` re-logs into the previous space. |
102
- | `arkclaw agents` | List the claws/agents you can chat with. |
102
+ | `arkclaw agents` | List the claws you've used from this machine (local history, newest first) + your default claw. Add a claw by chatting it once (`arkclaw chat ci-...`). Not a space-wide directory. |
103
103
  | `arkclaw chat [TARGET] [MSG]` | Chat. `TARGET` = a claw id (`ci-...`), an agent name (from `agents`), or a profile. With `MSG` → one-shot; without → interactive REPL. `-f` attach files, `-o` write the reply, `--session NAME` name the conversation, `--approve-all` auto-approve tool runs. |
104
104
  | `arkclaw <name>` | Shortcut: `arkclaw 答疑助手` ≡ `arkclaw chat 答疑助手`. |
105
105
  | `arkclaw ls` | List the claw's managed workspace files. |
@@ -16,7 +16,7 @@ arkclaw chat ci-xxxxxxxx
16
16
  ## What it can do
17
17
 
18
18
  - **Log in as you** — browser SSO (PKCE), short-lived token, auto-refresh. No AK/SK, no client secret, nothing permanent on disk but a token in your OS keychain.
19
- - **List your agents** — `arkclaw agents` shows the claws you can chat with.
19
+ - **List your agents** — `arkclaw agents` shows the claws you've chatted with (kept locally; add one with `arkclaw chat ci-...`).
20
20
  - **Chat** — interactive REPL or one-shot `-m`, streaming token-by-token, with a live "what's it doing" spinner and tool-event trace.
21
21
  - **Talk to a specific claw** — by id (`chat ci-...`) or by name (`chat 答疑助手`).
22
22
  - **Manage a claw's files** — read/write its managed workspace files (`AGENTS.md`, `SOUL.md`, `MEMORY.md`, …) with `ls` / `pull` / `push`.
@@ -53,7 +53,7 @@ arkclaw init
53
53
  # 1. log in (a browser opens; sign in with SSO)
54
54
  arkclaw login
55
55
 
56
- # 2. see which claws you can talk to
56
+ # 2. list the claws you've used (empty at first — you add them by chatting)
57
57
  arkclaw agents
58
58
 
59
59
  # 3. chat — interactive…
@@ -75,7 +75,7 @@ straight to `arkclaw login https://your-space...`. See **[Configuration](#config
75
75
  |---|---|
76
76
  | `arkclaw init [address]` | One-time interactive setup. Saves the address + (only what isn't auto-discovered) the CLI client and STS role to `~/.arkclaw/defaults.json`, so later commands need no env/flags. |
77
77
  | `arkclaw login [space-url]` | Browser SSO login. Uses `init` defaults if you omit the URL. `--transport a2a --endpoint <url>` for an agent endpoint. `--clawid ci-...` sets a default claw. Bare `arkclaw login` re-logs into the previous space. |
78
- | `arkclaw agents` | List the claws/agents you can chat with. |
78
+ | `arkclaw agents` | List the claws you've used from this machine (local history, newest first) + your default claw. Add a claw by chatting it once (`arkclaw chat ci-...`). Not a space-wide directory. |
79
79
  | `arkclaw chat [TARGET] [MSG]` | Chat. `TARGET` = a claw id (`ci-...`), an agent name (from `agents`), or a profile. With `MSG` → one-shot; without → interactive REPL. `-f` attach files, `-o` write the reply, `--session NAME` name the conversation, `--approve-all` auto-approve tool runs. |
80
80
  | `arkclaw <name>` | Shortcut: `arkclaw 答疑助手` ≡ `arkclaw chat 答疑助手`. |
81
81
  | `arkclaw ls` | List the claw's managed workspace files. |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arkclaw-webchat-cli"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "CLI to chat with an ArkClaw EE space's Claw over enterprise SSO — zero permanent AK/SK."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -11,7 +11,6 @@ and every error is a typed :class:`~ee_claw.errors.ArkclawError`.
11
11
  from __future__ import annotations
12
12
 
13
13
  import asyncio
14
- import dataclasses
15
14
  import json
16
15
  import os
17
16
  import pathlib
@@ -91,6 +90,30 @@ def _openclaw_creds(cfg: SessionConfig, id_token: str | None) -> dict[str, str]:
91
90
  return assume_role_with_oidc(str(id_token), str(cfg.role_trn), cfg.provider_trn)
92
91
 
93
92
 
93
+ def _user_identity(id_token: str | None) -> dict[str, str]:
94
+ """The SSO user's identity from the *verified* id_token, for per-user authz
95
+ at GetClawInstanceChatToken. When the CLI's STS role is a space *resource*
96
+ account, the control plane checks these against the claw's owner and denies
97
+ a non-owner (fail-closed) — so a leaked claw_id can't chat someone else's
98
+ claw. Read from the token ONLY (never user input), so it can't impersonate."""
99
+ if not id_token:
100
+ return {}
101
+ try:
102
+ claims = decode_claims(id_token)
103
+ except ArkclawError:
104
+ return {}
105
+ ident: dict[str, str] = {}
106
+ if claims.get("sub"):
107
+ ident["UserPoolUserUid"] = str(claims["sub"])
108
+ if claims.get("email"):
109
+ ident["Email"] = str(claims["email"])
110
+ for c in ("union_id", "uid"):
111
+ if claims.get(c):
112
+ ident["UnionId"] = str(claims[c])
113
+ break
114
+ return ident
115
+
116
+
94
117
  def _normalize(url: str) -> tuple[str, str]:
95
118
  base = (url if "//" in url else "https://" + url).rstrip("/")
96
119
  host = urllib.parse.urlparse(base).hostname or ""
@@ -473,6 +496,7 @@ def make_transport(
473
496
  *,
474
497
  session: str | None = None,
475
498
  approver: Approver | None = None,
499
+ identity: dict[str, str] | None = None,
476
500
  ) -> Transport:
477
501
  """The target-plane seam: pick the adapter from config, not code. The
478
502
  abstract session name is mapped to transport-specific addressing inside
@@ -484,6 +508,7 @@ def make_transport(
484
508
  clawid=clawid,
485
509
  region=cfg.region,
486
510
  creds=creds,
511
+ identity=identity,
487
512
  session_key=session_key_for(session),
488
513
  approver=approver,
489
514
  )
@@ -668,7 +693,10 @@ def _target_transport(
668
693
  hint="请加 --clawid <ci-...>(或 login 时带 --clawid 设默认)。",
669
694
  )
670
695
  creds = _openclaw_creds(cfg, id_token)
671
- return clawid, make_transport(cfg, clawid, creds, session=session, approver=approver)
696
+ return clawid, make_transport(
697
+ cfg, clawid, creds, session=session, approver=approver,
698
+ identity=_user_identity(id_token),
699
+ )
672
700
 
673
701
 
674
702
  def _save_session_state(cfg: SessionConfig, target: str, session: str | None, transport: Transport) -> None:
@@ -870,11 +898,16 @@ def _ago(ts: object) -> str:
870
898
 
871
899
 
872
900
  def do_agents(emitter: Emitter) -> dict[str, Any]:
873
- """``arkclaw agents``: the agents/claws you can chat with — nothing else.
874
- openclaw → the claws across your space(s) (space auto-discovered if login
875
- didn't pin one); a2a the agent at the endpoint (its card). Each is
876
- recorded so ``chat <name>`` works. Profiles live under ``arkclaw profile``,
877
- recent sessions under ``arkclaw sessions``. Human view is tabular."""
901
+ """``arkclaw agents``: the agents/claws you can chat with.
902
+
903
+ openclaw the claws YOU have used from this machine (kept locally, newest
904
+ first), plus your default claw. This is deliberately NOT a server-side
905
+ enumeration of the space that API returns every member's claws to anyone
906
+ (no per-user scoping is reachable by the CLI's role), so we don't call it.
907
+ You add a claw to this list by chatting it once: ``arkclaw chat ci-...``.
908
+
909
+ a2a → the agent at the endpoint (its card). Profiles live under ``arkclaw
910
+ profile``, full conversation history under ``arkclaw sessions``."""
878
911
  cfg = config_mod.load_config()
879
912
  if not cfg or not cfg.space:
880
913
  raise NoLoginError("尚未登录。", hint="先运行: arkclaw login <空间地址>")
@@ -931,73 +964,40 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
931
964
  )
932
965
  elif cfg.transport == "openclaw":
933
966
  emitter.line(f"★ 当前登录(openclaw)· 空间 {cfg.space}")
934
- _, id_token = _load_session()
935
- try:
936
- creds: dict[str, str] | None = _openclaw_creds(cfg, id_token)
937
- except ArkclawError as e:
938
- creds = None
939
- current["claws_error"] = {"code": e.code, "message": e.message}
940
- claws: list[dict[str, Any]] = []
941
- if creds is not None:
942
- # Which space(s) to list? If login didn't pin a space_id, discover
943
- # the user's spaces so `agents` lists the chat-able claws from the
944
- # address alone. Listing may be denied by a least-privilege role.
945
- space_ids: list[str] = [str(cfg.space_id)] if cfg.space_id else []
946
- if not space_ids:
947
- try:
948
- spaces = control.call_action(
949
- "ListClawSpaces", {"ProjectName": "default", "MaxResults": 50},
950
- region=str(cfg.region), creds=creds,
951
- )
952
- assert isinstance(spaces, dict)
953
- space_ids = [
954
- str(s["SpaceId"]) for s in (spaces.get("Spaces") or [])
955
- if isinstance(s, dict) and s.get("SpaceId")
956
- ]
957
- if len(space_ids) == 1: # cache it so later calls skip discovery
958
- config_mod.save_config(dataclasses.replace(cfg, space_id=space_ids[0]))
959
- except ArkclawError as e:
960
- current["claws_error"] = {"code": e.code, "message": e.message}
961
- for sid in space_ids:
962
- try:
963
- result = redact_obj(control.call_action(
964
- "ListClawInstances",
965
- {"SpaceId": sid, "ProjectName": "default", "MaxResults": 100},
966
- region=str(cfg.region), creds=creds,
967
- ))
968
- assert isinstance(result, dict)
969
- items = result.get("Items") or result.get("ClawInstances") or []
970
- claws += [it for it in items if isinstance(it, dict)]
971
- except ArkclawError as e:
972
- current["claws_error"] = {"code": e.code, "message": e.message}
967
+ # The claws you can chat = the ones YOU have used from this machine,
968
+ # remembered locally — NOT a server enumeration of the space. The
969
+ # space-wide ListClawInstances returns every member's claws to anyone
970
+ # (no per-user scoping reachable by the CLI's role), so we don't call it.
971
+ # New claws enter via `arkclaw chat ci-...`; each chat is recorded
972
+ # (record_session) and shows up here next time.
973
+ seen: dict[str, float] = {}
974
+ for s in config_mod.list_sessions():
975
+ cid = s.get("claw")
976
+ if s.get("space") == cfg.space and cid:
977
+ ts = s.get("last_used")
978
+ seen[str(cid)] = max(seen.get(str(cid), 0.0), float(ts) if isinstance(ts, (int, float)) else 0.0)
979
+ if cfg.claw and str(cfg.claw) not in seen:
980
+ seen[str(cfg.claw)] = 0.0 # the default claw, even if not chatted yet
981
+ claws = [
982
+ {"ClawInstanceId": cid, "last_used": ts}
983
+ for cid, ts in sorted(seen.items(), key=lambda kv: kv[1], reverse=True)
984
+ ]
973
985
  current["claws"] = claws
974
- for it in claws: # claw Name → chat <名字>
975
- cid = it.get("ClawInstanceId") or it.get("Id")
976
- if it.get("Name") and cid:
977
- config_mod.record_agent(str(it["Name"]), dataclasses.replace(cfg, claw=str(cid)))
986
+ current["source"] = "local-history"
978
987
  if claws:
979
988
  emitter.table(
980
- ["Claw(可 chat)", "名称", "状态", "默认"],
989
+ ["Claw(可 chat)", "上次使用", "默认"],
981
990
  [
982
991
  [
983
- it.get("ClawInstanceId") or it.get("Id") or "—",
984
- it.get("Name", ""),
985
- it.get("Status", ""),
986
- "★" if (it.get("ClawInstanceId") or it.get("Id")) == cfg.claw else "",
992
+ str(c["ClawInstanceId"]),
993
+ _ago(c["last_used"]) if c["last_used"] else "—",
994
+ "" if c["ClawInstanceId"] == cfg.claw else "",
987
995
  ]
988
- for it in claws
996
+ for c in claws
989
997
  ],
990
998
  )
991
999
  else:
992
- err = current.get("claws_error")
993
- emitter.table(
994
- ["Claw(可 chat)", "状态", "说明"],
995
- [[
996
- cfg.claw or "(用 --clawid 直连)",
997
- "默认目标" if cfg.claw else "—",
998
- f"列表不可用: {err['code']}" if isinstance(err, dict) else "没有可用 claw",
999
- ]],
1000
- )
1000
+ emitter.line(" 还没用过任何 claw。用 `arkclaw chat ci-xxxx` 开始,之后这里会记住它。")
1001
1001
 
1002
1002
  # `agents` is the list of agents you can chat — nothing else. Saved
1003
1003
  # profiles live under `arkclaw profile`, recent sessions under
@@ -1020,7 +1020,9 @@ def _openclaw_file_transport(clawid: str | None) -> tuple[OpenClawTransport, str
1020
1020
  if not cfg.region:
1021
1021
  raise RegionError("登录信息缺少区域。", hint="重新 login 并加 --region。")
1022
1022
  creds = _openclaw_creds(cfg, id_token)
1023
- return OpenClawTransport(clawid=clawid, region=str(cfg.region), creds=creds), clawid
1023
+ return OpenClawTransport(
1024
+ clawid=clawid, region=str(cfg.region), creds=creds, identity=_user_identity(id_token)
1025
+ ), clawid
1024
1026
 
1025
1027
 
1026
1028
  def do_ls(emitter: Emitter, *, clawid: str | None = None) -> dict[str, Any]:
@@ -121,11 +121,34 @@ def assume_role_with_oidc(
121
121
  return creds
122
122
 
123
123
 
124
- def get_chat_token(clawid: str, region: str, creds: dict[str, str]) -> tuple[str, str]:
125
- """GetClawInstanceChatToken with temp creds (ChatToken, Endpoint)."""
124
+ def _volc_error(body: str) -> tuple[str, str]:
125
+ """Extract ``(Code, Message)`` from a Volcengine error envelope
126
+ (``ResponseMetadata.Error``); ``('', '')`` if it can't be parsed. Lets the
127
+ CLI surface a clean message instead of dumping the raw JSON envelope."""
128
+ try:
129
+ err = json.loads(body)["ResponseMetadata"]["Error"]
130
+ return str(err.get("Code") or ""), str(err.get("Message") or "")
131
+ except (ValueError, KeyError, TypeError):
132
+ return "", ""
133
+
134
+
135
+ def get_chat_token(
136
+ clawid: str, region: str, creds: dict[str, str], *, identity: dict[str, str] | None = None
137
+ ) -> tuple[str, str]:
138
+ """GetClawInstanceChatToken with temp creds → (ChatToken, Endpoint).
139
+
140
+ ``identity`` carries the SSO user's identity (``UserPoolUserUid``/``Email``/
141
+ ``UnionId``) taken from the *verified* id_token. The control plane's
142
+ resource-account branch authorizes the user against the claw's owner with
143
+ these fields and denies a non-owner (fail-closed) — so a leaked claw_id is
144
+ not enough to chat someone else's claw. (Sent always; the owning-tenant
145
+ branch ignores it.)"""
126
146
  host = f"arkclaw.{region}.volcengineapi.com"
127
147
  query = {"Action": "GetClawInstanceChatToken", "Version": ARKCLAW_API_VERSION}
128
- body = json.dumps({"ClawInstanceId": clawid, "ProjectName": "default"}).encode()
148
+ payload: dict[str, str] = {"ClawInstanceId": clawid, "ProjectName": "default"}
149
+ if identity:
150
+ payload.update({k: v for k, v in identity.items() if v})
151
+ body = json.dumps(payload).encode()
129
152
  headers = sign_headers(
130
153
  "POST", host, query, body,
131
154
  ak=creds["AccessKeyId"], sk=creds["SecretAccessKey"],
@@ -138,9 +161,23 @@ def get_chat_token(clawid: str, region: str, creds: dict[str, str]) -> tuple[str
138
161
  urllib.request.Request(url, data=body, headers=headers), timeout=30
139
162
  ).read()
140
163
  except urllib.error.HTTPError as e:
141
- # Redact BEFORE truncating so a split token can't slip past the regex.
164
+ err_body = e.read().decode(errors="replace")
165
+ code, message = _volc_error(err_body)
166
+ if code == "AccessDenied" or "AccessDenied" in err_body: # coruscant per-user gate
167
+ raise StsError(
168
+ "权限不足:当前用户无权访问该 Claw 实例(非所有者且未获授权)。",
169
+ hint="运行 `arkclaw agents` 查看你有权访问的 Claw 实例,或使用 `--clawid` 指定其它实例。",
170
+ ) from e
171
+ if code == "ErrMissingUserIdentity": # gate ran but the CLI sent no identity
172
+ raise StsError(
173
+ "权限校验失败:请求未包含用户身份信息。",
174
+ hint="请将 CLI 升级至最新版本(uv tool upgrade arkclaw-webchat-cli),并重新登录(arkclaw login)。",
175
+ ) from e
176
+ # Anything else: surface the clean Code/Message — never the raw JSON envelope.
177
+ # Redact first so a split token can't slip past the regex.
178
+ clean = redact(message or err_body)[:200]
142
179
  raise StsError(
143
- f"GetClawInstanceChatToken failed: {redact(e.read().decode(errors='replace'))[:300]}"
180
+ f"获取会话令牌失败:{clean}", hint=(f"服务端错误码:{code}" if code else None)
144
181
  ) from e
145
182
  except urllib.error.URLError as e:
146
183
  raise NetworkError(f"cannot reach {host}: {e.reason}") from e
@@ -334,6 +334,7 @@ class OpenClawTransport:
334
334
  clawid: str
335
335
  region: str
336
336
  creds: dict[str, str]
337
+ identity: dict[str, str] | None = None # SSO user identity for per-claw authz
337
338
  session_key: str = SESSION_KEY
338
339
  approver: Approver | None = None
339
340
  idle_timeout: float = 30.0
@@ -355,7 +356,7 @@ class OpenClawTransport:
355
356
  TurnEvent(kind="info", text=f"⚠ 二进制文件无法内联,已跳过: {name}")
356
357
  )
357
358
  on_event(TurnEvent(kind="status", text="获取 ChatToken"))
358
- chat_token, endpoint = get_chat_token(self.clawid, self.region, self.creds)
359
+ chat_token, endpoint = get_chat_token(self.clawid, self.region, self.creds, identity=self.identity)
359
360
  on_event(TurnEvent(kind="status", text="连接 claw(wss)"))
360
361
  url = (
361
362
  f"wss://{endpoint}/?chatToken={urllib.parse.quote(chat_token)}"
@@ -415,7 +416,7 @@ class OpenClawTransport:
415
416
  async def _request(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
416
417
  """connect (hello-ok) → one control request → its res payload. Errors
417
418
  are redacted; the token-bearing URL is never echoed."""
418
- chat_token, endpoint = get_chat_token(self.clawid, self.region, self.creds)
419
+ chat_token, endpoint = get_chat_token(self.clawid, self.region, self.creds, identity=self.identity)
419
420
  url = (
420
421
  f"wss://{endpoint}/?chatToken={urllib.parse.quote(chat_token)}"
421
422
  f"&clawInstanceId={urllib.parse.quote(self.clawid)}"