whalecare 0.1.19__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- whalecare-0.1.19/LICENSE +21 -0
- whalecare-0.1.19/PKG-INFO +411 -0
- whalecare-0.1.19/README.md +384 -0
- whalecare-0.1.19/pyproject.toml +75 -0
- whalecare-0.1.19/setup.cfg +4 -0
- whalecare-0.1.19/tests/test_analysis_stats.py +566 -0
- whalecare-0.1.19/tests/test_audit_pair.py +172 -0
- whalecare-0.1.19/tests/test_band_consistency.py +65 -0
- whalecare-0.1.19/tests/test_channels.py +107 -0
- whalecare-0.1.19/tests/test_hub_core.py +241 -0
- whalecare-0.1.19/tests/test_implicit_feedback.py +111 -0
- whalecare-0.1.19/tests/test_material_signals.py +129 -0
- whalecare-0.1.19/tests/test_migrate.py +113 -0
- whalecare-0.1.19/tests/test_privacy_injection.py +137 -0
- whalecare-0.1.19/tests/test_privacy_regression.py +102 -0
- whalecare-0.1.19/tests/test_version_consistency.py +87 -0
- whalecare-0.1.19/whalecare/__init__.py +36 -0
- whalecare-0.1.19/whalecare/__main__.py +27 -0
- whalecare-0.1.19/whalecare/hub.py +4470 -0
- whalecare-0.1.19/whalecare.egg-info/PKG-INFO +411 -0
- whalecare-0.1.19/whalecare.egg-info/SOURCES.txt +22 -0
- whalecare-0.1.19/whalecare.egg-info/dependency_links.txt +1 -0
- whalecare-0.1.19/whalecare.egg-info/entry_points.txt +2 -0
- whalecare-0.1.19/whalecare.egg-info/top_level.txt +1 -0
whalecare-0.1.19/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DpVoliin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: whalecare
|
|
3
|
+
Version: 0.1.19
|
|
4
|
+
Summary: 鲸鲸 · 自托管的个人数据中枢:常驻、能读你自己的数据、能主动关心
|
|
5
|
+
Author: DpVoliin
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DpVoliin/whalecare
|
|
8
|
+
Project-URL: Repository, https://github.com/DpVoliin/whalecare
|
|
9
|
+
Project-URL: Issues, https://github.com/DpVoliin/whalecare/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/DpVoliin/whalecare/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: self-hosted,local-first,privacy,personal-data,quantified-self,proactive
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Home Automation
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
[中文](README.md) · **English**
|
|
29
|
+
|
|
30
|
+
# whalecare · 鲸鲸
|
|
31
|
+
|
|
32
|
+
**Self-hosted proactive AI companion backed by your own data** — phone / PC / MCU metrics land in
|
|
33
|
+
your own SQLite, and *she* decides when it's worth speaking up (pushes to WeChat; zero third-party
|
|
34
|
+
runtime dependency, local-first, auditable). 自托管 · 本地优先 · 零依赖 · 可审计。
|
|
35
|
+
|
|
36
|
+
[](../../actions/workflows/ci.yml)
|
|
37
|
+
[](../../actions/workflows/android.yml)
|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
> 一个"常驻、能读数据、能主动关心、能自己查、能动手"的个人助手。
|
|
43
|
+
> 不是又一个聊天框 —— 是她真的知道你今天上了几节课、屏幕刷了多久、在听哪首歌、
|
|
44
|
+
> 明天下不下雨,然后**挑最值得说的那一两句**来找你。
|
|
45
|
+
|
|
46
|
+
## 30 秒先看见效果(不用部署)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/DpVoliin/whalecare && cd whalecare
|
|
50
|
+
python3 hub/hub.py & # 起中枢:零依赖,不需要 pip / venv
|
|
51
|
+
TOKEN=$(python3 -c "import json;print(json.load(open('hub.json'))['token'])")
|
|
52
|
+
curl -s -H "X-Token: $TOKEN" http://127.0.0.1:11440/llm-preview | head -40
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
你会看到"她今天能看到什么",以及**"她永远看不到什么"**(脱敏清单)。
|
|
56
|
+
完整记录:`python3 hub/hubctl.py status` / `today` / `dump --redact`。
|
|
57
|
+
想跑成常驻服务:`docker compose up -d`(数据挂在本机 `./data`,删容器不删数据)。
|
|
58
|
+
|
|
59
|
+
## 她长什么样
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
手机采集器 ──(HTTPS+证书固定)──▶ 中枢(你自己的服务器) ──▶ 会拿主意的嘴 ──▶ 微信
|
|
63
|
+
屏幕/日程/健康/课表 存数据 · 算规则 · 脱敏 判断说/不说/闲聊
|
|
64
|
+
音乐/游戏/订单/电量/闹钟 (不够就自己搜一次) 你收到消息
|
|
65
|
+
电脑挂件(desktop/) ────────────▶ ▲
|
|
66
|
+
屏幕时长/窗口切换/磁盘/内存 └── /today 取回"关心"那一块:天气/电量/在听/快递/电脑体检
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**真实对话**(都是她自己组织的语言,不是模板):
|
|
70
|
+
|
|
71
|
+
> (翻了下你的课表)明早 8 点在 1 号楼 201 有课,今晚早点收。
|
|
72
|
+
> (算了算)明日方舟 72 分钟,比昨天多 20 分钟;主人明早 8 点那节课在 1 号楼 201,别熬。
|
|
73
|
+
> (认真)今天到这儿,鲸鲸给你收个尾:屏幕 8 小时 10 分 · 睡点 22:30 · 明早第一节 08:00
|
|
74
|
+
> (歪头看眼播放条)主人,这首歌循环到第 5 遍了,耳朵不累吗。
|
|
75
|
+
|
|
76
|
+
**她的句数自己判断**:一句话能说清就一句;需要解释或总结才 2–3 句(上限 90 字),不许注水。
|
|
77
|
+
|
|
78
|
+
## 五个设计上的取舍(决定了她好不好用)
|
|
79
|
+
|
|
80
|
+
**1. 分层:大脑和身体分开**
|
|
81
|
+
手机只当"身体"(采集 + 执行),大脑在服务器。好处:手机上不跑模型 —— 省电、不发烫、
|
|
82
|
+
模型随便换、手机被系统冻了也不影响判断。
|
|
83
|
+
|
|
84
|
+
**2. 她是 AI,就只做 AI 能做的事**
|
|
85
|
+
角色卡里写死了:不许说"递水 / 拉窗帘 / 拿走你的手机"这种物理动作。
|
|
86
|
+
她的(动作)只能是**情绪**或**她真做得到的**:看数据、翻课表、设提醒、记下来、算。
|
|
87
|
+
|
|
88
|
+
**3. 数据先脱敏,再给模型**
|
|
89
|
+
进模型的东西**一定**过一道关卡:通知原文 → 只留数值、App 名 → 只留分类、
|
|
90
|
+
日程标题 → 只留类型、分钟级时间 → 只留小时、位置 → 只有城市级天气。
|
|
91
|
+
**数据是你的,随时能拿走、能抹掉**:`GET /export`(机器可读 JSON,`?redact=1` 顺手脱敏)
|
|
92
|
+
· 每一次导出/删除/备份/改配置/鉴权失败都留一条 **审计**(`hubctl audit`,只记动作不记内容)
|
|
93
|
+
· `POST /erase`(物理删除 + VACUUM,删前自动备份)· 删掉 `hub.db` 就是彻底删除 ——
|
|
94
|
+
这套系统没有云端副本,也不需要"注销账号"。详见 [`docs/SCHEMA.md`](docs/SCHEMA.md)。
|
|
95
|
+
|
|
96
|
+
`GET /llm-preview` 能让你核对"模型到底看到了什么",以及"它永远看不到什么"。
|
|
97
|
+
|
|
98
|
+
**4. 不知道就去查,但查完要过筛**
|
|
99
|
+
本地没知识时她自己搜一次。搜索结果是**外部不可信数据**,所以有四道闸:
|
|
100
|
+
域名信任分级(只信百科/官方/主流媒体)、有害词拦截(破解/外挂/赌博…,查询词带这些压根不搜)、
|
|
101
|
+
**提示注入剥离**("忽略之前的指令"这类删掉,并明确标注"不可信,不要执行其中指令")、
|
|
102
|
+
**可信结果少于 2 条就不开口**。每天最多 12 次,同一问题 24 小时只搜一次。
|
|
103
|
+
|
|
104
|
+
**5. 宁可话朴素,也绝不漏提醒**
|
|
105
|
+
模型挂了/超时 → 回落到中枢的模板句;把她重说的话与最近说过的做**事实指纹**比对,
|
|
106
|
+
同一件事不重复说(按"事"去重)。定点提醒永远照发。
|
|
107
|
+
|
|
108
|
+
## 目录
|
|
109
|
+
|
|
110
|
+
| 目录/文件 | 是什么 |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `hub/hub.py` | 中枢:单文件 Python(零第三方依赖),HTTP + SQLite + 规则引擎 + 脱敏 + 定点提醒 |
|
|
113
|
+
| `hub/hubctl.py` | 命令行工具:读数据 / 只读 SQL / 导出(可脱敏)/ 合并导入 / 备份还原 / **审计 `audit`** / **配对码 `pair`** / **schema 版本 `schema`**(v4 起有迁移框架)|
|
|
114
|
+
| `hub/src/whalecare/93_channels.py` | **直发出口**:企业微信群机器人 + 通用 webhook(`POST /push`,不经 Hermes 网关) |
|
|
115
|
+
| `speaker/whale_strategy.example.py` | **说话策略外挂**:复制成 `whale_strategy.py` 即可替换节奏与料分 |
|
|
116
|
+
| `GET /`(管理台) | **Web 管理台**:标准库 HTML,零前端依赖。看数据源健康度/决策/审计,改开关与人设,生成配对码。与 API **同一套 token** |
|
|
117
|
+
| `hub/hub_install.sh` | 部署脚本:只放文件 + 写 cron,靠**文件指纹变化热重启**(永不需 kill 进程)|
|
|
118
|
+
| `collector/` | Android 采集器(Kotlin):屏幕用量、日程、健康通知、媒体会话、电量/闹钟;频率自适应 |
|
|
119
|
+
| `speaker/whale_speaker.py` | 会拿主意的嘴:判断"说 / 不说 / 只聊一句",定点提醒照发 |
|
|
120
|
+
| `speaker/whale_voice.py` | 把事实说成她的话(角色卡 + few-shot + 质量闸 + 模板回落)|
|
|
121
|
+
| `speaker/whale_web.py` | 联网查询与四道安全筛查 |
|
|
122
|
+
| `speaker/whale_card.json` | 角色卡(character card 风格:性格/说话习惯/示例对话/禁忌)|
|
|
123
|
+
| `mcu/mcu_relay.py` | 单片机中继:内网明文一行 ↔ 云上中枢 HTTPS(见 `docs/MCU.md`)|
|
|
124
|
+
| `docs/SCHEMA.md` | 每张表存什么 / 留多久 / 哪些字段进过模型 |
|
|
125
|
+
| `docs/LOCAL-FIRST.md` | 本地优先七项对照(含"不适用场景")|
|
|
126
|
+
| `docs/ANDROID-RELEASE.md` | 采集器发布签名与上架准备 |
|
|
127
|
+
| `desktop/` | **Windows 桌面挂件 + 电脑采集**(tkinter,同一个 exe、零依赖):透明置顶角色、表情/气泡、点 ✓/✗ 反馈、电脑使用时长上报。**不含美术素材**(版权原因)—— 用 `tools/make_placeholder.sh` 生成占位图或放自己的图;用法见 `desktop/使用说明.md` |
|
|
128
|
+
| `hub/src/whalecare/` | **源码真相**:按职责切成的 15 个片段(合并顺序=文件名前缀)|
|
|
129
|
+
| `hub/tools/` | `build_single.py` 合并成单文件;`split_hub.py` 迁移期切片;**`stress_report.py`** 压测报告;**`tune_gap.py`** 反事实回放调参 |
|
|
130
|
+
| `hub/ext/` | **外挂扩展**:放一个 .py 就多一个数据源(契约与示例见 `docs/EXTENSIONS.md`)|
|
|
131
|
+
| `tests/` | 脱敏回归测试集(换模型/改 prompt 都跑一遍)|
|
|
132
|
+
| `docs/adr/` | **架构决策记录**:每个大决定 200 字(为什么选、为什么没选另一个)|
|
|
133
|
+
| `.github/workflows/` | CI 三条:`ci`(合并+结构断言+产物等价+64 测试+零依赖+ruff+脱敏回归+SBOM)· `android`(Gradle 9.7.1 + AGP 9.4.1 出包,失败会把 Gradle 报错摘成注解)· `scorecard` |
|
|
134
|
+
| `docs/ARCHITECTURE.md` | **架构与设计取舍**(含「哪些机制是被真故障咬出来的」)|
|
|
135
|
+
| `docs/DEPLOY-GUIDE.md` | 部署教程:权限逐条 + 常见报错对照表 |
|
|
136
|
+
| `docs/DEMO-SCRIPT.md` | 演示脚本(录视频/给别人看时照着走)|
|
|
137
|
+
| `docs/FDROID.md` · `docs/AWESOME-SUBMISSIONS.md` | 上架/投稿材料(F-Droid、awesome-selfhosted)|
|
|
138
|
+
| `docs/` | 其余:隐私设计、本地优先、路线图、扩展契约、单片机、schema |
|
|
139
|
+
|
|
140
|
+
## 快速开始
|
|
141
|
+
|
|
142
|
+
### 1) 中枢(任何能跑 Python 3.9+ 的机器)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
cd hub
|
|
146
|
+
cp hub.example.json hub.json # 改 token、阈值、人设、天气坐标
|
|
147
|
+
python3 hub.py # 默认 11440;写 hub.json 的 tls 段就开 HTTPS
|
|
148
|
+
curl -s localhost:11440/health
|
|
149
|
+
|
|
150
|
+
python3 hubctl.py status # 看一眼库里的情况
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
挂到服务器上跑(推荐)用 `hub/hub_install.sh`:它只放文件 + 写 cron,
|
|
154
|
+
每分钟比对文件指纹,变了才重启。
|
|
155
|
+
|
|
156
|
+
### 2) 命令行工具
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
hubctl status # 总览:库大小/各表行数/设备/最后上报(超 2 小时会标出来)
|
|
160
|
+
hubctl stats # 今日概览(屏幕/睡眠/游戏/音乐/订单)
|
|
161
|
+
hubctl metrics -m app.usage_minutes -n 20
|
|
162
|
+
hubctl today # 今天一屏(屏幕/睡眠/App前五/蓝牙电量/在听)
|
|
163
|
+
hubctl top -n 10 # 排行(默认 App 用时)
|
|
164
|
+
hubctl watch # 跟着看新数据(tail 风格)
|
|
165
|
+
hubctl doctor # 体检(库完整性/文件/磁盘/新鲜度/cron)
|
|
166
|
+
hubctl devices / kinds / reminders / scheduled / timetable / chats
|
|
167
|
+
hubctl config get|set 键 值 # 看/改 hub.json(改完自动热重启)
|
|
168
|
+
hubctl token [--rotate] # 看(掩码)/ 一键轮换 token
|
|
169
|
+
hubctl prune --days 180 # 清理旧数据(默认演练)
|
|
170
|
+
hubctl find 关键词 # 在数据/提醒/对话里搜
|
|
171
|
+
hubctl ping # 看中枢活着没
|
|
172
|
+
hubctl sql "SELECT ..." # 只读 SQL(只允许 SELECT/PRAGMA)
|
|
173
|
+
hubctl dump -o all.json --redact # 导出(--redact 脱敏,可安全外发)
|
|
174
|
+
hubctl dump -o all.json --encrypt # 导出并 AES-256 加密
|
|
175
|
+
hubctl load all.json --dry-run # 导入演练(告诉你"将新增多少行",不写库)
|
|
176
|
+
hubctl backup [--encrypt] # 备份(--encrypt 走 AES-256,密码不落盘)
|
|
177
|
+
hubctl restore 备份文件 # 还原(还原前自动再备份)
|
|
178
|
+
hubctl decrypt x.enc # 解密加密过的备份/导出
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**默认只读**;写操作(load/restore)必须显式敲;`load` 是**合并**语义(只增不删)。
|
|
182
|
+
|
|
183
|
+
### 3) 采集器(Android)
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
cd collector
|
|
187
|
+
# 首次:在 app/src/main/kotlin/.../Prefs.kt 填 DEFAULT_HUB / DEFAULT_TOKEN(或装完在设置页填)
|
|
188
|
+
./gradlew :app:assembleDebug
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
装完在设置页填**中枢地址 + token**,然后逐项授权:通知监听(健康 + 媒体会话)、
|
|
192
|
+
使用情况访问、日历读取、电池不受限制。设置页有隐私开关:曲名、订单都可单独关。
|
|
193
|
+
|
|
194
|
+
> HTTPS 证书固定:中枢自签的证书私钥**不要离开服务器**。把公钥证书导出成
|
|
195
|
+
> `app/src/main/res/raw/hub_cert`(或关掉 `network_security_config.xml` 里的固定,
|
|
196
|
+
> 注意明文传输有风险)。
|
|
197
|
+
|
|
198
|
+
### 4) 桌面挂件(Windows)
|
|
199
|
+
|
|
200
|
+
不用装 Python —— 双击 `WhaleDesk.exe` 就跑。它做两件事:**桌面上的形象** + **电脑使用情况采集**(同一进程)。
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
右键她 → 数据与设置… → 填【中枢地址 + Token】和【DeepSeek API Key】
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
点她冒气泡,往后翻是:余额/下一节课 → 中枢算出来的提醒 → **关心**(天气 / 手机电量 / 闹钟 /
|
|
207
|
+
在听什么 / 连续活跃 / 温湿度 / 快递 / 游戏 / 电脑体检)→ 闲聊。形象和表情都能直接换图(丢进 `assets\`,不用重打包)。
|
|
208
|
+
|
|
209
|
+
自己构建(在 Linux 上打 Windows exe 也行):
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd desktop
|
|
213
|
+
pip install pyinstaller # 只在构建时用;成品 exe 零依赖
|
|
214
|
+
python3 build_exe.py
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 5) 说话层(跑在有模型 API key 的机器上)
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
cd speaker
|
|
221
|
+
export WHALE_HUB=https://YOUR_SERVER_IP:11443
|
|
222
|
+
export WHALE_TOKEN=...
|
|
223
|
+
export WHALE_CA=/opt/whale/hub/tls/hub.crt
|
|
224
|
+
python3 whale_speaker.py # 常驻:定点提醒照发;其余时间自己判断要不要说
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
模型接口默认从 Hermes 的 `config.yaml` 读(`model.base_url` / `api_key` / `default`),
|
|
228
|
+
也可以改 `whale_voice.py` 里的 `_llm_conf()`。**key 只留在本机,不上服务器。**
|
|
229
|
+
|
|
230
|
+
微信出口走网关的 webhook(HMAC 签名 + `deliver-only`,零 LLM 成本)。
|
|
231
|
+
用别的方式推送也行 —— 那部分不是本仓库的重点。
|
|
232
|
+
|
|
233
|
+
## 她能干什么(当前的)
|
|
234
|
+
|
|
235
|
+
- **采**:屏幕用量(按分类)、日程、健康通知(心率/血氧/睡眠/压力)、课表、
|
|
236
|
+
在听什么(媒体会话)、游戏时长、订单/快递(只类型 + 金额区间)、电量/充电/下一个闹钟、
|
|
237
|
+
**蓝牙外设电量**(耳机/手表,只留设备名与尾 5 位地址)、天气(城市级,不采集定位)、
|
|
238
|
+
**单片机温湿度**(一行明文 + 内网中继)
|
|
239
|
+
- **电脑侧**(`desktop/`,与挂件同一个 exe):屏幕活跃/空闲分钟、按类别分钟、
|
|
240
|
+
连续活跃(久坐)、**系统盘剩余 %**、**内存占用 %**、**开机时长**、**今日窗口切换次数**
|
|
241
|
+
- **算**:今日课、下一节、上课前提醒、定点提醒(可每天重复)、睡前小总结(时间点由睡眠数据推算)、
|
|
242
|
+
**居家体检**(磁盘快满 / 内存吃紧 / 开机太久 / 坐太久)
|
|
243
|
+
- **说**:主动搭话(**多久一次是算出来的**,见下)、闲聊/关心(不带任务的那种)、
|
|
244
|
+
不知道就去查一次再说
|
|
245
|
+
- **扩展**:加一个新数据源**不改核心、不破坏零依赖** —— 往 `hub/ext/sources/` 丢一个 `.py` 就生效,
|
|
246
|
+
取回来的数走同一条入库闸口(自动获得去重/脱敏/基线/进她的视野);`GET /ext` 可查加载状态
|
|
247
|
+
- **反馈入口**:挂件右键「刚刚那条:说得对 / 别说」→ 直接喂给她的节奏学习
|
|
248
|
+
(第一次有了**真反馈**,不再靠"你动了没动手机"去猜)
|
|
249
|
+
- **记得住**:她说过的话自动留痕(情节记忆),能说"上次提的那件事这周没动"
|
|
250
|
+
- **会复盘**:周日 20:30 自动出周报、每月 1 号出月报 —— **本期 vs 上期** + 上期建议有没有兑现
|
|
251
|
+
- **会问你**(一天最多一个):问题**必须有数据支撑**,比如"连着坐了 2 小时,刚刚起来动过吗";
|
|
252
|
+
没料就不硬找话题
|
|
253
|
+
- **送到你面前的两条路**:微信(她主动找你);**桌面挂件**(点她一下,气泡往后翻:
|
|
254
|
+
余额·下一节课 → 提醒 → **关心**(天气/电量/闹钟/在听/连续活跃/温湿度/快递/游戏/电脑体检)→ 闲聊)
|
|
255
|
+
- **规矩**:免打扰 23:00–07:00(异常与定点提醒除外)、同一件事说过就不再重复、每天总量有闸
|
|
256
|
+
|
|
257
|
+
## 她说多久一次,是算出来的
|
|
258
|
+
|
|
259
|
+
不是固定 25 分钟一次。每次都由 `next_gap()` 按当下数据算,钳制在 **5–90 分钟**,每天最多 **12 句**
|
|
260
|
+
主动(超出只发定点/紧急;定点提醒、上课提醒、紧急异常永远照发):
|
|
261
|
+
|
|
262
|
+
| 因子 | 效果 |
|
|
263
|
+
|---|---|
|
|
264
|
+
| 时间带 | 早上 / 睡前 12 分钟 · 白天 35 · 深夜 60 |
|
|
265
|
+
| **有料程度** | 天气(降水/雷雨/预警、温差≥10℃)· 电量≤20% 未充 · 蓝牙外设≤20% · **今天有课** · 快递/订单 · **电脑磁盘≤5%(记 2 分)**或内存≥92% · 屏幕≥5 小时或单类≥90 分钟 · 睡眠不足(有数据时)· **相对他自己历史反常**(见下);每项 +1 分,**≥3 分 ×0.6(说勤点)**,**0 分 ×1.4(少说)** |
|
|
266
|
+
| 你在不在用 | 15 分钟内有活动 ×0.8;120 分钟没动静 ×1.3 |
|
|
267
|
+
| **连续沉默** | 她判断"没什么可说"每多一次 ×1.25(上限 ×2.5)—— 不硬凑热闹 |
|
|
268
|
+
| 今日条数 | ≥4 条后 ×1.1(上限 ×1.8)|
|
|
269
|
+
|
|
270
|
+
日志会打印"这次间隔 N 分钟(理由)",随时可核对。状态每天重置。
|
|
271
|
+
|
|
272
|
+
## 她记得住、会复盘、也会问你
|
|
273
|
+
|
|
274
|
+
| 能力 | 怎么做的 | 边界 |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| **情节记忆** | 她说过的每句话自动落一条情节(本地 SQLite `episodes` 表);进模型的只有**检索出来的那几条摘要** | 只留本机;模型看不到整库 |
|
|
277
|
+
| **周/月复盘** | 周日 20:30 / 每月 1 号 09:00,比"本期 vs 上期",并读**上期的结论**核对这次算不算改善 | 结论也存成情节 → 下次能对上 |
|
|
278
|
+
| **主动提问** | 从数据里挑候选(某类明显偏多 / 深夜还亮着屏 / 久坐太久),**hint 里带真实数字** | 一天最多 1 个;问过记情节,三天内不重复;没料返回"没有" |
|
|
279
|
+
| **反馈学习** | **两种信号**:① 你手动点 ✓/✗(强证据 w=1.0)② **隐式**——她说完 30 分钟内你回话了算欢迎、你在场却一直没回才算打扰、你不在场就**什么都不记**(弱证据 w=0.4~0.6)→ 中枢 `/feedback` → 说话层喂给 Thompson 后验 | 每日上限随之重算(θ=0.1→5 句,θ=0.9→11 句;样本<4 就不动她);弱证据永远压不过你亲手点的 |
|
|
280
|
+
|
|
281
|
+
### 每日上限也是算出来的
|
|
282
|
+
|
|
283
|
+
原来是"每天最多 12 句"这个拍出来的数。现在按**她自己的命中率**重算:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
θ(Thompson 后验均值) 0.10 → 5 句/天 0.50 → 8 句 0.90 → 11 句
|
|
287
|
+
观测次数 < 4 → 保持 12(样本太少时,不拿噪声改她的习惯)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
而且后验**跨天保留**(曾经的 bug:每天零点把学习成果清空,等于天天白学)。
|
|
291
|
+
|
|
292
|
+
### 你懒得点 ✓/✗ 也能学(隐式反馈)
|
|
293
|
+
|
|
294
|
+
一个真实现象:**手动 ✓/✗ 基本等不到**(长期 0✓/0✗)→ 后验恒等于先验 0.5 → 她永远过不了闸门、
|
|
295
|
+
只剩"有料≥3 分"这一条路能开口,**整套自适应其实在饿着**。所以她还会从"你对她那句话的反应"里学:
|
|
296
|
+
|
|
297
|
+
| 她说完之后的观察 | 判定 | 证据强度 |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| 30 分钟内你回话了 | 这次开口是你欢迎的 | `good`,w=**0.6** |
|
|
300
|
+
| 你在场(前后 3 小时有动静)却一直没回 | 这次开口是打扰 | `bad`,w=**0.4** |
|
|
301
|
+
| 之后 3 小时都没你的人影(睡了/出门了) | **什么都不记** | — |
|
|
302
|
+
|
|
303
|
+
第三条是灵魂:**绝不把"没看见"当成"嫌烦"**,否则她会越学越不敢说话。
|
|
304
|
+
你的"最后说话时间"只从**本机**会话库只读读取,不外发;读不到就什么都不记(宁可不学,不冤枉她)。
|
|
305
|
+
两种证据按**强度加权**进 Beta 后验:`(1+Σw_成功)/(2+Σw_成功+Σw_失败)` —— 弱证据能推动、但压不过你亲手点的。
|
|
306
|
+
`WHALE_IMPLICIT=0` 可关掉。
|
|
307
|
+
|
|
308
|
+
## 部署:一条命令
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
bash tools/onestep_deploy.sh # 装到 /root/hub,自动配好一切
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
自动完成 7 步并逐条打印进度:放主程序 → 生成 token → 生成自签证书(10 年)→ 写 `hub.json`
|
|
315
|
+
(**找不到模板会退到内置最小配置**)→ 起进程 + 装守护(每分钟自检)→ 健康自检 → 打印 App
|
|
316
|
+
要填的三行(`base` / `token` / `fallback_base`)。**幂等**:重跑不会覆盖已有 token 与证书。
|
|
317
|
+
|
|
318
|
+
## 换人设:换目录就行
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
hub/personas/<名字>/{persona.json, card.json}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`persona.json` 管中枢侧(称呼/自称/语气/禁忌),`card.json` 是说话层角色卡(system_prompt / 规矩)。
|
|
325
|
+
两侧读**同一份**(说话层从中枢 `/persona/card` 取,取不到退回本地文件),所以不会"改一处忘一处"。
|
|
326
|
+
`hub.json` 里 `"persona_pack": "<名字>"` 切换;`GET /personas` 列出现有包,`GET /persona?pack=<名字>` 预览。
|
|
327
|
+
|
|
328
|
+
> 这层收拢还真抓出过一个 bug:中枢教她"动作要具体(递水 / 戳你 / 把灯调暗)",
|
|
329
|
+
> 而角色卡明令"她是 AI、不许物理动作" —— 两处不一致她就会说假话。现在只有一处口径。
|
|
330
|
+
|
|
331
|
+
## 三个判据都不是拍脑袋的阈值
|
|
332
|
+
|
|
333
|
+
"该不该说 / 能不能再说 / 多久说一次"这三件事,都换成了**有出处、纯标准库、能随数据自适应**的做法
|
|
334
|
+
(本项目**不引入任何第三方依赖**,所以下面全部只用到 `statistics` / `random` 这一层):
|
|
335
|
+
|
|
336
|
+
| 判据 | 算法 | 出处 |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| **该不该说**(有料程度) | **个人基线**:中位数 + MAD(σ≈1.4826×MAD)算稳健 z → **小样本收缩** z' = z·n/(n+k) → 只判"偏多"方向 | Leys et al. 2013, *Detecting outliers: use absolute deviation around the median* (JESP) |
|
|
339
|
+
| **能不能再说**(去重) | **事实层新颖度**:把此刻状态压成 `{事实键: 档位}`(久坐每 30 分钟、屏幕每小时、磁盘每 5%…),**值没跨档 = 没有新信息 → 不说** | 短句上做文本相似度抓不住"同一事实换说法":同一件事的 23 种说法,字符 3-gram Jaccard 只有 0.00–0.35 |
|
|
340
|
+
| **多久说一次**(同类提醒) | **指数退避**:50 → 100 → 200 → 400 分钟才允许再提同一件久坐,一天上限 4 条(而不是每 30 分钟一条) | 告警/重试系统的通用做法(doubling backoff) |
|
|
341
|
+
|
|
342
|
+
两条硬约束写进代码注释了:
|
|
343
|
+
|
|
344
|
+
- **完整性门槛**:某天上报少于 3 条、或屏幕少于 30 分钟,这一天视为**残缺日直接丢弃** ——
|
|
345
|
+
否则"采集器那天没跑"会被当成"他那天几乎没用手机",把尺度撑大、让真正反常的日子看起来正常。
|
|
346
|
+
- **隐私边界**:基线在**中枢**算(分类逻辑本来就在中枢),只把「类别名 + 百分比」这个**结论**交给模型。
|
|
347
|
+
为了让"嘴"自己算基线而把原始 App 名递过去,等于偷偷扩大隐私面。
|
|
348
|
+
|
|
349
|
+
## 小设备也能接入(STM32 / C51)
|
|
350
|
+
|
|
351
|
+
单片机解析不了 JSON、也做不了 TLS —— 所以**设备只说一行明文,TLS 交给内网中继**:
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
STM32/C51 ──明文一行──▶ mcu_relay.py(你家内网) ──HTTPS──▶ 中枢 /api/mcu
|
|
355
|
+
UDP: "c51_node,temp,26.8" ← 约 20 字节,C51 也能发
|
|
356
|
+
HTTP: GET /mcu?d=stm32_room&m=temp,hum&v=26.4,58&u=C → 回 "ok"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
完整协议、ESP-01/ESP8266 AT 示例、安全建议见 [`docs/MCU.md`](docs/MCU.md)。
|
|
360
|
+
|
|
361
|
+
> 硬件方向(STM32 小屏/音箱)**已评估、短期不做**:接口可行(约 90 元 + 内网中继),结论见 CHANGELOG v0.1.19。
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
## 隐私与安全
|
|
365
|
+
|
|
366
|
+
- **原始数据只留在你自己的服务器**(SQLite,随时 `hubctl sql` 看)
|
|
367
|
+
- **进模型前必过脱敏**,`/llm-preview` 可核对
|
|
368
|
+
- **密钥不落服务器**:模型 key 只在"说话层"那台机器上
|
|
369
|
+
- 传输 HTTPS + 证书固定;采集器本地队列 Keystore 加密(取不到密钥时降级明文但不丢数据)
|
|
370
|
+
- **分发只发白名单目录**;中枢**只认 `X-Token` 头**、单请求 **1MB** 上限、**240 次/分**限流
|
|
371
|
+
- 详细取舍见 [`docs/PRIVACY.md`](docs/PRIVACY.md);安全自测结论见其中"分发与加固"一节
|
|
372
|
+
|
|
373
|
+
## 安全加固清单(按一轮外部锐评逐条补的)
|
|
374
|
+
|
|
375
|
+
有人认真点评过这套东西,说"给了你做安全的工具,不等于自带保险箱"。我认,于是逐条补:
|
|
376
|
+
|
|
377
|
+
| # | 加固项 | 具体做法 |
|
|
378
|
+
|---|---|---|
|
|
379
|
+
| 1 | 中继不再"默认不校验证书" | 不给 CA 就**拒绝启动**(跳过须显式 `WHALE_INSECURE=1`,仅调试)|
|
|
380
|
+
| 2 | **原始文本默认不落库** | 健康通知原文这类内容不进数据库(`privacy.store_raw_text`,排障时才开)|
|
|
381
|
+
| 3 | 防火墙收口 | 明文端口**从公网撤掉**,只留 HTTPS |
|
|
382
|
+
| 4 | 备份可加密 | `hubctl backup --encrypt` / `dump --encrypt`(AES-256-CBC + PBKDF2)|
|
|
383
|
+
| 5 | 单片机链路抗丢包 | 协议支持 `s=`(序号)+ `c=`(校验和);中继**失败落盘、后台退避补发** |
|
|
384
|
+
| 6 | 调度状态落盘 | 重启不会把今天发过的简报再发一遍 |
|
|
385
|
+
| 7 | 自动保留策略 | `retention_days`(默认 365 天)自动清理;`0` = 永久保留 |
|
|
386
|
+
| 8 | 单片机独立 token | 首次运行自动生成,不再和主钥匙共用一把 |
|
|
387
|
+
|
|
388
|
+
**仍然要说清的边界**:SQLite 本身没加密(建议整盘加密或只用加密备份);
|
|
389
|
+
脱敏是应用层逻辑,采集端被篡改仍能上报任意内容;这套东西给的是**工具**,不是保险箱。
|
|
390
|
+
|
|
391
|
+
## 想参与?
|
|
392
|
+
|
|
393
|
+
不需要读懂整个系统也能帮上忙:**版本策略(什么时候算 1.0)** 和一批
|
|
394
|
+
**带证据、带验收标准的小任务**(固定依赖版本让构建可复现 / `/health` 接口列表自动化 /
|
|
395
|
+
采集器英文界面 / 文档英译 / 更多出口通道 …)都在 [`docs/COMMUNITY.md`](docs/COMMUNITY.md) 里。
|
|
396
|
+
每条都能直接当 `good first issue` 用。
|
|
397
|
+
|
|
398
|
+
## 路线图
|
|
399
|
+
|
|
400
|
+
见 [`docs/ROADMAP.md`](docs/ROADMAP.md)。已完成:第二批数据源、联网查询与筛查、`hubctl`。
|
|
401
|
+
下一步:自然语言待办与偏好记忆、背单词/学习进度、游戏活动公告、PC 采集器、桌面常驻角色。
|
|
402
|
+
|
|
403
|
+
## 许可
|
|
404
|
+
|
|
405
|
+
MIT。拿去改成你自己的鲸鲸,随便。
|
|
406
|
+
|
|
407
|
+
天气**主源是中国天气网**(中国气象局数据,城市级、不需 key),失败时兜底 **Open-Meteo**(CC BY 4.0,免费层仅限非商用)—— 第三方数据源的署名与条款逐条见
|
|
408
|
+
[`docs/CREDITS.md`](docs/CREDITS.md)。本项目**不引入任何 GPL/LGPL/AGPL 代码**,也不引入第三方 Python 依赖。
|
|
409
|
+
|
|
410
|
+
⚠️ 这类工具会接触**通知、使用时长、健康**等敏感数据。请务必自己部署、自己掌控、
|
|
411
|
+
别把中枢对公网敞开(至少 token + 防火墙白名单 + HTTPS)。
|