# 方案空间（space.24haowan.com）· 给 Agent 的接入指引

你是用户正在使用的工作 Agent。方案空间是 agent 的发布按钮：把你和用户一起干完的活 —— 周报、进度页、会议纪要、方案、看板，学习笔记、备考计划、读书卡片，旅行攻略、家庭计划、聚会邀请，以及报告、网页、演示稿、Markdown 文档 ——
变成一个可分享、会更新的网页：**在微信里点开就能看**，分享给要看的人（同事、客户、朋友、家人），收回阅读记录与反馈；同一页可反复更新，链接不变。
先带用户完成接入，再按他的具体任务操作。

## 托管契约：网页怎么写，发上来就怎么跑

方案空间是**托管服务**，不是另一套写法。这一节是唯一的权威说法；下面各节与它冲突时以这里为准。

1. **包 = 一个目录**，入口 `index.html`，包内相对路径原样可用（`./css/a.css`、`img/b.png`）。发布后跑在工作区自己的子域名 `<工作区>.hy.24haowan.com` 上，每个工作区一个，互不连累。
2. **常见网页写法都能用，默认开放**：引用网上的脚本 / 样式 / 图片 / 字体 / 音视频（CDN 上的 echarts、tailwind 照常加载）；页面里 `fetch` / XHR / WebSocket 调外部接口；嵌外部 iframe（视频、地图、问卷）；普通 `<form>` 提交到本站或站外；`alert` / `confirm` / `prompt` / `window.print()`；`localStorage` / `sessionStorage`；用户点了之后 `target="_top"` 跳出外框；密码框；摄像头 / 麦克风 / 定位（浏览器照常逐次问读者）。页面连什么、收什么由用户决定，方案空间不拦。发布回执里的 `external_origins` / `external_form` 只是**说明**页面会连哪些站外地址、表单交到哪 —— 不用改。
3. **读写数据用最普通的写法**，本地放示例数据就能跑：
   - 读：`fetch('./_data/<表>.json')` → 行对象数组，修订号在响应头 `X-Space-Revision`。线上有这张表就出活数据（底表 + 过审的读者写回），没有就回落包里同名的 `_data/<表>.json`。
   - 写：`POST ./_data/<表>`（只追加一行）。JSON ⇒ `201 {id, status}`；普通表单（`<form action="./_data/<表>" method="post">`，不用写脚本）⇒ `303` 回到提交的那一页并带 `?space_submitted=<表>`（隐藏字段 `_next` 填相对路径可换落地页；`_` 开头的字段不入库）。
   - 这张表没开写回 ⇒ `403 {"error":{"code":"append_closed"}}`；页面要把原因给读者看，别假装「已提交」。开写回、限频、审核、读回见下面「数据表」一节。
4. **方案空间只在你的页面上加一行**：每个 HTML 文件的 `<head>` 里多一行 `<script data-space-sdk src="/_space/sdk.js?…">`，其余字节原样（`read_version` 读回来的就是读者拿到的字节，可以自己 diff）。这一行在读者的浏览器里做这几件事：记阅读统计；HTML 成果默认录制访问画面（敏感正文加 `data-space-private`）；站外链接在新窗口打开、包内文件的 `download` 链接真的下载；`kind:"deck"` 时加载演示引擎；manifest 声明了 `theme` 时在根节点写 `data-theme`。`render:"inline"` 对新版本已停用（照收、按普通页面出）。
5. **本地先试**：`curl -sSO https://space.24haowan.com/cli/space.mjs` 后 `node space.mjs serve <目录>`（零依赖、不要令牌），打开它打印的地址 —— 同一份 CSP、同一行 SDK、同一套 `./_data/` 读写（数据读写本地 `_data/` 目录；`--closed 表1,表2` 模拟没开写回）。本地这里能跑，线上就能跑；线上与本地有没有走样由平台的自动对比检查守着。
6. **仍然不行的只有这几件**（原因是「每个页面还没有独立的根目录」，不是不让用）：以 `/` 开头的路径指向工作区子域名的根而不是你的包 ⇒ 404，改相对路径；Service Worker 会被拒收；包里不能有 `_space/` 目录（留给平台那一行）；讲解提示 `data-speaker-notes` 不能留在包里（CLI 会剥进 `manifest.notes[]`）。
7. **不变的底线**：读者的登录状态页面脚本读不到（查看 cookie 是 `HttpOnly`）；发布者的登录凭据不进内容域；内容审核与防钓鱼照常。**出了事**：运营把出问题的**工作区或页面**切回收紧规则（只能连本域、不能提交表单、不加载站外资源；`list_proposals` 那一行会标 `content_lockdown`），不按域名拉黑、不动别人的页面。
8. **旧写法继续能用，但是可选**：SDK `space.data.get` / `space.data.append`、`data-space-form`、平台表单块 `set_form` 都保留；新写的页面优先用上面的普通写法。

## 先带用户接入：每次只走一步

用户把接入任务交给了你。请担任他的接入向导，技术说明由你消化，不要整段转发给用户。

1. **先确认自己能做什么。** 已有方案空间工具时，直接进入下面的只读验证。尚未连接时，确认当前应用是否支持远程 MCP / 连接器，以及你是否能操作该应用的设置。不能判断时，只问用户正在使用哪个应用；不要编造菜单名称或假称已经配置。
2. **优先使用客户端的登录授权。** 接入端点是 `https://space.24haowan.com/mcp`。能在当前授权范围内完成的配置由你处理；需要用户点击时，一次只给一个具体操作，说清在哪个页面、点哪个按钮。入口不确定时先核对该应用的当前界面或官方说明，不能让用户去理解 MCP、OAuth、JSON 或命令行后再回来。
3. **登录与确认由用户亲自完成。** 让用户在方案空间官方页面登录、核对助手和工作区、点击「允许」，然后回到当前对话。图文对照页是 https://space.24haowan.com/start 。不要索取微信验证码、访问令牌或完整授权回跳地址，也不要替用户确认授权。
4. **以真实读取为接入成功。** 授权页面显示「已确认授权」不代表客户端已经连接成功；实际调用 `list_proposals` 或 `list_folders`，成功返回即可，空列表也是成功。REST 接入则读取 `GET /api/me`。用一句普通话告诉用户验证结果。仅为接入验证时，不创建示例成果、不上传、不修改可见性、不分享内容。
5. **失败时继续带路。** 根据真实错误给出下一步，修正后重新只读验证；不要重复创建连接或令牌。助手不能联网时可请用户上传从图文页下载的接入说明；如果也无法调用外部工具，明确说明当前能力不足，建议换到支持连接的工作助手，不声称所有聊天应用都能接入。API token / REST 仅作为已确认需要的备用方式，让用户在客户端的凭据设置中填写，不要求把秘密发到对话里。
6. **接入与发布分开。** 手机号和工作区资料未完善，不等于连接失败；等用户准备发布时，再按 `trust.missing` 补齐 —— **手机号**在官方页面补（`/app/onboarding`），**工作区资料**的网页表单已退役，由你调 `set_workspace_profile` 填。连接成功后，用户没交代第一件事就按下一节提两条现成的任务；他说「先给我预览」时，上传必须使用 `--hold`，已有公开版本也不提前替换，等确认后再切换或对外分享。
7. **用户有多个工作区时，先切换再授权。** 授权页上「接入的工作区」就是用户在网页上当前所在的那一个，页面上不能改选。要连另一个工作区：让用户先打开 https://space.24haowan.com/app/workspaces 切换到它，再重新发起连接。每个工作区各是一次独立的授权 —— 再连一个不会替换已有的连接，两边可以同时连着、各自在 https://space.24haowan.com/app/tokens 撤销。一次连接只作用于授权时的那个工作区。

## 接入之后：把第一份成果发出去（连接成功 ≠ 已发布）

连接验证通过、用户又没交代任务时，别停在「已经接好了」：只提**两条现成的事**，让他挑一条，或者直接说自己的：

- **生活**：把已有的旅行攻略发给亲友，在微信里方便看。
- **工作**：把已有的报告或演示稿发给同事，在微信里查看。

先问用户要用**哪一份已有的**成果（一个文件，或这段对话里刚做好的那份）。内容由用户的 Agent 产出、由用户确认；没有现成的就用他手上任何一份，不替他编内容，也不拿平台样例冒充他的成果。

**两层意图分开**：
- 连接授权**不等于**同意上传，更不等于同意对外分享。只授权、没提发布任务时，什么都不传。
- 用户已经明确提出发布任务（「把这份攻略发给家人」）时，沿用这次授权直接做完，**不重复确认已经说定的事**（发哪份、给谁看）；只有可见性档位没说清时问一次。
- 没明确要求公开就不切 `public`；用户说不发就停下，**不再追问**。

**六步走完才叫「已分享」**，每一步把真实状态和下一步告诉用户：
1. **选成果**：`list_proposals` 先看有没有现成的（更新就复用 proposal_id），没有再新建。**换了会话、手里什么都没有时也走这条**：它每行带 `current_version_id`（客户现在看到的那一版），拿着它就能直接 `get_version` 查状态、按第 4 步预览；要翻历史版本或回滚用 `list_versions`。成果号不是版本号 —— 拿 `proposal_id` 调 `get_version` 会 `not_found`。
   **先看这一行有没有 `pending_version` / `held_version`**：前者是还没发完的那一版（停在上传 / 转换 / 审核），后者是已过审但被 hold 按着、还没成为客户可见版的那一版。有就**接着处理那一版**（第 3–4 步），别当成「没发过」再建一份 —— 换个会话就重建，正是生产上一份成果底下堆出一串半截版本的来路。
2. 上传走哪条路：有终端 ⇒ space-cli（先 create_cli_token）；没终端、文字文件合计 ≤ 2 MB ⇒ publish_file；其它或拿不准 ⇒ create_upload_link。 校验失败按 issues 修正后重传；用户还没确认就带 hold。
3. **等可用版本**：`get_version` —— `converting` = 还在转逐页图，稍后再查；`moderating` = 审核暂时不可用，稍后重新 finalize 一次；`review` = 命中规则等人工复核，在那之前别人看不到这一版；`preview_ready` 或带 hold 的 `published` = 可预览、待确认。
4. **预览**：把预览链接给用户看；确认后 hold 版才 `publish_version`。★ **你自己先看一眼**：`read_version` 把这一版**渲染后的正文**读回来（读者在浏览器里拿到的就是这段字节），不用截图、不用等用户替你看；这一版有哪些文件、入口是哪个，看 `get_version` 的 `artifacts`。
5. **确认访问范围**：用户点头后 `set_visibility`：家人、朋友、同事用 `passcode`；`public` 只在用户明确要求时。把默认链接（和口令）交给用户。
6. **手机打开**：用户在电脑上时，让他打开这条链接、点页面外层的二维码入口（「扫码到微信」），或直接打开返回的 `card_url` 用微信扫码；打开后再点右上角转发。用户在手机上就把链接直接发到微信。讲稿遥控的二维码是另一件事（只给讲的人），别混用。

**判据**：收件人手机微信能打开并看到内容，几分钟后 `get_engagement_summary` 出现这次访问。停在「已授权」「已上传」「已过审待确认」「仅自己可见」中任何一步都**不能说成「已分享」**—— 说清停在哪一步、为什么、下一步做什么。

## 发出去之前问一句：要不要收投票 / 报名 / 反馈 / 收款

能力一直都在（数据表写回、`set_form` 表单块、`set_payment_qr` 收款码），但读者最多的几类成果 —— 邀请函、行程、图册、落地页 —— 一份都没开写回：助手从不主动提，作者就不知道有这一项。所以**任何一页发出去之前都问一句**「要不要顺手收一下投票 / 报名 / 反馈 / 收款？」，并按页面类型给出下面的默认做法：用户点头就一起做好再发，说不用就不加，**只问这一次**。

| 页面类型 | 默认写回 | 表名 / 工具 |
|---|---|---|
| 行程 / 旅行攻略 | 两种走法让大家**投一下**（高铁 vs 自驾、A 线 vs B 线），文末一张小表单，票数显示在下面；**路上有变就更新同一链接**，不另发一份 | 数据表 `rsvp`（列 `name` / `plan` / `note`），`set_data … append_open=true` |
| 邀请函 / 聚会 / 宴席 | **出席回执**：能来 / 来不了、几位大人几位小朋友、称呼、备注；页面统计已回复几家几位 | 数据表 `rsvp`（列 `name` / `attend` / `adults` / `kids` / `note`） |
| 图册 / 落地页 / 产品目录 / 服务页 | **询价或报名**表单；标了价的再加经营收款码，读者提交后原地看到应付金额与你的码 | `set_form`（表 `inquiries`，字段最多 20 个）+ `set_payment_qr`（只收**商户收款码**：个人静态码不得用于经营性收款，传之前跟用户确认那是他本人的码） |
| 方案 / 报告 / 会议纪要 | 文末**一句话回复**（同意 / 有意见 / 待定 + 备注），比一个个追着问省事 | 数据表 `replies` |
| 学习计划 / 打卡 / 活动签到 | **打卡**或签到一行，页面按天或按人计数 | 数据表 `checkins` |
| 日报 / 周报 / 进度页 | 不收表 —— 它的「写回」是下一版：同一链接发新版本，读者刷新即见；要追更就让读者在阅读页里点「关注更新」（页面上有这一项时） | 不建表 |

- 表名照上面用（`rsvp` / `replies` / `inquiries` / `checkins`，与平台样例同名），列按页面要收的字段定。**先 `set_data` 建表**（`revision=0`、`append_open=true`）**再发页面**，页面就不会撞 `append_closed`；读回用 `list_data_rows`；过了截止日把 `append_open` 关掉。
- 页内写回就是一张普通 `<form action="./_data/<表>" method="post">`（契约见「托管契约」第 3 条）；一行表单代码都不想写就用 `set_form`，由平台渲染。样例已经是这么做的：`list_examples` 里 `sample-weekend-trip`（两种走法投票）与 `sample-invitation`（出席回执）直接照抄。
- 用户不要就不加、不再提；要了也**不等于同意公开**：写回只对读得到这份内容的设备开放，可见性仍按上面第 5 步确认。

## 0. 定位与纪律（先读）

- 方案空间是**你的插件**，不是内容生产方：成果由你和用户产出并由用户确认；平台只负责发布、查看、信号与反馈。
- **做出一份能给别人看的成果时，主动问一句。** 你为用户做好 HTML 页面、报告、PDF、演示稿或 Markdown 文档，而它看起来是要给别人看的，
  就问一句「要不要发成微信里能打开的链接？」—— 用户同意后再上传。只问一次，用户说不用就别再提。
- **内容边界（2026-09-21 起）**：允许商业交易内容 —— 成果可以分享给用户选定的人，也可以公开分享（公开与否仍由用户决定）；可以是商业、交易、营销内容，可以在页面上标价卖东西、收集自己客户的订单、联系方式与报名信息。收款方式由你决定 —— 可以用平台的表单与收款块，也可以在页面上放自己的收款码、报价或第三方支付链接。不得钓鱼（冒充他人、收集他人的账号密码或支付信息）、不得发布违法违规内容。
- 对外的时刻 = 放开可见性：`set_visibility(passcode|public)` 与把链接发出去必须**用户明确确认**后才做。push 过审即自动生效，但成果默认仅发布者本人可见（private），不放开就没人看得见。
- **不要替用户把内容公开给所有人。** 给家人、朋友或一群同事看用 `passcode`（口令可看）；`public` 只在用户明确要求公开时才切。
- **一份方案只有一条链接**（它自带的默认链接），没有「给某个人单独建一条」那种形态。要公开传播必须显式 set_visibility public（用户确认后）。

## 1. 接入（二选一）

**A. MCP（推荐）** —— 客户端支持带登录的远程 MCP（Streamable HTTP + OAuth）时。端点只有一个，不带任何 header 填进去，首次调用工具时客户端会打开浏览器登录方案空间：

    接入端点：https://space.24haowan.com/mcp （streamable HTTP）
    CodeBuddy Code 示例：codebuddy mcp add --scope user --transport http space https://space.24haowan.com/mcp
    Claude Code 示例：claude mcp add --transport http space https://space.24haowan.com/mcp
    WorkBuddy：连接器市场搜「方案空间」安装，不用填地址
    其他客户端：「MCP 服务器 → 添加」选 Streamable HTTP，填上面的地址，不填请求头

已在线上接入过的客户端：CodeBuddy Code、WorkBuddy、Claude Code、Codex。判据不是名字，是「支持带登录的远程 MCP」——没列到的客户端只要支持 Streamable HTTP + OAuth（或能填请求头）就能接。
首次连接通过 OAuth 在浏览器登录并确认授权；已登录用户直接核对工作区。客户端不支持登录授权、或无浏览器环境时改用 API token：
让用户到 https://space.24haowan.com/app/tokens 创建 `sk-space-…`，作为 `Authorization: Bearer` 头（CodeBuddy 写进 `~/.codebuddy/.mcp.json` 的 `headers`，`type` 填 `http`；WorkBuddy 一类 `type` 填 `streamableHttp`）。

**B. 纯 REST** —— 不支持 MCP 时：同一批能力有 REST 等价端点（`/api/*`，Bearer token 鉴权）。
`GET /api/me` 可核验身份与开通状态（`trust.missing` 列出还差什么：手机号 / 工作区资料 —— 后者你自己能填，见 `set_workspace_profile`）。

## 2. 标准动线（每步一个 MCP 工具）

1. `create_proposal`。**容器是可选的**：一次性的东西不传 `folder_id`，就落在根目录 —— 别为了放一份方案先编一个容器。要按客户/项目分组就先 `list_folders` 看有没有现成的，没有再 `create_folder`。
   - 「先建客户、再建商机」那条老动线已于 2026-09-15 **退役**：`upsert_customer` / `upsert_opportunity` 两个工具都已删除，别再去找。
2. **上传**。
   **上传走哪条路（按顺序判，第一条对上就用它）**：
   1. **你能跑终端命令、读得到本地文件** ⇒ 本地上传助手 space-cli（`node space.mjs push`）：任何文件、任意大小；凭据调 `create_cli_token` 现取一把。
   2. **没有终端，要发的是文字文件**（一个 HTML / Markdown，或网页连同它的 css / js / json / svg 等几个文字文件，合计 2 MB 以内）⇒ `publish_file`，把文本内容直接交进来。
   3. **其它情况，或者拿不准** ⇒ `create_upload_link`：把返回的上传网页原样交给用户，用户在手机（微信里也行）或电脑上自己选文件（PDF / PPTX / 图片 / 大文件都行），你每 15–30 秒用 `get_version` 查一次结果。
   `create_upload_session` 不是第四条路，是上传助手自己调的一步：没有终端就别建会话 —— 预签名地址你传不上去，版本会永远停在 `uploading`。
   - 走第 2 条时：网上的脚本、样式、图片可以直接引用 URL；包里自带的图片只能内联进文字（data URI），内联前同样先转 WebP、长边 ≤ 1600px —— 2 MB 上限一张原图就能撞到；图片多、内联装不下就改走第 1 或第 3 条。之后的校验、审核与生效规则和 `finalize_upload` 完全相同，返回也一样；用户还没确认对外就传 `hold: true`。
   - 走第 3 条时：链接 2 小时内有效、只能成功用一次、只能往这一份成果里传、默认先不对外；用户说「传好了」之前别把版本当结论。

   第 1 条（本地上传助手）怎么跑：

       curl -sSO https://space.24haowan.com/cli/space.mjs   # 只需一次
       SPACE_TOKEN=sk-space-… node space.mjs push <目录或 PDF> --proposal <proposal_id> [--manifest manifest.json] [--note "V2：按客户意见改了第三页"] [--hold]

   - **`SPACE_TOKEN` 从哪来**：用 API token 接入的直接用手上那把；**走 MCP + OAuth 接入的你读不到自己的令牌**（它锁在客户端的凭据库里，也喂不进子进程）——
     调 MCP 工具 `create_cli_token` 铸一把**只活 30 分钟**的 `sk-space-…`，原样填进上面那一行。只用于这一次上传、**不要写进任何文件**（不要进 .env / 脚本 / 提交 / 日志）、跑完不用管（会自己过期）；
     用户想立刻断掉可以在 `/app/tokens` 撤销那条 `CLI · …`。
   - **不要拿 `create_upload_session` 的预签名地址手 curl 代替 CLI**：CLI 还做目录遍历规则（跳 `.` 开头 / `node_modules` / 无扩展名 / `.map` —— 传了平台不收的文件会 `ext_not_allowed` 打死整个 push）、
     逐文件 sha256 / size / mime、manifest 入口推断、6 路并发 PUT + 指数退避、finalize 后的转换与过审轮询。几十个文件的包手 curl 一定会半截，或者把过渡态当结论报给用户。
   - 支持四种入口：`index.html`（网页 Deck / Demo）、单个 PDF、单个 PPTX、**单个 Markdown**（.md 直接 push，平台渲染成阅读页，`##` 标题自动成为目录锚点，源文件按下载清单选择）；否则写 `manifest.json`
     （字段：`title, entry, kind(pdf|html|deck|demo), scene(proposal|quote|report|demo), downloads, sections[{id,title,page,anchor}], notes[{anchor,text}], share{title,desc,cover}, note, theme(system|light|dark), theme_switch, images(auto|keep)`）。
   - `theme` / `theme_switch`：**文档级**主题。HTML 包自带深色配色（`prefers-color-scheme: dark`）时，观看者系统是深色模式就会看到深色版 —— 与作者本地截图不一致；写 `"theme": "light"` 固定成浅色（`dark` 同理），`"theme": "system"`（缺省）跟随系统。`"theme_switch": true` 让观看者在页面上手动切换（按方案记住）。
     平台只把当前主题写到文档根节点 `data-theme="light|dark"`，包的 CSS 要按这个约定响应：`:root[data-theme="dark"]{…}` / `@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){…}}`。不声明 = 现状（不写属性、不出按钮）。
   - `scene` 决定**微信转发卡片的描述缺省**（来自 X 的方案 / 报价 / 月度报告 / 演示）。不写会自动推导（kind=demo 即演示，否则方案；「挂了报价=报价」那条随报价台退役已删）；
     月报这类推导不出来的**值得显式写**，否则客户上级看到的卡片上会写着「方案」。卡片缩略图默认是方案空间品牌图，不随 scene 变；想用自己的图就写 `share.cover`（它最高）。
   - `sections` 值得写：阅读摘要与反馈会按「第 N 页『章节名』」说话。HTML 包的每个 section 给 `anchor`
     （= 包内元素 id，`<h2 id="…">` 最省），目录才能跳。
   - PPTX 可作入口（服务端转逐页图），但缺字体会被替换、新 emoji 可能空白；稳妥是 PDF 入口、PPTX 放 downloads。
   - **做成演示稿（deck）**：manifest 写 "kind": "deck"，包里用 `<deck-stage>` 包住一页一个 `<section>`。
     平台出流时**自动注入**演示引擎（键盘 ←/→ · 点按左右半屏翻页 · 16:9 自动缩放 · 缩略图目录 · 打印一页一张），
     **包里不要自己挂引擎脚本、也不要写版本号**；壳页会多出「上一页 / 下一页 / 全屏」。
     ⚠️ `kind:"deck"` **不要**配 `"render": "inline"` —— 写了会在校验阶段被拒（`manifest.render`）；
     `render:"inline"` 对新版本已停用（见上面「托管契约」第 4 条），不用写。
   - **讲解提示（只有讲的人看得到）**：在每个 `<section>` 上写 `data-speaker-notes="这页讲什么 / 对方可能问什么"`。
     push 时会把它们**从包里剥掉**、放进 `manifest.notes[]`，平台只渲染给发布者 ——
     客户拿到的字节里一个字都没有（View Source 也读不到）。现场话术、砍页理由可以放心写。
     ★ 不走 CLI 手工发布时，包里**残留** `data-speaker-notes` 会被**拒收**（`html_speaker_notes`）：
       自己把这些属性删掉、把讲稿写进 `notes[]`。
     ★ 发布者打开方案页会多一个「讲稿」按钮：开第二个窗口看提示，或扫码开到手机上（两端翻页互相同步）。
       告诉用户**优先用手机** —— 投屏投的是整台笔记本，第二个窗口一样会被投出去。
   - **要给客户一份 PDF 讲义**：本机把 deck 打印成 PDF（引擎的 `@media print` 保证一页一张），与包一起 push
     并写进 `downloads.attachments`。平台**不做** HTML→PDF ⇒ **改了 deck 要重导一次**，否则客户下载到的是上一版。
   - **不知道 HTML 包该长什么样？别从零发明** —— 平台自带一份最小骨架，直接抄：
     `GET https://space.24haowan.com/cli/starter.html`（仓内源码 `core/space/examples/starter/`）。
     **做 deck 的另抄一份**：`GET https://space.24haowan.com/cli/deck-starter.html`
     （仓内源码 `core/space/examples/deck-starter/`）—— 它把「一页一个 section」「引擎不用自己挂」
     「讲稿写在哪」「目录锚点 = 元素 id」四件摆对了。
     网页文档骨架（`kind:"html"`）已按样例品质标准写好默认版式与手机适配，并把「写错不会报错、只会一声不响不生效」的事摆对了：
     每节的 `id` = `sections[].anchor` = `data-track-section`（目录能跳、阅读回执知道看到哪一节）。
     文末确认做成普通 `<form action="./_data/<表>">`，写进这份方案的数据表（见「托管契约」第 3 条）。
     手写翻页稿才需要 `window.__track.slide(i,label,total)`。
   - **页面能用什么、不能用什么，以上面「托管契约」为准**：外部资源、外部请求、表单、弹窗、打印、存储、摄像头定位都能用。finalize 返回的 `compat_notes` 分两种：「会失效 / 手机上会撑宽」这类（以 `/` 开头的路径、脚本现生成的 Blob 下载、宽表格、长代码行、过重的 GIF）**看到了就改掉再发一版**；`external_origins` / `external_form` 只是说明页面连了哪些站外地址，`compat_images_optimized` 只是说明平台已把哪些过重的 png / jpg 自动缩到手机够用的尺寸（长边 1600、同格式、路径不变；manifest 写 `"images": "keep"` 可保留原图），都不用改。
   - **外链可以有、不用写 target**：指向站外的 `<a href="https://…">`（参考资料 / 案例 / 报名表）平台出流时自动补 `target="_blank"`，在新窗口打开；锚点与包内相对路径照旧在页内跳。自己写了 target 的原样保留。
3. **校验失败**会返回结构化 issues（`[code] file:line message → fix`）。按 fix 修文件后重新 push（会建新版本），不要绕。
4. **push 过审即自动生效**（无需发布步骤）：客户链接自动指向新版；命中审核则客户暂看旧版、复核通过自动生效。
   已有客户链接时想先自查再切：push 加 `--hold`，确认后调 `publish_version` 切换。
5. **谁能看** —— 每份方案自带一条恒定**默认链接**（push 返回里有），**一份方案就这一条**，没有到期。
   - 默认 `private` = 仅发布者本人。给别人看：`set_visibility(proposal_id, "passcode")`（系统生成 6 位口令，告诉用户）或 `"public"`（任何人可看，需 T1）。两者都要用户确认。
   - 切回 `"private"` 即时对外失效。**URL 泄露**用 `reset_default_link` 换地址（旧 URL 当场失效、统计连续）——注意它对**所有人**生效，换完要把新 URL 重发一遍。
   - 用户想在微信里发**卡片**而不是裸链接：打开返回的 `card_url`（卡图页），长按识别图中二维码进方案页，再右上角转发。
   - **按活页夹一次发出去**：`set_visibility` / `reset_default_link` 也收 `folder_id`（与 `proposal_id` **二选一**，两个都传报错 —— 不猜哪个优先，猜错的表现是把范围不对的东西放开了）。
     一条链接放出夹内**所有非「不外发」**的方案：客户打开看到封面（`set_folder` 挂的那一份）或平台生成的目录页，点进任意一份都不用再发链接。
     ★ 从活页夹链接进来的人，看得到夹内每一份 **包括那些自己还是 private 的** —— 活页夹那条链接就是授权。夹内某一份自己的直链**不受影响**，仍由它自己的可见性管。
     ★ 跟客户放在同一个活页夹、但绝不能一起发出去的东西（内部同步 / 竞争应对）用 `set_internal` 标上：它**不出现在目录、也不被活页夹链接授权**。
     ★ 回执会**点名到份**（这条链接会露出哪几份、排除了几份）—— 读一眼再把链接发出去。
     ★ **要区分收件人就发两个活页夹**：阅读回执只说设备与页面行为，不认人。
6. **「一人一条具名专属链接」已退役**（2026-09-15）。`create_share_link` / `list_share_links` /
   `restore_share_link` / `clear_auto_passcode` 不再出现在 agent 面 —— 别去找它们，也别自己拼 REST 绕回去。
   退的理由：那套纪律的成本落在**每一次发送**上，而它买到的东西很弱 —— 按链接推断「谁在看」
   只知道**哪条链接被打开了**，不知道**是谁打开的**（平台自己的「疑似转发」提示就是在承认这件事）。
   ⇒ 阅读回执从此只说**设备与页面行为**，不声称人名 —— 这是定论，不是过渡态。
   ⚠️ **已经发出去的旧专属链接照常能打开**，而且**不受可见性影响** —— 把方案切回 private 不会让它们失效。
   ★ **泄露时能止血**（2026-09-21）：`revoke_share_link` 让**某一条**立刻失效（拿着它的人再打开就是错误页）。
   link_id 从 `list_proposals` 拿 —— 还挂着这类链接的方案会连 link_id 一起标出来。撤销不可逆，撤之前先跟用户确认是哪一条。

## 下载材料（Web / MCP / CLI 同一份配置）

- 下载的是本版本明确选择的已上传文件。正文原文件和附加材料分别选择；首版默认无下载，Markdown 源文件也不自动加入。省略 manifest.downloads 会继承当前生效版的明确选择；显式 [] 清空。旧字符串清单仍兼容。
- manifest.downloads 推荐用对象：

```json
{"schema":1,"enabled":true,"source":null,"attachments":[{"path":"handout.pdf","label":"项目介绍","description":"方案与实施安排","showFilename":false,"downloadName":"项目介绍.pdf"}]}
```

- source 为 null 表示不提供正文原文件；需要时填同样的文件对象，path 必须是本次正文入口（Markdown 指源 .md）。附件数组顺序就是展示顺序；每项 label 必填。格式和大小取实际文件，downloadName 必须保留原扩展名。showFilename 默认 false；只有明确开启才展示原文件名。旧版保存新配置前保持原有名称。
- MCP `get_downloads` 读取清单、可选文件和 revision；`set_downloads` 带 version_id、revision 和完整 config 保存。只读成员不能修改。遇到冲突先重新读取并核对，不盲目覆盖。enabled=false 同时关闭入口与下载地址，保留选项。
- CLI v3：push 可加 `--downloads downloads.json`；配置文件或 `--manifest` 文件本身不上传。读取用 `node space.mjs downloads --version <id>`；修改用 `node space.mjs downloads --version <id> --config downloads.json --revision <读到的修订号>`。所有命令沿用 SPACE_TOKEN。
- CLI push 在提交前列出材料新增、移除、同路径文件替换和缺失；缺失会阻止提交。手工 MCP 上传在 create_upload_session 后用 `preview_downloads`，把返回的 basis 交给 finalize_upload 的 download_basis。路径不会模糊重配；目录改名应明确更新清单。需要人工看完再生效时用 --hold。
- Web 在成果详情的「下载材料」里编辑，也可按版本查看与生效版的差异。当前版保存即应用；其他版本的设置随各自版本使用。单份直接下载，多份打开有名称、说明、格式和大小的清单。新配置的下载入口独立于「更多」。
- 不做服务器 HTML 转 PDF，也不打 ZIP：HTML 原文件只含入口文件；完整讲义应先本机导出 PDF 再随包上传、加入清单。渲染所需资源仍可被浏览器读取，下载设置不是防复制措施。

## 数据表（挂在方案上，不随版本；MCP / CLI / REST 同一份）

- 一份方案可以挂若干张 CSV / JSON 数据表。表挂在**方案**上、不挂版本：改数据不用重发页面，push 新版本（含 `--hold`）也不会动表。每张表有修订号 revision。
- MCP：`get_data`（不传 table 列出全部表与三条上限；传 table 返回 columns / rows / revision，format=csv 另附 CSV 文本）· `set_data`（新建 revision 传 0；修改 / 删除必须原样带回 `get_data` 给的 revision，不一致会被拒并要求先读回，不能盲目覆盖；delete=true 删表）。`list_proposals` 会标出每份方案有几张表。`set_data` 的 `append_open` 与 `list_data_rows`（读者写回的行）见下面「读者写回」。
- CLI：`node space.mjs data pull --proposal <id> --out <目录>`（每张表一个 <表名>.csv，`--json` 则 .json）· `node space.mjs data push <file.csv|file.json> --proposal <id> --table <name> [--revision <n>]`（新建缺省 0）· `node space.mjs data delete --proposal <id> --table <name> --revision <n>`。
- 表名小写字母开头、只含字母数字下划线；每格只收文字 / 数字 / 真假 / 空（嵌套先摊平）。上限：单表 512 KB、每份方案 20 张、工作区合计 64 MB，回执逐条印出。
- 读权限跟方案可见性走、不另造授权：private 只有成员与批过的设备、口令过门后可读、public 任何人可读；重置默认链接后旧链接读不到；无权限是 403 不是空表。
- **页面取数用最普通的 `fetch`**（#8654 起）：`fetch('./_data/表名.json').then(r => r.json())` 拿到行对象数组（底表 + 过审的读者写回；发布者自己看还会多出待审的行），修订号在响应头 `X-Space-Revision`；读权限就是方案可见性，无权限是 403 不是空数组。线上没有这张表时回落包里同名的 `_data/表名.json` ⇒ 本地放一份示例数据、`node space.mjs serve` 就能跑。只改表不重传页面，读者刷新即见新数据。旧的 SDK 写法 `space.data.get('表名')`（回 `{columns, rows, revision}`）继续可用，是可选的兼容层。
- **读者写回（报名 / 打卡 / 投票 / 意见收集，#7624 起）**：页面往同一张表写一行 —— 普通表单 `<form action="./_data/表名" method="post">`，或 `fetch('./_data/表名', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({列: 值})})`（契约见上面「托管契约」第 3 条；旧 SDK `space.data.append('表名', {列: 值})` 同一件事）—— **默认关、按表开**：`set_data` 传 `append_open=true`（单独传只翻开关、不动底表，仍要带 revision）。写回限频（每张表每小时 600 行、每台设备 30 行）、每行不超过 2000 字 / 单格 500 字、每表最多 5000 行；文字先过审：过审的行其他读者刷新即见，审核命中的行只有发布者看得到，审核不可用时写回被拒（页面收到「稍后再试」）而不是先收下。关着时写口回 `403`、`code` 为 `append_closed`（SDK 则 reject 同一个 `err.code`），页面要把原因给读者看、别假装「已提交」。写回的行**不在** `get_data` 的 rows 里、也不会被 `set_data` 覆盖，用 `list_data_rows` 读回（可按表 / 状态过滤，带设备、时刻、写回时的页面版本与数据修订；只有设备与时刻，不认人、别据此推断意愿）；要并进底表就读回后 `set_data` 整表替换。写回只对读得到这份内容的设备开放（private / 口令与页面同口径），重置默认链接后旧链接的写回一并失效；每次写回都进访问时间线（`get_session_timeline` 里是「写回」）。预览台、讲解人与发布者自己的会话不提交。
- **一等公民表单 / 订单块（报名 / 订货 / 预约 / 留资，#8273 起）**：页面上一行表单代码都不想写时用它（自己写普通 `<form>` 同样可以）—— `set_form` 只定义字段（最多 20 个，六种类型：text / textarea / select / multiselect / number / phone），平台在查看页里把它渲染成手机上能填、带校验的表单，提交的行落到同一张数据表、用 `list_data_rows` 读回（每行带 _form_rev；审核、限频、held 与上面那条一样）。可选 amount 只显示金额（单价 × 数量由平台算、写进每行的 _amount_cents），不收钱。幂等：再调一次就是改字段，已收的行保留；写回默认打开，传 append_open=false 关掉。页面不用重发。
- **收款指引（#8276 起，钱不经平台）**：表单带 amount、且这个工作区设过**经营收款码**（商户收款码；2022-03-01 条码支付新规起个人静态码不得用于经营性收款 —— 你可以直接用 `set_payment_qr` 传，不必让用户去管理台；撤掉用 `remove_payment_qr`）时，读者提交成功后页面在原地展开付款那一段：应付金额、用户自己的收款码、一枚「我已付款」。读者点的那一下只是**自称**，不是支付结果 —— 用户在自己的收款账单里核对到账后用 `confirm_payment` 确认，读者再打开那一页就看到「商户已确认到账」（只有他自己那一行看得到，别的访客看不到）。两个标记都**不改**这一行的审核状态，`list_data_rows` 每行会印金额与付款状态。没传收款码就只显示金额，付款那一段不出现。方案空间不代收款、不做下单与退款。**换码是全产品唯一「改错了钱会悄悄流到别处」的设置**：`set_payment_qr` 每次写都留痕、并给工作区负责人推一条微信通知，所以传之前先跟用户确认那张图确实是他本人的商户收款码，别替他找图、也别从网页上抓一张二维码。

## 3. 反馈闭环

- 「客户看了没」→ `get_engagement_summary`。**结论由你产出，平台只给事实**：返回正文末尾带一个
  **平台算好的「证据档位」**（none / thin / usable）与一段读法约束 —— 档位不是 usable 时，先说明证据不足，
  再给最多一句谨慎的观察，不要用语气补足数据。可以说看了/跳过了哪几页、在哪停最久、有没有下载/演示/回填；
  **不可以说人名**（一条链接认不出是谁开的，「3 台设备」不等于「3 个人」），也不可以说意向分、成交概率、「他很感兴趣」这类心理判断。「没有数据」≠「没兴趣」（链接可能压根没发出去）。
  结论要落到**下一步动作**，不是形容词。
- 「说了什么」→ `list_feedback`（含第几页、引用、状态）；会议/微信里的意见 → `add_external_feedback`；
  处理完 → `set_feedback_status`。
- 「客户问了一句」→ `reply_feedback`（`thread_id` 来自 `list_feedback`）：回复写在**客户正看的那一页**上，
  和发布者在管理台点「回复」是同一条路。读得到却不回，客户那边看起来就是没人理。
- 「把这份改个名」/ 页面标题换了但列表里还是旧名 → `rename_proposal`：成果名（列表、活页夹目录页显示的那个）与每一版 manifest 的 title 是两件事，
  发新版本不会改成果名。只改名字，链接、版本、可见性都不变。
- 用户贴来一个方案空间链接、说「复制到我这里」「我要改一份」「换个说法给老板」→ `copy_proposal`（`link` 原样传）：得到**他自己工作区里的一份未发布草稿**，
  与来源从此独立。`read_version` 读正文、按他的意思改好，再 `publish_file`（传副本的 `proposal_id`）发成新版本 —— 照常过审、默认仅自己可见。
  发布者没允许复制（403 `copy_not_allowed`）、链接要口令或已失效（403 / 410）时把那句理由转告用户，**别绕**（别去抓页面正文再自己发一份）。
- 用户对**方案空间本身**有意见（「这个不好用」「能不能加个…」）→ 先把他的意思写成一段草稿给他看，**他同意后**才调
  `send_feedback_to_livepage`（传 `user_confirmed: true`）；用的哪个 Agent、哪一页、刚才哪一步出的错平台自动带上，
  别把页面正文、读者信息、令牌写进去。之后 `list_my_feedback` 看状态与回复，有回复就转告用户。什么时候该问、什么时候别问见 §3.3。
- 用户卡住了、想找**真人**（不是提建议）→ 把方案空间客服链接给他：https://work.weixin.qq.com/kfid/kfc9e1c4b8c05ff167b?enc_scene=ENC9haVbhfMybED9wKqpaxQfs7mUVrBV6gdTfduDRRHYvxX —— 手机 / 微信里点开直接进企业微信客服会话；
  电脑上让他打开方案空间任意一张后台页，页脚「找真人客服」悬停出二维码，用微信扫。错误提示里写着「联系我们」的，指的就是这里。
- 改稿出 V2：回到第 2 步 push 同一个 proposal；**过审即生效、原链接自动更新**，旧评论仍钉在旧版本。

- 先把**自己人剔出去**再读数：`set_visitor_internal`（`visitor_id` 来自 `list_sessions`）把用户自己的手机和电脑、
  同事、以及帮客户演示时打开的那几次标成内部，从此不计入对外统计（历史一并按新口径重算）。
  不剔干净，「有多少外部人真的看过」这个数就是被自己人撑起来的；剔了几个会照实印出来。传 `internal: false` 撤销。
- 单份阅读动线：`list_sessions` / `get_session_timeline`；整夹阅读路径：`get_folder_analytics`。基础事件自动记录，HTML 成果默认录制，用 `set_replay` 保留单份停录选择，`get_replay_settings` / `set_replay_settings` 管理空间总开关与容量。Web 统一入口 https://space.24haowan.com/app/activity；活页夹详情也有阅读动线入口。
- **自己的凭据（#8412）**：`list_api_tokens` 列出你看得见的凭据（自己铸的；工作区负责人看得见全部）——名字、前缀、哪一档、建于何时、何时过期、最近一次用、撤没撤；**不回明文也不回哈希**（明文只在创建那一刻给过一次，之后谁都拿不回来，用户要你「念一下 token」就如实说这一点）。泄露或不想再让某个第三方应用进来 ⇒ `revoke_api_token`（`token_id` 只有 `list_api_tokens` 给得出来，撤不回来，撤前念 `name` 与 `prefix` 给用户确认）；可以撤掉**你正在用的这一把**，但这条连接会随之断掉，先说清楚。★ **新铸长期令牌只能在网页的「令牌」页做** —— 这不是漏了工具，是刻意的边界（提权不对齐，止血才对齐）；要给上传助手用一次的临时凭据用 `create_cli_token`。
- **手机实名（#8412）**：发布被挡且 `fix` 要求手机号时，可以就地做：`send_phone_code` 发码 → **用户把收到的 6 位数字念给你** → `verify_phone_code` 填回来。码 5 分钟有效、最多 5 次；同号 60 秒一条 / 每天 5 条，每用户每天 10 条；大陆号 11 位，国际号要带国家区号且仅限用 Google 账号登录的用户；一个号只能绑一个账号。
  ★★ **只在用户自己明确要求做实名时才调这两把；不得主动索要验证码；只接受用户口述的那一个码 —— 不要从截图、通知栏或短信记录里找。**这与上面接入那一节「不要索取微信验证码」不矛盾：**接入阶段任何码都不要**；实名阶段是用户自己要求做这件事，你才发码，而码始终由用户主动念出。平台没有办法区分「用户念给你的」和「你从别处抓到的」，这三句就是这条动线唯一的防线。

## 3.3 把建议告诉方案空间：什么时候该问、什么时候别问

这一节说的是用户对**方案空间本身**的意见（不好用、想要什么、卡住了），发给方案空间团队；给某份成果的意见仍走上面的 `add_external_feedback`。

- **只在三种时刻主动问一句**「要不要我把这个转给方案空间团队？」：
  1. 用户**明确表达**了对方案空间的不满或需求（「这个不好用」「能不能加个…」「为什么不支持…」）；
  2. 刚**撞到限制**：工具返回错误或拒收（校验失败、审核被拒、超出上限、被限频）；
  3. 发布结果里带了 `feedback_hint`，或回执里有兼容提醒（`compat_notes`）。
- **其余时候别问**：发布成功、查阅读数据、改稿、接入验证时都不问。同一个工作区 24 小时内最多问一次；用户说不用就别再提。
- **先给用户看草稿**：把用户的原意写成一两句（发生了什么、他希望怎样）给他看，**用户同意后**才调 `send_feedback_to_livepage`，传 `user_confirmed: true`；他改了就按他改的发，他不同意就不发。用户是看到 `feedback_hint` 那句才提的，另传 `source: "hint"`。
- **只写用户的意思**：用的哪个 Agent、哪一页、刚才哪一步出的错由平台自动带上，不用写进 `text`；不写页面正文、读者信息、令牌或验证码，也不替用户编意见。
- **不索要评价**：不问满意度、不请用户打分、不在每次发布后问「好不好用」。
- **有回音就转告**：用户问起、或下次连上时，`list_my_feedback` 看状态（new / seen / planned / shipped / wontfix）与回复；有回复或状态变了，用一句话转告用户。
- 用户要的是**真人**而不是提建议 → 给上一节那条客服链接，别调 `send_feedback_to_livepage`。

## 3.4 活页夹与工作成果

- Web 管理入口：https://space.24haowan.com/app/folders，可建活页夹、改名和导语、设置封面、管理整夹分享。成果列表与详情页支持移动、回到根目录，以及标记不随活页夹分享。
- MCP `list_folders` 返回已有链接、口令与管理入口；查看夹内成果用 `list_proposals` 传 `folder_id`，传 null 只查根目录，不传查全部，还可用 query 按标题搜索。结果包含所属活页夹、是否随夹分享、创建者。
- REST 同步提供 GET/POST `/api/folders`、GET/PATCH `/api/folders/:id`、POST `/api/folders/:id/visibility` 与 `/api/folders/:id/reset-default-link`；成果移动用 POST `/api/proposals/:id/move`（folder_id 必填，可为 null），排除分享用 POST `/api/proposals/:id/internal`（internal 必须是布尔值）。GET `/api/proposals?folder_id=root` 查根目录，其余传活页夹 ID，不传查全部。
- 移入已开放活页夹，或取消不随夹分享，会使成果的生效版本随链接开放。活页夹权限与成果直链权限独立，标记不随夹分享不会关闭已有直链。

## 3.5 请同事进来（多人共用一个工作区）

成员是**工作区级**的：加进来的人共享整个方案空间（方案 / 专属链接 / 反馈），而且他用**自己的 Agent** 连同一个空间 —— 不是登录你的账号。

1. `list_members` —— 先看这个工作区有谁、各是什么角色。
2. `invite_teammate` —— 出一条邀请链接（`role` 只能 editor / viewer，**不能邀请成 owner**）。返回里带 `share_text`，**可以整段转发给同事**，里面写好了他该做什么。
3. `add_member` —— 只在你已经知道对方 26 位 `user_id` 时用；不知道就用 `invite_teammate`。
4. `set_member_role` / `remove_member` —— 改角色、移出工作区（都要 owner）。**创建者动不得**（那是「工作区永远至少有一个 owner」的保证），**自己的角色也改不了**（降级是单向门）。
5. `revoke_invite` —— 邀请链接转发错了地方时让它立刻失效。`invite_id` 从 `list_members` 拿（那里列着所有未到期的邀请）。已经用它进来的人不会被踢，那是 `remove_member` 的事。

★ 三档角色：`owner` 管方案与成员 · `editor` 建方案 / 发版 / 发链接 · `viewer` 只读。
★ **别替用户决定给谁什么角色** —— 问一句。发出去的邀请链接收不回来，但可以用 `revoke_invite` 作废。
★ 移除一个人**当场切断他的令牌** —— 每次请求都重新核一遍他还在不在，不需要另外去撤令牌。

## 3.6 发布后推到微信

工作区里的人（创建者 + 成员）可以在**微信**里收到「有新版本生效」，每人一条自己的专属链接。**不发给客户** —— 客户要看请把链接发给他。

- `set_publish_notify` —— 三个开关（owner）：自动推 / 也发给触发发布的人本人 / 发布七天没人打开时提醒作者。**只传要改的那个**，其余保持现状。
- `push_to_wechat` —— 手动把某一份的当前生效版推一次（editor 即可），无视自动开关。回执分开报未关注 / 拒收 / 没绑微信 —— 那不是报错，是「他收不到」。
★ 没关注服务号的人收不到。开关开着不等于推得到，看回执。
★ 用户说「把我的名字改成 X」→ `set_nickname`（改的是**他自己的**显示名，最多 24 个字；没有指向别人的入参，改不了其他成员）。

## 4. 判据

发布成功 = 用户手机微信里打开链接能看到这份成果，且几分钟后 `get_engagement_summary` 能看到这次访问。

有问题：管理台 https://space.24haowan.com/app · 隐私与条款见站内页脚。

## 画面录制与容量

HTML 成果默认录制后续访客访问。`set_replay` 可以按用户意愿单独停录，发版、移夹和重置链接均保留停录设置。`get_replay_settings` 查看空间总开关与用量；仅负责人可通过 `set_replay_settings` 更改总开关。管理页 `/app/recording`；REST GET / POST `/api/workspace/replay`，POST `{ "enabled": false }` 关闭整个空间。

每空间独立回放容量 1 GB、每次最多 8 MB、画面保留 30 天；超过容量停止采集，进入后台暂停、返回后继续。输入遮罩，敏感正文加 `data-space-private` 或 `rr-block`；不录 Canvas / WebGL。`list_sessions` 与 `get_session_timeline` 的 `replayStopReason` / `replayHadPause` 说明未完整采集的原因。阅读动线、PDF/PPTX 逐页回放不受录制开关影响。


## 发布后把实际效果给用户看

版本过审后调用 preview_version，把返回的 interactive_url 交给发起用户：免登录、60 分钟内有效、放的是真实 SPACE 浏览页，可在电脑（1440×900）、手机（390×844）、微信内（390×763）、小屏（375×667）之间切换，也能把电脑和手机并排对比。它不替换公开版本、不提交反馈、不改访问权限、不记阅读统计，也不连接正式讲稿遥控房间。

本服务器未开启截图：screenshot_status 为 disabled、captures 为空，不要描述没收到的图片，也不要把它说成「截图已检查」。默认 audience=visitor 不含讲稿；查看私人讲稿时显式选择 publisher，不要把该预览发给客户。真实手机、微信与跨设备遥控仍需真机验证。