🧰 Tools —— 聊天里比贴纸更大的「互动小工具」
Jeff 2026-08-28 22:26 语音派题(原话见①)。这份是系统设计 + as-built: 凡是「我们现在有什么」的断言都带 file:line 或一条真跑过的命令;没验过的一律写没验过,不编。
已上生产标 已落地 · 没开工标 待做 · 要你拍板标 待拍板 · 被挡住标 卡住。
Tora2026-08-29代码基线 origin/master 5788e95a迁移 000310App v1.442

一句话

App 里只做「一个会播的壳」,画面和规则全部由服务器下发。 以后新增一个 Tool = 往一张表插一行 + 传一份网页,不用重新出 App 包。

这句不是我定的,是他自己在语音里定的 —— 我只是照着做:
「所以你讲的 App 只是播放能力,而不是这些画面,对不对?所以它并不冲突,是不是。」

他的原话(2026-08-28 22:26 语音,本地 Whisper 转的)

「我记得不是哦,不能动到那个 App。我的想法是,本来是想做一个 Tools,它比贴图还要大一点… 这个 Tools 也是很多人做…它可以夹夹进去,还有各种各样的功能呢。 比如它做一个 paper scissor rock,然后你可以点,我可以点,它在屏幕上来玩各种各样的这种小 Tools。 像是这个小 Tools 最好是做到用我们做 App 的延伸,比如你这样它泼墨的效果啦,我可能泼水啊,我可能泼红色的墨啊,各种各样。 所以你讲的 App 只是播放能力,而不是这些画面,对不对?所以它并不冲突,是不是。」

之前那两条把方向定死的话也在同一段对话里:

  • 22:23:05 —— 「我是要以后可以直接加载,不用去 build 呀… 不是更好?」
  • 22:35:04 —— 「@Gode 你别动代码,但详细告诉 Tora 怎么写」;Gode 随后给了字段清单,这份图纸照着落的。
「Tools」这个词是问出来的,不是猜的。 语音转出来是 TOOS, 可能是 Tools(工具) 也可能是 Toss(往屏幕上丢) —— 这两个做出来是两样东西。 问了一句,他回「tools」。猜错的代价是整套白做,问一句是 30 秒。

三层模型

整件事只有三层,每一层只知道自己那一份。这是为了让「加一个新玩法」不需要动前两层。

┌──────────────────────────────────────────────────────────┐ │ ① 目录 chat_tools 表(后端) │ │ 有哪些 Tool · 去哪儿取 · 多大 · 播多久 · 要不要同步 │ │ 加一个玩法 = 插一行 │ └──────────────────────────────────────────────────────────┘ ↓ GET /v1/tools ┌──────────────────────────────────────────────────────────┐ │ ② 容器 chat_tool_layer.dart(App) │ │ 打开 · 盖住 · 收掉 · 把两边的动作接起来 │ │ 这一层里一行都不是关于猜拳或小猫的 │ └──────────────────────────────────────────────────────────┘ ↓ 加载 entry_url ┌──────────────────────────────────────────────────────────┐ │ ③ Tool 本体 一份静态网页(服务器上) │ │ 画面 + 规则 + 素材(WebP / 音效 / 逻辑) │ │ 改它不用碰 ① ②,更不用出 App 包 │ └──────────────────────────────────────────────────────────┘

互动怎么走

猜拳这种「你点一下、我点一下」的,事件不直连,一律经后端转:

我的页面 ──hoopTool.send()──▶ App 容器 ──WS tool.event──▶ 后端 │ ① 这个 tool 是不是 live? │ ② 我是不是这间会话的成员? │ 判据 ③ 序号往前走了吗?(挡重放) │ ④ 这一分钟是不是点太多了? │ ▼ 对方的页面 ◀──hoopTool._recv()── 对方 App ◀──WS tool.event── 广播给这间房的人
为什么不点对点直连: 直连就没人验「这条是不是他真发的」。 经后端转,「谁出的拳」由后端填 —— 客户端报的那个字段后端根本不收。

分工:判据在一边,转发在另一边

问题谁答得了住在哪
这个 Tool 是不是 live?序号重放了没?这一分钟几条了?只有 Tools 这个域知道domain/chattool
这条要转给谁?他是这间会话的人吗?只有消息/会话域知道domain/message

两边各答自己知道的那一半,谁都不用抄对方的表(铁律 23)。 合成一处的话,总有一边要复制另一边的数据,而复制出来的那份一定会腐。

已经做好的 已落地

两刀:e00bd1fb(后端)· 5788e95a(App,v1.442)。

后端 —— 已上生产

东西在哪证据
目录表 chat_toolsmigrations/000310_chat_tools.up.sql 生产库 select version,dirty → 310 | f;18 个列都在
GET /v1/toolsdomain/chattool/handler.go:21 打真接口 HTTP 200 {"tools":[]}
读目录domain/chattool/chattool.go:109 ListLive只返回 status='live'
转发判据chattool.go:203 AllowEvent · :163 ValidateShape · :229 allowRate · :258 bumpSeqGo 单测 + 3 把坏刀
WS 那条路domain/message/handler.go:1087 case "tool.event" 生产二进制里 tool.event 命中 2 处
转给谁domain/message/tool_relay.go:34 RelayToolEvent—
接线server/router.go:111-112 · :485-491—

App —— 写好了,但真机上还看不到

东西在哪
容器app/lib/widgets/chat_tool_layer.dart:43 ChatToolLayer · :231 showChatTool
同源闸(纯函数,能穷举测)chat_tool_layer.dart:31 toolUrlAllowed
目录模型app/lib/models.dart:2196 ChatTool
取目录app/lib/api/api_client.dart:916 chatTools()
⚠️ 还没接进聊天页 —— chat_screen.dart 一个字没碰, 所以这一版装到手机上也看不到入口。而且整包没编过(出这份图纸的机器编不了 iOS)。

验过什么、没验过什么

验过(能复跑): 后端 go build ./... / go vet ./... 全绿; Go 侧 8 组用例、App 侧 11 条用例全绿;flutter analyze 0 issue; 9 把「故意弄坏」的刀,每把都红在该红的那一条(见⑤)。
没验过(照实说): ① 真机上网页到底加载得出来吗 —— 平台视图在测试里起不来,这要真机; ② 两台手机对着玩一局猜拳 —— 没做过; ③ 那几句建表 SQL 推之前在任何库上都没跑过(这台机器没有 Postgres)。 生产上跑通了,但那是赌对了,不是验过了 —— 所以事先把列名 trigger 改成了 trigger_mode(TRIGGER 是 PostgreSQL 的关键字)。

怎么写一个 Tool

写一份单文件网页就够了。它在容器里跑,能用的东西只有下面这三句。

目录里那一行长这样

字段意思写错了会怎样
id短名,只收英数和 - _带冒号/斜杠会被拒(它要进 Redis 键名)
version换了内容就 +1不加 = 用户手机上还是旧的那份
entry_url网页地址。同时就是白名单必须 https
width/height0 = 铺满聊天画面;>0 = 一块浮层(dp)—
duration_ms>0 = 到点自动收;0 = 用户自己关铺满的那种不给上限 = 挡住对方的聊天
trigger_modesend 发出去双方就播 / tap 点了才开 / manual 只从「+」开认不得的值客户端一律当 tap(最保守那个)
interactive要不要双方同步false 的后端一条事件都不转
perms能力白名单,默认空相机/通讯录不在可选项里,不是"默认关"
statusdraft / live / off只有 live 进目录、只有 live 转事件

页面里能用的三句(经 /tools/_kit.js,2026-08-29 命名审读后的定稿)

<script src="/tools/_kit.js"></script> // 和工具页同源,闸①拦不着 // ① 我做了个动作 → 转给对方(payload 传对象,kit 负责序列化;≤4 KB) hoopTool.send('throw', {m: 'rock'}); // ② 对方的动作进来 hoopTool.onEvent(function (e) { // e = {tool_id, instance_id, sender_id, action, sequence, payload} // sender_id 是**后端填的**,可以信 }); // ③ 我玩完了,自己收掉 hoopTool.close();
就这三句,没有第四句。 一个 Tool 拿不到 HOOP 的令牌、拿不到真实用户 id、 碰不到相机通讯录、跳不出自己那个网址。能力少 = 以后能放心让外面的人写。
为什么隔一层 kit(它就 40 行):以前页面直接喊 window.flutter_inappwebview.callHandler —— 框架名漏进每份工具页,容器哪天换实现,所有页一起崩。现在框架名只活在 kit 一份文件里。 _recv 是容器递事件的内部口,工具页别碰;正门是 onEvent。
instance_id 由开局方铸(UUID) —— 它是「这一局」,不是「这个 Tool」。

上架一个 Tool 的三步

  1. 把网页传到 Tools 专属静态站:hoop-games 仓 tools/ → ./tools/deploy_tools.sh → hoop-tools.pages.dev(08-29 从游戏部署分家 —— 游戏的版本化铸号要令牌,Tools 没有版本化,不该被那把钥匙挡路;脚本自带逐网址核 sha)
  2. 往 chat_tools 插一行,status='draft' 先自己试
  3. 改成 status='live' —— 用户下次打开聊天就有了,不用出包

五道闸

每一道后面那句「不装会怎样」才是它存在的理由。 五道全都有「故意弄坏 → 确认它真报红」的记录。

先看一眼:谁守哪道(2026-08-29 补,Lisa 问「怎么一会五道一会四道」问出来的)

闸谁守住在哪
① 同源客户端chat_tool_layer.dart 的 toolUrlAllowed(纯函数,穷举测)
② 权限拒客户端onPermissionRequest 一律 DENY
③ 到点收客户端duration_ms 定时器(收自己那条 route,不是 maybePop —— 坏刀 11)
④ 一局一次两边客户端 _openInstances 去重 + 后端序号线
⑤ 后端四条后端chattool/gate.go(sender 后端填 · 序号前进 · 限流 · live 才转)
⑤′ 收侧会话校验客户端收事件 instance 和会话都要对上(坏刀 10 —— 08-29 审读补的洞)
①′ 子资源客户端fetch / XHR 跨源一律 ABORT(09-02 审读补:光拦导航拦不住页面偷偷往外 POST)。管不到 <img>/<script src>/WebSocket —— WebView 不给钩子,真要堵死要走原生拦截,先如实写在这
⑤″ 发卡限流后端AllowCard 复用事件的 allowRate,每 Tool 每人每分钟(09-02 补:send 类自动盖屏,不限流=按住小猫泼到天荒地老),超了回 429
容器注释里说的「四件事」= 客户端那份(①~④);第⑤条本来就叫「后端那四条」。分工不是矛盾 —— 但这张表以前没画出来,才让人起疑。

① 只准去它自己那个源

白名单不是另一张表 —— 就是这一行的 entry_url 自己的源。 协议 + 主机 + 端口三样都一样才放行;子域名不算。 http:// · javascript: · file:// · data: · about: 全拦。

不装会怎样: 页面一句 location.href 就能把整块画面换成别人的站, 而在手机上看起来完全正常(它照样是一个网页窗)。
为什么白名单不另立一张表: 那张表得靠人记得和 entry_url 同步, 漏了的表现是这个 Tool 白屏,而且零报错。同一件事只能有一个出处(铁律 23)。

② 权限一律拒

相机 / 麦克风 / 定位一律 DENY,不许自己开新窗口,读不了本地文件。

写成"拒"而不是"不配置": 不配置时各平台的默认值不一样,而"默认"会随版本变。

③ 到点自己收

不装会怎样: 一个铺满屏幕的 Tool 能把对方的聊天挡到天荒地老。

④ 同一局只开一次

「这一局」用 instance_id 认,不是用 Tool 的编号认。

不装会怎样: 网络重发那条广播会把刚开的又开一遍,表现是玩到一半整个重来,零报错。
而 instance_id 认错了同样出事: 用 Tool 编号的话,同一间房先后开两局, 第二局的第 1 手会被当成第一局的重放丢掉。

⑤ 后端那四条

闸不装会怎样
「谁发的」由后端填谁都能冒充对方出拳,两边界面都不报错
序号必须往前走(每人每局各一条线)抓一条「我出石头」重发十遍,对面收到十次
合成一条线的话:你出拳会被判成我的重放,对面永远等不到
必须是这间会话的成员拿到别人的会话号就能往里灌事件(路人替你出拳)
不互动的 Tool 一条都不转「转了也没人用」的路,就是没人盯着的滥用面

坏刀记录(15 把,每把红在该红的那一条)

第 10、11 把(2026-08-29 全文件审读挖出,先红后修): 10 收事件只认 instance 不认会话 —— 同 App 别的群里的人拿到 instance_id 就能替这局的对手出拳(修:会话也要对上); 11 到点自动收用的 maybePop —— Tool 上面压着别的弹层时误杀那层、自己留下继续挡聊天(修:收自己那条 route)。 两把都是先写刀、看它红、再修到绿;App 侧测试 11 → 13 条。
第 12~15 把(2026-09-02 二次审读,Lisa「细节是不是做的不好」): 12 同一局重开手数从头数 → 后端判重放静默丢(修:按 instance 记在类外 + 时间戳兜底); 13 发卡第 11 张不被拒(修:AllowCard 接 allowRate,429); 14 停在第 3 页时工具变少,页码越界炸屏(修:页码回夹。当时的容器是 IndexedStack,2026-09-11 换成跟手滑动的 Stack 后回夹照旧要做 —— 越界的是页码,不是容器); 15 打点 conversation_id 畸形串(LooksUUID,不像的存 NULL)。另有一处没法用刀量的:闸① 补 fetch/XHR 拦截。
把哪儿弄坏哪条守卫报红
拆掉重放检查Replay
序号键里不带发件人SeqIsPerSender
放行不互动的 ToolRejections/不互动
不查会话成员RelayToolEvent_Guards
sender_id 改成信客户端报的SenderIsFilledByServer
同源改成「结尾对得上就行」别人家的一律拦 + 子域不算(两条)
拿掉自动收到点自己收掉
拿掉「同一局只开一次」同一局只开一次
触发方式默认改成 send认不得当 tap
还有一把是砍扫描器自己的:喂它一段故意不合格的样本,确认它真分得出来。 不然正则写错时「零命中」会冒充「全都合格」—— 那是最像绿灯的假绿灯。

两个 Tool 待做

Gode 定的验收判据是:同一个容器要能跑出两种完全不同的玩法 —— 一个要互动、一个不要;一个是浮层、一个铺满;一个没有时限、一个到点就收。 挑这两个不是随便挑的,它们把容器的每一档都撑到了两头。

✊ 猜拳🐱 小猫泼墨
互动要(两边各出一手)不要(发出去就播)
尺寸浮层(约 300×380)铺满聊天画面
时长0 —— 打完自己关3000 ms 到点收
触发tap(收到一张卡,点了才开)send(发出去双方就播)
它验证了什么事件转发那条路真的通播放能力真的能盖满、能自己退场

猜拳的一局怎么走

A 点开 → 出「石头」 → send('throw', {m:'rock'}) seq=1 B 收到 _recv(...) → 出「布」 → send('throw', {m:'paper'}) seq=1 两边各自算胜负(同样的输入 → 同样的结果,不用再问服务器)
胜负不放服务器算。 两边收到的是同一组动作,算出来必然一样; 放服务器算就等于给一个纯展示的玩法加一条后端接口 —— 那条接口以后每加一个玩法都要改。
✊ 猜拳页 已写(08-29):规格照上表,协议照下图; 双人测试台(tools/rps/_test_pair.html,两个 iframe 装真页+模拟后端语义)全场景驱动过: 胜负两边镜像 · 平局 · 网络重发丢弃 · 路人闯入零接收(对手锁定)· 三轮比分对齐 · 暗色主题截图看过。 页面自带一道图纸没写但该有的保守:没都出齐前,对面只显示「出了没」(✅),不露是哪只手。 🐱 泼墨页也已写(08-29 同晚):铺满透明盖在聊天上演 3 秒,墨团二次曲线(直线是碎玻璃,曲线才是湿墨), 一场一种墨色随机;假聊天验证台截图核过、close 后备验过、外部请求 0。 三页已上线(08-29 深夜,Tools 专属项目 hoop-tools.pages.dev,逐网址核过 sha): /tools/calc/ · /tools/rps/ · /tools/inkcat/。 目录 draft 三行已在生产库(08-30 部署,迁移 310→314 全上、验收全绿)。真机对玩 = P1 刀 6,只等 TF;验完把三行 draft 翻 live 即全量可见。 ⚠️ 顺带一条给刀 6:铺满型的「点空白收掉」在容器里点不到(WebView 盖在手势层上面)—— 3 秒自收的泼墨无所谓,但真机验收时别拿这条当用例。

还没做的

  1. 待做 接进聊天页 —— 容器做好了,但聊天里还没有开它的入口 (从「+」里选一个 Tool 发出去 / 收到那张卡点开)。
  2. 待做 猜拳和小猫泼墨两个网页。
  3. 卡住 把网页传上线 —— 这台机器没有 Cloudflare 的钥匙。 可执行的解法:在终端跑一次 npx wrangler login,或者给一个 Pages:Edit 的 API token。
  4. 待做 两台真手机对着玩一局 —— 这是唯一能证明"真的通了"的验收, 而它得等下一班 TestFlight。

待 Jeff 拍板 待拍板

  1. Tool 从哪儿进? 现在的设想是聊天「+」面板里一格。 但那个面板已经有 9 项了(相册/相机/文件/名片/群/投票/接龙/怪兽/歌)。 是再加一格,还是和贴纸面板并成一排(它本来就"比贴纸大一点")?
  2. 群里能不能用? 现在的转发是发给这间会话的所有人,一对一和群都能跑。 但猜拳是两个人的事 —— 20 个人的群里三个人同时开三局猜拳,界面上怎么摆? 最省事的一刀:互动型的 Tool 只在一对一开放,群里只给不互动的(泼墨那种)。要不要这么切?
  3. 谁能上架 Tool? 现在只有改数据库这一条路(等于只有我们自己)。 这个问题 08-29 和 Lisa 聊出了一份完整提案(行业尸检 + 三级火箭),全文在 ⑧ —— 拍板请拍那一节,这里不抄第二份。
  4. 泼墨那种能不能"强制"盖住对方的屏幕? 现在的设计是能(trigger_mode=send 发出去双方就播)。 这会被拿来骚扰 —— 现有的挡法只有「每分钟几条」。 要不要加一条「对方可以关掉这类特效」的开关?

相关

  • 这套东西的近亲是小游戏平台(docs/GAME_PLATFORM_SPEC.md)和 小程序平台(docs/MINIAPP_PLATFORM.md)—— 都是「服务器下发一份网页」。 故意没有并进它们:那两个都是整屏的、有排行榜/收入/审核队列, 而 Tool 是聊天里的一小片、活几秒钟。并进去的代价是每一条现有查询都得加一句"排除 Tool",漏一处就会有一个猜拳出现在游戏中心里。
  • 全屏特效的老做法是 怪兽(app/lib/widgets/chat_monster.dart)—— 那是 Flutter 手画的,加一个新的就要出一次包。Tools 就是来替掉这条路的。

谁来造 —— 行业尸检 + 三级火箭 待拍板

起因是 Lisa 2026-08-29 文字聊天:「在聊天室里留个空间给用户,让他们可以 build 或拥有一些很特别的 tools…… 我们是 lego,我的接龙不好,他们可以删了,然后加载更好的接龙。」
这一节回答待拍板第 3 条「谁能上架」。先看别人怎么死的,再说我们怎么走 —— 下面的行业事实都是 08-29 现查的,带出处。

同一个想法,四家巨头做过

谁哪年做的是什么下场
Apple · iMessage App Store2016聊天里的小 app / 小游戏抽屉 —— 和我们的 Tools 几乎同一个想法 flop。苹果自己弃养,几年没大更新,沦为贴纸抽屉
Snapchat · Minis2020聊天里的 HTML5 小工具(一起冥想、一起订票)—— 连技术选型都和我们一样 2022-08 随裁员 20% 一起砍(SEC 文件原话:对增长和收入没贡献),2023-02 正式下线
Facebook · Messenger 小游戏2016聊天里直接开小游戏悄悄撤出聊天,挪去独立的 Gaming 标签页
Slack · 互动消息至今消息里的按钮/表单/审批(纯 JSON 原生渲染)唯一活着的 —— 但只活在企业场景
三条共同死因(要背下来的是这个,不是名单): ① 造的成本 > 用的价值 —— 「群里报名器」对用户很有用,但对职业开发者不值得开工:写两周代码,服务一个群的饭局。长尾撑不起专业开发,货架永远半空。 ② 平台自己不信 —— 它不直接产收入,永远是紧日子里第一个被砍的(Snap 原话就是证据)。 ③ 发现和进入太绕 —— iMessage 要去 App Store 装,藏在抽屉里没人翻。
注意:没有一条死因是「用户不想要」。反证:iMessage 那片废墟里唯一长盛不衰的是 GamePigeon —— 聊天里跟朋友玩台球猜拳。需求一直在,断的是供给。

三条路,只有一条没人走过

谁来造案例死穴
平台自己造微信原生功能;我们的猜拳质量高、数量死 —— 平台想象不出也养不起长尾:我们造 20 个,用户要 2 万种
开放给职业开发者iMessage / Snap / Telegram bot经济账不成立(死因①),消费级聊天里全灭
开放给用户,AI 代工没人做过待验证 —— 但上面两个死穴它天生没有
第三条路妙在哪,一句话:需求方和供给方合成了一个人。 想要「我们球队专用报名器」的人自己说一句话就拥有它 —— 不需要说服任何开发者「这值得做」, 因为 AI 把造的边际成本压到几十秒。前两条路死在「想要的人不会造,会造的人看不上」,AI 把这个错位直接抹掉。 这也是为什么这个想法 2016 / 2020 年不成立、2026 年成立:变的不是需求,是造的成本。

提案:三级火箭 待拍板

级谁造干什么依赖
一级 · 点火(现在)我们自己头 20 个 Tool(猜拳/泼墨已在走)。目的不是数量,是三件事:货架不空着开张 · 五道闸先在自己人身上磨利(坏刀已砍 9 把) · 给后来者立质量标杆无,已在跑
二级 · 主引擎用户说,AI 造用户在聊天里描述 → AI 工人造出网页 → 平台托管 → 五道闸圈住 → 上目录。这是差异化赌注所在游戏线的工人池 + 三闸代推纹路照抄(docs/GAME_WORKERS.md)
三级 · 顺风职业开发者门开着,想来就来 —— 但不指望:历史已证明这条道养不活消费级小工具开发者平台纹路现成(docs/DEVELOPER_PLATFORM.md)
警句:别把顺风当引擎。「开放 API 给程序员」是苹果和 Snap 走过的死路 —— 它是配套,不是主线。主线始终是 AI 把造东西的门槛砍到零。

乐高桶对目录的两条硬要求(随二级一起拍)

  1. 同类多款并存,靠使用量排序。不是一个品类一个坑 —— 三个作者的三款接龙可以同时在架上, 差的被好的顶下去。原生功能也不豁免:原生接龙做得不好,就该被 Tool 接龙抢走用户。
  2. 房间级装删。每个群自己管「装了哪些 Tool」,可装、可删、可换 —— Telegram「把 bot 加进群」的模型。 现在的图纸还没有这一层,这是真正的增量(目前目录是全局一张表)。

连带一条:闸要分两种看待(影响⑤的措辞,拍了板再改那页)

安全闸(①只准自己的源 · ②权限拒 · ⑤后端四条)是硬的,谁来都不松 —— 这是 Discord 和微信用血换来的共识。
体验闸(③到点自己收 · ④一局一次)是参数,按 Tool 类型调。 ③的本意是「别把聊天挡到天荒地老」,不是「禁止活得久」—— 一个缩成角落药丸的音乐小播放器(播 HOOP 生态的歌,源是自己的)不违反本意。 判据:安全闸看「代码能碰到什么」,体验闸看「用户被挡住了什么」 —— 前者不谈判,后者谈参数。

边界:Bot 是另半边,别揉进来

Tool 是手,bot 是嘴。Tool = 物件(界面、一局、被动等人开);bot = 假人成员(身份、对话、会主动开口)。 两者会在边上碰头(bot 弹出窗、Tool 推条消息),但里子是两套 —— 嘴那半边的图纸在 docs/AI_BOTS.md, 两份图纸、两个入口、两套审核,不合并。
Snap 死因②对我们的警告:Tools 要尽早接进绿能量(打赏/分成纹路音乐线现成), 否则它在公司账上永远是成本项 —— 哪天紧日子一来,第一个被砍的就是它。Snap 的 SEC 文件就是前车之鉴。

行业事实来源(08-29 查):techstory.in / screenrant.com(Snap Minis 之死) · medium.com @aleksanders「The state of the iMessage App Store」 · macworld.com(iMessage 反思) · docs.discord.com(Activities 沙盒) · developers.weixin.qq.com(小程序双线程) · docs.slack.dev(Block Kit)

产品蓝图 —— 把「一个功能」做成「一个平台」缺哪几块 待拍板

Lisa 2026-08-29:「从产品平台角度,分析我们如何做完整这个。我们要有设计蓝图,知道我们需要做什么,清楚知道我们的产品。」
这一节是全景图:产品是什么 · 平台由哪十块组成 · 每块现状(带出处)· 分几期 · 怎么算成了。 机制细节在①~⑤,谁来造在⑧ —— 这里不重抄,只指路(铁律 23)。

产品一句话

HOOP Tools = 聊天里的道具层:让每个群拥有自己的小工具。
和另外两根柱子的关系 —— 小游戏是整屏的娱乐(分钟~小时),小程序是整屏的服务(常驻), 小工具是聊天里的一片(秒~分钟,一局有「完」的时刻)。三者共用「服务器下发网页」的地基,各有目录、互不混。
差异化赌注(⑧的结论):造工具不需要会编程 —— 用户说一句话,AI 造,平台托管,群里可装可删可换(乐高桶)。

四个角色,四条旅程

角色旅程现状
用的人(群成员)聊天里点开一张 Tool 卡 → 玩一局 → 到点自己收容器已写好,等 TF;入口在哪待拍板(⑦-1)
管的人(群主/成员)给自己的群装 / 删 / 换 Tool —— 乐高桶整层缺失(⑧-乐高桶第 2 条)
造的人(任何用户)说一句「我要一个 XX」→ AI 造 → 先只在自己群用 → 好用了上公共目录缺失,纹路现成(⑧-三级火箭二级)
平台(我们)守闸 · 排序 · 下架 · 分账运行时闸已落地;审核/排序/经济缺失(下表)

平台十块 —— 每块现状与缺口(这就是「我们需要做什么」)

#部件现状缺什么借谁的纹路
1运行时(窗 + 五道闸)已落地后端上生产;App 写好等 TF(③)两台真机验一局(⑦-4)——
2首批内容待做猜拳/泼墨两页(⑥)头 20 个 Tool(⑧-一级点火);候选清单见下——
3入口待拍板(⑦-1)「+」面板一格 or 并进贴纸排——
4目录(App 里能逛)只有后端一张表,App 里没有货架可浏览目录 + 同类多款、使用量排序(⑧-乐高桶 1)游戏中心 / Apps 目录页
5房间级装删整层缺失每群一份「装了什么」+ 装/删/换交互(⑧-乐高桶 2)Telegram「加 bot 进群」模型
6AI 造的管线缺失说→造→沙盒验→先私有后公开工人池 + 三闸代推(GAME_WORKERS.md)
7审核与下架运行时闸有,上架前的闸没有AI/用户造的过什么检查才上目录 · 举报 · 一键下架(kill switch)游戏线 CI 三闸 · 音乐线四种下架
8数据层一局之内:Redis 序号 30 分钟 TTL,已建成不动(chattool.go seqTTL) 跨局:chat_tool_kv 一张表,键 (tool_id, conversation_id)(房间的数据,全群同一份 —— 这正是它和 game_saves 个人存档的差别), 单格 JSONB ≤16KB + expires_at(活跃期 Tool 自己声明,平台上限 30 天)。 记录不陪葬(Lisa 08-29 抓出的窟窿):工作台可以到期,但到期前必须先把最终结果定格成一条消息发进聊天流 —— 记录从此跟着聊天历史走(普通房永存、幽灵房随房毁,自动继承房间的生死规则),然后才许物理删工作台。 铁顺序:没定格的不许删。微信接龙就是这个模型:接龙结束,那条消息就是永久记录。 页面 API 就两句 hoopTool.kv.get/set,写入走现有转发管道(复用闸④⑤,不开新口) game_saves(16KB 限)· miniapp_kv(命名)· 幽灵房(TTL 毁灭)
9经济缺失绿能量打赏 → 造者分成;Snap 死因②的疫苗(⑧末警告)音乐线两层分成 · 绿能量 as-built
10度量缺失局数/完成率打点 —— 没有它,目录排序(4)和「算不算成」都是瞎的游戏线打点
读法:1~3 是「能玩」,4~5 是「乐高桶」,6~7 是「人人能造」,8 随 6 出,9~10 是「养得活」。 第 10 要提前:打点必须从第一局就有 —— 排序靠它,验收靠它,补是补不回来的。

分期与刀数 —— 每刀独立可验,顺序即依赖(Lisa 08-29:「每一个 P 都要详细写刀数」)

P1 能玩 —— 7 刀

刀干什么状态
1「+」入口:Lisa 08-30 拍板「放聊天+里,做翻页」。面板改分页器(第0页老九格逐像素不变,Tools 每页12格横滑+圆点;2026-09-11 起横滑是跟手的 —— Mike「不要给 instant animation」,位置从整数下标改成连续量,手指走多少页面走多少,松手用弹簧归位,过半即翻不必甩);点开=铸 instance 开容器。1b 也已落(同日):'tool' 进类型名单(000314)+ 发卡端点 + ChatToolCard 气泡(名字图标查目录不进 body)+ 点卡进同一局 + 泼墨照怪兽先例在 message.new 自动播。三种触发方式全通已做 08-30,等 TF + 迁移上云
2计算器页:第一个 Tool,单文件 6.5KB,manual 触发、interactive=false、零外源已做且已上线 hoop-tools.pages.dev/tools/calc/;目录 draft 行 = 迁移 000313
3猜拳页:第一个互动 Tool,吃转发管道(闸④⑤的第一个真用户)已做 08-29,双人测试台全场景验过(⑥)
4泼墨页:第一个 send 触发 Tool,duration 上限必设(闸③)已做 08-29,透明验证台核过(⑥)
5打点:open/close 进 chat_tool_events(迁移 000312,POST /v1/tools/track;容器开局/关局自动发射,fire-and-forget)。round_done 故意不进表 —— 它 = P3 定格卡消息,再记一份是手抄镜像(铁律 23)已做 08-29;迁移待上云、App 半等 TF
6三个 Tool draft→live,两台真机对玩一局(总验收刀)待做,等 TF
7TF 出包(NOVA 捎班,铁律 17)待做
P1 验收:两台真手机在聊天里玩完一局猜拳,后台看得到这一局的打点。

P2 乐高桶 —— 6 刀

刀干什么
1conversation_tools 表(房间装了什么)+ 迁移 000328 已做 09-02。主键 (群,工具);工具下架不删装配记录(下架可逆,装配是群主的选择)
2装 / 删 / 列三个端点 已做 09-02:GET/PUT/DELETE /v1/conversations/{id}/tools[/{tool}]。看=成员;装删=CanManageTools(群主/管理员;私聊两人皆可;9 格穷举测)。装删都幂等
3工具商店 已做 09-02(v1.676):按类分组、组内按后端用量序、每行「本周 N 局」;入口=「+」货架末尾 Add 格(零工具也有 Tools 页)
4群装删 已做 09-02(v1.676):群信息页「Tools」一行进同一个商店页,装/拆按钮当场翻;403 一句话说清。货架读会话的,后端没记录=默认集(全部 live),第一次装拆先落成显式记录 —— 拆一个不会连坐其它
5使用量排序 已做 09-02:读时聚合(最近 7 天 open 数,rank.go),不建榜表不起定时任务 —— 榜表就是一份要人记得刷新的手抄镜像(铁律 23)。目录 JSON 多一列 opens_7d
6category 已做 09-02(000329):game / utility / effect / social,目录属性不是人手标签;商店按它分组,组内按用量 —— 同类多款并存、差的被顶下去
P2 验收:一个群主不问我们任何人,自己给群装上一个 Tool、又删掉一个 Tool。代码 09-02 全落(后端在生产,App 等 TF);真机走一遍就算数。

P3 人人能造 —— 7 刀

刀干什么
1chat_tool_kv 表 + 定格卡铁顺序 后端已上生产 09-02(000331;到期清理那半留着,清之前先定格)
2SDK v2 已上线:kv/shared/collection/finish/players 全包;合同和范例在 /tools-sdk。容器 v2 已做 09-02(v1.686):注入 ctx、影子身份(SHA-256(局:id) 前 16 位,页面永远见不到真 id)、kv/finish 桥;等 TF。09-11(v2.037)加注入 CSS 变量 --hoop-safe-top = 真实状态栏高度 —— 工具的 WebView 是整屏铺开的,而这个容器里 WKWebView 报的 env(safe-area-inset-top) 是 0,页面自己问不到;写 var(--hoop-safe-top, 自己的兜底),新容器给真值、老容器吃兜底
3定格卡:不新开消息类型,复用 tool 卡加 result{title,lines} 后端已收;App 结果卡 已做 09-02(v1.686):标题 + ≤12 行,不可点
4AI 工人造 Tool:模板 + 沙盒验证(照 GAME_WORKERS.md 三闸代推)
5chat_tools 加 owner_conversation_id 可空列:私有 Tool 只在那个房可见 后端已做 09-02(000332;目录/装卸/发卡/转事件四处都按会话过滤,别人群的私有工具等于不存在)
6「造一个工具」的聊天入口 已做 09-02(v1.704):货架顶行 → 造工具屏(复制提示给你的 AI / 填清单 / 贴网页 / 交到这个群;拒绝原话原样摆出来;非开发者给「申请」)。现在的引擎是用户自己的 AI;「说一句话 → 工人接单」那半是刀 4
7上传门 后端已做 09-02:POST /v1/tools/submit 四闸(开发者身份 / 清单+群角色 / 网页静态安检 / 留痕)→ 文件 commit 进 HOOP-games tools/p/<id>/ → CI 部署 → 私有 live + 自动装进那个群;publish-request 打时间戳,审核是人。顺序改了(Lisa 09-02):先开开发者的门,模板池/工人那两刀(4、6)排它后面。规格全在 /tools-sdk ⑨
P3 验收:一个不会写代码的用户,说一句话得到一个能用的 Tool,先只在他的群里活。

P4 看得见 —— 1 条尺子(原「养得活」4 刀,Lisa 09-02 砍成这样)

原刀下场
打赏 / 分成不做。Lisa 09-02:「不给他们分钱,我们都无法收取」—— 钱不经平台的手就没有"收取",做了只是替人转账。真要做,前提是先有一条钱进平台的路,那是另一张图纸
开发者上传门提前到 P3 刀 7 做完了
北极星周报留:每周活跃群里开过 Tool 局的比例。数据在 chat_tool_events(000312),一条 SQL,不算一刀
SDK 怎么"下载"(Lisa 09-02 问):不是安装包,是三个网址 —— 指南 /tools-sdk、类型合同 hoop-tool.d.ts、模拟器 _sim.html;_kit.js 由工具网页运行时从站上加载。入口放在首页左上角三条线的 HOOP SPACE 侧栏「Tools SDK」一格(她点的位置),三个链接都能复制,给 AI 的提示一键复制。
P4 验收:第一个非我们造的 Tool 收到第一笔打赏。
飞轮(平台成立的判据):有得用(P1)→ 找得到、换得动(P2)→ 造得快(P3)→ 养得活(P4)→ 更多人造 → 回到「有得用」。 四环缺一环,就退化成「我们维护的一堆小功能」—— 那正是 iMessage 抽屉的死相(⑧)。

P1 首批候选(Lisa 08-29 那批 + 补充,已按五道闸过筛)

✅ 直接做:Calculator(已做 08-29,P1 刀 2)· 报名器 · 专业调查 · 任务榜(轻量版) · AA 分账 · 挑时间 · 转盘抽签 · 井字棋(复用猜拳后端纹路) · 倒计时 · 快问快答
✅ 改个源就成:新闻轻读窗(读我们自己的新闻后端) · HOOP 音乐小播放器(缩成药丸,不挡聊天)
🔀 归别处:发定位/翻译/文件扫描 → 原生功能线;YouTube → 外域,安全闸不放;真钱捐箱 → 合规,用绿能量心意箱替

新增待拍板(蓝图带出来的,和⑦⑧的合并算总账)

  1. 跨局存储:方案与时机。方案已写死在第 8 块(KV 工作台可到期 + 结果定格成消息永生,铁顺序「没定格的不许删」)。 时机推荐:P1 不等它(P1 只上「一局完」的 Tool),P3 第一刀落表,报名器/任务榜跟着上。要拍的是「定格成消息」这个产品行为。
  2. 私有 Tool 的边界:AI 给一个群造的 Tool,默认只在那个群活(私有)。上公共目录要过第 7 块的闸 —— 私有的那份要不要也过一道轻闸?(它毕竟是外来代码在我们的壳里跑)
  3. 北极星指标选哪个:建议「周活跃群里开过 Tool 局的比例」—— 它同时逼供给、发现、质量三件事;单看局数会被一个爆款游戏骗。
  4. 产品名:对用户叫什么?「小工具」贴切但平淡;候选:工具箱 / 道具 / Tools 原文。英文优先(铁律 11),中文跟着定。
  5. 装删的权限:Lisa 09-02 拍「先群主」→ 已按此落地:群主/管理员能动、成员不能;私聊两人皆可。放开是 CanManageTools 改一行。