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.
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/PKG-INFO +4 -4
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/README.md +3 -3
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/pyproject.toml +1 -1
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/flows.py +68 -66
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/sts.py +42 -5
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/openclaw.py +3 -2
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/.gitignore +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/__init__.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/attachments.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/cli.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/config.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/control.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/core.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/doctor.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/errors.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/identity.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/oauth.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/output.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/policy.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/providers.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/secrets_store.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/__init__.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/a2a.py +0 -0
- {arkclaw_webchat_cli-0.3.0 → arkclaw_webchat_cli-0.5.0}/src/ee_claw/transport/base.py +0 -0
- {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
|
+
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
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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(
|
|
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
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
|
996
|
+
for c in claws
|
|
989
997
|
],
|
|
990
998
|
)
|
|
991
999
|
else:
|
|
992
|
-
|
|
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(
|
|
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
|
|
125
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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)}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|