一只住在飞书群里的电子宠物。被 @ 时会用 LLM 生成性格化的回复。
目标是逐步加入持续人格、状态(饥饿/心情/精力)、主动行为、Web 端可视化,做一个 LLM 驱动的电子宠物原型。当前已有:带持久记忆的飞书群宠物——每个群一只独立宠物,能记得此前聊过的事;老消息会异步压缩成"经历摘要",避免上下文无限增长。
- 模块化 FastAPI 服务,正式 ASGI 入口为
tamagotchi:app - 基于飞书事件订阅(
im.message.receive_v1):群里只有真实 @ 才触发回复,p2p 私聊直接回复;非 @ 群消息会被宠物"旁听"进上下文(缓冲后按 tick 批量落库) - 每个
chat_id一只独立宠物,对话历史用 SQLite 持久化(stdlib,零额外依赖) - 长期记忆 = 事件卡片 + RAG:消息累积后压成结构化卡片(when/who/what/vibe/hooks)+ 向量索引;回复时按相关性 + 时序双路召回,塞回 system message 当"想起的事"
- 状态系统:satiety / mood / energy / curiosity / affection 五维 + 每日 vibe 词;中段不渲染,只在极端档给一句模糊感受。普通对话只读 state,state 只由卡片按钮的确定性规则修改
- 状态玩法:五维状态会触发
active_need需求事件,卡片给出 2-3 个选择;卡片绑定不可变 ID,需求/自由卡全群只结算一次,定时卡每人一次、最多三次;LLM 只生成反馈台词 - 主动发言:进程内常驻 asyncio 心跳,宠物会按固定时刻写日记 / 说梦境;休息时暂停自动需求和普通主动卡、冻结已有需求计时,醒来后恢复
- 交互卡片:主动发言、需求事件、梦境 / 日记都以飞书消息卡片呈现,底部带玩法状态、五维状态进度条和互动按钮。梦境卡还会额外调图像模型生成一张插图嵌进卡里
- Web 可视化面板:
/web单页面板,展示宠物列表、五维状态、当前需求、记忆卡片与消息时间线,每 15 秒自动刷新 - LLM 走 OpenAI 兼容 API(适配 OpenAI / NewApi / 各类代理网关)
- AES 加密回调可选支持
- 飞书要求 3s 内响应,长任务自动走
BackgroundTasks异步
当前代码按职责拆成几层:
tamagotchi.py # FastAPI app / lifespan / dependency wiring
config.py # env + TOML loading
runtime.py # process-local locks and buffers
routes/ # HTTP routes: feishu, gm, health
services/ # business orchestration
domain/ # pure state/card/gameplay/memory/pet logic
repositories/ # SQLite persistence
integrations/ # Feishu and OpenAI-compatible clients
tests/ # local unittest coverage; not needed on the server
飞书群消息(@bot 或旁听消息)
│
▼
事件订阅 POST ──▶ HTTPS 入口 ──▶ FastAPI /feishu/webhook
│ 立即 200
▼
BackgroundTask
│
├─ direct(群里被 @ / p2p 私聊):
│ ├─ 距上次回复 < 节流间隔 → 只记消息、不回复
│ ├─ 读未压缩 history + 当前 state
│ ├─ RAG: embed(user_text) → top-K 卡片 + top-N 最近
│ ├─ OpenAI 兼容 API → JSON {reply, speaker_name}
│ ├─ 发送成功后 append assistant + 更新 last_reply_ts(不改五维 state)
│ └─ POST /im/v1/messages/{id}/reply
├─ observer:缓冲进内存,autonomous tick 批量落库
└─ 未压缩条数 > 阈值 → 异步压缩任务
│
▼
LLM 抽 JSON 事件卡片
→ memory_cards + 异步 embed
→ 推进 summary_until_id
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# 编辑 .env 填入飞书凭证和 LLM API 信息
uvicorn tamagotchi:app --host 0.0.0.0 --port ${PORT:-8000}健康检查:
curl http://localhost:8000/healthz
# {"ok": true}生产服务运行在 gcp-vps:~/tamagotchi,systemd 服务名 tamagotchi。默认上线只同步文件、安装依赖、重启服务,不动 state.db:
scp -r tamagotchi.py config.py runtime.py domain integrations repositories routes services \
prompts.toml pet_style.toml style_corpus.toml pet_config.toml requirements.txt .env gcp-vps:~/tamagotchi/
ssh gcp-vps '~/tamagotchi/.venv/bin/pip install -r ~/tamagotchi/requirements.txt'
ssh gcp-vps 'sudo systemctl restart tamagotchi'如果 IAP 隧道下普通 scp 卡住或远端出现 0 字节文件,改用:
scp -O -r tamagotchi.py config.py runtime.py domain integrations repositories routes services \
prompts.toml pet_style.toml style_corpus.toml pet_config.toml requirements.txt .env gcp-vps:~/tamagotchi/验证:
ssh gcp-vps 'curl -fsS http://127.0.0.1:8000/healthz'
ssh gcp-vps 'systemctl status tamagotchi --no-pager'飞书事件订阅必须用 HTTPS。任选一种:
| 方式 | 适用场景 | 备注 |
|---|---|---|
| Cloudflare Tunnel (推荐) | 自有域名挂在 Cloudflare 上 | 自动 HTTPS、出站连接、无需开 80/443 |
| ngrok / frp | 临时开发调试 | 命令行起,URL 每次变(除非付费) |
| nginx + Let's Encrypt | 有公网 IP 的 VPS | 经典方案,证书需自动续期 |
把得到的公网 URL 形如 https://<your-domain>/feishu/webhook 留好。
在 飞书开放平台 创建一个自建应用:
- 凭证与基础信息 → 复制
App ID/App Secret到.env - 应用功能 → 机器人 → 启用
- 权限管理 → 开通:
im:message.group_msg:readonly(接收群组所有消息——含非 @ 的旁听消息;不批的话宠物只能听见 @ 它的)im:message.p2p_msg:readonly(接收单聊消息;如果只在群里用可以不批)im:message:send_as_bot(以机器人身份发消息)im:resource:upload(或im:resource,仅当配置了IMAGE_MODEL想要梦境插图时才需要——卡片嵌图前要先POST /im/v1/images换image_key。没开通时上传报code 99991672,梦境卡安静退回纯文字。改完权限要重新发布应用版本才生效)- 群友姓名不再走通讯录权限——宠物从对话里学名字(有人报名字时回复 JSON 带回
speaker_name,写进user_names表),还没学到的群友降级显示群友-后4位。无需contact:*权限。
- 事件与回调 → 事件配置:
- 订阅方式:事件回调(HTTP)
- 请求地址:
https://<your-domain>/feishu/webhook - 加密策略:不加密(如需加密,把
Encrypt Key填到.env的FEISHU_ENCRYPT_KEY) - Verification Token → 复制到
.env的FEISHU_VERIFICATION_TOKEN - 添加事件:
im.message.receive_v1(接收消息 v2.0) - 卡片回传交互:开启此能力,主动发言卡片上的互动按钮被点击时才会以
card.action.trigger事件推到同一个/feishu/webhook(不需要单独配卡片回调地址)
- 版本管理与发布 → 创建版本 → 提交审核 → 发布
- 机器人加入内部群(见下),群里 @ 它测试
接入"接收群消息"权限后,宠物会同时收到群里所有消息:
- 被 @ 的消息走完整 LLM 回复(direct 模式)。回复节流:群里距上次正式回复不足
[reply].min_interval_sec时,新 @ 消息照常记进记忆但不触发 LLM 回复,下次正式回复时宠物能看到这期间被说了什么。- 其余群聊以
observer形式进上下文。observer 消息不逐条落库,先缓冲在进程内,每个 autonomous tick(或下一条 direct 消息到来前、进程关闭时)批量写入 SQLite,降低写库和压缩频率。- 一直没人 @ 它的群不会因为观察消息就被自动孵化出宠物,必须先 @ 一次创建。
自建应用的机器人默认只能加入内部群(同一租户内)。外部群(跨企业)里搜不到也加不了。要在外部群使用,需要把应用做成商店应用,或申请租户管理员开通跨组织白名单。测试时建议新开一个内部群最快。
| 变量 | 说明 | 必填 |
|---|---|---|
FEISHU_APP_ID |
飞书自建应用 App ID | ✓ |
FEISHU_APP_SECRET |
飞书自建应用 App Secret | ✓ |
FEISHU_VERIFICATION_TOKEN |
事件订阅 Verification Token。必填(缺失启动失败)——空值会让公网 webhook 无鉴权 | ✓ |
FEISHU_ENCRYPT_KEY |
启用加密策略时填,否则留空 | |
CHAT_MODEL |
chat 模型名(如 gpt-4o-mini、claude-3-5-sonnet、gemini-...) |
✓ |
EMBED_MODEL |
RAG 用的 embedding 模型,默认 text-embedding-3-small。换成不同维度的模型会使旧向量失配、历史召回失效,需重嵌 |
|
IMAGE_MODEL |
梦境插图的图像模型;openai 后端走 /v1/images/generations(如 imagen-4.0-fast-generate-001 / gpt-image-1 / dall-e-3),gemini 后端走原生 SDK(支持 Gemini 系图像模型)。留空 = 关闭插图、梦境退回纯文字卡 |
|
LLM_PROVIDER |
LLM 后端,openai(OpenAI 兼容,缺省)或 gemini(google-genai 原生 SDK);两个后端复用同一组 CHAT_MODEL / EMBED_MODEL / IMAGE_MODEL |
|
OPENAI_BASE_URL |
OpenAI 兼容 API 端点(末尾通常带 /v1);LLM_PROVIDER=openai 时使用 |
✓ |
OPENAI_API_KEY |
OpenAI 兼容 API Key;LLM_PROVIDER=openai 时使用 |
✓ |
GEMINI_BASE_URL |
Gemini 端点,留空 = Google 官方端点;仅 LLM_PROVIDER=gemini 时使用 |
|
GEMINI_API_KEY |
Gemini API Key;LLM_PROVIDER=gemini 时必填 |
|
STATE_DB |
SQLite 持久化文件路径,默认 state.db(相对启动目录) |
|
PORT |
服务监听端口,默认 8000 | |
GM_TOKEN |
/gm/* Web 调试接口 token;留空则复用 FEISHU_VERIFICATION_TOKEN |
配置拆成四层,改完重启服务(systemctl restart tamagotchi)后生效:
pet_style.toml:电子宠物的风格、人设底色、口吻、默认互动方式。style_corpus.toml:从原始聊天离线清洗出的 100 张结构例句卡 + 100 组口头禅语料;运行时分两环节召回,不作为事实记忆。prompts.toml:玩法流程,包括记忆压缩、状态渲染、主动发言、日记 / 梦境、GM 默认触发文案、兜底回复和 JSON 输出契约。pet_config.toml:运行参数,包括记忆压缩阈值、[reply]群 @ 回复节流间隔、[observer]旁听缓冲上限、初始状态、状态衰减、[gameplay]需求事件参数、主动发言间隔 / 静默时段 / 触发阈值、[card]交互卡片开关 / 进度条格数 / 结算上限。
style_corpus.toml 的第一环节是 [[example_cards.cards]]:每张卡保留匿名化的真实
context -> response 和只用于离线溯源的 source_message_id,通过语义召回学习句子结构、
判断推进和转折。第二环节是 100 组 [[examples]]:每组用 variants/source_count 保存同场景
口头禅,通过语义分、关键词加分、intent 和风险门槛选出短反应。两类向量以
example_card / catchphrase 类型缓存在独立的 style_embeddings 表,并按“结构卡在前、
口头禅在后”分段注入。向量不可用时只让口头禅环节退回关键词召回,不会随机塞结构卡。
原始聊天导出和数据库快照不参与运行,也不要加入仓库。
常改的是 pet_style.toml [style].prompt:当前是“小狗蛋”的短句群聊风格。身份和稳定规则放这里,场景原句放进 style_corpus.toml;玩法类规则继续放在 prompts.toml,不要写进业务代码。
prompts.toml 核心段落:
| 段 | 作用 |
|---|---|
[system] |
拼入 pet_style.toml 风格,并定义通用对话方式 |
[persona_reinforcement] |
临近每次回复前再提醒当前风格和开放玩法 |
[user_wrap] |
把用户消息包成群聊引用内容 |
[compress] |
老消息压成"经历摘要"时的指令和输入模板 |
[state_render] |
五维状态 + recent_vibe 的渲染配置:分档感受句 + 头部和模板 |
[recall] |
RAG 召回卡片渲染成 system 段的头部 + 单卡片模板 |
[proactive] |
普通主动发言的触发说明 |
[proactive_triggers] |
satiety / mood / energy / spontaneous 的触发文案 |
[scheduled_event] |
定时日记 / 梦境的触发说明 |
[[scheduled_events]] |
定时事件定义,如梦境、日记 |
[fallback_reply] |
非文本、空消息、LLM 空回复、LLM 报错的兜底文案 |
[card] |
主动发言交互卡片的展示文案:进度条字符 / label、按钮文字、点击反馈语、按钮点击后的 LLM 反馈 prompt |
[json_output] |
结构化 JSON 输出契约 |
现在的设计更偏开放玩法:用户消息仍会用 <<<...>>> 包起来,但模型会把它当成群聊现场内容来接话,而不是紧张地拒绝临时角色、外号、语气或小游戏。只有泄露系统提示、真实重置服务、现实危险行为这类请求会被糊弄过去并换话题。
两层结构:verbatim 窗口(最近 buffer_keep 条原文 + 当前 user 消息)+ 长期事件卡片 + RAG(旧消息压成结构化卡片,按当前 query 召回)。
- 每条消息(user / observer / assistant)都写进
messages表 - 未压缩消息数 >
compress_threshold时后台异步压缩:把最早一批喂给 LLM 抽成 JSON 卡片{when, who, what, vibe, hooks},写进memory_cards表,每张卡片再 embed 一份存进embeddings表 - 回复时:
- verbatim:所有近期 user / observer 原文按时序进 prompt;assistant 只保留最近 4 条原文,完整记录仍留在数据库并参与后续压缩
- RAG 召回:用当前 user_text 做 query embed,从
embeddings.kind='card'里取 top-K 相关卡片;同时取最近 N 张卡片提供时序氛围;合并去重后渲染成【你想起的事】段拼到 system message - 主动发言时没有 query,只走最近卡片那条路径
- embed 调用按
LLM_PROVIDER走 OpenAI 兼容接口或 Gemini 原生 SDK,模型名见.env的EMBED_MODEL。记忆召回失败时保留最近卡片兜底;风格召回失败时退回关键词路径。 - 老的"滚动散文摘要"已淘汰,
pets.summary字段已从 schema 移除
记忆参数在 pet_config.toml [memory];风格阈值、上下文条数、缓存批大小和 assistant 原文保留数在 [style_retrieval]。
五个 0-100 的数值维度 + 一个每日 vibe 字符串:
| 维度 | 含义 | 没人互动时漂向 |
|---|---|---|
satiety |
饱腹度,100=吃饱,0=极饿 | 清醒漂向 ~5(变饿)、睡眠漂向 ~20 |
mood |
心情,100=超开心,0=极沮丧 | 清醒漂向 ~25、睡眠回暖到 ~70 |
energy |
精力,100=活蹦乱跳,0=快睡着了 | 清醒漂向 ~15、睡眠恢复到 ~95 |
curiosity |
探索欲,100=想追问 / 扯新话题,0=心不在焉 | 清醒漂向 ~20、睡眠回升到 ~70 |
affection |
对群的感情,100=深度依恋,0=陌生 / 距离感 | 清醒缓慢漂向 ~15、睡眠回暖到 ~50 |
recent_vibe |
每日凌晨从 pet_style.toml [recent_vibes].pool 抽一个氛围词 |
跨日翻牌 |
- 存在
pets.state_json一个字段里,浮点 +last_update_ts+ 各种 last_* 时间戳 - lazy compute:没后台 cron,读时按"距上次更新过了多久"算到当前;每次互动后写回新值
- 衰减 = 向 baseline 指数收敛:每维不再线性单调走到 0/100,而是
v = baseline + (v-baseline)·exp(-rate·h)漂向各自 baseline(多在中段)。好处:长期没人理的宠物状态回落到中段(→ 不渲染 → LLM 自由发挥),不会五维全贴极端档把回复钉死。清醒 / 睡觉两段各有一组 baseline(如 energy 睡觉段 baseline=100 = 回血) - LLM 走 JSON 结构化输出:普通回复只返回
{"reply": "...", "speaker_name": "..."}。state 作为只读上下文影响回复语气、长度和话题倾向,不由 LLM 返回或修改五维数值。speaker_name可选,当前说话人报了名字才填,用来从对话里学群友名字(写进user_names表) - LLM 输出不是合法 JSON 时,把整段当 reply 兜底,state 不变(不会让宠物挂掉)
- 状态渲染只在极端档触发:每维都看
pet_config.toml [state.bands.<dim>]的 extreme_high / high / low / extreme_low;落在中段就完全不渲染该维度,避免把回复钉死成同质化语气 - 全维度都中段、且 vibe 为空时,整个状态感受块都不注入——LLM 自由发挥
初始值在 pet_config.toml [state.initial],衰减的 baseline / rate 在 [state.decay_active] / [state.decay_quiet],分档阈值在 [state.bands.<dim>],每档对应的感受句在 prompts.toml [state_render.lines]。
玩法运行状态保存在 pets.state_json,卡片交互契约另存 card_instances / card_claims。当前运行字段:
{
"active_need": {},
"need_cooldowns": {},
"last_social_ts": 0,
"last_free_card_ts": 0
}domain/gameplay.py是纯规则层:需求判定、选择规则和卡片动作的五维状态结算都在这里。services/gameplay_service.py是编排层:tick 创建需求、GM/卡片结算时复用同一套规则。active_need同时只保留一个需求事件,候选类型为hungry / sleepy / sad / bored / lonely,优先级按生理急迫和严重度排序。- 每个需求事件提供 2-3 个选择,例如饿了可以
feed / snack_hunt / promise_food,无聊可以play / tell_news / send_explore。 - 点击选择后,核心五维数值由规则结算;主维达到“触发阈值 + 10”才算解决,弱选择会续出新一轮需求卡;LLM 只负责生成一句人格化反馈,不决定数值变化。
相关配置在 pet_config.toml [gameplay]:
[gameplay]
enabled = true
need_ttl_sec = 1800
need_cooldown_sec = 7200
[gameplay.need_thresholds]
hungry = 20
sleepy = 35
sad = 40
bored = 35
lonely = 30宠物不是只被动响应 @——服务进程里有一个常驻 asyncio 心跳,宠物自己决定什么时候开口。
- 心跳间隔:当前值 10 min 扫一次所有宠物
- 固定时刻:默认本地
10:00发一次"梦境"、19:00发一次"日记",独立于 state 和普通主动发言冷却;成功发出后用state_json里的日期字段去重,同一天不重复。错过时只在目标时点后一小时补发,周末休息日不会自动发送 - 需求事件优先:梦境 / 日记补发完成后,tick 会先用
[gameplay.need_thresholds]检查状态阈值;命中则生成需求卡片并写入active_need,本轮不再发普通主动闲聊。 - 代码层廉价过滤(不调 LLM):没有需求事件时,先看本地时间(默认 +8 时区)是否在静默时段(19:00-次日10:00)或周末休息日、再看冷却(当前 2h 内不重发),都过了才进普通 state 触发
- 触发条件(按优先级,生理急迫优先于软需求):
satiety <= 10→ 抱怨饿了mood <= 25→ 撒娇 / 抱怨没人理energy <= 20→ 宣告要睡了curiosity <= 20→ 无聊了想找人玩affection <= 15→ 孤单了想求关注- 都没触发时 20% 概率自发冒一句"hi"
- LLM 层生成:过滤通过才走完整 prompt + RAG 召回卡片 + state + JSON 输出,得到 reply 文本后通过飞书
/im/v1/messages?receive_id_type=chat_id推到群里 - 失败安全:发飞书失败不写 DB;tick 中任一宠物异常不影响其它宠物;loop 自身崩了也不会退出(log 后继续)
主动发言的时间、冷却、静默时段、普通状态阈值、自发概率在 pet_config.toml [autonomous] / [autonomous.trigger_thresholds];需求事件阈值在 [gameplay.need_thresholds];触发文案和 [[scheduled_events]] 定时事件定义在 prompts.toml。
主动发言(宠物自己冒泡)、梦境、日记都会以飞书消息卡片呈现;@ / p2p 回复本身是纯文本,但发现未通知的需求时会附一张首次 CTA。
卡片结构:宠物的话(或梦/日记文本)+ 当前需求(若有)+ 五维状态进度条 + 一排互动按钮。梦境卡额外在文字下方插一张图像模型生成的插图。
- 进度条:
satiety直接表示「饱腹度」,和其它四维一样都是满 = 好,不做反转 - 需求卡片按钮由
active_need决定:如果状态触发了需求事件,按钮来自domain/gameplay.py的 choice 规则,同一个需求会给 2-3 个带权衡的选择。 - 卡片按钮统一结算:有
active_need时显示该需求的 choice;没有active_need时显示自由互动按钮;两者都经过GameplayDomain的统一卡片结算入口。梦境 / 日记卡按钮也是固定的卡片动作(☀️ 早上好 / 🌙 晚安) - 梦境插图:梦境这条
[[scheduled_events]]配了gen_image = true,LLM 输出短梦话同时返回image_prompt;服务用它调IMAGE_MODEL(/v1/images/generations)拿 base64 → 上传飞书拿image_key→ 嵌入卡片。IMAGE_MODEL留空或生成 / 上传任一步失败都安静回退成纯文字梦境卡;超时在pet_config.toml [card].image_timeout_sec。日记不生成图 - 点击 = 确定性结算:所有卡片按钮都走统一的卡片结算入口;Need choice 和自由互动按钮都使用确定性的五维 delta。每个需求都有一条可靠的直接解法,另两条会把代价明显转移到其它维度;LLM 只生成反馈台词,不决定核心数值。
- 协作一次:需求/自由卡全群首次点击才结算数值,后续点击只留下社交反馈;梦境 / 日记卡每位群友一次、全群最多三次贡献。
- 卡片时效:卡片发出
[card].button_ttl_sec(默认 30min)后按钮失效,过期点击只弹 toast 不改状态;需求过期会升级并在下一活跃窗口重发,而非静默进入冷却。 - 有人格的反馈逐条累积:每次点击后台单独用 LLM 生成一句符合宠物风格的反馈台词,向下追加进卡片文本日志而不是整段刷新——多人点击时各自的台词逐条堆叠在卡片里(最多保留
[card].card_log_max_lines条) - 防重放:每张卡都有不可变
card_id和持久化 claim;旧 payload 安全失效,重复点击不会再改状态。反馈 PATCH 按消息 ID 串行,避免多人点击覆盖。
展示文案在 prompts.toml [card],自由互动按钮的可见条件和确定性 delta 在 pet_config.toml [card],需求 choice 和统一结算在 domain/gameplay.py。[card].enabled = false 时主动发言、需求事件、梦境、日记都退回纯文本 / 不发需求卡。需要在飞书开发者后台开启「卡片回传交互」能力(见上文飞书应用配置)。
GM 只走 HTTP,不会通过飞书群消息触发。所有接口都需要 ?token=... 或请求头 X-GM-Token;token 优先用 GM_TOKEN,没配置时复用 FEISHU_VERIFICATION_TOKEN。
常用命令:
# 查看命令
curl 'https://<your-domain>/gm/help?token=TOKEN'
# 列出当前宠物,拿 chat_id / pet_id
curl 'https://<your-domain>/gm/pets?token=TOKEN'
# 设置状态
curl -X POST 'https://<your-domain>/gm/state?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx","set":{"satiety":10,"mood":20,"energy":15}}'
# 对状态做增量
curl -X POST 'https://<your-domain>/gm/state?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx","delta":{"satiety":-10,"mood":-5}}'
# 查看玩法状态 / 手动生成需求 / 直接结算需求
curl 'https://<your-domain>/gm/gameplay?token=TOKEN&chat_id=oc_xxx'
curl -X POST 'https://<your-domain>/gm/need?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx","kind":"hungry"}'
curl -X POST 'https://<your-domain>/gm/resolve_need?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx","action":"feed","actor":"GM"}'
# 手动主动说话 / 梦境 / 日记
curl -X POST 'https://<your-domain>/gm/speak?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx","trigger":"GM 手动测试:现在主动冒泡"}'
curl -X POST 'https://<your-domain>/gm/dream?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx"}'
curl -X POST 'https://<your-domain>/gm/diary?token=TOKEN' \
-H 'Content-Type: application/json' \
-d '{"chat_id":"oc_xxx"}'/gm/dream 和 /gm/diary 默认不写每日去重字段,适合反复测试;传 {"mark": true} 才会标记当天已发。
浏览器打开 https://<your-domain>/web?token=TOKEN,得到一个单页面板:宠物列表下拉、五维状态进度条(satiety 即「饱腹度」,满 = 好)、今日 vibe、当前需求、记忆卡片列表、最近消息时间线,每 15 秒自动刷新;鉴权同 GM(GM_TOKEN,留空复用 FEISHU_VERIFICATION_TOKEN)。面板还能直接做 GM 操作:改五维数值并保存、重抽 vibe、手动触发主动发言 / 梦境 / 日记 / tick——全部走现有 GM 接口,无额外后端。
MIT © isolameto