# @awiki/dsh-plugin

为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 提供 AWiki
账号、授权、恢复与消息能力。主包包含 Host Service、IM Core Provider、Agent 工具，
以及带可拖动 AWiki Me 悬浮入口的 Web 客户端；通用 DID 文档与私钥由独立的
`@agent-network-protocol/dsh-anp-identity` 插件管理，并通过 Cordis 注入。

两个插件的状态所有权相互独立：ANP Identity 管理多 DID Store、DID 文档、私钥和发布事务；
AWiki IM Core 管理 Handle/账号绑定、设备与恢复流程、认证令牌、消息、邮件、SQLite 和缓存。
Host-only Provider lease 不会进入 Browser、Remote、Agent tools 或模型 API。

## 功能

- 在 Web UI 的统一入口按 Core 公布的能力选择身份方法（默认 WBA，启用后可选 Web），输入 Handle 和手机号，先查找该 Handle 的未完成恢复；命中则继续原操作，否则发送注册验证码。新 Handle 创建部署级身份；已有 Handle 优先进入普通 Device Join。首次选择 Recovery V4 只打开独立的替换 DID 风险确认页，不取消 Join、不创建 Recovery operation、也不发送 OTP；只有第二次明确点击“发送恢复验证码并替换 DID”才取消待处理 Join 并发送用途隔离的恢复验证码。根 Agent 与子 Agent 共用最终身份。
- DSH 可以作为独立 member 设备加入已有 Handle；当 DSH 创建或恢复 Handle、当前设备为 ready-admin 时，前台“设备”页可列出设备、通过 SAS 验证并批准 member、拒绝请求或撤销其他设备。管理动作不进入 Agent 工具，并要求显式输入 `APPROVE` / `REVOKE`。
- 本机 Darwin x64 WBA ready-admin 可在“设备”页准备 Root Transfer，并经系统级用户认证把管理能力发送给一个精确的 active member；authorization handle 和 Root material 不进入 Browser。Linux、远程无头或系统认证不可用时失败关闭。Recovery 后旧设备 re-Join 复用同一认证端口，但仍只恢复为 member。
- Web 身份沿用独立设备 Join／撤销；当前 ready-admin 可在设备页编辑公开服务，系统服务保持只读。响应丢失后展示 Core 的未决更新，用户显式继续原操作。Web 不支持恢复或管理权转移，首个管理员丢失后无法恢复管理能力。重开时读取 Core 未完成注册，用户选择原注册并重新验证，即使新建能力关闭也能续接；不会自动重新注册。合同见 [Web 身份方法与非设备服务更新](docs/dsh-device-join-design.md#45-web-身份方法与非设备服务更新)。
- 点击 AWiki 面板左上角图标可打开账户菜单；普通退出只锁定本机会话，不删除加密身份或消息数据库，重新进入及重启 DSH 后仍恢复同一个 DID 和 Handle。退出页默认只提供重新进入本机身份和使用其他身份；只有本机重新进入失败后才显示手机号恢复入口。改用其他身份必须先确认永久清除本地 AWiki 数据。
- 一般身份入口失败时保留当前挂载表单中的手机号、Handle 和验证码，以便修正后重试。例外是 Node 返回精确错误码 `short_handle_invite_required`：注册会清空验证码并返回可编辑的 Handle／手机号表单，显示“注册少于5位的handle需要使用邀请码，目前暂不支持自主注册。”；该提示在关闭重开面板后保留，修改 Handle 后清除。界面不根据 Handle 长度或远端错误文案推断该错误。手机号和验证码不进入 Browser 持久化状态、controller snapshot 或公开 Remote 结果。恢复操作编号以及按租户和操作隔离的非敏感重发截止时间可用于崩溃续跑；后者只恢复服务端冷却，不包含手机号、Handle 或验证码。注册未开放、验证码状态失效和提交冲突会给出对应的安全处理提示。
- 私聊和已有群聊列表、未读角标、最新消息预览、时间更新与昵称持久化。Core SQLite 是持久化真相源：Host 将持久化的对端资料合并进私聊列表，浏览器再按当前身份保留最后一次可信的私聊资料和群名，稀疏轮询中的 Handle、DID 或 Group DID 占位不会覆盖真实名称。恢复已有 Handle 后，Host 会先同步账号投影，再让 Core 自动恢复旧群聊成员身份；未完成或受阻的群聊会显示可重试状态，但不影响私聊和其他群聊。打开会话时先显示 Core 已提交的本地时间线，并从 Core 显示资料缓存补齐群消息发送者名称，再在后台补齐远端历史和私聊资料；刷新失败不会清空本地消息。后台会话轮询失败也不会用全局红条打断仍可用的本地页面，用户主动加载失败仍会正常提示。当前 local-first 只覆盖本地最新一页，“加载更早消息”仍需访问远端 history。向上阅读时显示下滑箭头，新消息到达后在同一控件中累计数量且不打断阅读位置。只有最新一条已渲染消息到达可视区域底部后，当前会话才会自动标记为已读。
- 可从 Web UI 输入群名和 1–50 个 Handle 或 DID 发起私有发现、开放加入、传输保护的群聊。建群成功后立即进入新会话；个别成员添加失败会单独提示，不会隐藏已经创建的群。
- 文本和单附件消息；Enter 发送、Shift+Enter 换行，发送中立即显示带 loading 动画的乐观气泡，并通过精确的客户端消息 ID 与已提交消息对账，避免同一条消息显示两个气泡；同时支持图片预览、附件说明与 SHA 校验。校验通过的图片字节使用三层有界缓存：浏览器运行期 LRU 让会话重新挂载时立即出图，按身份隔离的 IndexedDB 在整页刷新后无需访问 Host，Host 私有磁盘缓存则应对浏览器缓存丢失并跨 Harness 重启复用；“清除本地数据”会删除三层缓存，并精确删除全部 AWiki Mail list/folder localStorage 投影而保留无关 origin storage。
- 圆形可拖动入口、自适应四角弹窗、深色模式和当前会话记忆。
- 用户点击后才生成的 AI 对话总结：最多处理 50 条最近或未读消息，按会话保留本次运行期缓存，并支持过期提示、重试、复制与跳转原消息。
- 新注册少于5位的handle需要使用邀请码，目前暂不支持自主注册。限制由服务端仅在新建账户时校验。前端和 Host 不再按长度或 Handle 可用性拦截验证码与注册请求，已有短 Handle 保留 Join／恢复流程。
- OTP 身份入口会保留验证码输入表单，并按服务端返回的冷却时间显示重发倒计时、禁用提前重发；已有 Handle 在消费 registration OTP 后再选择 Join 或 Recovery，Recovery 不复用 registration grant。Join 来源 Recovery 的第二次确认会重新检查当前 phone capability；若已关闭则回到普通 Join，且不取消 Join、不发送 Recovery OTP。已存在的 Core Recovery operation 与真实 `recovery-required` 会话仍可继续查询或进入恢复，不由瞬时 capability discovery 失败隐藏。
- Recovery V4 进入 `applied` 且用户继续进入恢复后的身份时，Host 会用 current DID 解析已恢复 Handle 的原邮箱，并为该身份重新挂载收件箱与发件箱；发件历史固定来自 Mail Service 的 `mail.list(direction=outbound)`，不再读取已删除的 Host 本地 sent store。可选 Model Proxy 包会独立使用 current DID 认证，并且只向现有 Model endpoint 发送严格 `{}`。它不请求或携带 User Service 恢复凭证、DID path、proof、assurance 或账本 owner，只消费 Model 实际的 outcome-only 响应（`restored`、`already_current` 或 `not_applicable`）；transition assurance 由 Model 服务端 operation/audit/DB oracle 验证，DSH 不从公开响应推断。
- 安装独立的 `@awiki/dsh-model-proxy` 后，仅在 Harness 没有任何可用模型时，首次引导才会在官方 API Key 步骤前提供 AWiki 托管模型选项；用户可以明确启用，也可以跳过并继续原版 API Key 流程。已经配置官方或其他 Provider 时，新会话不会显示 AWiki 模型或支付提示。
- 可选 Model Proxy 包独占 Host 内部短期 Token 和全部模型托管界面：首次引导，以及“设置 → 快速充值”中的“账户与充值”“用量明细”。它提供 `deepseek-v4-flash` 和 `deepseek-v4-pro`，默认推荐 Flash；Token 不进入 Browser。
- AWiki 主包只保留身份、域名和本地数据设置。只安装主包时，不会注册模型启停、充值、用量或模型首次引导界面。
- 在设置页危险区域中，经输入确认词的二次确认后，永久清空本机 AWiki 身份、密钥、令牌、注册草稿和消息索引。
- 五个消息 Agent 工具：身份、会话、历史、需审批的文本发送和需审批的附件发送。
- 五个按需邮件 Agent 工具：邮箱账户、收件箱、纯文本读取、需审批的标记已读和需审批的纯文本发送。
- 默认身份级 realtime 连接统一调度 Direct、Group 和 System Notification 同步；独立、可选的 Agent consumer 允许 exact allowlist 中的私聊对方续接一个 DSH Agent 会话，或使用 `/new`、`/status`、`/help`。

## 界面截图

### 消息

![DeepSeek Harness 中的 AWiki 私聊与群聊界面](./assets/screenshots/awiki-messaging.png)

### 邮件

浏览器选取的附件通过 Host 鉴权 HTTP 调用 `/mail/rpc` 的 `mail.send` 和 `mail.getAttachment`，不新增 IM Core 附件接口。最多选择 10 个附件。当前签名接口的请求体上限为 4 MiB，扣除 Base64 和 JSON 元数据开销后，实际上传上限为单文件及总计 2.625 MiB（配置可进一步降低）；下载支持单文件 10 MiB。界面从 Host 获取准确限制，发送前确认文件名和大小；附件发送包括鉴权失败在内均不自动重放。

下载响应在 JSON 解析前限制大小，并校验附件索引、安全文件名、MIME、标准 Base64 和字节数，浏览器再核对所选元数据与 Host 提供的 SHA-256。收件箱和发件箱均使用服务端邮件 ID。身份或租户切换后，旧操作结果不能进入新会话。关闭重开抽屉可保留按租户和身份隔离的内存附件草稿，文件不会写入浏览器持久存储。Agent 工具继续仅发送纯文本。

Host 的 `mailAttachmentMaxCount`、`mailAttachmentMaxBytes`、`mailAttachmentTotalMaxBytes` 可降低服务上限；上传还会按鉴权传输预算限制，`mailAttachmentMaxBytes` 同时约束下载。

![DeepSeek Harness 中的 AWiki 邮箱界面](./assets/screenshots/awiki-mail.jpg)

首版不包含端到端加密、多身份、建群后的成员或群设置管理和单消息多附件。Agent listener 只接受明文私聊文本；
群聊、附件、加密/payload 内容和未知斜杠命令都不会进入 Agent。

邮件 v1 提供按需浏览器邮箱/写信界面和五个按需 Agent 工具。收件箱沿用 Core inbound 查询；
发件箱通过固定、仅 Host 可用且绑定 current identity 的 `mail.list(direction=outbound)` 查询。
身份隔离的浏览器 cache 可以在显式刷新失败时保留最近可见页面，但绝不是发件历史权威；发送
成功后浏览器只触发一次服务端发件箱刷新。邮件不会以新邮件唤醒 Agent；不渲染或发送 HTML，
不支持回复、转发和会话串联。邮件主题、地址、预览、正文、
时间戳和附件元数据都是不可信外部数据，不能作为 Agent 指令。`awiki_mail_mark_read` 和
`awiki_mail_send` 每次执行都需要审批。邮件发送只尝试一次且不自动重试；超时或传输中断返回
`delivery-unknown`，再次审批发送前应先检查邮箱。

身份恢复不新增服务端私聊恢复。清空本地状态后不会重新构造历史私聊会话；只有 Rust SDK
已经保留的普通本地数据继续遵循 Core 既有迁移规则。邮箱恢复与私聊边界相互独立。

版本查询、手动升级命令及租户隔离规则见[版本与更新](docs/updates.md)。

## 安装

先安装独立身份插件，再安装 AWiki：

```bash
dsh plugin --profile web add @agent-network-protocol/dsh-anp-identity@latest
dsh plugin --profile web add @awiki/dsh-plugin@latest
```

主包不再默认安装 AWiki 托管模型 Provider。仅在需要该能力时，另行安装独立版本的
Model Proxy 包：

```bash
dsh plugin --profile web add @awiki/dsh-model-proxy@latest
```

Profile 安装器会同时添加包并激活 bundle layer。在 DSH 项目根目录执行普通的
`npm i @awiki/dsh-plugin` 只会安装依赖，不会激活 bundle，因此仍推荐使用上述
Profile 命令。本发布线面向 `0.1.1-rc.2` 包族，并精确锁定所有直接 Host peer，
防止 npm 在 DSH 根依赖树中混用不同的预发布版本族。

从 `0.2.0-rc.4` 起，`@awiki/dsh-plugin` 是唯一规范包名。原
`@awiki/dsh` registry 条目已被 unpublish，不再作为本发布线的安装来源。

请在常规 DSH base 和 Web app bundle 之后应用两个插件。独立 ANP Identity 插件拥有
Identity Service 与 Native Provider layer；本包 `cordis.patch.yml` 只加入 AWiki Host Service、
AWiki IM Core Provider 和 Summary Provider。浏览器客户端由 DSH 根据包元数据自动发现并注入。卸载时先关闭 IM Core，
再撤销身份 lease，最后关闭身份 Store Provider。主 patch 不再插入 Model Proxy。可选包使用自己的 patch，
只插入一个 `awiki-model-proxy`，并显式依赖已经加载的 `awiki` 服务。

## 配置

完整标识符、来源、作用与默认值见 [docs/configuration.md](docs/configuration.md)。

全新安装只包含打包配置指定的两个内置租户槽位，并进入配置的默认槽位；仓库默认值是
AWiki 中国与 AWiki 全球。已有官方端点仍匹配时会原地提升并继续保持激活，身份、消息、附件、
Vault 与状态目录均不搬迁。租户注册表出现前已存在的非空状态根继续使用当时发布过的
`awiki.info` 端点快照；只有运维掌握其属于当前官方槽位的明确证据时，才通过
`DSH_AWIKI_LEGACY_TENANT_SLOT` 做一次性分类。
“设置 → AWiki → 租户”由 Host 事务化切换运行时；两个官方租户不可修改，自定义租户使用独立
存储 Scope。使用 `pnpm run build -- --tenant-config /绝对路径/tenants.json` 可完整替换两个
槽位；不做局部合并，也没有隐藏的官方 fallback。以下变量只作为旧私有/开发部署的迁移输入：

| 环境变量 | 用途 | 默认值 |
| --- | --- | --- |
| `DSH_AWIKI_USER_SERVICE_URL` | 旧 AWiki user service 绝对 URL | 打包默认槽位 Origin |
| `DSH_AWIKI_USER_SERVICE_DOMAIN` | 旧 Handle 提供方域名 | 打包默认槽位 DID host |
| `DSH_AWIKI_LEGACY_TENANT_SLOT` | 有部署证据时对旧状态做一次性官方槽位覆盖 | 未配置；保留历史 `awiki.info` 快照 |
| `DSH_AWIKI_MESSAGE_SERVICE_URL` | 旧 message service URL | 打包默认槽位 Origin |
| `DSH_AWIKI_MAIL_SERVICE_URL` | Host 调用的 mail service URL | 解析后的 user service URL |
| `DSH_AWIKI_MESSAGE_SERVICE_DID` | 旧权威消息服务 DID | 打包默认槽位 DID |
| `DSH_AWIKI_MESSAGE_SERVICE_PUBLIC_URL` | 旧协议公开 endpoint | 打包默认槽位 Origin |
| `DSH_AWIKI_ALLOWED_ATTACHMENT_ORIGINS` | 额外附件 HTTPS origin 的 JSON 数组 | `[]` |
| `DSH_AWIKI_STATE_ROOT` | 私有 Rust IM Core 状态目录 | `$DSH_HOME/awiki/im-core` 或 `~/.dsh/awiki/im-core` |
| `DSH_ANP_IDENTITY_STATE_ROOT` | 独立的 ANP Identity 多 DID Store | `$DSH_HOME/anp-identity` |
| `DSH_ANP_IDENTITY_ROOT_KEY_PROVIDER` | Store 根密钥提供方（`keyring`、`local-file`、`env` 或程序注入的 `injected`） | `keyring` |
| `DSH_ANP_IDENTITY_ROOT_KEY_PROVIDER_ID` | Keyring account、环境变量名或 injected provider 标识 | `anp-identity/dsh` |
| `DSH_AWIKI_VAULT_ROOT_KEY_FILE` | 含 base64/base64url 32-byte Vault root key 的既有私有文件 | `$DSH_HOME/awiki/secret-vault/root-key.b64u` |
| `DSH_AWIKI_VAULT_WORKSPACE_ID` | 稳定、非秘密的 Vault workspace context | `dsh-awiki` |
| `DSH_AWIKI_VAULT_DEVICE_ID` | 稳定、非秘密的 Vault device context | `local-device` |
| `DSH_AWIKI_POLL_INTERVAL_MS` | 弹窗打开时的轮询间隔 | `5000` |
| `DSH_AWIKI_ATTACHMENT_MAX_BYTES` | 解码后的附件上限 | `10485760` |
| `DSH_AWIKI_IMAGE_CACHE_MAX_BYTES` | 私有图片预览缓存的磁盘预算 | `67108864` |
| `DSH_AWIKI_REALTIME_ENABLED` | 开启身份级 Direct/Group/System Notification WSS | `true` |
| `DSH_AWIKI_LISTENER_ENABLED` | 开启私聊到 Agent 的 listener | `false` |
| `DSH_AWIKI_LISTENER_ALLOWED_PEERS` | exact Handle/DID JSON 数组；开启时必填 | `[]` |
| `DSH_AWIKI_LISTENER_WORKSPACE_PATH` | 所有 AWiki Session 共用的绝对 Workspace 路径 | `$DSH_HOME/workspaces/awiki` 或 `~/.dsh/workspaces/awiki` |
| `DSH_AWIKI_SUMMARY_MAX_INPUT_BYTES` | Host 最小化后的 UTF-8 输入上限 | `32768` |
| `DSH_AWIKI_SUMMARY_TIMEOUT_MS` | 单次模型调用超时 | `30000` |
| `DSH_AWIKI_SUMMARY_MAX_OUTPUT_TOKENS` | 结构化摘要输出上限 | `768` |
## AWiki 托管模型账户

该能力现在需要单独安装 `@awiki/dsh-model-proxy`。它通过
`ctx.awiki.externalHttpAuth` 在 Host 内向模型代理换取
短期 Token，并复用 Harness 的 DeepSeek Adapter。Browser 只能通过 loopback RPC 读取经过
裁剪的账户、用量和订单状态；DID 签名、Bearer Token 和上游平台密钥都不会进入浏览器包。

旧运行时导入 `@awiki/dsh-plugin/model-proxy` 已移除，请改用
`@awiki/dsh-model-proxy`；浏览器安全的公共契约继续保留在
`@awiki/dsh-plugin/model-proxy-contract`。只安装主包时，模型首次引导、账户/充值和
用量入口保持隐藏，高级 AWiki 设置仍可正常使用。

拆包从 `@awiki/dsh-plugin@0.3.0` 与 `@awiki/dsh-model-proxy@0.1.0`
开始。当前候选 manifest 分别为 `0.3.9` 和 `0.1.4`，Model Proxy 要求主包
`^0.3.9`，从而让共享 `awikiClient` Browser bridge 与租户 capability 契约保持在同一条
已审查版本线上。

以下环境变量归可选包所有：

| 环境变量 | 用途 | 默认值 |
| --- | --- | --- |
| `DSH_AWIKI_MODEL_CONTEXT_WINDOW` | AWiki 托管模型上下文窗口 | `1000000` |
| `DSH_AWIKI_MODEL_MAX_TOKENS` | AWiki 托管模型单次最大输出 | `8192` |
| `DSH_AWIKI_MODEL_TOKEN_REFRESH_SKEW_SECONDS` | 短期 Token 提前刷新秒数 | `60` |

插件默认关闭 AWiki 托管模型，启用意愿与回退模型按租户分别持久化。用户在首次引导或“设置 → 快速充值 → 账户与充值”明确启用后，
才注册 `awiki-deepseek` 路由并把 Flash 设为默认模型；停用时会恢复启用前的默认 Provider、
模型和 reasoning effort。充值到账只刷新余额，不会自动启用 AWiki 或切换当前模型。

设置页同时支持支付跳转和通企付 `ALI_QR` 二维码。支付功能关闭时会显示“开发环境暂未开放
充值”，但只要账户响应中的 `model_access_available` 为真，仍可启用模型。开发绕过模式会
展示计算费用和实际扣费的区别，实际扣费固定为 0；未激活价表时不显示臆造价格。
客户端充值发布门禁位于 `packages/dsh-model-proxy/src/client/recharge-availability.ts`，当前正式版中已打开。创建订单仍要求账户响应返回
`payments_available=true`；否则界面会提示充值不可用且不发送订单 RPC。该门禁继续作为
现有支付、轮询和取消流程的紧急回退开关。
正式计费时，账户摘要不会显示内部的“计费模式”项。后端返回
`model_access_reason=insufficient_balance` 时，界面会把充值作为当前主要操作，余额到账前
不显示误导性的启用按钮。Host 会在每次重新打开设置时恢复最新的待支付订单和支付入口，
持续查询状态并阻止重复创建；支付到账后仍必须由用户明确启用托管模型。
充值金额在订单创建后不可修改。需要更换金额时，用户必须确认“取消并修改金额”；Host 会先
关闭支付平台订单，再恢复金额输入框，并且不会自动创建替代订单。关闭失败时原支付入口继续
有效；若支付在关闭竞态中先完成，界面会刷新已入账账户，而不会误报订单已取消。

“设置 → AWiki”分为“租户”“设备”“本地数据”“临时消息集成”四个页签。注册前、恢复中、退出后和
登录后都可进入同一租户面板。切换成功前先打开目标 Scope；任一步失败都会重建旧运行时。
旧 `awiki.domain` 设置仅作为迁移输入，并继续引用原有状态路径。

设置页通过插件自有的 Connection 通道访问 Host，Host 只接受 loopback 来源。
因此独立安装的 `@awiki/dsh-plugin` 无需修改 DSH 核心设置白名单；非本机浏览器来源不能
读取或修改这项 Host 设置。

“设置 → AWiki → 危险区域”中的清空操作只删除此安装的本地 AWiki 状态，不删除
服务端账号或 Handle。执行前必须在确认弹窗中输入指定确认词；成功后本机 DID 私钥、
访问令牌、注册草稿、会话记录、附件索引和图片预览缓存无法通过应用恢复，原身份也可能无法再由本机使用。

普通“退出登录”与危险区域的永久清空相互独立。退出只写入一个 Host 私有会话标记，
同时阻止 Web UI 和 Agent 使用该身份；SecretVault 中的身份、密钥、令牌、会话、附件索引和图片预览缓存
全部保留。“重新进入本机身份”会移除标记并恢复同一个本机身份，不需要重新注册。退出页默认不显示恢复入口；
只有重新进入失败后，才提供原身份的手机号恢复。“使用其他身份”必须先勾选不可逆数据清除确认，清理成功后回到
统一身份入口，并由同一个 Handle 表单判断是创建新身份还是恢复已有身份。

Provider 域名和消息服务 DID 都是协议标识，不能根据 API host 猜测。生产环境 URL
必须使用 HTTPS。IM Core 状态目录含访问材料，应置于仓库外，限制文件权限，并为磁盘
和备份提供保护。

Node facade 独占 `stateRoot/vault/root-key.b64u`；Host 不提供、不复制也不记录 Vault key
material。普通重启与升级期间应完整保留 SDK state root。

身份级 realtime supervisor 默认开启，独占部署身份的一条 Core WebSocket，不依赖 Workspace 或
Agent 配置。Direct、Group 和 System Notification 事件只调度 canonical reliable sync；WSS 本身
不推进 checkpoint，也不授权设备。显式设置 `DSH_AWIKI_REALTIME_ENABLED=false` 可回退到 HTTP
refresh。私聊 Agent consumer 仍只有在 `DSH_AWIKI_LISTENER_ENABLED=true` 且 exact allowlist
非空时才启用；它只读取合格同步原因之后的已提交 Direct 文本，不能启动或停止 WSS。每个私聊的
DSH Session route 和消息 watermark 按身份隔离持久化，重启后可续接；所有 AWiki 来源 Session 都
创建并 attach 到已注册的共享 AWiki Workspace。Listener 消息始终是不可信用户数据，不会自动批准
工具，也不会桥接 approval 或 user-question prompt。

Host-only realtime diagnostics 另外保留最近一次成功同步的分页数、hydrated 消息数和
`olderHistoryExcluded`。它们只用于安全诊断；不会暴露 cursor、page ref、token、manifest、消息正文，
也不会进入 Browser Remote、Agent 工具或模型上下文。

默认附件上限为解码后 10 MiB；反向代理请求体上限至少应为 14 MiB，以容纳
base64 与 JSON 封装开销。

AI 总结只在用户点击“AI 总结”后生成。打开会话时若存在未读消息，Host 总结该未读
尾部；否则总结最近 50 条。Host 最终强制 50 条与 UTF-8 字节上限，附件只发送文件名、
MIME、大小和说明，不发送文件二进制；序列化后的对话内容始终按不可信数据处理。
总结只按会话缓存在本次浏览器运行期；新消息只会把已有结果标记为过期，不会自动再次
调用模型。可替换的 `@awiki/dsh-plugin/summary-provider` 使用 Harness 当前默认 provider/model
执行一次直接的 `ctx.llm.stream`，不会创建 Agent，也不会写入 Agent session。

## 外部 HTTP ANP 身份认证

可信的 DSH Host 同进程插件可以认证由外部 transport 发送的 HTTP 请求，而无需自行处理
ANP 签名、Access Token、challenge 或重试：

```ts
const response = await ctx.awiki.externalHttpAuth.dispatch(
  new Request('https://api.example.com/orders', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ productId: '123' }),
  }),
  request => fetch(request),
)
```

回调函数仍是唯一网络 transport owner。AWiki 最多缓冲 4 MiB 精确 body bytes，强制 manual
redirect，由 Rust 自动选择当前 origin 的进程内 Bearer Token 或新 HTTP Message Signature，
只观察认证相关响应头，并且每个逻辑请求最多调用 transport 两次，第二次只能是一次受限的
`401` 认证重试。最终 `Response` 正文不会被读取；transport rejection 保留原始错误对象。

输入请求不得自行携带 `Authorization`、`Signature-Input`、`Signature` 或
`Content-Digest`。生产目标必须使用 HTTPS；测试用 loopback HTTP 复用现有
`allowInsecureLoopbackForTesting` 部署开关。Token 只接受成功响应中的
`Authentication-Info`，并按当前 identity、signing key 和 origin 隔离；Harness 重启后不保留。

`externalHttpAuth` 不进入 Browser Remote、Agent tools、Typert Remote 或 Web client bundle，
避免形成跨不可信边界的签名 oracle。

## 开发与验证

依赖模式、个人配置及跨仓 PR 提交规则见 [Debug / registry / 源码联调](docs/dependency-modes.md)。
默认使用 registry，显式本地联调用 `pnpm run build:local`，正式构建用 `pnpm run build:release`。
首次切换需要按该文档确认依赖已发布并刷新 registry lock；旧 workspace 锁不能作为线上安装证据。

需要 Node.js 22.19+（或 24+）以及 pnpm 11.22：

```bash
pnpm install --frozen-lockfile
pnpm run verify:workspace
pnpm run e2e:smoke
DSH_AWIKI_E2E_CONFIG=/absolute/path/to/rwiki-cn-testing.json pnpm run e2e:live
pnpm pack --dry-run
```

`e2e:smoke` 使用 Playwright Chromium，把当前 tarball 安装到隔离的真实 DSH Web profile，
通过 Harness 自带的首次运行对话框并打开 AWiki 身份入口；它不发送 OTP。受保护的 `e2e:live`
在 `rwiki-cn-testing` 上创建一个 DSH 身份和一个真实 CLI Peer，验证双向私聊、群聊及同 root
Harness 重启，并要求 artifact secret scan 与 exact managed cleanup 零残留。当前完成门禁以
无桌面 Linux Chromium headless 为准；WebKit 只保留为后续可选兼容性检查。详见
[Web E2E 技术方案](docs/e2e-automation-testing-cli-peer.md)。

生产 Host 加载固定版本 `@awiki/im-core-node@0.2.3`；平台原生 addon 由它的
optional dependencies 选择，并保持在 JavaScript bundle 外。使用者无需安装 Rust，
也无需检出 `awiki-cli-rs2`。来源与许可证见 `THIRD_PARTY_NOTICES.md`。

Typert Host/Remote 产物与当前 Host 契约一同提交；在独立 Typert 生成器支持根级
包之前，`pnpm check:generated` 会固定检查完整的 58 个 Remote 方法。

## 安全

不要提交 OTP、访问令牌、私钥、身份状态、`.env` 或远程测试报告。
`pnpm check:public` 是验证和打包前的公开仓库安全门禁。

## 许可证

插件使用 MIT 许可证；Rust IM Core 运行时依赖使用 AGPL-3.0-only，并继续适用其
自带的许可证与声明。

### 群成员与发送人名称

Host `getDisplayProfiles` 返回 Core 本地展示投影并调度后台刷新；Browser 沿用会话轮询更新成员与发送人，首帧无需等待公开资料网络请求。刷新按 DID 去重，Core 管理 TTL、失败重试和 owner 隔离，既不创建联系人也不创建 Direct 会话。成功的空昵称回退到 Handle/DID，网络失败保留已缓存名称；身份切换和会话切换后的旧结果不回写界面。该路径使用 Node native API v14，必须与本地 Core Node 包配套构建。


### 本地邀请码注册验收

`DSH_REGISTRATION_FIXTURE=<绝对路径的0600 JSON> pnpm exec playwright test --project=local-registration-chromium --reporter=line`
执行 `DSH-WEB-REGISTRATION-001`：三位新 Handle 缺少邀请码、错误邀请码、纠正后获取验证码及完成注册；独立 DSH 状态下同一个已有短账号不要求邀请码并进入加入设备入口。
配置字段为 `url`、`freshUrl`（两个隔离 DSH 实例）、`userServiceUrl`、`handle`、`phone`、`otp`、`inviteCode`，三个 URL 必须是 HTTP loopback。外部本地服务 provisioner 负责创建一次性账号和邀请、核对消费次数及清理数据库；本用例不是生产环境入口。
该项目关闭 trace、录像及截图；失败上下文仍可能包含表单内容，只能保存到受保护的本地目录并在分享前脱敏。
本地 TLS 测试可以通过 `DSH_AWIKI_CA_BUNDLE` 指定专用 CA，由 Host 传给 Core；它不会关闭证书或域名校验。Host 自身的 Node HTTPS 请求另遵循 Node 的 CA 配置。

### 后端能力与隔离候选

Host 通过本轮 Core 的 backendCapabilities 获取公开和认证本域声明，向 Browser
只投影允许列表中的支持状态。界面与工具执行前共用判断；未声明和不支持分开表达。
设置中的“服务器功能”可查看当前范围，邮件等独立模块按声明隐藏；已有加密聊天不能
自动改成明文。同租户发现暂时失败保留旧可选服务结果，租户代次变化丢弃迟到响应。

能力合同由 awiki-cli-rs2 的 docs/api/im-core-public-api.md 维护。跨仓候选必须使用
scripts/dependencies/run.py 的显式 local/source 模式，并保存 resolution 与配套锁文件；
默认 registry 构建不能作为未发布能力 API 的验证证据。
