Repository navigation
下载 1.3.0-alpha
| 平台 | 安装包 | 大小 |
|---|---|---|
| macOS(Apple Silicon) | Ondra-1.3.0-alpha-macos-arm64.dmg |
56.0 MB |
| Windows(x64) | Ondra-1.3.0-alpha-windows-x64-setup.exe |
33.2 MB |
| Android(arm64) | Ondra-1.3.0-alpha-android-arm64.apk |
14.8 MB |
内测版:版本号末尾的 -alpha 就是标记。不是公测版,也不是正式版。
首次打开会被系统拦下
三个包都没有正式签名(macOS 是链接器 ad-hoc 签名,Windows 没有代码签名证书,Android 是 debug 签名)。
- macOS:右键点应用 →「打开」;或执行
xattr -dr com.apple.quarantine "/Applications/空谷 Ondra.app" - Windows:SmartScreen 提示时点「更多信息」→「仍要运行」
- Android:系统会提示「未知来源」。允许浏览器/文件管理器安装即可;
这个包只支持 arm64(近几年的手机都是),老设备装不上是正常的。
装完之后
桌面端首次启动会拉起本机的插件宿主与内核,自带 Node 运行时,不需要另装。
安卓端不跑 Node:记忆与情绪在 WebView 里用 IndexedDB 直接算,
推理直连你在设置里填的接口,所以 APK 只有几十 MB,也不需要额外依赖。
数据全部留在本机,无账号、无遥测。推理来源在设置里三选一:在线接口 / 本地大模型 / 免费模型。
1.3.0-alpha · 安卓端接入插件系统(2026-11-14)
这一版把 75 个真插件装进了安卓 WebView。此前手机端是「手工复刻」路线,这次彻底换掉了。
复刻路线为什么必须放弃
src/lib/local/ 里原来手工重写了情绪与记忆的纯函数(emotionPad.ts、memoryStability.ts,逐字从插件源码抄来),扩展抽屉里硬编码返回 totals: { all: 0 }——用户看到的是「0 个包」。
根本缺陷是第二份真源:能抄的只有无状态纯函数,记忆链、人设链、提示词装配那些有状态的插件抄不过来;而抄过来的部分一旦桌面端改了算法、手机端不跟,同一句话在两个平台会算出不同的情绪值。
把插件本体打包进前端
验证过 cordis 是纯 ESM、零 Node 依赖,在 WebView 里能直接创建 Context 并 apply 插件。剩下唯一的问题是 30 个插件 import 了 node:*。
src/lib/browser/ 下新增 9 个替身:
| 模块 | 实现 |
|---|---|
fs |
内存镜像 + IndexedDB 异步落盘,同步 API 全部可用(插件的「写临时文件 → rename」原子写模式原样成立) |
path / os / url / module |
POSIX 语义,homedir() 返回虚拟根 / |
crypto |
自带 SHA-256 与 MD5(约 250 行),randomUUID 走 WebCrypto |
zlib |
自带 raw-deflate 解码器(约 250 行),人设包解压依赖它 |
process |
在 main.tsx 第一个 import 注入——ESM 求值顺序决定了写在模块体里已经太晚 |
sqlite |
故意加载失败,让 ondra-store-sqlite 走它自己的降级链 |
vite.config.ts 用正则别名(不是字符串——字符串前缀匹配会把 node:fs/promises 吃成 node:fs)把 node:* 与 75 个插件包接到源码位置,只在 ONDRA_LOCAL=1 时生效。
三个关键设计决定
sqlite 不实现,让它失败。 在 IndexedDB 上模拟 SQL 必然有语义缺口,而缺口会以「某类查询静默返回错数据」的形式出现。插件从设计之初就写了 loadSqliteModule() 失败时 return 且不注册服务的路径,下游用 ctx.get('ondra-store-sqlite') ?? ctx.get('ondra-store-memory') 取端口,自然落到 json / memory 版。一行插件代码都不用改。
插件模块改成动态 import。 静态 import 会让桌面端前端从 490 KB 涨到 1955 KB——全是它永远不会执行的插件代码。改成 import() 后切成独立 chunk(plugins-*.js 1471 KB),桌面端永远不会请求它。
复刻版保留但降级为兜底。 插件取不到服务时才走它,一致性由 check-local-parity 逐行比对(130 + 249 行)守着。
实测结果
- 75/76 个插件在安卓 WebView 里装载成功(
pnpm check:mobile-plugins) - 扩展抽屉显示「75 个包 / 75 个在跑」
- 情绪由
ondra-emotion-core计算:mood 0.5、energy 60、tension 0(基线正确) - 6 个配置文件落进 IndexedDB,刷新后仍在
- 唯一装不上的需要
node:net起端口探测,WebView 里没有对应物
顺带修的单位错位
companionView() 原读插件的 axes.energy(0–100),而界面按 0–1 画滑杆,60 被钳到满格显示成「精力 1.00」。改用 normalized(插件已做过除法),与桌面端 GET /api/companion/state 同源。
CI 三端全挂的修复
打 tag 后三端全部失败,报同一处:
lib/local/plugins.ts(44,35): error TS2307: Cannot find module
'@ondra/cordis-plugin-ondra-core' or its corresponding type declarations.
本地能过、CI 全红,是两个独立的顺序问题叠在一起。
一、前端 tsc 不该依赖插件的编译产物。 tsconfig.paths 原来指向真包,tsc 要读它的 lib/index.d.ts——而 CI 的 build:web 跑在 build:plugins 之前,那时 lib/ 不存在。本地能过是因为之前构建残留了那些文件。
→ 新增 src/lib/local/ondra-core.d.ts(手写的、与插件接口对齐的存根),tsconfig.paths 指向它。实现仍由 Vite 的 pluginAliases 指向真包,两边互不依赖。存根只声明真实用到的三个成员,不是 any——插件接口改名时这里要跟着改,那是故意的摩擦。
二、桌面端不该把插件打进产物。 host.ts 里 type PluginRuntimeModule = typeof import('./plugins.ts') 看着是类型位置,但对打包器来说是一次真实的模块解析请求。改成手写接口后,桌面端构建又报 MISSING_EXPORT: loadCore:Vite 在非 localMode 时没有插件别名,退回 tsconfig.paths 的存根,而存根只有类型没有实现。
→ loadPluginModule() 加构建期守卫 if (!__ONDRA_LOCAL_MODE__)。那是 define 注入的字面量,桌面端构建时是 false,整个分支(含 import())被摇掉。
三、安卓 job 缺一步「编译插件」。 build:mobile 单独跑,那时插件的 lib/ 同样不存在。→ release.yml 的 android job 加「编译插件」步骤,放在 build:mobile 之前。
验证
模拟 CI 干净态(删掉全部 154 个插件产物)后依次跑:
src的tsc --noEmit→ exit 0(存根生效)pnpm run build:plugins→ 75 个包全部 Donevite build→ 492 KB,无插件ONDRA_LOCAL=1 vite build→ 493 KB + plugins 1471 KBcheck:mobile-plugins→ 15/15
check-mobile-plugins 15/15、check-mobile-drawer 126/126、check-vfs 10/10、check-ui 432/432、check-acceptance 97/97、check-contrast 67/67、check-local-parity 全过、两端 tsc 全过。
反向验证:注释掉 installProcess() → 插件全部装载失败(0 个包、0 个文件),精确报红 4 条;还原后 15/15 转绿。