arkclaw-webchat-cli 0.6.1__tar.gz → 0.6.2__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.6.1 → arkclaw_webchat_cli-0.6.2}/PKG-INFO +8 -5
  2. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/README.md +7 -4
  3. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/pyproject.toml +1 -1
  4. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/cli.py +66 -2
  5. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/flows.py +106 -9
  6. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/transport/openclaw.py +29 -0
  7. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/.gitignore +0 -0
  8. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/__init__.py +0 -0
  9. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/attachments.py +0 -0
  10. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/config.py +0 -0
  11. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/control.py +0 -0
  12. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/core.py +0 -0
  13. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/doctor.py +0 -0
  14. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/errors.py +0 -0
  15. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/identity.py +0 -0
  16. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/oauth.py +0 -0
  17. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/output.py +0 -0
  18. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/policy.py +0 -0
  19. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/providers.py +0 -0
  20. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/secrets_store.py +0 -0
  21. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/sts.py +0 -0
  22. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/transport/__init__.py +0 -0
  23. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/transport/a2a.py +0 -0
  24. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/src/ee_claw/transport/base.py +0 -0
  25. {arkclaw_webchat_cli-0.6.1 → arkclaw_webchat_cli-0.6.2}/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.6.1
3
+ Version: 0.6.2
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
@@ -31,7 +31,7 @@ at a prompt and for another agent/script (`--json` + exit codes).
31
31
  ```bash
32
32
  arkclaw init # one-time interactive setup (asks only what it can't auto-detect)
33
33
  arkclaw login # browser SSO
34
- arkclaw agents # list the claws you can chat
34
+ arkclaw agents # list the agents you can chat
35
35
  arkclaw chat ci-xxxxxxxx
36
36
  ```
37
37
 
@@ -40,7 +40,8 @@ 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've chatted with (kept locally; add one with `arkclaw chat ci-...`).
43
+ - **List your agents** — `arkclaw agents` shows the agents in your accessible claws (add a claw by chatting it once: `arkclaw chat ci-...`).
44
+ - **Manage agents** — `agents create` / `agents delete` add or remove named agents in a claw, right from the terminal.
44
45
  - **Chat** — interactive REPL or one-shot `-m`, streaming token-by-token, with a live "what's it doing" spinner and tool-event trace.
45
46
  - **Talk to a specific claw** — by id (`chat ci-...`) or by name (`chat 答疑助手`).
46
47
  - **Manage a claw's files** — read/write its managed workspace files (`AGENTS.md`, `SOUL.md`, `MEMORY.md`, …) with `ls` / `pull` / `push`.
@@ -99,14 +100,16 @@ straight to `arkclaw login https://your-space...`. See **[Configuration](#config
99
100
  |---|---|
100
101
  | `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
102
  | `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 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
+ | `arkclaw agents` | List the agents in your accessible claws (verified over the ws; inaccessible claws are pruned). Candidate claws = the ones you've used from this machine + your default add one by chatting it once (`arkclaw chat ci-...`). Not a space-wide directory. |
104
+ | `arkclaw agents create` | Create a named agent in a claw: `--name` `--role` `--soul` (+ optional `--description`, repeatable `--skill`). Then `chat <claw> --agent <agentId>`. |
105
+ | `arkclaw agents delete <agent>` | Delete a named agent by agentId (`a-...`) or display name. Asks for confirmation unless `--yes`; the `main` agent is protected. |
103
106
  | `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
107
  | `arkclaw <name>` | Shortcut: `arkclaw 答疑助手` ≡ `arkclaw chat 答疑助手`. |
105
108
  | `arkclaw ls` | List the claw's managed workspace files. |
106
109
  | `arkclaw pull <name> [local]` | Download a managed file to local disk. |
107
110
  | `arkclaw push <local> [name]` | Write a local text file into a managed file. |
108
111
  | `arkclaw fanout "<msg>" --clawid ci-a --clawid ci-b` | Same message to many claws in parallel. |
109
- | `arkclaw sessions` | Recent chat sessions on this machine. |
112
+ | `arkclaw sessions [claw]` | An agent's conversations from the server (`--agent` to pick one), newest first — resume with `chat --session <会话ID>`, fork with `--new`. Falls back to the local record when offline / a2a. |
110
113
  | `arkclaw profile save/use/list` | Named snapshots of the session config (multi-space / multi-claw). |
111
114
  | `arkclaw doctor` | Self-check: login freshness, keychain, pool/STS/endpoint reachability. |
112
115
  | `arkclaw schema --json` | Machine-readable command surface (for agents/tooling). |
@@ -7,7 +7,7 @@ at a prompt and for another agent/script (`--json` + exit codes).
7
7
  ```bash
8
8
  arkclaw init # one-time interactive setup (asks only what it can't auto-detect)
9
9
  arkclaw login # browser SSO
10
- arkclaw agents # list the claws you can chat
10
+ arkclaw agents # list the agents you can chat
11
11
  arkclaw chat ci-xxxxxxxx
12
12
  ```
13
13
 
@@ -16,7 +16,8 @@ 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've chatted with (kept locally; add one with `arkclaw chat ci-...`).
19
+ - **List your agents** — `arkclaw agents` shows the agents in your accessible claws (add a claw by chatting it once: `arkclaw chat ci-...`).
20
+ - **Manage agents** — `agents create` / `agents delete` add or remove named agents in a claw, right from the terminal.
20
21
  - **Chat** — interactive REPL or one-shot `-m`, streaming token-by-token, with a live "what's it doing" spinner and tool-event trace.
21
22
  - **Talk to a specific claw** — by id (`chat ci-...`) or by name (`chat 答疑助手`).
22
23
  - **Manage a claw's files** — read/write its managed workspace files (`AGENTS.md`, `SOUL.md`, `MEMORY.md`, …) with `ls` / `pull` / `push`.
@@ -75,14 +76,16 @@ straight to `arkclaw login https://your-space...`. See **[Configuration](#config
75
76
  |---|---|
76
77
  | `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
78
  | `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 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
+ | `arkclaw agents` | List the agents in your accessible claws (verified over the ws; inaccessible claws are pruned). Candidate claws = the ones you've used from this machine + your default add one by chatting it once (`arkclaw chat ci-...`). Not a space-wide directory. |
80
+ | `arkclaw agents create` | Create a named agent in a claw: `--name` `--role` `--soul` (+ optional `--description`, repeatable `--skill`). Then `chat <claw> --agent <agentId>`. |
81
+ | `arkclaw agents delete <agent>` | Delete a named agent by agentId (`a-...`) or display name. Asks for confirmation unless `--yes`; the `main` agent is protected. |
79
82
  | `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
83
  | `arkclaw <name>` | Shortcut: `arkclaw 答疑助手` ≡ `arkclaw chat 答疑助手`. |
81
84
  | `arkclaw ls` | List the claw's managed workspace files. |
82
85
  | `arkclaw pull <name> [local]` | Download a managed file to local disk. |
83
86
  | `arkclaw push <local> [name]` | Write a local text file into a managed file. |
84
87
  | `arkclaw fanout "<msg>" --clawid ci-a --clawid ci-b` | Same message to many claws in parallel. |
85
- | `arkclaw sessions` | Recent chat sessions on this machine. |
88
+ | `arkclaw sessions [claw]` | An agent's conversations from the server (`--agent` to pick one), newest first — resume with `chat --session <会话ID>`, fork with `--new`. Falls back to the local record when offline / a2a. |
86
89
  | `arkclaw profile save/use/list` | Named snapshots of the session config (multi-space / multi-claw). |
87
90
  | `arkclaw doctor` | Self-check: login freshness, keychain, pool/STS/endpoint reachability. |
88
91
  | `arkclaw schema --json` | Machine-readable command surface (for agents/tooling). |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arkclaw-webchat-cli"
7
- version = "0.6.1"
7
+ version = "0.6.2"
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"
@@ -259,14 +259,76 @@ def push(
259
259
  _run(emitter, lambda: flows.do_push(local, remote, emitter, clawid=clawid))
260
260
 
261
261
 
262
- @app.command()
263
- def agents(json_mode: bool = _json_opt()) -> None:
262
+ agents_app = typer.Typer()
263
+ app.add_typer(agents_app, name="agents")
264
+
265
+
266
+ @agents_app.callback(invoke_without_command=True)
267
+ def agents(ctx: typer.Context, json_mode: bool = _json_opt()) -> None:
264
268
  """List the agents you can chat with (openclaw: the agents in your accessible
265
269
  claws; a2a: the agent at the endpoint). Then `chat <claw-or-name>`."""
270
+ if ctx.invoked_subcommand is not None:
271
+ # `agents --json create …` must honor the group-level flag — stash it
272
+ # for the subcommand (click contexts inherit obj), never drop it.
273
+ ctx.obj = {"json": json_mode}
274
+ return
266
275
  emitter = Emitter(json_mode=json_mode)
267
276
  _run(emitter, lambda: flows.do_agents(emitter))
268
277
 
269
278
 
279
+ def _group_json(ctx: typer.Context, json_mode: bool) -> bool:
280
+ """A subcommand's effective --json: its own flag OR the group-level one."""
281
+ return json_mode or bool((ctx.obj or {}).get("json"))
282
+
283
+
284
+ @agents_app.command("create")
285
+ def agents_create(
286
+ ctx: typer.Context,
287
+ name: str = typer.Option(..., "--name", help="Agent display name."),
288
+ role: str = typer.Option(..., "--role", help="One-line role (what this agent does)."),
289
+ soul: str = typer.Option(
290
+ ..., "--soul", help="Persona / system instructions (the agent's SOUL.md)."
291
+ ),
292
+ description: str | None = typer.Option(None, "--description", help="Optional description."),
293
+ skill: list[str] = typer.Option(
294
+ [], "--skill", help="Skill to enable (repeat for several)."
295
+ ),
296
+ clawid: str | None = typer.Option(
297
+ None, "--clawid", help="Claw instance id (ci-...). Omit to use your default claw."
298
+ ),
299
+ json_mode: bool = _json_opt(),
300
+ ) -> None:
301
+ """Create a named agent in a claw. Then `arkclaw chat <claw> --agent <agentId>`."""
302
+ emitter = Emitter(json_mode=_group_json(ctx, json_mode))
303
+ _run(
304
+ emitter,
305
+ lambda: flows.do_agents_create(
306
+ emitter,
307
+ name=name,
308
+ role=role,
309
+ soul=soul,
310
+ description=description,
311
+ skills=list(skill),
312
+ clawid=clawid,
313
+ ),
314
+ )
315
+
316
+
317
+ @agents_app.command("delete")
318
+ def agents_delete(
319
+ ctx: typer.Context,
320
+ agent: str = typer.Argument(..., help="agentId (a-...) or display name to delete."),
321
+ clawid: str | None = typer.Option(
322
+ None, "--clawid", help="Claw instance id (ci-...). Omit to use your default claw."
323
+ ),
324
+ yes: bool = typer.Option(False, "--yes", help="Skip the confirmation prompt."),
325
+ json_mode: bool = _json_opt(),
326
+ ) -> None:
327
+ """Delete a named agent from a claw (asks for confirmation unless --yes)."""
328
+ emitter = Emitter(json_mode=_group_json(ctx, json_mode))
329
+ _run(emitter, lambda: flows.do_agents_delete(emitter, agent, clawid=clawid, yes=yes))
330
+
331
+
270
332
  @app.command()
271
333
  def fanout(
272
334
  message: str = typer.Argument(..., help="The message to send to every claw."),
@@ -412,6 +474,8 @@ def _schema_data() -> dict:
412
474
  for name, cmd in sorted(getattr(root, "commands", {}).items()):
413
475
  subs = getattr(cmd, "commands", None)
414
476
  if subs:
477
+ if getattr(cmd, "invoke_without_command", False):
478
+ commands.append(info(name, cmd)) # the bare group is a command too
415
479
  commands.extend(info(f"{name} {s}", c) for s, c in sorted(subs.items()))
416
480
  else:
417
481
  commands.append(info(name, cmd))
@@ -52,7 +52,7 @@ from ee_claw.identity import (
52
52
  read_chrome_recent_claw,
53
53
  )
54
54
  from ee_claw.oauth import OAuthClient
55
- from ee_claw.output import Emitter
55
+ from ee_claw.output import Emitter, sanitize
56
56
  from ee_claw.providers import ProviderContext, resolve_token_source
57
57
  from ee_claw.sts import assume_role_with_oidc
58
58
  from ee_claw.transport.a2a import A2ATransport, agent_card
@@ -1201,15 +1201,14 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
1201
1201
  return {"current": current}
1202
1202
 
1203
1203
 
1204
- def _openclaw_file_transport(clawid: str | None) -> tuple[OpenClawTransport, str]:
1205
- """Build an OpenClawTransport for file transfer (openclaw-only). Returns
1206
- (transport, clawid)."""
1204
+ def _openclaw_claw_transport(
1205
+ clawid: str | None, *, feature: str, hint: str
1206
+ ) -> tuple[OpenClawTransport, str]:
1207
+ """Build an OpenClawTransport against the given (or default) claw for an
1208
+ openclaw-only feature. Returns (transport, clawid)."""
1207
1209
  cfg, id_token = _load_session()
1208
1210
  if cfg.transport != "openclaw":
1209
- raise ValidationError(
1210
- "文件传输目前仅支持 openclaw transport(claw 工作区文件)。",
1211
- hint="A2A agent 没有可直传的工作区文件系统。",
1212
- )
1211
+ raise ValidationError(f"{feature}目前仅支持 openclaw transport。", hint=hint)
1213
1212
  clawid = clawid or cfg.claw
1214
1213
  if not clawid:
1215
1214
  raise NoClawError("未指定 claw。", hint="加 --clawid <ci-...>。")
@@ -1221,6 +1220,104 @@ def _openclaw_file_transport(clawid: str | None) -> tuple[OpenClawTransport, str
1221
1220
  ), clawid
1222
1221
 
1223
1222
 
1223
+ def _openclaw_file_transport(clawid: str | None) -> tuple[OpenClawTransport, str]:
1224
+ """Build an OpenClawTransport for file transfer (openclaw-only). Returns
1225
+ (transport, clawid)."""
1226
+ return _openclaw_claw_transport(
1227
+ clawid, feature="文件传输", hint="A2A agent 没有可直传的工作区文件系统。"
1228
+ )
1229
+
1230
+
1231
+ def _agent_mgmt_transport(clawid: str | None) -> tuple[OpenClawTransport, str]:
1232
+ return _openclaw_claw_transport(
1233
+ clawid, feature="agent 管理", hint="A2A 模式连接的是单个 agent 端点,没有可管理的 claw。"
1234
+ )
1235
+
1236
+
1237
+ def do_agents_create(
1238
+ emitter: Emitter,
1239
+ *,
1240
+ name: str,
1241
+ role: str,
1242
+ soul: str,
1243
+ description: str | None = None,
1244
+ skills: list[str] | None = None,
1245
+ clawid: str | None = None,
1246
+ ) -> dict[str, Any]:
1247
+ """``arkclaw agents create``: create a named agent in a claw (ws RPC
1248
+ ``arkclaw.team.agent.create``; live-calibrated 2026-06-10). The server
1249
+ requires name+role+soul; skills may be empty."""
1250
+ for label, value in (("--name", name), ("--role", role), ("--soul", soul)):
1251
+ if not value.strip():
1252
+ raise ValidationError(f"{label} 不能为空。")
1253
+ if name.strip().startswith("a-") or name.strip() == "main":
1254
+ raise ValidationError(
1255
+ f"--name 不能是 {name.strip()!r} — 会与 agentId 命名空间(a-…/main)冲突。"
1256
+ )
1257
+ transport, clawid = _agent_mgmt_transport(clawid)
1258
+ raw = asyncio.run(
1259
+ transport.create_agent(
1260
+ name=name, role=role, soul=soul, description=description, skills=skills or []
1261
+ )
1262
+ )
1263
+ agent = redact_obj(raw)
1264
+ assert isinstance(agent, dict) # redact_obj preserves the dict shape
1265
+ aid = str(agent.get("agentId") or "")
1266
+ emitter.line(f"✓ 已创建 agent {agent.get('name') or name}({aid or '?'})· claw {clawid}")
1267
+ if aid:
1268
+ emitter.line(f" 开聊: arkclaw chat {clawid} --agent {aid}")
1269
+ return {"claw": clawid, "agent": agent}
1270
+
1271
+
1272
+ def do_agents_delete(
1273
+ emitter: Emitter, agent: str, *, clawid: str | None = None, yes: bool = False
1274
+ ) -> dict[str, Any]:
1275
+ """``arkclaw agents delete``: remove a named agent from a claw (ws RPC
1276
+ ``arkclaw.team.agent.delete``). Accepts an agentId (``a-…``) or the agent's
1277
+ display name; either way the target is resolved against the claw's live
1278
+ agent list first — so a typo'd id can't delete anything, an ``a-…``-looking
1279
+ display name can't shadow another agent's id, and the claw's default agent
1280
+ (``main`` or ``default: true``) stays protected."""
1281
+ if agent == "main": # fail fast — no network needed to refuse this
1282
+ raise ValidationError("默认(main)agent 不能删除。")
1283
+ transport, clawid = _agent_mgmt_transport(clawid)
1284
+ agents = asyncio.run(transport.list_agents())
1285
+ matches = [a for a in agents if str(a.get("agentId") or "") == agent]
1286
+ if not matches: # not an id → try the display name
1287
+ matches = [a for a in agents if str(a.get("name") or "") == agent]
1288
+ if len(matches) > 1:
1289
+ ids = ", ".join(str(a.get("agentId") or "?") for a in matches)
1290
+ raise ValidationError(
1291
+ f"名称 {agent!r} 对应多个 agent({ids}),请直接用 agentId。"
1292
+ )
1293
+ if not matches:
1294
+ raise ValidationError(
1295
+ f"claw {clawid} 里没有 agentId 或名称为 {agent!r} 的 agent。",
1296
+ hint="用 `arkclaw agents` 查看 agentId 与名称。",
1297
+ )
1298
+ target = matches[0]
1299
+ agent_id = str(target.get("agentId") or "")
1300
+ if not agent_id:
1301
+ raise ValidationError(f"服务端没有给出 {agent!r} 的 agentId,无法安全删除。")
1302
+ if agent_id == "main" or bool(target.get("default")):
1303
+ raise ValidationError("默认(main)agent 不能删除。")
1304
+ if not yes:
1305
+ if emitter.json or not sys.stdin.isatty():
1306
+ raise ValidationError("headless 删除必须显式 --yes。")
1307
+ # agent_id is upstream content: sanitize before it touches the TTY —
1308
+ # input() bypasses Emitter.line's control-char stripping.
1309
+ prompt = sanitize(f"确认删除 claw {clawid} 的 agent {agent_id}? [y/N] ")
1310
+ answer = input(prompt).strip().lower()
1311
+ if answer not in ("y", "yes"):
1312
+ emitter.line("已取消。")
1313
+ return {"cancelled": True}
1314
+ result = redact_obj(asyncio.run(transport.delete_agent(agent_id)))
1315
+ if isinstance(result, dict) and result.get("success") is False:
1316
+ raise NetworkError(f"claw 未确认删除 agent {agent_id}(success=false)。")
1317
+ emitter.line(f"✓ 已删除 agent {agent_id} · claw {clawid}")
1318
+ return {"claw": clawid, "agentId": agent_id, "result": result}
1319
+
1320
+
1224
1321
  def do_ls(emitter: Emitter, *, clawid: str | None = None) -> dict[str, Any]:
1225
1322
  """``arkclaw ls``: list files in the claw workspace."""
1226
1323
  transport, clawid = _openclaw_file_transport(clawid)
@@ -1426,7 +1523,7 @@ def do_claw(
1426
1523
  if op == "delete" and not yes:
1427
1524
  if emitter.json or not sys.stdin.isatty():
1428
1525
  raise ValidationError("headless 删除必须显式 --yes。")
1429
- answer = input(f"确认删除 claw {clawid}? [y/N] ").strip().lower()
1526
+ answer = input(sanitize(f"确认删除 claw {clawid}? [y/N] ")).strip().lower()
1430
1527
  if answer not in ("y", "yes"):
1431
1528
  emitter.line("已取消。")
1432
1529
  return {"cancelled": True}
@@ -527,6 +527,35 @@ class OpenClawTransport:
527
527
  return [a for a in v if isinstance(a, dict)]
528
528
  return []
529
529
 
530
+ async def create_agent(
531
+ self,
532
+ *,
533
+ name: str,
534
+ role: str,
535
+ soul: str,
536
+ description: str | None = None,
537
+ skills: Sequence[str] = (),
538
+ ) -> dict[str, Any]:
539
+ """Create a named agent in this claw (ws RPC ``arkclaw.team.agent.create``).
540
+ The server requires name + role + soul + skills (array, may be empty);
541
+ returns the created agent ``{agentId, name, role, soul, …}``."""
542
+ params: dict[str, Any] = {
543
+ "name": name,
544
+ "role": role,
545
+ "soul": soul,
546
+ "skills": list(skills),
547
+ }
548
+ if description:
549
+ params["description"] = description
550
+ payload = await self._request("arkclaw.team.agent.create", params)
551
+ return payload if isinstance(payload, dict) else {}
552
+
553
+ async def delete_agent(self, agent_id: str) -> dict[str, Any]:
554
+ """Delete an agent from this claw (ws RPC ``arkclaw.team.agent.delete``).
555
+ Returns the server ack (``{success: true}``)."""
556
+ payload = await self._request("arkclaw.team.agent.delete", {"agentId": agent_id})
557
+ return payload if isinstance(payload, dict) else {}
558
+
530
559
  async def list_sessions(self, agent_id: str = "main") -> list[dict[str, Any]]:
531
560
  """An agent's conversations (ws RPC ``sessions.list``). Each item:
532
561
  ``{key, sessionId, derivedTitle?, updatedAt, status, ...}`` — ``key`` is