Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/verify-deepseekbot/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ One file per user-facing feature. Each file has four sections: `Sub-features`, `
| Historical Overview: idle PersonaBot (retired by #2026) | no | `scripts/e2e-activity-idle.mjs` | [activity-center-idle.md](activity-center-idle.md) |
| Temporary Workspace Grant authorization, read and revocation | no | `scripts/e2e-workspace-grant.mjs` | [workspace-grant.md](workspace-grant.md) |
| Default Bot Browser open, observe, stop and Access refusal | no | `scripts/e2e-browser-lifecycle.mjs` | [browser-lifecycle.md](browser-lifecycle.md) |
| Skill Invocation from `/` in a Bot DM | yes for the reply | `verify-app` Bot DM and `skills-list` / `send --skill` | [skill-invocation.md](skill-invocation.md) |
| Skill Invocation from `/` in a Bot DM or Group Channel | yes for the reply | `verify-app` DM/Group; `skills-list`, `send --skill` / `channelSend` | [skill-invocation.md](skill-invocation.md) |
| Composer footer customization and optional reply speed | yes for a speed sample | none (Web interaction path) | [composer-footer.md](composer-footer.md) |
| Disposable Memory history, restore and stale-head refusal | no | `scripts/e2e-memory-restore.mjs` | [memory-restore.md](memory-restore.md) |
| Version-bound Memory images, Markdown previews and CLI export | no | `scripts/e2e-memory-images.mjs`, `scripts/e2e-memory-markdown.mjs`, `scripts/e2e-memory-formats.mjs` | [memory-images.md](memory-images.md) |
Expand Down
29 changes: 23 additions & 6 deletions .agents/skills/verify-deepseekbot/features/skill-invocation.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
# Skill Invocation in a Bot DM
# Skill Invocation in a Bot DM or Group Channel

A Human types `/` in a PersonaBot DM, picks a Skill, and sends it. The Host stores the Skill's text with the message and the Bot reads it as a `<skill_invocation>` block ([ADR-0346](../../../../docs/adr/0346-skill-invocation-is-snapshotted-text-that-addresses-no-one.md)).
A Human types `/` in a PersonaBot DM or Group Channel, picks a Skill, and sends it. The Host stores the Skill's text with the message and the Bot reads it as a `<skill_invocation>` block ([ADR-0346](../../../../docs/adr/0346-skill-invocation-is-snapshotted-text-that-addresses-no-one.md)).

## Sub-features

- `menu` lists composer commands first, then Skills: Host Skills and the Project Skills under the Bot's Memory Repository `.agents/skills` or `.dsh/skills`. Each row shows its owner (Bot avatar or Skill icon), `/name` and a one-line description.
- `rank` orders Skills by name prefix, then name subsequence, then description text.
- `menu` lists composer commands first, then Skills: Host Skills and the Project Skills under each conversation member Bot's Memory Repository `.agents/skills` or `.dsh/skills`. Each row shows its owner (Bot avatar or Skill icon), `/name` and a one-line description.
- `rank` orders Skills by name prefix, then name subsequence, then owning Bot display name or slug substring, then description substring; order within each tier stays stable. Typing `alice` lists all of Alice's Skills.
- `chip` inserts the picked Skill as one token; Backspace removes it whole; the draft keeps it across reloads.
- `snapshot` stores `{name, owner, path, content}` with the message; editing or deleting the `SKILL.md` later does not change the sent message.
- `deliver` gives the Bot the message with `[/name]` and the `<skill_invocation>` block, so DSH does not load the Skill a second time.
- `cli` `deepseekbot skills-list --bot <slug> [--query]` and `send <slug> --body "/name …" --skill <owner>/<name>`.
- `group-routing` invokes Alice's Skill while mentioning Bob: only Bob wakes under a mentions-only wake policy, and his Bot Inbox text contains Alice's snapshot, owner `bot:alice` and path. Without a mention, existing per-Bot Group wake policies decide delivery; the Skill owner is never implicitly addressed.
- `im-mirror` falls back to plain `/name` on Discord/Lark, without `[/name]`, XML or snapshot content.
- `command-collision` keeps commands first and reserves a bare command name while its same-named Skill row remains selectable. External IM DM command intake recognizes supported bare commands before ordinary intake; Channel/CLI sends require explicit Skill tokens or `--skill`.
- `cli` `deepseekbot skills-list --bot <slug> [--query]` (or `--channel <id>`) and `send <slug> --body "/name …" --skill <owner>/<name>`.

## How to get to it (user POV)

- Enter Bot mode, open a Bot DM, type `/` and a few letters in the composer, choose a Skill with the arrow keys and Enter (or click it), write the rest of the message, press Enter.

- Open a Group Channel with Alice and Bob, type `/`, inspect both owners and the single Host Skill entry, then pick Alice's Skill and explicitly mention Bob.

## Driving it with verify-app

Preconditions: `launch` reported a model credential and `doctor` passes.
Expand All @@ -25,8 +30,20 @@ Preconditions: `launch` reported a model credential and `doctor` passes.
4. **Canonical state.** `rpc botharness/channelTimeline '{"channelId":"<dm id>"}'`: the Human entry has `skillInvocations[0]` with `owner {kind: bot}`, the `path` of the seeded file and its body; the Bot entry replies `SKILL_QA_7731`.
5. **CLI.** `deepseekbot skills-list --bot <slug> --query qa` lists `qa-echo`; `deepseekbot send <slug> --body "/qa-echo" --skill bot:<slug>/qa-echo` returns a committed reply.

### Group Channel path

1. Seed `qa-echo` in Alice's Memory Repository and a distinct Skill in Bob's. Create or open a Group containing Alice and Bob. Set both Group wake policies to mentions-only so an ordinary digest wake cannot obscure the recipient check.
2. Type `/` in the Group composer: both Project Skills appear with their owning Bot avatars; each Host Skill appears once. Type Alice's display name or slug: all her Skills remain, below name matches and above description-only matches.
3. Pick Alice's `qa-echo` row, explicitly mention Bob using the mention picker, and send. Bob alone replies `SKILL_QA_7731`. Read `rpc botharness/channelTimeline '{"channelId":"<group id>"}'`: the Human message stores owner `{kind: bot, botSlug: alice}`, path and original content; canonical Bot Inbox/Admission state identifies Bob as the recipient, not Alice.
4. Repeat without a mention: with both policies mentions-only neither Bot wakes. Change Bob's policy to all messages and repeat: Bob wakes through ordinary routing with Alice's identical snapshot. Editing/deleting the Skill after send leaves the stored snapshot unchanged.
5. `deepseekbot skills-list --channel <group id> --query alice` lists Alice's Skills by display name and slug. Send with `verify-app rpc botharness/channelSend '{"channelId":"<group id>","body":"/qa-echo","skillInvocations":[{"name":"qa-echo","owner":{"kind":"bot","botSlug":"alice"},"start":0,"end":8}]}'` to exercise the same snapshot and ordinary routing without the UI.
6. For a same-named command/Skill, inspect commands-first ordering, exercise the bare command, then explicitly select the Skill row and inspect the snapshot.
7. Through the designated Discord/Lark test destination and the IM browser QA procedure, inspect a mirrored Human message: it shows plain `/qa-echo`, with no brackets, XML or snapshot content. Correlate it with Source Event/Outbox facts. The automated provider-carrier tests alone do not verify browser rendering; do not infer that a Group bridge automatically forwards Human messages.

## Gotchas

- The `/` menu reads the catalog when the DM opens. A Skill written while the DM is open appears after reopening the DM until S3 (live refresh) lands.
- The `/` menu reads the catalog when the conversation opens. A Skill written while the conversation is open appears after reopening it until S3 (live refresh) lands.
- DSH discovers `<memory>/.agents/skills` because the Orchestrator's cwd is the Memory Repository; a Skill placed in `.botharness/` is not found.
- Recalling an older message with ArrowUp, or restoring a failed send, keeps `/name` as plain text without the chip.

- S2 non-UI automation covers catalog membership, ranking, routing, snapshot delivery and plain-text provider sends. Group menu avatars, command selection and Discord/Lark browser rendering still require the UI/IM journey above.
11 changes: 11 additions & 0 deletions changes/4233-group-skill-ranking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
section: Added
---

## en

- Added owning Bot display name and slug matching to Skill search in Group Channels and `skills-list`, after Skill name matches and before description matches; invoking another Bot's Skill preserves normal routing and the shared text snapshot ([#4233](https://github.com/BotHarness/BotHarness/issues/4233)).

## zh

- 群聊与 `skills-list` 的 Skill 搜索新增所属 Bot 显示名和 slug 匹配,排在 Skill 名称匹配之后、描述匹配之前;调用其他 Bot 的 Skill 仍遵循原有路由,并保留共享文本快照([#4233](https://github.com/BotHarness/BotHarness/issues/4233))。
12 changes: 1 addition & 11 deletions docs/bot-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ deepseekbot folder-browse [<folder-path>] [--offset <n>]
deepseekbot grant-create <bot-id> --workspace <workspace-id>
```

`skills-list` returns the Skills the `/` menu offers in that conversation, ranked like the menu by `--query` (name prefix, then name subsequence, then description): Host Skills (`owner.kind` `host`) and the Project Skills under each PersonaBot's Memory Repository `.agents/skills` or `.dsh/skills` (`owner.kind` `bot`), as that Bot's Orchestrator sees them. Pass `--channel <channel-id>` instead of `--bot` for any Channel. `send --skill <owner>/<name>` (owner `host` or `bot:<bot-id>`, repeatable) turns the first unused `/name` token in the body into a Skill Invocation for each flag, in flag order (the token must start and end at whitespace or a body boundary; the send is refused if no unused occurrence remains): the Host stores the Skill's text as it is now with the message and the Bot reads it as a `<skill_invocation>` block. The Skill does not choose who replies; a Skill that is gone refuses with `skill-unavailable`, and Skill text longer than 64,000 characters refuses with `skill-too-large` instead of being truncated. See [ADR-0346](adr/0346-skill-invocation-is-snapshotted-text-that-addresses-no-one.md).
`skills-list` returns the Skills the `/` menu offers in that conversation, ranked like the menu by `--query` (name prefix, then name subsequence, then owning Bot display name or slug substring, then description substring): Host Skills (`owner.kind` `host`) and the Project Skills under each PersonaBot's Memory Repository `.agents/skills` or `.dsh/skills` (`owner.kind` `bot`), as that Bot's Orchestrator sees them. Pass `--channel <channel-id>` instead of `--bot` for any Channel. `send --skill <owner>/<name>` (owner `host` or `bot:<bot-id>`, repeatable) turns the first unused `/name` token in the body into a Skill Invocation for each flag, in flag order (the token must start and end at whitespace or a body boundary; the send is refused if no unused occurrence remains): the Host stores the Skill's text as it is now with the message and the Bot reads it as a `<skill_invocation>` block. The Skill does not choose who replies; a Skill that is gone refuses with `skill-unavailable`, and Skill text longer than 64,000 characters refuses with `skill-too-large` instead of being truncated. See [ADR-0346](adr/0346-skill-invocation-is-snapshotted-text-that-addresses-no-one.md).

`tool-approval-status` returns `status` and, while the card is still `pending`, its `expiresAt`, the time the Web card counts down to. `--outcome allowed-always-kind` is the `secret_cli` card's "Always allow this kind": it allows this call and saves a rule for the same program, account and environment variable names, which `tool-approval-rules` lists with kind `secret-cli`; any other card refuses it.

Expand Down Expand Up @@ -479,22 +479,14 @@ printf '{"token":"…"}' | deepseekbot im-app-add discord --credentials-stdin
deepseekbot im-app-status discord
deepseekbot im-bind <bot-id> --app <app-id>
deepseekbot im-status <bot-id>
deepseekbot im-status <bot-id> --read-mode saved
deepseekbot messaging-grants <bot-id> --read-mode status
deepseekbot channel-bridges <channel-id> --read-mode saved
deepseekbot channel-ingests <channel>
deepseekbot channel-connect <channel> --bot <bot-id> --conversation <external-id>
deepseekbot im-reconnect <bot-id> --app <app-id>
deepseekbot im-unbind <bot-id> --app <app-id>
deepseekbot im-app-remove discord --app <app-id>
```

`im-app-add` covers token-based platforms (Discord, Slack, QQ, DingTalk, WeCom, Matrix); Feishu and WeChat keep `im-authorize`. Credentials arrive only on stdin and are never printed; output lists each app's id, name, connection state and error code. `im-bind` binds a connected app (the `id` from `im-app-status`, also the `ref` in `im-status`) to a Bot, and `im-status` shows each binding's `reception`. `channel-connect` attaches an external group conversation to a Channel as context through that Bot's binding; the conversation must already be known to the binding, so mention the app there once and pick the id from the `candidates` in `channel-ingests`. Otherwise the Host refuses with `conversation-unavailable`.

`im-status` and `messaging-grants` include process-local `recovery` metadata: `retrying` with `reason`, `attempt`, `nextRetryAt`, or `action-required` with `reason`. Temporary provider readiness failures retry exponentially from 250 ms to a 30 s cap until recovery. Host restart reconstructs receivers from enabled bindings and grants. `im-reconnect` uses the existing Owner and current revision without changing conversation access. Failed mutations are reported, never automatically replayed.

`im-status`, `messaging-grants` and `channel-bridges` support `--read-mode saved|status`. `saved` returns local configuration promptly with `checks: pending`; capability/availability fields are not verified permissions. `status` checks only bound accounts and grants with `checks: complete`, sharing account inspections only within the current request. Omitting the flag retains full app/setup discovery. Mutations reject this flag and keep their fresh Owner checks. See [ADR-0343](adr/0343-im-settings-project-saved-configuration-before-live-checks.md).

### IM messaging, approvals and failure alerts from the CLI

```bash
Expand Down Expand Up @@ -2114,5 +2106,3 @@ deepseekbot roles update <roleId> --models all [--as <username>]
deepseekbot roles update <roleId> --models provider/model,provider/model [--as <username>]
deepseekbot models allowed [--as <username>]
```

Tool effect declarations (E1) preserve the existing tool commands and approval outcomes. Full Access, saved rules, Secrets, device targets and Workspace Grants retain their existing checks; no new route or CLI authority is introduced.
12 changes: 1 addition & 11 deletions docs/bot-cli.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ deepseekbot folder-browse [<文件夹路径>] [--offset <n>]
deepseekbot grant-create <bot-id> --workspace <workspace-id>
```

`skills-list` 返回该对话 `/` 菜单提供的 Skill,并按 `--query` 以与菜单相同的规则排序(名称前缀、名称子序列、描述):包括 Host Skill(`owner.kind` 为 `host`),以及各 PersonaBot Memory Repository 下 `.agents/skills` 或 `.dsh/skills` 中的 Project Skill(`owner.kind` 为 `bot`)。任意 Channel 可用 `--channel <channel-id>` 代替 `--bot`。`send --skill <owner>/<name>`(owner 为 `host` 或 `bot:<bot-id>`,可重复)按参数顺序,为每个参数把正文中第一个尚未选中的 `/name` token 变成 Skill Invocation(token 两端必须为空白或正文边界;没有未用匹配时拒绝发送):Host 把该 Skill 当前的文本随消息保存,Bot 以 `<skill_invocation>` 块读到它。Skill 不决定由谁回复;已不存在的 Skill 会以 `skill-unavailable` 拒绝;Skill 文本超过 64,000 个字符时会以 `skill-too-large` 拒绝,不会截断。见 [ADR-0346](adr/0346-skill-invocation-is-snapshotted-text-that-addresses-no-one.md)。
`skills-list` 返回该对话 `/` 菜单提供的 Skill,并按 `--query` 以与菜单相同的规则排序(名称前缀、名称子序列、所属 Bot 显示名或 slug 子串、描述子串):包括 Host Skill(`owner.kind` 为 `host`),以及各 PersonaBot Memory Repository 下 `.agents/skills` 或 `.dsh/skills` 中的 Project Skill(`owner.kind` 为 `bot`)。任意 Channel 可用 `--channel <channel-id>` 代替 `--bot`。`send --skill <owner>/<name>`(owner 为 `host` 或 `bot:<bot-id>`,可重复)按参数顺序,为每个参数把正文中第一个尚未选中的 `/name` token 变成 Skill Invocation(token 两端必须为空白或正文边界;没有未用匹配时拒绝发送):Host 把该 Skill 当前的文本随消息保存,Bot 以 `<skill_invocation>` 块读到它。Skill 不决定由谁回复;已不存在的 Skill 会以 `skill-unavailable` 拒绝;Skill 文本超过 64,000 个字符时会以 `skill-too-large` 拒绝,不会截断。见 [ADR-0346](adr/0346-skill-invocation-is-snapshotted-text-that-addresses-no-one.md)。

`tool-approval-status` 返回 `status`;卡片仍为 `pending` 时还返回 `expiresAt`,即网页卡片倒计时的截止时间。`--outcome allowed-always-kind` 对应 `secret_cli` 卡片上的“总是允许此类”:批准这一次,并为同一程序、账号和环境变量名保存一条规则;`tool-approval-rules` 会以 `secret-cli` 类型列出它。其他卡片会拒绝这个选项。

Expand Down Expand Up @@ -463,22 +463,14 @@ printf '{"token":"…"}' | deepseekbot im-app-add discord --credentials-stdin
deepseekbot im-app-status discord
deepseekbot im-bind <bot-id> --app <app-id>
deepseekbot im-status <bot-id>
deepseekbot im-status <bot-id> --read-mode saved
deepseekbot messaging-grants <bot-id> --read-mode status
deepseekbot channel-bridges <channel-id> --read-mode saved
deepseekbot channel-ingests <channel>
deepseekbot channel-connect <channel> --bot <bot-id> --conversation <external-id>
deepseekbot im-reconnect <bot-id> --app <app-id>
deepseekbot im-unbind <bot-id> --app <app-id>
deepseekbot im-app-remove discord --app <app-id>
```

`im-app-add` 适用于基于 token 的平台(Discord、Slack、QQ、钉钉、企业微信、Matrix);飞书和微信仍用 `im-authorize`。凭证只从标准输入读取,绝不打印;输出列出每个应用的 id、名称、连接状态和错误码。`im-bind` 把已连接的应用(`im-app-status` 里的 `id`,即 `im-status` 里的 `ref`)绑定到 Bot,`im-status` 显示每个绑定的 `reception`。`channel-connect` 通过该 Bot 的绑定,把外部群聊作为上下文接入 Channel;这个会话必须已被该绑定知晓,所以先在那里 @ 一次应用,再从 `channel-ingests` 的 `candidates` 里选 id。否则 Host 会以 `conversation-unavailable` 拒绝。

`im-status` 和 `messaging-grants` 返回进程内的 `recovery` 状态:`retrying` 包含 `reason`、`attempt`、`nextRetryAt`;`action-required` 包含 `reason`。Provider 暂未就绪时从 250 毫秒开始指数退避,上限 30 秒,持续重试直至恢复。Host 重启后从启用的绑定和授权重建接收器。`im-reconnect` 通过现有 Owner 和当前修订号重连,不改变会话权限。失败的修改会返回错误,不会自动重放。

`im-status`、`messaging-grants` 和 `channel-bridges` 支持 `--read-mode saved|status`。`saved` 快速返回本地配置,标记 `checks: pending`;其中能力和可用性字段不代表已验证的权限。`status` 只检查绑定的账号和会话,返回 `checks: complete`,账号检查仅在本次请求内共享。不传此参数时仍进行完整应用和接入方式发现。修改操作拒绝此参数,并保留实时 Owner 检查。见 [ADR-0343](adr/0343-im-settings-project-saved-configuration-before-live-checks.md)。

### 用命令行管理 IM 消息、审批和失败提醒

```bash
Expand Down Expand Up @@ -2055,5 +2047,3 @@ token 或原始响应。
Web 手动检查请求最多等待 25 秒,关闭自检卡片会取消请求。公网 WebSocket 在验证 Host 身份前将握手消息限制为 1 KiB,并禁用压缩。

公网阶段仅对 DNS、网络、超时及 HTTP 429/5xx 短暂失败重试:最多四次,间隔 1/3/6 秒,仍受同一个 20 秒总时限约束。证书或身份失败不重试。每次自检完成后,界面刷新一次账号电脑列表。

工具效果声明(E1)保持既有工具命令与审批结果。Full Access、保存规则、Secrets、设备目标和 Workspace Grant 仍使用原检查;不新增 route 或 CLI 权限。
Loading
Loading