arkclaw-webchat-cli 0.6.0__tar.gz → 0.6.1__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.6.0 → arkclaw_webchat_cli-0.6.1}/PKG-INFO +1 -1
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/pyproject.toml +1 -1
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/cli.py +25 -7
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/flows.py +174 -64
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/transport/openclaw.py +32 -3
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/.gitignore +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/README.md +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/__init__.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/attachments.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/config.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/control.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/core.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/doctor.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/errors.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/identity.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/oauth.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/output.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/policy.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/providers.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/secrets_store.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/sts.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/transport/__init__.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/transport/a2a.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/transport/base.py +0 -0
- {arkclaw_webchat_cli-0.6.0 → arkclaw_webchat_cli-0.6.1}/src/ee_claw/update.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "arkclaw-webchat-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.1"
|
|
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"
|
|
@@ -161,11 +161,20 @@ def chat(
|
|
|
161
161
|
message_opt: str | None = typer.Option(
|
|
162
162
|
None, "-m", "--message", help="One-shot message (same as the positional argument)."
|
|
163
163
|
),
|
|
164
|
+
agent: str = typer.Option(
|
|
165
|
+
"main",
|
|
166
|
+
"--agent",
|
|
167
|
+
help="Which agent in the claw to talk to (its agentId; see `arkclaw agents`). "
|
|
168
|
+
"Defaults to the claw's main agent.",
|
|
169
|
+
),
|
|
164
170
|
session: str | None = typer.Option(
|
|
165
171
|
None,
|
|
166
172
|
"--session",
|
|
167
|
-
help="
|
|
168
|
-
"
|
|
173
|
+
help="Resume a specific conversation by its 会话ID (see `arkclaw sessions`). "
|
|
174
|
+
"Omit to continue the agent's main session.",
|
|
175
|
+
),
|
|
176
|
+
new: bool = typer.Option(
|
|
177
|
+
False, "--new", help="Start a fresh conversation instead of continuing the last one."
|
|
169
178
|
),
|
|
170
179
|
approve_all: bool = typer.Option(
|
|
171
180
|
False,
|
|
@@ -192,6 +201,8 @@ def chat(
|
|
|
192
201
|
target=target,
|
|
193
202
|
message=message_opt or message,
|
|
194
203
|
session=session,
|
|
204
|
+
agent=agent,
|
|
205
|
+
new=new,
|
|
195
206
|
approve_all=approve_all,
|
|
196
207
|
files=list(file) or None,
|
|
197
208
|
output=output,
|
|
@@ -200,10 +211,17 @@ def chat(
|
|
|
200
211
|
|
|
201
212
|
|
|
202
213
|
@app.command()
|
|
203
|
-
def sessions(
|
|
204
|
-
|
|
214
|
+
def sessions(
|
|
215
|
+
claw: str | None = typer.Argument(
|
|
216
|
+
None, metavar="[CLAW]", help="Claw id (ci-...). Omit to use your default claw."
|
|
217
|
+
),
|
|
218
|
+
agent: str = typer.Option("main", "--agent", help="Which agent's conversations to list."),
|
|
219
|
+
json_mode: bool = _json_opt(),
|
|
220
|
+
) -> None:
|
|
221
|
+
"""List an agent's conversations from the server (newest first). Resume one
|
|
222
|
+
with `arkclaw chat <claw> --session <会话ID>`, or start fresh with `--new`."""
|
|
205
223
|
emitter = Emitter(json_mode=json_mode)
|
|
206
|
-
_run(emitter, lambda: flows.do_sessions(emitter))
|
|
224
|
+
_run(emitter, lambda: flows.do_sessions(emitter, clawid=claw, agent=agent))
|
|
207
225
|
|
|
208
226
|
|
|
209
227
|
@app.command(name="ls")
|
|
@@ -243,8 +261,8 @@ def push(
|
|
|
243
261
|
|
|
244
262
|
@app.command()
|
|
245
263
|
def agents(json_mode: bool = _json_opt()) -> None:
|
|
246
|
-
"""List the agents
|
|
247
|
-
|
|
264
|
+
"""List the agents you can chat with (openclaw: the agents in your accessible
|
|
265
|
+
claws; a2a: the agent at the endpoint). Then `chat <claw-or-name>`."""
|
|
248
266
|
emitter = Emitter(json_mode=json_mode)
|
|
249
267
|
_run(emitter, lambda: flows.do_agents(emitter))
|
|
250
268
|
|
|
@@ -20,6 +20,7 @@ import signal
|
|
|
20
20
|
import sys
|
|
21
21
|
import time
|
|
22
22
|
import urllib.parse
|
|
23
|
+
import uuid
|
|
23
24
|
from typing import Any
|
|
24
25
|
|
|
25
26
|
from ee_claw import config as config_mod
|
|
@@ -53,7 +54,7 @@ from ee_claw.identity import (
|
|
|
53
54
|
from ee_claw.oauth import OAuthClient
|
|
54
55
|
from ee_claw.output import Emitter
|
|
55
56
|
from ee_claw.providers import ProviderContext, resolve_token_source
|
|
56
|
-
from ee_claw.sts import assume_role_with_oidc
|
|
57
|
+
from ee_claw.sts import assume_role_with_oidc
|
|
57
58
|
from ee_claw.transport.a2a import A2ATransport, agent_card
|
|
58
59
|
from ee_claw.transport.base import Approver, Attachment, Transport, TurnEvent, TurnResult
|
|
59
60
|
from ee_claw.transport.openclaw import OpenClawTransport, session_key_for
|
|
@@ -116,35 +117,77 @@ def _user_identity(id_token: str | None) -> dict[str, str]:
|
|
|
116
117
|
return ident
|
|
117
118
|
|
|
118
119
|
|
|
119
|
-
def
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
def _agent_display_name(agent: dict[str, Any]) -> str:
|
|
121
|
+
"""Friendly name for an agent. The main agent often comes back named "main";
|
|
122
|
+
show a friendly label instead of leaking the internal id (mirrors the web)."""
|
|
123
|
+
name = str(agent.get("name") or "").strip()
|
|
124
|
+
agent_id = str(agent.get("agentId") or "")
|
|
125
|
+
is_main = bool(agent.get("default")) or agent_id == "main"
|
|
126
|
+
if is_main and (not name or name == "main"):
|
|
127
|
+
return "ArkClaw 智能助手"
|
|
128
|
+
return name or agent_id or "(未命名)"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _list_agents_for_claws(
|
|
132
|
+
cfg: SessionConfig, creds: dict[str, str], identity: dict[str, str], claw_ids: list[str]
|
|
133
|
+
) -> dict[str, tuple[str, list[dict[str, Any]]]]:
|
|
134
|
+
"""For each claw, list its agents over the ws (``arkclaw.team.agent.list``) —
|
|
135
|
+
which also proves access (the ChatToken mint denies non-owners). Returns
|
|
136
|
+
``clawid → (status, agents)``, status ∈ {"ok","denied","error"}. Parallel."""
|
|
127
137
|
from concurrent.futures import ThreadPoolExecutor
|
|
128
138
|
|
|
129
|
-
creds = _openclaw_creds(cfg, id_token) # may raise (offline / expired / no creds)
|
|
130
|
-
identity = _user_identity(id_token)
|
|
131
139
|
region = str(cfg.region)
|
|
132
140
|
|
|
133
|
-
def
|
|
141
|
+
def one(clawid: str) -> tuple[str, str, list[dict[str, Any]]]:
|
|
142
|
+
t = OpenClawTransport(clawid=clawid, region=region, creds=creds, identity=identity)
|
|
134
143
|
try:
|
|
135
|
-
|
|
136
|
-
return cid, True
|
|
144
|
+
return clawid, "ok", asyncio.run(t.list_agents())
|
|
137
145
|
except ClawAccessDeniedError:
|
|
138
|
-
return
|
|
146
|
+
return clawid, "denied", []
|
|
139
147
|
except ArkclawError:
|
|
140
|
-
return
|
|
148
|
+
return clawid, "error", [] # transient — keep the claw, mark unknown
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
denied: set[str] = set()
|
|
150
|
+
out: dict[str, tuple[str, list[dict[str, Any]]]] = {}
|
|
144
151
|
with ThreadPoolExecutor(max_workers=min(8, max(1, len(claw_ids)))) as ex:
|
|
145
|
-
for
|
|
146
|
-
|
|
147
|
-
return
|
|
152
|
+
for clawid, status, agents in ex.map(one, claw_ids):
|
|
153
|
+
out[clawid] = (status, agents)
|
|
154
|
+
return out
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _resolve_session_key(
|
|
158
|
+
cfg: SessionConfig, id_token: str, clawid: str, agent: str, session: str | None, new: bool
|
|
159
|
+
) -> str | None:
|
|
160
|
+
"""Compute the OpenClaw sessionKey ``agent:<agentId>:<label>`` for a chat:
|
|
161
|
+
|
|
162
|
+
* ``--new`` → a fresh ``agent:<agent>:cli-<uuid>`` (new conversation)
|
|
163
|
+
* ``--session <id>`` → resume: a full ``agent:…`` key passes through; a short
|
|
164
|
+
sessionId is resolved to its key via ``sessions.list``
|
|
165
|
+
* neither, agent=main → ``None`` (the default ``agent:main:main`` session)
|
|
166
|
+
* neither, other agent→ ``agent:<agent>:main``
|
|
167
|
+
"""
|
|
168
|
+
agent = agent or "main"
|
|
169
|
+
if new:
|
|
170
|
+
return f"agent:{agent}:cli-{uuid.uuid4()}"
|
|
171
|
+
if not session:
|
|
172
|
+
return None if agent == "main" else f"agent:{agent}:main"
|
|
173
|
+
if session.startswith("agent:"):
|
|
174
|
+
return session # already a full sessionKey
|
|
175
|
+
# A hex token is a 会话ID copied from `arkclaw sessions` → resolve to its key.
|
|
176
|
+
if re.fullmatch(r"[0-9a-f]{6,}", session):
|
|
177
|
+
creds = _openclaw_creds(cfg, id_token)
|
|
178
|
+
t = OpenClawTransport(
|
|
179
|
+
clawid=clawid, region=str(cfg.region), creds=creds, identity=_user_identity(id_token)
|
|
180
|
+
)
|
|
181
|
+
for s in asyncio.run(t.list_sessions(agent)):
|
|
182
|
+
sid = str(s.get("sessionId") or "")
|
|
183
|
+
if sid and (sid == session or sid.startswith(session)):
|
|
184
|
+
return str(s.get("key") or f"agent:{agent}:{session}")
|
|
185
|
+
raise ValidationError(
|
|
186
|
+
f"会话 {session!r} 不存在。",
|
|
187
|
+
hint="用 `arkclaw sessions` 查看现有会话,或加 `--new` 开新会话。",
|
|
188
|
+
)
|
|
189
|
+
# Otherwise a human-named session label (created/resumed by this key).
|
|
190
|
+
return f"agent:{agent}:{session}"
|
|
148
191
|
|
|
149
192
|
|
|
150
193
|
def _normalize(url: str) -> tuple[str, str]:
|
|
@@ -783,6 +826,8 @@ def do_chat(
|
|
|
783
826
|
target: str | None = None,
|
|
784
827
|
message: str | None = None,
|
|
785
828
|
session: str | None = None,
|
|
829
|
+
agent: str = "main",
|
|
830
|
+
new: bool = False,
|
|
786
831
|
approve_all: bool = False,
|
|
787
832
|
files: list[str] | None = None,
|
|
788
833
|
output: str | None = None,
|
|
@@ -838,6 +883,12 @@ def do_chat(
|
|
|
838
883
|
)
|
|
839
884
|
|
|
840
885
|
approver = _build_approver(emitter, approve_all=approve_all, interactive=interactive)
|
|
886
|
+
# Resolve agent + session → a concrete sessionKey (openclaw only; a2a uses
|
|
887
|
+
# the session name as the context label).
|
|
888
|
+
if cfg.transport == "openclaw":
|
|
889
|
+
effective_claw = clawid or cfg.claw
|
|
890
|
+
if effective_claw:
|
|
891
|
+
session = _resolve_session_key(cfg, id_token, effective_claw, agent, session, new)
|
|
841
892
|
target, transport = _target_transport(
|
|
842
893
|
cfg, clawid, id_token, session=session, approver=approver
|
|
843
894
|
)
|
|
@@ -879,8 +930,9 @@ def do_chat(
|
|
|
879
930
|
pass
|
|
880
931
|
emitter.line(
|
|
881
932
|
f"ArkClaw chat · {target}"
|
|
933
|
+
+ (f" · agent {agent}" if agent and agent != "main" else "")
|
|
934
|
+
+ (f" · 会话 {session.split(':')[-1][:16]}" if session else "")
|
|
882
935
|
+ (f" · 空间 {cfg.space}" if cfg.space and cfg.space != target else "")
|
|
883
|
-
+ (f" · 会话 {session}" if session else "")
|
|
884
936
|
+ " (Ctrl+C 退出)"
|
|
885
937
|
)
|
|
886
938
|
while True:
|
|
@@ -911,11 +963,54 @@ def do_chat(
|
|
|
911
963
|
emitter.line("")
|
|
912
964
|
|
|
913
965
|
|
|
914
|
-
def do_sessions(
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
966
|
+
def do_sessions(
|
|
967
|
+
emitter: Emitter, clawid: str | None = None, agent: str = "main"
|
|
968
|
+
) -> dict[str, Any]:
|
|
969
|
+
"""``arkclaw sessions [<claw>] [--agent <id>]``: the conversations of an agent
|
|
970
|
+
in a claw, from the server (ws ``sessions.list``) — newest first. Resume one
|
|
971
|
+
with ``arkclaw chat <claw> --session <会话ID>``. Falls back to the local
|
|
972
|
+
record for a2a / when the claw can't be reached."""
|
|
973
|
+
cfg = config_mod.load_config()
|
|
974
|
+
target = (clawid or (cfg.claw if cfg else None)) if cfg else None
|
|
975
|
+
if cfg and cfg.space and cfg.transport == "openclaw" and target:
|
|
976
|
+
try:
|
|
977
|
+
_, id_token = _load_session(cfg)
|
|
978
|
+
creds = _openclaw_creds(cfg, id_token)
|
|
979
|
+
t = OpenClawTransport(
|
|
980
|
+
clawid=target, region=str(cfg.region), creds=creds, identity=_user_identity(id_token)
|
|
981
|
+
)
|
|
982
|
+
sessions = asyncio.run(t.list_sessions(agent))
|
|
983
|
+
sessions.sort(key=lambda s: float(s.get("updatedAt") or 0), reverse=True)
|
|
984
|
+
emitter.line(f"★ claw {target} · agent {agent} · 空间 {cfg.space}")
|
|
985
|
+
if sessions:
|
|
986
|
+
emitter.table(
|
|
987
|
+
["标题", "上次活动", "会话ID", "状态"],
|
|
988
|
+
[
|
|
989
|
+
[
|
|
990
|
+
(str(s.get("derivedTitle") or "(无标题)"))[:48],
|
|
991
|
+
_ago_ms(s.get("updatedAt")),
|
|
992
|
+
str(s.get("sessionId") or "")[:8],
|
|
993
|
+
str(s.get("status") or "—"),
|
|
994
|
+
]
|
|
995
|
+
for s in sessions
|
|
996
|
+
],
|
|
997
|
+
)
|
|
998
|
+
emitter.line(" 续聊: arkclaw chat <claw> --session <会话ID>;新开: --new")
|
|
999
|
+
else:
|
|
1000
|
+
emitter.line(" 该 agent 还没有会话。`arkclaw chat` 开始第一段对话。")
|
|
1001
|
+
return {
|
|
1002
|
+
"claw": target, "agent": agent,
|
|
1003
|
+
"sessions": [
|
|
1004
|
+
{"sessionId": s.get("sessionId"), "key": s.get("key"),
|
|
1005
|
+
"title": s.get("derivedTitle"), "updatedAt": s.get("updatedAt"),
|
|
1006
|
+
"status": s.get("status")}
|
|
1007
|
+
for s in sessions
|
|
1008
|
+
],
|
|
1009
|
+
}
|
|
1010
|
+
except ArkclawError as e:
|
|
1011
|
+
emitter.line(f" ⚠ 无法从服务端列会话({e.code}),回退本地记录:")
|
|
1012
|
+
|
|
1013
|
+
rows = config_mod.list_sessions() # a2a / not-logged-in / no-claw / server-error fallback
|
|
919
1014
|
if rows:
|
|
920
1015
|
emitter.table(
|
|
921
1016
|
["会话", "目标", "空间", "最近使用"],
|
|
@@ -964,13 +1059,15 @@ def _ago(ts: object) -> str:
|
|
|
964
1059
|
|
|
965
1060
|
|
|
966
1061
|
def do_agents(emitter: Emitter) -> dict[str, Any]:
|
|
967
|
-
"""``arkclaw agents``: the agents
|
|
1062
|
+
"""``arkclaw agents``: the agents you can chat with.
|
|
968
1063
|
|
|
969
|
-
openclaw → the
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
(
|
|
973
|
-
|
|
1064
|
+
openclaw → the AGENTS hosted in your accessible claws. The candidate claws
|
|
1065
|
+
are the ones YOU have used from this machine (kept locally, newest first)
|
|
1066
|
+
plus your default; for each we list its agents over the ws
|
|
1067
|
+
(``arkclaw.team.agent.list``), which also verifies access — inaccessible
|
|
1068
|
+
claws are pruned. This is NOT a space-wide server enumeration (not per-user
|
|
1069
|
+
scoped for the CLI's role). You add a claw by chatting it once: ``arkclaw
|
|
1070
|
+
chat ci-...``.
|
|
974
1071
|
|
|
975
1072
|
a2a → the agent at the endpoint (its card). Profiles live under ``arkclaw
|
|
976
1073
|
profile``, full conversation history under ``arkclaw sessions``."""
|
|
@@ -1030,11 +1127,11 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
|
|
|
1030
1127
|
)
|
|
1031
1128
|
elif cfg.transport == "openclaw":
|
|
1032
1129
|
emitter.line(f"★ 当前登录(openclaw)· 空间 {cfg.space}")
|
|
1033
|
-
#
|
|
1034
|
-
#
|
|
1035
|
-
#
|
|
1036
|
-
#
|
|
1037
|
-
#
|
|
1130
|
+
# The chat-able units are AGENTS, and one claw hosts several. Candidates
|
|
1131
|
+
# are the claws YOU have used from this machine (local history) + your
|
|
1132
|
+
# default; for each we list its agents over the ws (which also proves
|
|
1133
|
+
# access). Inaccessible claws are pruned from history (+ cleared as
|
|
1134
|
+
# default). NOT a space-wide server enumeration (not per-user scoped).
|
|
1038
1135
|
seen: dict[str, float] = {}
|
|
1039
1136
|
for s in config_mod.list_sessions():
|
|
1040
1137
|
cid = s.get("claw")
|
|
@@ -1045,43 +1142,56 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
|
|
|
1045
1142
|
seen[str(cfg.claw)] = 0.0 # the default claw, even if not chatted yet
|
|
1046
1143
|
|
|
1047
1144
|
claw_ids = sorted(seen, key=lambda c: seen[c], reverse=True)
|
|
1145
|
+
rows: list[list[str]] = []
|
|
1146
|
+
agents_data: list[dict[str, Any]] = []
|
|
1048
1147
|
checked = False
|
|
1049
1148
|
if claw_ids:
|
|
1050
1149
|
if not emitter.json:
|
|
1051
|
-
emitter.line("
|
|
1150
|
+
emitter.line(" 列出 agents…")
|
|
1052
1151
|
try:
|
|
1053
1152
|
_, id_token = _load_session(cfg)
|
|
1054
|
-
|
|
1153
|
+
creds = _openclaw_creds(cfg, id_token)
|
|
1154
|
+
identity = _user_identity(id_token)
|
|
1155
|
+
results = _list_agents_for_claws(cfg, creds, identity, claw_ids)
|
|
1055
1156
|
checked = True
|
|
1056
|
-
for
|
|
1057
|
-
|
|
1058
|
-
if
|
|
1059
|
-
config_mod.
|
|
1060
|
-
cfg
|
|
1061
|
-
|
|
1062
|
-
|
|
1157
|
+
for clawid in claw_ids:
|
|
1158
|
+
status, agents = results[clawid]
|
|
1159
|
+
if status == "denied":
|
|
1160
|
+
config_mod.forget_claw(str(cfg.space), clawid) # stop listing it
|
|
1161
|
+
if cfg.claw == clawid:
|
|
1162
|
+
config_mod.save_config(dataclasses.replace(cfg, claw=None))
|
|
1163
|
+
cfg = config_mod.load_config() or cfg
|
|
1164
|
+
current["default_claw"] = cfg.claw
|
|
1165
|
+
continue
|
|
1166
|
+
if status == "error" or not agents:
|
|
1167
|
+
rows.append(["(无法列出 agent)", "—", clawid, "★" if clawid == cfg.claw else ""])
|
|
1168
|
+
continue
|
|
1169
|
+
for a in agents:
|
|
1170
|
+
aid = str(a.get("agentId") or "")
|
|
1171
|
+
is_main = bool(a.get("default")) or aid == "main"
|
|
1172
|
+
rows.append([
|
|
1173
|
+
_agent_display_name(a), aid or "—", clawid,
|
|
1174
|
+
"★" if (clawid == cfg.claw and is_main) else "",
|
|
1175
|
+
])
|
|
1176
|
+
agents_data.append({
|
|
1177
|
+
"clawId": clawid, "agentId": aid, "name": _agent_display_name(a),
|
|
1178
|
+
"description": a.get("description"), "default": is_main,
|
|
1179
|
+
})
|
|
1063
1180
|
except ArkclawError:
|
|
1064
|
-
checked = False # offline / expired / no creds →
|
|
1181
|
+
checked = False # offline / expired / no creds → can't list agents
|
|
1065
1182
|
|
|
1066
|
-
|
|
1067
|
-
current["claws"] = claws
|
|
1183
|
+
current["agents"] = agents_data
|
|
1068
1184
|
current["source"] = "verified" if checked else "local-history-unchecked"
|
|
1069
|
-
if
|
|
1185
|
+
if checked and rows:
|
|
1186
|
+
emitter.table(["Agent", "agentId", "所属 Claw", "默认"], rows)
|
|
1187
|
+
elif checked:
|
|
1188
|
+
emitter.line(" 本空间没有你可访问的 claw / agent(历史里的都已无权或不存在)。")
|
|
1189
|
+
elif claw_ids:
|
|
1190
|
+
emitter.line(" ⚠ 未能连接核对(离线/会话过期),无法列出 agent;以下为本地历史的 claw:")
|
|
1070
1191
|
emitter.table(
|
|
1071
|
-
["Claw
|
|
1072
|
-
[
|
|
1073
|
-
[
|
|
1074
|
-
str(c["ClawInstanceId"]),
|
|
1075
|
-
_ago(c["last_used"]) if c["last_used"] else "—",
|
|
1076
|
-
"★" if c["ClawInstanceId"] == cfg.claw else "",
|
|
1077
|
-
]
|
|
1078
|
-
for c in claws
|
|
1079
|
-
],
|
|
1192
|
+
["Claw", "上次使用"],
|
|
1193
|
+
[[c, _ago(seen[c]) if seen[c] else "—"] for c in claw_ids],
|
|
1080
1194
|
)
|
|
1081
|
-
if not checked:
|
|
1082
|
-
emitter.line(" ⚠ 未能核对权限(离线/会话过期),以上为本地历史,可能含你无权访问的。")
|
|
1083
|
-
elif checked:
|
|
1084
|
-
emitter.line(" 你在本空间没有可访问的 claw(历史里的都已无权或不存在)。")
|
|
1085
1195
|
else:
|
|
1086
1196
|
emitter.line(" 还没用过任何 claw。用 `arkclaw chat ci-xxxx` 开始,之后这里会记住它。")
|
|
1087
1197
|
|
|
@@ -451,9 +451,10 @@ class OpenClawTransport:
|
|
|
451
451
|
# agents.files.get {agentId, name} → {file:{name,path,size,content}}
|
|
452
452
|
# agents.files.set {agentId, name, content} → write
|
|
453
453
|
|
|
454
|
-
async def _request(self, method: str, params: dict[str, Any]) ->
|
|
455
|
-
"""connect (hello-ok) → one control request → its res payload
|
|
456
|
-
are redacted; the token-bearing
|
|
454
|
+
async def _request(self, method: str, params: dict[str, Any]) -> Any:
|
|
455
|
+
"""connect (hello-ok) → one control request → its res payload (dict or
|
|
456
|
+
list, depending on the method). Errors are redacted; the token-bearing
|
|
457
|
+
URL is never echoed."""
|
|
457
458
|
chat_token, endpoint = get_chat_token(self.clawid, self.region, self.creds, identity=self.identity)
|
|
458
459
|
url = (
|
|
459
460
|
f"wss://{endpoint}/?chatToken={urllib.parse.quote(chat_token)}"
|
|
@@ -510,3 +511,31 @@ class OpenClawTransport:
|
|
|
510
511
|
return await self._request(
|
|
511
512
|
"agents.files.set", {"agentId": agent_id, "name": name, "content": content}
|
|
512
513
|
)
|
|
514
|
+
|
|
515
|
+
async def list_agents(self) -> list[dict[str, Any]]:
|
|
516
|
+
"""The agents living in this claw (ws RPC ``arkclaw.team.agent.list``).
|
|
517
|
+
Each item: ``{agentId, name, description?, default?}``. Raises
|
|
518
|
+
ClawAccessDeniedError (via the ChatToken mint) if the user can't access
|
|
519
|
+
this claw."""
|
|
520
|
+
payload = await self._request("arkclaw.team.agent.list", {})
|
|
521
|
+
if isinstance(payload, list):
|
|
522
|
+
return [a for a in payload if isinstance(a, dict)]
|
|
523
|
+
if isinstance(payload, dict): # some gateways wrap the array
|
|
524
|
+
for k in ("data", "agents", "list", "items"):
|
|
525
|
+
v = payload.get(k)
|
|
526
|
+
if isinstance(v, list):
|
|
527
|
+
return [a for a in v if isinstance(a, dict)]
|
|
528
|
+
return []
|
|
529
|
+
|
|
530
|
+
async def list_sessions(self, agent_id: str = "main") -> list[dict[str, Any]]:
|
|
531
|
+
"""An agent's conversations (ws RPC ``sessions.list``). Each item:
|
|
532
|
+
``{key, sessionId, derivedTitle?, updatedAt, status, ...}`` — ``key`` is
|
|
533
|
+
the sessionKey to resume with."""
|
|
534
|
+
payload = await self._request(
|
|
535
|
+
"sessions.list", {"agentId": agent_id, "includeDerivedTitles": True}
|
|
536
|
+
)
|
|
537
|
+
if isinstance(payload, dict):
|
|
538
|
+
sessions = payload.get("sessions")
|
|
539
|
+
if isinstance(sessions, list):
|
|
540
|
+
return [s for s in sessions if isinstance(s, dict)]
|
|
541
|
+
return []
|
|
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
|
|
File without changes
|