Releases: kunpengtalk/OmniStudio
Release list
v0.1.1
Added / 新增
-
网关页端点列表新增「嵌入服务」两行:网关代理地址
{网关地址}/v1/embeddings与直连嵌入实例的实时地址http://127.0.0.1:{port}/v1/embeddings——端口取最后一个运行中的嵌入实例(与主进程解析规则同源),host 固定 127.0.0.1(SERVER_HOST可能是0.0.0.0,照抄实例 endpoint 会复制出连不上的地址);无运行实例时直连行显示「未运行」+ 引导且复制禁用,行不消失。 -
默认模型新增「全局默认嵌入模型」(快照语义):面板可配嵌入模型 + 可选服务地址/密钥(
EMBEDDING_MODEL/EMBEDDING_BASE/EMBEDDING_API_KEY三键,bun 侧唯一读取点globalEmbeddingDefaults())。全局默认只在写入时生效:新建 KB 自动预填三字段;既有 KB 不被静默切换,在知识库设置页点「启用向量检索」显式启用(写入该库配置并真的排入重嵌,无可用嵌入后端时按钮禁用并提示);共享记忆设默认即走向量检索(MEMORY_EMBEDDING_MODEL=none可显式关回纯关键词,清空全局默认同样回落)。既有 KB 端点零漂移:resolveEmbeddingBase与 rerank 解析链保持原样(快照语义的设计出发点——解析期全局层级会让既有 KB 的服务地址被静默改道、维度漂移只在检索时报错)。 -
新建知识库弹窗自动预填全局默认嵌入模型(真实探活门控):设置了「默认模型 → 向量嵌入」且探活通过时,新建弹窗打开后自动选中默认模型(候选列表外也显示);探活异步进行、弹窗照常秒开。探活由主进程完成(新增 RPC
kbDefaultEmbeddingProbe,经testEmbedding→callEmbeddings与建库后真实嵌入同一条地址解析链,webview 只消费 configured/reachable 结论、不复制地址回落逻辑);结论三态:未配置(与今天一致)、已配置但不可达(留空 + 提示「已配置默认嵌入模型 {model},但当前不可达…」)、已配置且可达(预填)。用户在探活返回前/后的任何手动选择(含显式选「不使用」)永不被覆盖(touched 守卫),探活失败不预填,杜绝「建库时填了、首次导入才失败」;预填快照中立——建库结果与留空提交完全一致。随行小修:弹窗每次打开都会重置嵌入 / 重排两个字段(弹窗常驻挂载,此前跨开合残留上一次的选择);name / description 的残留行为不变。 -
嵌入类模型现在真的能当嵌入服务用(自动
--embeddings+ 专属端口 + 聊天零扰动):过去把嵌入模型(GGUF)加进来只能当聊天模型启动 —— llama.cpp 默认禁用嵌入端点、而运行时从不传--embeddings,POST /v1/embeddings直接 501(错误原文就写着 "Start it with --embeddings")。更糟的是知识库的嵌入模型选择器按模型名过滤(名字里带 embedding 就列出来),选中必失败 —— 一个「列得出、调不通」的完整陷阱。现在:类别为 embedding 的模型经应用启动时自动以嵌入模式服务(--embeddings --pooling,池化方式由EMBEDDING_POOLING设置,默认last),监听独立的嵌入端口段(EMBEDDING_PORT默认 18190,与聊天 18080 段各自 +100 顺延、互不重叠);嵌入实例完全不参与聊天活动状态 —— 不写SERVED_ACTIVE_ID/CHAT_MODEL/LOCAL_MODEL_PATH、不抢活动端口、聊天实例停止时也不会被自动提升成聊天目标(命令行快照测试锁定聊天路径逐字节不变)。嵌入服务地址解析改为四层:逐库显式地址 > 运行中的嵌入实例 >SERVER_MODE=remote的VLLM_API_BASE> 活动端口;非 llama.cpp 引擎收到 embedding 类别模型时明确拒绝启动,不再起一个没有嵌入端点的实例。 -
网关新增
POST /v1/embeddings:外部客户端(OpenAI SDK / Continue / LangChain 等)可以经网关取嵌入,鉴权与 Origin/Host 防线与其他/v1/*完全一致;没有运行中的嵌入实例时返回 503 并给出可操作引导(「先在模型页启动嵌入类别的模型,或在知识库设置里配置嵌入服务地址」)。路由同时登记进endpoints列表与openApiSpec(/docs、/openapi.json同步可见)。 -
模型类别可改,分类不再被平台弱标签压过:HF 仓库常给嵌入模型挂
conversational/text-generation这类宽泛标签,而分类器是「标签优先」,于是WeMM-Embedding-9B这类模型被归成对话模型(实测该仓库 tags 确实含conversational)。现在名字判定为嵌入时不再被弱对话标签压过(text-to-image等强标签仍优先);模型详情页新增类别下拉(chat / embedding / rerank / tts / asr / image / video,仅市场下载的模型可改,改完写.vllm-meta.json并回读确认落盘);知识库的嵌入模型选择器改为只列运行中嵌入实例提供的模型,没有嵌入实例时显示空态与引导,而不是列出聊天端口上名字带 embedding 的模型。 -
Agent 会话可以「回退到这里」(OW-12):消息操作条上多一个动作,落在用户消息上是「回到这条提问」——连它一起删,正文回填输入框,改一改再发;落在助手消息上是「保留到这里」——保留这条回答,只删它后面的内容,对应"这个回答对了、后面几轮跑偏了"这个最常见的诉求。删除前弹确认框,并写明只动对话不动工作区文件(要还原这轮改过的文件用旁边的「撤销本轮」)。删除边界抽成纯函数
planRevertToMessage():这条规则错一次的代价是"用户想保留的那条回答被删了",界面上看不出来,只有单测盯得住(4 条用例含角色异常值的保守处理)。删消息的同时清掉agent_events,并重置会话实例,避免残留的工具轨迹影响下一轮。 -
引擎启动失败从"一行错误原文"变成"一句能照做的建议"(LIE-05):新增
shared/engine-errors.ts把失败分八类 —— 缺依赖 / 显存不足 / 磁盘满 / 端口被占 / 权重缺失或没下完 / 架构或量化不支持 / 权限被拒 / 认不出来。分类是纯函数,主进程在失败时就算好(ServedModelInfo.errorKind、getServerStatus().errorKind),界面按类型给下一步(engine.error.hint.<kind>,中英各八条):端口被占指路去控制台卸载实例,显存不足指路调小上下文或换小量化,权限被拒提醒 macOS 的隔离属性。顺带把"三处各判各的"收成一处:原先 webview 里另有四个正则(只认得 llama.cpp 那一句unknown model architecture),聊天、控制台、模型页因此可能对同一个错误给出不同说法;现在统一走同一张规则表,.hint.arch/.hint.engine两条死词条随之删除。26 项单测(八类各有用例、缺依赖优先于显存、警告行不误判、不误伤正常文本)。- 第九类「权重还在下载中」只能由上下文判定:实机撞到的场景 —— 配的模型正在下载(9.5GB 预分配的稀疏文件 + 四个
.part分片),启动时 llama.cpp 只回一句exiting due to model loading error,光看原文分不出来(架构不认识也是这句),于是界面显示"认不出来,见原文",用户会去查架构、换量化、重下模型,全是白费。现在启动失败时先查下载队列(downloadManager.list()里这个文件是不是 downloading / queued / paused),是就直接说"权重还在下载中(X%),现在加载必然失败",并且这类算模型侧失败 —— 配了备选链的话会换一个下完的先用起来。原文留在detail.raw,日志与界面说同一句话。
- 第九类「权重还在下载中」只能由上下文判定:实机撞到的场景 —— 配的模型正在下载(9.5GB 预分配的稀疏文件 + 四个
-
推理服务器空闲自动卸载(PERF-01):连续一段时间没有请求就把模型服务停掉,把显存让出来 —— 生图 worker 的那套(
IMG_MLX_IDLE_MINUTES)现在做到了推理服务器层,设置页「服务生命周期」里配(SERVER_IDLE_UNLOAD_MINUTES,默认 0 = 关闭:默认打开会让"昨晚还跑着的模型今天不见了"变成一个用户无法解释的意外)。判据是三个活动来源:应用内推理调用(挂在用量账本这一个收口上,聊天 / Agent / 翻译 / OCR / 网关全覆盖)、实例输出是否还在变化(外部工具直连端点时唯一看得见的信号)、llama.cpp 的/slotsis_processing(唯一一个精确的「正在忙」信号 —— 只有它有这么个口子)。卸载动作进 app.log。故意没做题述的「在飞请求计数」:一个请求就是一条可能很长的 SSE 流,要正确计数就得在正常结束 / 客户端断开 / 引擎报错每条路径上都减回去,漏一条就变成永久性的「再也不会卸载」—— 读数不准的闸门比没有更危险。局限改为如实写在设置页上(外部长请求中间不打印任何东西时可能被误杀),窗口默认关着。 -
默认模型起不来时回退到备选模型(PERF-03):控制台多一张「备选模型」卡片(从已下载的对话模型里挑、可排序、可移出),设置项是
SERVER_FALLBACK_MODELS。两条规矩写在实现里:只有模型侧失败才回退(架构不支持 / 权重没下完 / 显存不够)—— 端口被占、缺依赖、权限不足换哪个模型都一样,回退只会把真正的失败原因盖住,用户看到的是"备选也起不来";回退不静默 —— 写 app.log(served_model.fallback.used/.skipped/.exhausted)并进通知中心,因为是后台自动发生的模型变更,用户必须知道现在在用哪个。整条链都失败时,错误里两边的失败原因都在。7 项单测。 -
TopK 与重复惩罚提为界面参数(ENG-04):
SERVER_TOP_K(默认 40,与 llama.cpp 自己的默认值一致)与SERVER_REPEAT_PENALTY(默认 1.12,与各 profile 现行值一致),llama.cpp 启动参数发--top-k/--repeat-penalty,本地模型页的启动参数面板里可改。优先级是"设置优先、模型档案兜底",空串表示没设过 —— 设置页显示的就是实际发出去的那份。 -
日志脱敏从"只看字段名"补到"也看内容"(FUT-03):老实现只把
{apiKey: "…"}这种规整体换成***,而真正泄漏密钥的路径基本都不长这样 ——curl -H "Authorization: Bearer sk-…"这样的命令行、?api_key=…这样的 URL、api_key=…这样的环境变量转储,字段名分别是command/url/args,谁也没写在名单里。现在redactSecrets()认这些形状:Authorization / x-api-key 头、裸Bearer、sk-/osk-/hf_/ghp_/glpat-/xoxb-等固定前缀、api_key=/?token=/--password赋值形态、URL 里的 userinfo;logEvent的message也过一遍(原文常把整条命令行抄进来)。规则都要求一个明确的密钥形状,宁可漏掉一个"长得不像密钥的密钥",也不把整份日志变成***—— 因此同类里也有"不误伤"的用例(prompt_tokens=1024、模型路径、token=abc这种短值)。字段名名单另补了access_key/secret_key/credential等别名(裸key故意不收:{ key: "SERVER_TEMP" }这种调试字段很常见)。14 项单测。 -
应用日志终于能在应用里看了(OPS-01 / OPS-02):
app.log以前只能omi logs或在文件系统里翻 —— 设置 → 数据 → 控制台(ConsoleScreen)的下半屏现在是一个统一的日志区,顶部一个来源切换器:应用日志(结构化条目表:时间 / 级别 / 来源 / 事件 / 正文,detail点开看 JSON)+ 每个已启动实例的实时输出(推理服务器 stdout,沿用原来的终端渲染)。应用日志还能翻轮转文件(app-*.log,带时间与体积,倒序),跟随最新时每秒轮询一次、只取新条目(memoryOnly+since),日志再大也不会把主进程拖住。最近 100 / 500 / 2000 条是两类来源共用的档位:应用日志走limit(主进程"取最新 N 条再排序"),实例输出按行裁(仍受 ANSI 渲染上限约束)。安全性上,文件名只能取appLogFiles()列出的 basename,越界名字直接拒绝并记app_log.file.rejected(../../omni-studio.db、绝对路径都有用例)。ROADMAP 里原来指的server-logs.tsx早已随多实例改造删除,这次是按现状补的。 -
基准测试可以一次扫完 batch × ctx(OPS-03):原来一趟只跑一个固定并发数,想比较 1/2/4 并发就得手工跑三遍、还得自己对齐上下文档位。现在并发与上下文、缓存场景一样是多档位(
batchSizes[],UI 上是 chips + 逗号批量输入,CLI 是--batches 1,2,4,--batch N仍是简写),扫描顺序 ctx × 并发 × 缓存,进度按三维相乘计数。汇总按并发档分开给(summary.byBatch),跨档的平均值在界面与报告里明确标注口径 —— 不同并发下的 tps 本来就不是一回事,混在一起平均等于没有意义;缓存对比也按ctx@batch分组,不会把 4 并发的冷启动和 1 并发的热缓存画进同一列。早停(上下文溢出 / 超时)带上撞墙的那个并发档。报告里加「按并发」表。 -
概览页能看到显卡与"哪个模型占了多少显存"(OPS-05 / OPS-06):硬件占用那一栏多出显卡采样卡 —— 显存已用 / 总量条 + 利用率 + 温度 + 功耗,一卡一张;下面多一张「运行中的实例」,每个在跑的实例给出引擎 / 端口 / 状态、权重体积(扫描得到的真数)与显存占用。显存是实测而不是猜的:
nvidia-smi的逐进程显存按实例 pid 归属(这是唯一能回答"哪个模型占了多少"的来源)。读不到的地方一律显示「—」并写明原因(Apple 芯片是统一内存、非 N 卡的 Mac 没有 nvidia-smi、命令在却答不上来),不拿别人的数顶上 —— 按错的数字调参数比没有数字更坏;Apple 芯片连命令都不跑。顺带修掉旧实现的一个糊弄:实例清单来自注册表而不是"最近用过就算已加载",/slots只用于活跃模型那栏的 loaded 判定。 -
模型加载模式可配(PERF-02):启动参数里多一项「模型加载模式」,管的是权重在内存里的驻留方式 ——
mmap(内存紧张时系统能回收权重页)/mlock(锁在内存不换出)/mmap+mlock/none(整块读进内存)/dio(DirectIO)。这是"内存不够时是变慢还是整机卡住"那一层的旋钮,与 KV 缓存量化合起来才叫分层防护。mlock 有真实代价(模型比内存大时会让整机变慢甚至被系统杀掉),所以设置项下面直接写明。上游已经把--mlock/--no-mmap标成 DEPRECATED、换成--load-mode,所以启动前会探一次--help:新版发--load-mode <值>,旧版按--help的措辞折算成旧开关(旧版默认就是 mmap,所以mmap不发参数;dio旧版没有 → 不发并记一条日志说明不支持,不静默降级)。取值走白名单(这个值最终进 argv),手改设置行塞不进别的参数。 -
SGLang 的分块预填充进了启动参数面板(PERF-05):
SGLANG_CHUNKED_PREFILL_SIZE一直存在,但此前只能手改数据库 —— 长 prompt 场景要压住预填充的显存峰值就得改它。llama.cpp 没有单独的分块预填充开关(那就是--batch-size/--ubatch-size这一对,已在面板里),vLLM 的两个开关等有真机的环境再验。
Changed / 变更
-
设置页分组重排:概览 / 通用 / 外观 合成顶部一组,「关于我们」下沉到最底部的「系统」:原来「偏好」组三条并排挂在工具与数据之间 —— 通用(代理)与外观是这个应用自己的设置,却被隔在模型 / 服务 / 工具三组之后;关于我们则混在一堆"可调"的页面里,通读一遍菜单也猜不到版本号在哪。现在首组不带标题(概览 + 通用 + 外观:这个应用在跑什么、长什么样、出网走不走代理),中间四组(模型 / 服务 / 工具 / 数据)保持原样,末尾新开**「系统」只放关于我们一条**(版本 / 更新 / 开源信息是查的,不是调的)。
settings.group.prefs词条删除、新增settings.group.system。- 回归:
settings.test.tsx锁住按钮顺序(概览 / 通用 / 外观 在最前,关于我们在最后)与分组标题序列(首组无标题,末尾是「系统」且只有一条),并断言导航里不再出现「偏好」。 - "去哪儿配"的文案跟着改:代理设置原先写作「设置 → 偏好 → 通用」,凡是指这条路的地方(
shared/proxy.ts/bun/proxy.ts/bun/index.ts/bun/db/settings.ts/bun/rpc/index.ts的注释、general-tab/prefs-tabs页头注释、proxy-smoke冒烟说明、docs/architecture.md、omni-doctor 排查表三条)统一改成「设置 → 通用」——报错与排查表把人指到一个已经不存在的菜单,比不指路更糟。
- 回归:
-
模型那六个菜单按"一个问题一条"重排:模型库(页签:模型市场 / 本地已下载 / 我收藏的模型)、运行模型、云端模型、模型引擎。此前
模型云服务、默认模型、本地模型、引擎、模型库、在线模型市场并排挂在设置页的「模型」分组里,同一件事要在两三个页面之间来回跳:下载模型去「在线模型市场」,看下载结果去「模型库」,改启动参数去「本地模型」,给某个功能换模型去「默认模型」。- 模型库只管"有哪些模型、从哪儿下"(
mainview/app/model-library/):三个页签左右切换 —— 模型市场(原「在线模型市场」:平台切换 ModelScope / Hugging Face、格式过滤、检索,没搜过时给官方推荐清单)、本地已下载(默认页签,含来源 / 分类筛选与每行的运行 / 激活 / 收藏 / 删除 / 复制启动命令)、我收藏的模型。运行与配置不在这页里。 - 空态说人话:一个本地模型都没有时,本地已下载页直说「本地还没有模型」,按钮直接切到隔壁的模型市场页签 —— 新装的应用就停在这个状态,空表格等于什么都没说。收藏页有自己的空态,不复读"本地还没有模型"。
- 已下载页故意不按引擎过滤:GGUF 在 vLLM 下、MLX 权重在 llama.cpp 下照样列出来(行上标「将自动切换引擎」)—— 严格过滤会让切一次引擎就有一批模型"凭空消失",而它们其实照样能跑。
- 收藏页同样跨引擎,并把来源筛选那一行收起来(跨来源的一份清单,按来源再切一次是噪音)。
- 运行模型(原「本地模型」改名,
app/local-models/index.tsx):模型引擎选择、启动参数、启动条、默认模型与目录 —— 只管"怎么跑",模型清单不重复摆一份(那是模型库的事)。一个模型都没有时给一条去模型库的路。 - 云端模型(原「模型云服务」改名,
CloudProviderPanel+DefaultModelsPanel):厂商目录、密钥、各用途默认模型。默认模型卡片里 4/6 张本来就是云厂商选择器,没有启用厂商就是空的,放同页才配得起来。 - 模型引擎(原「引擎」改名):引擎本体的安装 / 升级 / 卸载,页内与文案里的「引擎」一并改成「模型引擎」。
- 旧标签 id 全部映射,不留空白页:
network/defaults→ 云端模型,model→ 运行模型,store→ 模型库,market→ 模型库的市场页签,cli navigate的models→ 模型库;小应用omni.openSettings("network")(两个 HTML 里写死的)因此不用改。顺手把navigate拓宽到带tab/sub:omi start --cloud与omi models现在真的落在云端模型页上(此前只跳"设置",提示文案却写着"设置 → 云端")。 - "去哪儿配"的文案全改成新路径(65 处
设置 → 模型云服务→设置 → 云端模型,含主进程 20 来条错误消息:生图 / 生视频 / 生音乐 / OCR / 实时语音 / 打包媒体能力检查,以及cloud.where、omi的 CLI 帮助与docs/omi-cli.md、omni-doctor的排查表)—— 报错指路指到一个已经不存在的菜单,比不指更糟;医生技能排查表左列是"匹配错误原文",跟着改是为了它继续匹配得上。 - 回归:
model-library/index.test.tsx(页签横向、顺序与默认值正确,空态按钮切到市场页签、市场页渲染检索界面、已下载页跨引擎、收藏页只列收藏)、settings.test.tsx(导航就是这一份、旧 id 落在对应页面与页签、每页宽度仍走同一个 `P...
- 模型库只管"有哪些模型、从哪儿下"(
v0.1.0
Added / 新增
-
阶跃星辰(StepFun)的语音三件套接进对应应用:TTS / ASR / 实时通话各走自己的端点。「模型云服务」里的阶跃预设补上了
stepaudio-3-tts、stepaudio-3-asr-max、stepaudio-3-realtime-preview(含 2.5 代作对照),语音页、语音识别页、通话页直接就能选到 —— 预设新增的模型会自动补进存量厂商行(syncPresetModels),老用户不需要先去设置页点「获取模型列表」。三家的接口差异都在实现里写死了,用户不必知道:TTS 走 OpenAI 兼容的/v1/audio/speech(stepaudio-3-tts,voice必填);ASR 走/v1/audio/asr/sse(base64 + SSE 增量文本 —— StepAudio 3 ASR 只在这个端点,另一个同步文件接口不支持它、带时间戳的那条又要求音频是公网可下载的 URL,桌面端给不了;说话人分离因此不支持,会记一条日志而不是假装成功);实时通话走wss://api.stepfun.com/v1/realtime,事件名与百炼相同但字段不同:pcm16而不是pcm、断句只认server_vad、上行 24k 而不是 16k,且不发手工 commit(服务端 VAD 自己管,补一个提交空缓冲的 commit 只会换来一条报错)。方言判定在realtimeDialectFor(地址为主、模型名为辅),换厂商时地址、模型、音色一起跟着换(isPresetRealtimeEndpoint让"从阶跃切回百炼"不再把阶跃的 wss 当成用户自填值留着)。- 音色不再需要用户去猜:新增
shared/tts-voices.ts(阶跃官方音色清单 +resolveTtsVoice),TTS 页与通话页在认得出的厂商下给官方音色下拉、同时保留自由输入(复刻音色直接填 id,填什么用什么);没配过音色或存着别家的占位值(alloy这类 OpenAI 兼容名)时按厂商给默认值 —— 出厂默认的alloy直接发给阶跃只会得到一句invalid voice,而用户什么都没动过。保存厂商时主进程会把生效音色回给页面,界面上显示的和真正发出去的是同一个。网关的/v1/audio/speech同样走这套解析:外部调用方通常不传voice。 - 顺手修掉两处地址拼接:ASR 的远端分支过去把服务商 baseUrl 再拼一次
/v1(预设自带/v1→/v1/v1/audio/transcriptions,稳定 404 且报错来自上游网关);normalizeApiBase收敛到shared/cloud-providers.ts并按"有没有版本段"判断,TTS / ASR / 网关三处共用。 - 实时语音模型不再进对话选择器:
stepaudio-3-realtime-preview这类名字自动识别落在other,而other在对话列表里是保留的("认不出来的宁可多给")—— 选中后只会在发消息时被上游拒掉,现在按isRealtimeModelId明确挡掉(百炼的qwen-audio-*-realtime同样受益)。 - 回归测试:
shared/realtime-voice.test.ts(端点推导 / 方言 / 采样率 / 模型名过滤)、shared/tts-voices.test.ts(音色解析规则)、bun/stepfun-voice.test.ts(TTS 与 ASR 的线上报文形状:地址不重复拼/v1、音色替换、SSE 定稿与 error 事件)、bun/realtime-voice.test.ts(用真 WebSocket 收报文:阶跃pcm16+server_vad+ 无 commit,百炼保持pcm+smart_turn+ 有 commit、transcript字段的用户转写)、bun/cloud-providers.test.ts(预设模型补齐与幂等)、bun/chat-model.test.ts(实时语音不进对话列表)。
- 音色不再需要用户去猜:新增
-
AI 生成的回答可以一键存进「笔记」:对话与 Agent 的助手消息操作条上多了「保存到笔记」(右键菜单同一份动作)。标题从内容里取(
# 标题优先,否则短首行当标题并从正文里摘掉,免得卡片上标题与摘要一模一样),正文原样存 Markdown,日期取本地当天,标签按来源自动打(对话 / Agent)—— 两条消息渲染是各自独立的实现,但"这段文字怎么变成一条笔记"只有一份规则(mainview/lib/note-draft.ts)。保存成功后按钮变成 ✓,不把用户从正在读的回答里拽到别的应用。 -
笔记会沉淀成记忆,Agent 因此能"想起"你写过的东西:以前笔记只是笔记 —— Agent 读不到,问它「我上次记的爬山那条」它一无所知。现在每保存一条笔记就同时写一条索引级记忆(
笔记《标题》(日期):正文摘要,≤500 字,sourceRef = note:<id>),走的是既有的三条通路(常驻核心块 / 每轮召回 /memory_search工具),记忆页里能看到它来自哪条笔记。三个刻意划下的边界:记忆是单行短句(记忆会被注入所有 Agent 的上下文,2 万字的正文搬进去既超限也会污染上下文,细节仍以笔记为准);幂等(同一条笔记反复保存只更新同一条记忆,删笔记时连记忆一起删掉,否则 Agent 会"记得"一条已经不存在、也打不开的笔记);疑似凭据的笔记不进记忆库(正文照常保存,只是跳过沉淀 —— 这条通路复用记忆系统已有的凭据拦截)。开关NOTES_MEMORY_SYNC在笔记小应用的设置里(默认开),关掉只影响之后的保存。 -
Agent 能自己查、自己读笔记了(不用再提醒它):上一版让笔记沉淀成记忆之后,模型能"想起"用户写过一篇日记,却读不到正文 —— 用户问「看看我的日记」,它只能去 grep 工作区、翻知识库,最后回一句"这只是历史记忆里的内容,无法确认是否仍保存为独立文件"。现在补上
note_list/note_search/note_read三个只读工具(Plan 模式同样可用),并把记忆摘要末尾挂上(完整正文:note_read #<id>)—— 让"想得起"直接接上"读得到",模型顺着记忆就能去调工具。工具结果克制(列表默认 8 条、摘要 140 字、正文单次 12000 字上限),一篇 2 万字的日记不会一口气吃光上下文。开关与记忆沉淀合并成一把NOTES_AGENT_ACCESS(笔记小应用设置里,「对 Agent 可见」,默认开):关掉时既不写记忆、也不给这三个工具。 -
小应用新增「笔记」(日记 / 随手记):带日历与标签两个视图,正文进主库、附件进数据目录:这是第一个需要留存数据的小应用,也因此暴露了小应用的一条硬约束 —— 沙箱 iframe 不给
allow-same-origin,那里window.localStorage连读都会抛 SecurityError,随手记的东西刷新即没。路径有两条:交给用户(omni.files.save落到下载目录)或交给宿主存储,笔记选后者 —— 正文进miniapp_notes表、附件进<数据目录>/images/notes/<附件 id>/,于是笔记跟着omi backup一起保存与恢复、重装不丢、omiCLI 也读得到。界面是一套"窄栏 + 内容区"的应用壳:最左边图标窄栏(日记 / 日历 / 标签 / 设置),日记页是瀑布流卡片墙 + 顶部分类页签(全部 + 标签)+ 右下浮动「+」,日历带年月下拉、有记录的日子打彩色圆点、下方按月列出条目(2026/09/15 / 星期二 - 16:24:18的格式),设置页做成仪表盘(统计小卡 + 功能 / 显示与个性 / 数据三组)。正文是 Markdown:点开一条笔记就在内容区里编辑(不是弹窗),顶部标题与日期 / 标签 / 置顶、底部一条格式工具栏(B / I / S / H1 / H2 / 列表 / 编号 / 引用 / 行内代码 / 代码块 / 链接 / 插入图片 / 分割线 / 撤销 / 重做)、右上角 ✓ 保存并返回,另有 编辑 / 分栏 / 预览 三档;图片以插进正文(保存时正文里已删掉的图片会连文件一起回收)。渲染器是页面自带的(小应用引不进依赖):先整段转义再套标记、链接只放行 http(s),卡片摘要则把语法剥成纯文本。导出全部 / 复制全部 / 打开备份设置都在设置页与「更多」菜单里,「AI 助手」(润色 / 续写 / 起标题)钉在同一条工具栏上,没配对话模型只禁用那几个按钮,不影响写正文。- 新增小应用动作
notes.list/notes.save/notes.remove/notes.attach(shared/miniapps.ts的动作清单 +lib/miniapp-bridge.ts的分发分支,两边都夹长度、标签数、附件数、日期形状)与对应的四个 RPC(miniappNotes*,实现在bun/notes.ts)。 - 两条安全规则写在代码里并有回归测试:附件 ref 只认宿主自己生成的形状
notes/<附件 id>/<文件名>.<png|jpg|webp|gif>—— 删笔记要按 ref 反推目录并整目录删掉,形状放松一点就等于给沙箱开了一个"删数据目录里任意目录"的口子(bun/notes.test.ts里notes/xxxxxx/../../omni-studio.db这类输入必须被丢掉);体积与类型在宿主侧再判一次:只收 base64 图片、类型听 mime 不听文件名(x.sh不会带着图片后缀落盘)、单张上限 12MB、长边超 2048 压到 2048 并转 webp(列表网格里放十二张手机原图会卡)。 - 迁移
0035_miniapp_notes(miniapp_notes表 + 日期 / 更新时间两个索引),已登记进scripts/migrations-smoke.ts的关键表清单。 - 备份作用域新增「笔记」(默认勾选):正文表
miniapp_notes与附件根images/notes各自登记 —— 少任何一样都是"恢复到一半":只备份表则恢复后全是碎图,只备份附件则正文全丢。附件没有落进原来的images/(media)根,是因为那一档默认不勾(生成的音视频动辄几个 GB)。回归测试钉住"默认作用域里必须同时含这张表与这个附件根"。
- 新增小应用动作
-
引导页先看清这台机器,再推荐模型:首次启动的本地模式首屏多了一张「这台机器」卡片 —— 芯片(
Apple M3 Ultra/Intel(R) Core(TM) i7-…/AMD Ryzen 9 …)、核数、内存与当前空闲、显卡与显存,以及一个推理可用预算(后面所有"约占多少内存"都拿它当基准)。预算有三个口径:Apple 统一内存取物理内存的 75%(macOS 默认的 GPU wired 上限)、独显取显存的 90%、纯 CPU / 核显取内存的 60%。探测只用系统自带命令 —— macOS 走sysctl machdep.cpu.brand_string与hw.physicalcpu/logicalcpu,只有 Intel Mac 才跑system_profiler拿独显与显存;Linux / Windows 走nvidia-smi;结果缓存在进程里并随getSetupEnvironment一起下发,引导页没有新增 RPC。- 推荐不再是写死的:引擎按机器给(装了 mlx-lm 的 Apple 芯片推 MLX;≥48GB 显存的 NVIDIA 且装了 vLLM 才推 vLLM —— 它的权重只有 bf16、没有量化档,显存不够大时"上 vLLM"反而把能跑的模型砍小一档;其余 llama.cpp),并给出理由("Apple M3 Pro 的 GPU 走 Metal,装一个二进制就能跑")。模型按内存估算挑:权重体积 + KV 缓存(层数 × KV 头 × head_dim × 2 × 2B × 上下文)+ 运行期开销,与预算的比例分四档 —— 列表每行标出「本机流畅 / 本机可用 / 内存偏紧 / 超出内存」和估算占用(顶部写明"按 8K 上下文估算"),量化下拉里装不下的档位直接标红。
- 不动手就按推荐走:进入模型步骤时预选本机最合适的模型与档位(8GB 的 Mac 选 4B / Q4_K_M,16GB 选 9B / Q4_K_M,32GB 选 35B-A3B / Q3_K_M,512GB 抬到 Q8_0,24GB 显存选 27B / Q3_K_M,4GB 的小机器全装不下则保留原选择并标出超出内存);点过任意模型或档位之后,推荐不再覆盖用户的选择。
- 探测失败只降级不报错:读不到芯片名回落 CPU 上报的型号、读不到显卡按「未识别」处理(预算退回内存口径)、
nvidia-smi存在但驱动不通按无独显处理 —— 三种情况都在app.log留一条可排查的记录,而"这台机器没有 nvidia-smi"(Mac / 纯 CPU 机器)是最常见的情况,不写告警。hasNvidiaGpu也从"装没装 nvidia-smi"改成"探测成功才算"。 - 回归测试:
shared/hardware.test.ts(预算口径 / 估算与分级边界 / 挑档与挑模型的规则 / 引擎推荐 /nvidia-smi与system_profiler输出解析,23 条)、bun/hardware.test.ts(Apple 芯片 / Intel Mac / Linux+NVIDIA / 无显卡四条路径用假 runner 各走一遍 + 缓存与失败路径的日志,8 条)、mainview/app/setup-screen/constants.test.ts(真实模型目录 × 九档机器:8GB→4B/Q4_K_M、16GB→9B/Q4_K_M、32GB→35B-A3B/Q3_K_M、64GB→Q5_K_M、512GB→Q8_0、24GB 显存→27B/Q3_K_M…,13 条)、mainview/app/setup-screen/local-flow.test.tsx(首屏文案 → 引擎推荐理由 → 模型列表的适配级别 → 什么都不改时下载的就是推荐的那个仓库与量化文件,4 条)。
-
推理引擎一键安装:不用再去终端复制安装命令。引导页的引擎步骤(以及启动步骤那张「未就绪」卡片)现在直接给「一键安装」按钮,点下去由应用把引擎装好 —— 装完自动重新检测、那一行变成 ✓,日志与进度实时显示在同一个位置(可展开看 pip / 下载的实时输出)。两条路径:
- llama.cpp:下载官方预编译二进制。上游的
releases/latest指向一个只放nightly-tag.txt的稳定标签,没有可用的「latest」可依赖,所以安装时读 releases 列表挑最新的b<构建号>,再按平台 / 显卡在资产名里匹配(llama-b10976-bin-macos-arm64.tar.gz)—— 构建号每次都在变;上游还改过-bin-前缀与.tar.gz后缀,规则逐条降级(GPU 变体拿不到就退 CPU)。有 NVIDIA 显卡的 Linux / Windows 会连配套的cudart-*包一起下(缺了它 CUDA 构建起不来),装完用--list-devices验证 GPU 后端真的能起来,起不来就自动回退 CPU 构建重装;AMD / Intel 独显走 Vulkan。解包 → 补执行位 → 跑--version自证 → 原子落到<dataDir>/engines/llama.cpp/current(macOS 上跑不起来先兜一次 ad-hoc 重签名再判失败)。 - mlx-lm / vLLM / SGLang:在数据目录里建独立 venv 装 pip 包(uv 优先、回退
python -m venv;默认 PyPI 源失败自动换清华镜像重试),装完以「模块导得进来」为准验证,导不进来就把这半个环境删掉,绝不让它下次被判成"已安装"。vLLM / SGLang 官方只发 Linux 的 CUDA 轮子,macOS / Windows 上界面只给原因与手动提示 —— 判定写在shared/engines.ts,按钮与主进程行为读同一份,不会出现"点下去必然失败"的按钮。 - 托管安装优先于 PATH:四个 runtime 的
checkBinary都先看托管目录(llama.cpp 的current/llama-server、Python 引擎的 venv 解释器)再回落Bun.which/ 常见路径,用户自己装过的照旧能用、不接管不删除;getSetupEnvironment新增installSupport/installedVersions/installing,最后一项让界面在重载之后仍显示"安装中"(推送来的阶段随卸载没了,但安装还在跑)。安装过程的日志与阶段经initEngineInstallBroadcast推送(日志 80ms 合批、阶段合并到最新一帧),与 mflux / PaddleOCR / cloudflared 同一条链路与同一套收尾行约定(安装成功:…/… 安装失败)。 - 真机验证(Apple M3 Ultra):llama.cpp 实际跑通(下载
b10976→ 解包 → 验证 →current/llama-server,checkBinary解析为mode: "managed",二进制输出version: 0.4.1-dev (build 10976));mlx-lm 实际跑通(建 venv → 装出0.31.3→VERSION标记 →checkBinary指向托管 venv 解释器、import mlx_lm成功)—— 顺带量出这个包真装起来要十八分钟,「安装过程必须能在后台跑 + 有实时日志」不是装饰。回归测试:shared/engines.test.ts(平台能力判定 5 条)、bun/engine-install.test.ts(装配清单选择 / 下载-解包-验证-落位 / CUDA 失败回退 / 失败不留半个安装 / 上游改名仍能挑对,15 条)、bun/python-engine.test.ts(跳过已装、镜像回退、验证与清理、缺 Python / 缺 python3-venv,13 条 —— 全部用假 runner,不联网也不在测试机上建 venv)、mainview/app/setup-screen/local-flow.test.tsx新增 4 条界面用例(点按钮真的调了 RPC 且装完变成就绪、装不了的引擎不给按钮、安装中的阶段与进度、重载后仍在装)。
- llama.cpp:下载官方预编译二进制。上游的
-
新增「音乐」应用:一句话出歌 —— 两个云端协议各走各的,本地引擎是留好的位置:图标栏新增一级菜单
音乐(排在视频之后),生成页给任务类型 / 风格描述 / 歌词(可留空让上游写,也能开纯音乐)/ 参考音频 / 标题 / 输出格式与采样率,历史页按时间列出作品、可播放与删除(音频落<数据目录>/images/music/,与其它素材一样走固定回环端口的媒体服务)。记录进music_records表(迁移0036_add_music_records:提交参数、上游改写后的歌词 / 描述、任务 id、音频路径、错误)。- 协议是唯一的开关,存在厂商行上的
musicApi(bun/music-gen.ts从不按厂商名分支):stepfun是异步的(POST /v1/audio/music/submit拿 task_id → 轮询/query,音频以 Base64 回),minimax是同步的(POST /v1/music_generation一次阻塞请求直接出音频,hex 与 url 两种回法都认)。同步那条不能在 RPC 里干等 —— 它由后台任务在请求返回后回填记录,界面从processing起照常轮询,于是两条路在界面上长得一样。加一家厂商 = 加一个协议取值 + 一对 submit/poll 实现,设置面、厂商面板与界面选择器都不用动。 - 本地引擎是预留位:设置槽位(
MUSIC_LOCAL_*)、记录字段(localBase)与界面开关都已就位,localUnavailable()会给出可读原因,而不是假装成功一个不存在的后端。 - 回归测试:
scripts/music-gen-smoke.ts用 mock 的两条云端上游端到端跑「提交 → 轮询 / 后台回填 → 落盘 → 记录与删除」,外加协议分派、参数校验与"本地后端为预留位"三块。
- 协议是唯一的开关,存在厂商行上的
-
小应用(Mini Apps):一个新的应用形态 —— 七个开箱小应用 + 应用中心:图标栏新增一级菜单「小应用」。小应用就是一份自包含的 HTML(
src/mainview/miniapps/<id>.html,?raw进sandboxiframe 的srcdoc,不给allow-same-origin),于是它既碰不到宿主 DOM / store / localStorage(不透明源里连读localStorage都会抛 SecurityError),也不需要为每个新应用改构建配置。- 能力由宿主逐个放行:小应用只会说
shared/miniapps.ts里列的那几个动作(生图 / 修图 / 转写 / 一次性文本 / 选文件 / 存文件 / 打开设置 / 记日志 / 笔记四件套),由mainview/lib/miniapp-bridge.ts翻译成具体 RPC。刻意没有"按方法名透传"的口子 —— 那等于把整个 RPC 面交给 iframe 里的脚本;参数按不可信输入处理(长度 / 范围 / 形状两边都夹)。语言与主题随宿主下发(主题直接读<html class="dark">,不在小应用里重算一遍"设置值 + 系统偏好",否则两边迟早算出不同结果)。 - 应用中心(
app/apps/)按分类(图像 / 音频 / 文本)陈列,带搜索(关键词中英都塞,"抠图""换底色...
- 能力由宿主逐个放行:小应用只会说
v0.0.9-canary.0
Added / 新增
-
设置 → 数据 → 使用统计:每一次模型调用都记一笔,按模型与厂商归类:以前只有「概览」页那两个进程内计数器(重启即清零,且只覆盖对话与 OCR 两条路),而真正的大头根本记不下来 —— 外部 agent(Claude Code / Codex / Pi…)经本地网关转发出去的请求不产生任何本地消息,网关对这些请求连日志都不留;内置 Agent 一次回答跨多步,只有回合合计;生图 / 生视频接口连
usage字段都没解析。现在新增账本表usage_records(每次上游请求一行:渠道 / 厂商 / 模型 / 输入 / 输出 / 缓存 / 思考 tokens / 是否估算,day冗余存本地日期,跨时区旅行也不会让历史记录挪一天),写入点覆盖对话(含联网检索的查询改写)、Agent(主循环逐步、子智能体逐步、上下文压缩摘要)、网关(/v1/chat/completions、/v1/messages、/v1/responses)、生图 / 修图、生视频、文档 OCR、翻译;生图 / 生视频没有 token 口径(按秒 / 次计费),照样记一行只计次数,接口回了usage就照实填。统计页画出概要(累计 / 累计调用 / 今日 / 峰值 / 当前连续 / 最长连续)、近一年活动热力图、每日 Token 趋势(按模型分线,超过 6 条并成「其他」)、模型环形图、厂商与调用来源分栏。- 透传不变样:
/v1/chat/completions是纯字节透传,记账只能在流上"顺路"看 —— SSE 的 data 行解出来找usage/ llama.cpp 的timings,其余字节原样转发。多数推理服务(vLLM / SGLang)默认不报用量,所以客户端没主动要时替它补一个stream_options.include_usage,再把上游因此多回的那个只带 usage 的空 chunk 挡在客户端之外:客户端收到的流与不记账时完全一致(回归测试bun/gateway.test.ts的「用量记账」五条,含"客户端自己开了 include_usage 时那个块要原样透传")。 - 口径写在类型里:
input/output是计费口径,cached/reasoning分别是它们的子集,单独存字段、界面上当"其中"展示,不重复累加;上游没回usage时退回本地估算并标estimated,概要里如实报出"其中 N% 为本地估算"。TTS / ASR 按时长计费、没有 token 口径,不进这套统计;向量化与重排也没进 ——embeddings.ts刻意保持零主进程依赖(omiCLI 在应用没启动时直接调它),把用量带出来要改 6 处调用签名,而这些调用通常是本地的、免费的(取舍写在shared/usage.ts的注释里)。 - 聚合口径有单测钉住(
bun/usage.test.ts:连击的两种计时、峰值、占比分母、区间内外、趋势「其他」不漏总量、空库不炸),页面渲染也有回归(mainview/app/usage-screen.test.tsx:中文万 / 亿换算、渠道名走 i18n、空库提示、切区间重新取数)。图表是手写 SVG(与概览页 Sparkline 同一套路,不为四张图引图表库),热力图用 viewBox 等比缩放,窄窗口不会撑出横向滚动条。
- 透传不变样:
-
输入框新增
/omni-doctor:内置排障技能终于有了入口:omni-doctor一直随包分发、启动时播种到中央技能库(src/bun/builtin-skills/omni-doctor),但此前只有模型自己能通过系统提示里的技能清单 +read_skill想到去用它 —— 用户遇到"生图失败 / 服务器起不来 / 应用闪退"时,输入框里没有可敲的东西,只能自己描述症状,还不一定接得上技能。现在/omni-doctor与/init走同一条路子:发一段任务书(先read_skill读技能正文,再按流程用omi status/omi logs --level error -v/scripts/omni-diag.ts取证,每个结论都要能指到一行日志、一条记录或一条命令输出),命令后面跟的那段话按「现象:」原样带下去(/omni-doctor 生图一直失败),不用再打一遍。
Fixed / 修复
-
斜杠命令只在回车那条路上生效,敲完
/goal去点发送按钮会把 "/goal" 当消息发给模型:命令的判定与执行只写在onKeyDown里,发送按钮直接走handleSend—— 同一条/goal,回车切模式、点发送按钮则变成一条发给模型的聊天消息,模型只能一脸茫然地回一句,用户看到的就是"命令执行了但没生效"。现在两条路共用同一个runSlashIfExact()判定。回归测试:app/agent/composer-slash.test.tsx。 -
命令名里带连字符的命令既不在补全面板出现、也执行不了:补全面板与执行判定都把命令名限成
[a-z],连字符进不来,于是/omni-doctor这种名字输进去只会被当成一条普通消息发出去。两处字符集对齐成[a-z0-9-];同时给命令条目加了takesArgs—— 带参数的形式只有声明过的命令才吃(/omni-doctor 生图失败),其余(如/goal 开始干活)仍旧按普通消息发出去,不静默吞掉用户那句话。 -
执行轨迹跑到一半就不再追加记录(库里其实一直在写):轨迹在界面上只靠
agentEvent推送一条条长,而推送不是可靠账本 —— 窗口被系统节流、webview 刷新过、消息在信道里丢了,界面就停在丢消息的那一刻:库里 80 条事件、界面停在 10 条,看起来就是"执行到一半记录加不上",而且不会再自己好(唯一的补救是切走再切回会话时的整份加载)。现在listAgentEvents支持afterId增量(afterId = 0仍是整份),界面在跑动中每 4 秒按已知的最后一条 id 只取新增的那几条并进列表,收尾(running转 false)再补一次:没丢推送时每次返回空数组,几乎不花钱;真丢了就把缺的补上。存储层的mergeEvents按 id 去重排序(同一条可能既走了推送又被追平取回),切会话时在途的那次追平也不会写进新会话。新增单测:bun/agent-events.test.ts(增量只回新增、别的会话不串)、stores/agent.test.ts(合并去重)。 -
产出物卡片不出现:
message_id挂在了 NULL 上:产物登记用的消息 id 是在建工具集那一刻取的(toolsForMode的messageId参数),而会话实例(连同那份工具集)跨回合复用 —— 第一轮里助手消息还没建出来、自动化跑的那一轮同样如此,于是所有产出物都挂空;第二轮起又换成挂在上一轮的消息上。界面上就是"它明明写了报告,消息底下什么都没有"(得去右侧面板里翻)。现在工具回调(产物登记、提问、授权请求、派子智能体)一律在调用时现取当前消息 id,建会话时的值只作兜底;子智能体那条路径仍按父消息钉住。scripts/agent-live-check.ts新增断言"产出物挂在本轮助手消息上"—— 用旧行为跑会红(实测messageId: null),正是用户库里那条 NULL 记录。 -
时间线上画出
agent.tool.memory_search这样的内部 key:记忆工具(memory_search/memory_save/memory_forget/memory_list)一直没有词条,而t()缺键时返回 key,于是工具行直接显示一串内部标识。补上四条中英文文案,并给工具行加了兜底:没有词条的(MCP / 外部工具、以后新加的内置工具)显示工具名本身,而不是agent.tool.xxx。 -
侧栏会话行只有点在文字上才有反应(整行的点击区被
<button>的 fit-content 尺寸缩掉了):会话行是<button>,而<button>即使在display: flex下,width: auto也按内容算 —— 行外面又套着一层定位用的 div(用来装行尾的「更多操作」按钮),拿不到父级 flex 的stretch,于是整行的可点区域就等于标题文字的宽度:点在行右侧的空白处什么都不发生,正在看的那条会话(.pi-thread.active)高亮也只裹着文字。同一条规则还有第二个后果:标题长的行会被撑到内容宽度(实测 320px,而侧栏只有 275px),行尾的「更多操作」按钮被顶到侧栏外面,根本点不着。两处都随.pi-thread的width: 100%一起消失(.pi-menu-item/.pi-notif-item/.pi-search-item早就各自补过这一条,漏的是会话行)。真机命中区在 happy-dom 里量不出来(没有排版引擎),所以回归测试钉住这条声明:styles/agent-pi.test.ts。 -
点发送之后那段干等没有任何反馈(屏幕上只有自己刚发出去的那句话):助手消息的行是第一个增量到达时才在界面上建出来的(
appendChunk见 id 不匹配就新插一条),而按下发送到第一个 token 之间隔着建会话、起推理服务、模型加载、长提示词预填充、知识库检索 —— 实测首 token 2.5~7 秒,本地模型要加载时更久。这段"什么都没发生"的等待与"卡死了"在观感上没有区别;Agent 页更重:它开跑后先调一批工具、再开口,那批工具调用在第一条正文之前完全不可见。现在后端在建好助手行的那一刻(agent.ts的回合开跑、chat.ts发送 / 重新生成 / 翻译 / 语音通话四条路收敛成的insertAssistantMessage)就推一条chatMessageStarted,界面立刻把这条消息画出来:对话页是「生成中… · 已等待 N 秒」(秒数每秒在走,头一秒不报数),Agent 页是「处理中 · N 秒」+ 末尾的转圈行,随后的工具行按发生顺序接在同一行下面。收尾的失败路径(拿不到真实 id 就用时间戳兜底)改成填那一行而不是再挂一条,界面上不会再出现"一个空回复 + 一个报错回复"。回归测试:bun/chat.test.ts(started 早于第一个增量、推的 id 就是最终写回正文的那一行、重新生成同样先推)、stores/chat.test.ts(占位行去重、首字写进同一行、失败填那一行)、message.test.tsx(刚开跑那一屏确实有「处理中 · N 秒」且在走秒)。 -
测试套件的结果随机器核数变化(本机 32 核红、3 核绿、单跑某个文件又绿):
mock.module()的替换是进程级的,而 bun 默认让同一个 worker 连跑多个文件、共享一份 module registry —— "谁先加载谁说了算"。media-setup.test.ts把手写的./cloud-providersfake 注册进去之后,同 worker 里排在后面的文件拿到的是那份 fake;反过来secrets.test.ts要的是真实的./db/settings,被别的文件的 fake(updateSettings是空函数)盖住之后,"落盘是密文"的断言只可能看到旧值。现在bun run test走bun test --isolate(每个测试文件各自一份 global / module registry,理由写在bunfig.toml里),并把两个真实的脆弱点一并修掉:media-setup的云服务商 fake 改成"透传真实模块、只替换读服务商行的那几个函数"(手写清单里漏掉的fetchRemoteModels正是"选了厂商没选模型"两条用例红掉的原因),permissions.test.ts那条转存目录断言自己把目录建出来(isSpillPath按真实路径比对,根目录在盘上不存在就无从解析 —— 之前是靠在别的测试先写过一次转存才绿)。
Changed / 变更
- 时间轴把同类的连续工具调用收成一行(「查阅 · 2 搜索, 1 列表」),子智能体那一行报出类型:一次调研动辄十几次 read / grep,一个调用占一行的话,四十行工具里三十行是 read_file —— 过程把时间轴铺满,模型说的话反而要翻半天。现在相邻且同家族的调用合成一行(查阅 / 联网 / 编辑三族,见
timeline-model.ts的toolFamily):行内按首次出现的顺序报出每种各几次(2 文件, 1 搜索),悬浮给出完整路径,点开还是原来那一行行(各自的 diff / 命令 / 输出,信息一点没少),编辑家族另外报 +/- 合计。断开规则是"只并相邻":中间夹了正文、状态行、授权卡片、终端命令或子智能体就断成两行,顺序即因果;单独一次调用不收组 —— 那一行带着参数(读到哪个文件),收成「查阅 · 1 文件」是净损失。子智能体那一行从「子任务:描述 · N 次工具调用」改成「子智能体 探索 · 找配置」:类型与描述各占一格,subagent_start现在同时记一份结构化参数(老记录回退到从探索:…这段 label 里切),写入格式不再画到界面上。顺带三处降噪:跑完的工具行不再缀绿点(满屏绿点等于没说任何事,只有出错才画点)、思考行跑完不再缀思考原文的截断预览(一行「思考 · 持续了 37 秒」是它的常规形态,预览只在跑动中当"它在想什么"的证据)、消息最上面那行状态铺满整列并压一条发丝线(它是"这一轮"的表头,上面是别轮的历史)。回归测试:timeline-model.test.ts(分组与断开规则)、message.test.tsx(分组行的渲染 / 展开 / 子智能体两种记录)。 - 侧栏每段列表默认只露 5 条,多的收在「查看更多」后面;列表一律新的在最上面:会话一多,侧栏就是一整屏标题,"今天早上那个"得靠翻。「会话」段与每个项目段现在各自只露 5 条(每段各算各的,展开互不影响),段尾一行「查看更多(还有 N 条)」展开全部、再点收起;置顶段与归档段不走这条(前者是用户自己攒的短名单,要的就是一眼看到;后者本来就藏在"归档"折叠后面)。上限成立的前提是顺序:列表按最近更新倒序,排序在侧栏里显式做一遍而不依赖服务端返回顺序 —— 顺序反了,被收起来的就是最新那批,而这种错在界面上看不出来;项目段按"组内最新那条会话"排,所以在哪个项目里新建会话 / 发消息,那个项目就往前排,时间相同时排序稳定、行不会自己来回跳。回归测试:
app/agent/session-sidebar.test.tsx(5 条上限与展开 / 收起、每个项目各算各的、数据回来是旧→新也照样倒序渲染、项目按组内最近活动重排)。 - 消息流改成时间轴:模型说的话与工具调用按发生顺序混排:一条助手消息的正文在库里是整轮拼接的一整块,而工具调用是另一条事件流 —— 界面于是只能画成"所有工具在上、所有正文在下",读起来完全看不出"它先说了一句、再调工具、再接着说一句"这条线(40 次工具调用的一轮里,回看就是一堆命令行堆在开头,正文挤在最后)。现在每个模型步骤说的话在这一步的工具开始执行之前落成一条
text事件(agent.ts的flushStepText,模型循环结束时再冲最后一段):时间轴上就是 正文 → 这批工具 → 正文 → …,顺序与发生顺序一致;消息最上面留一行状态(跑动中「处理中 · N 秒」每秒在走,跑完换成「已处理 · N 秒 · N 次工具调用」,与用量胶囊同源),「思考」行仍在顶部、仍可展开。正文与事件两条来源用前缀对齐:界面按事件画时间轴,只把"还没落成事件的那截"当流式正文渲染(跑动中就是正在写的那段,跑完为空,不会重复渲染);老消息没有text事件 → 整条正文照旧渲染,行为与改动前一致。scripts/agent-live-check.ts新增三条断言(说 → 做 → 说的顺序、text 事件拼起来正是正文前缀、收尾段也落事件),桩服务也补了"先说一句再调工具"的剧本形态。 - 产物统一挂在消息底部,点一下直接在右侧打开预览:产出物过去只在"挂得上消息"时才在消息底下出现卡片(见上一条),落在 NULL 上的那些只能去右侧面板翻。现在按归属渲染:归属某条消息的卡片归各自的消息,没有归属的(
message_id为空的老数据、自动化跑出来的、归属消息已被重跑 / 删除的)一律挂到最后一条助手消息下面,排在它自己的产物之后(规则抽成纯函数artifactsByMessage,带单测,覆盖"最后一条是用户消息"与"一条助手消息都没有"两种情况)。消息底部多了一行「查看所有产物(N)」入口(对齐参考实现的交互):会话里积了几轮的产物时,不必去右上角找面板开关,点它直接在右侧聚焦常驻的产出物页签;卡片点击照旧打开该产物的预览页签 —— 同一个产物重复点只聚焦已有页签,不叠重复页签。
Security / 加密
- API Key 落盘加密(FUT-02,第一阶段):模型云服务与
VLLM_API_KEY的密钥不再明文躺 SQLite。新增bun/secrets.ts(AES-256-GCM,主密钥存<dataDir>/secrets.key,0600 权限):cloud_providers.apiKey在所有写入口(updateCloudProvider/ensureMigrated/ensureAppProvidersMigrated)加密、所有读出口(rowToInfo/ 探测探针 / 回写 settings 的syncActiveSlot)解密;settings.VLLM_API_KEY走 settings 层透明加解密(ENCRYPTED_KEYS+maybeEncrypt/maybeDecrypt),几十处getSetting消费点零改动。新增ensureApiKeysEncrypted()启动兜底扫描:历史遗留的明文 key 在ensureMigrated时一次性加密写回(幂等,EMPTY哨兵与空串跳过不制造密文)。读取侧decryptSecret对旧明文透传,老库兼容。
完整变更日志见仓库 CHANGELOG.md。
v0.0.8-canary.2
Fixed / 修复
- Landlock 辅助程序:规格里
access类型写错会被静默跳过(fail-closed 没兜住):mask_from()
遇到不是数组的access返回 0,而规则循环里if (!allowed) continue把"规格写错了"和
"这条规则在当前内核上没有有效权限(老内核,合法)"合成了同一个出口。于是一份坏规格
("access":"write_file")不是被拒绝,而是 handled 里留着write_file、放行规则一条没加 ——
Landlock 的语义是"handled 里的权限默认全拒,只放行规则里给过的路径",结果整条命令的写操作
全被拒:命令照跑、退出码来自 sh 自己(重定向失败是 2),用户看到的是莫名其妙的 EPERM,
而本文件开头写的"看不懂规则就拒绝执行命令"被静默违反。现在只有"缺 access"沿用宽容处理,
类型不对一律拒绝执行。这是新增的 Linux 沙箱作业(真内核 + 真编译器)抓出来的:同一次
运行还暴露了好几条"只在 macOS 成立"的断言(/dev/pts、/dev/shm在 macOS 上不存在,
于是整段断言被静默跳过;landlockReady留给本机探测又让"Linux 一定不支持"在装了 gcc 的
runner 上必然失败)—— 都改成注入式、与宿主无关。 - Linux 沙箱端到端把 FUSE 的控制接口当成了 FUSE 数据挂载:检测用的
/^fuse/会匹配到
fusectl(挂在/sys/fs/fuse/connections),而那不是能在上面放工作区的数据文件系统 ——
于是 CI runner 上"不兼容样本"选中了它,接着断言"canary 在这里必须失败",可读取/sys
本来就被规则允许(根目录给了 read_dir / read_file),canary 必然通过:一个假样本制造出
两条永远红的用例。现在精确匹配真数据文件系统(fuse/fuse.<name>/fuseblk/
fakeowner)并显式排除fusectl;另外补了一条确定性用例(canary 指向打不开的路径时
必须如实拒绝)—— 原来那条依赖宿主机恰好有 FUSE 挂载,CI 上没有,等于"不兼容文件系统 →
别硬上"这条分支在 CI 里从来没被走过;单测里也用注入 runner 钉住了这段 plumbing。
v0.0.8-canary.1
v0.0.8-canary.0
v0.0.7-canary.0
Added / 新增
- 全局备份 / 恢复(设置 → 数据 → 备份与恢复):把云端模型配置与 API Key、本地技能、提示词、聊天记录、记忆库、知识库与本地生成的音频 / 图片 / 视频按作用域打包成一个
.omnibackup文件(gzip + tar,内含VACUUM INTO数据库快照与manifest.json清单),换机或重装后一键恢复。界面支持逐项勾选并显示体积 / 条数预估、选择保存位置(含可用空间校验)、剔除明文密钥(便于把备份发给别人排错)、压缩开关、恢复前预览来源机器与内容、恢复时的实时进度与取消、自动生成pre-restore-*.omnibackup回退点,以及备份记录列表(恢复 / 定位 / 删除)。未勾选的作用域既不进体积也不留残页(表按secure_delete删除后VACUUM),恢复按表整表替换(列取交集,兼容旧版本备份),文件同名覆盖且不删除备份里没有的文件。 - 备份加密(密码保护):创建备份可设置密码(AES-256-GCM + scrypt,流式加密,密码不落盘);加密归档里连清单都读不到,没密码只能看到文件名与体积。容器头部带 keyCheck,密码错误立即报明确错误而不是解出乱码;GCM 认证 + 头部 AAD 保证被截断 / 篡改的归档一定报错(顺带修掉了明文 gzip 归档"截断到 tar 结束标记仍算读成功"的静默问题)。CLI 用
--password/--password-file,界面有密码框与"忘记密码=数据打不开"的提示。 - 备份远端存储(S3 兼容 / WebDAV):设置 → 数据 → 备份与恢复 新增「远端存储」,可配置 S3 兼容对象存储(AWS / Cloudflare R2 / MinIO / 阿里云 OSS / 腾讯云 COS,自实现 Signature V4,无需 SDK)或 WebDAV(坚果云 / Nextcloud / 群晖,Basic 认证),带「测试连接」「创建后自动上传」「上传后删除本地文件」;远端备份列表可直接下载并恢复,CLI 有
omi backup remote list|test|download与omi backup create --upload。网络请求带超时(元数据 60 秒 / 传输 20 分钟上限),不会无限挂起。 - 备份默认值调整:生成的音频 / 图片 / 视频(
media)与知识库向量改为默认不备份(体积大、可重算),配置 / 聊天 / 提示词 / 技能 / 记忆仍默认备份;勾选项按「配置与记忆 / 内容 / 大文件」分组展示,每项带体积与条数预估。 omi backupCLI:list/create/inspect/restore/remote子命令,与界面共用同一套内核(src/bun/backup/);--scopes选择作用域、--out指定目录、--redact剔除密钥、--json供脚本消费,omi help backup、omi guide、docs/omi-cli.md与设置页「命令行」页同步更新。该内核刻意不 import 数据层与 electrobun,因此应用没启动、甚至数据库迁移失败起不来时也能把数据备份出来(scripts/backup-smoke.ts专门用一个坏库验证了这一点);restore需要独占数据库,应用在运行时会拒绝并提示改用应用内页面。- 在线模型市场 · 双平台检索:检索新增「平台」维度——ModelScope(modelscope.cn)与 Hugging Face(优先国内镜像 hf-mirror.com,失败回退 huggingface.co),按钮上直接标出真正请求的域名;检索、列仓库文件、下载字节三件事走同一平台,结果行 / 模型详情 / 下载任务 / 本地模型列表统一打来源徽标。HF 侧按下载量排序并过滤 private 与需登录的 gated 仓库(401/403/404/451 立即报明确错误,不再换域名空等一轮超时);分页改为每页 20 条「加载更多」,ModelScope 显示真实命中总数,HF 无总数接口只如实显示「已加载 N 条」;平台与格式选择存全局 store,进详情页再返回不重置。
- 在线模型市场 · 格式筛选:新增「跟随引擎 / 全部 / GGUF / safetensors / MLX」筛选,默认跟随当前引擎对应格式(llama.cpp→gguf、vLLM/SGLang→safetensors、MLX→mlx,换引擎即换格式)。格式只认平台元数据(HF 的
tags/library_name/ siblings,ModelScope 的library:*/custom_tag:*)与仓库实际文件后缀,不再从模型名里猜(名字带 GGUF 不再参与判断);HF 走服务端filter=,ModelScope 检索接口实测忽略一切过滤参数,改为把格式词并进检索词并按返回标签二次确认,界面文案说明两边差异;元数据缺失的仓库不会被筛掉。 - 模型详情 · 文件与下载同源 + 整仓库下载:文件区新增「文件与下载来源」切换(默认取发现该模型时的平台,另标「原始来源:X」),列文件与下载严格同源,避免同一仓库两边路径不同导致的「列表里有、下载 404」;GGUF 按单文件下载,safetensors / MLX 这类仓库型模型的「下载整仓库(N 个文件)」会连同
config.json/ tokenizer 等加载必需文件一起下;"已下载"判断改为按文件名(basename)比对,兼容 HF 的BF16/xxx.gguf子目录路径。 - 本地模型 · 三类来源与目录管理:本地模型列表把「应用下载目录」「用户添加的目录」「Hugging Face 官方缓存」合并为一个列表,每行带来源徽标(应用下载 / 本地目录 / HF 缓存)、下载平台徽标与「整仓库」标记,顶部可按来源筛选并显示各来源计数,可「在文件夹中显示」。新增目录管理器:列出三类目录各自的模型数与占用体积,「添加目录」走系统选择器并先扫描预览(模型数 / 总体积 / 前 5 个文件,认不出模型不允许添加;应用自身目录、HF 缓存目录与已存在目录会被拒绝),移除只从列表摘掉、不删磁盘文件。扫描不要求标准目录结构(任意深度、文件直接放根目录、HF snapshot 指向 blobs 的符号链接都能认,隐藏文件跳过),HF 缓存按
models--org--repo聚合为「整仓库」一行,并尊重HF_HOME/HUGGINGFACE_HUB_CACHE。 - 本地模型 · 仓库型模型可加载:vLLM / SGLang / MLX 的仓库型模型(目录内有
config.json)激活时记录并加载整个仓库目录(单个 safetensors 分片加载不了),GGUF 仍指向文件本身;复制出的启动命令与实际启动共用同一套运行时目标解析,两者一致。目录型条目的权重格式按目录内容判定,不再拿目录名当文件名猜扩展名。 - Agent 素材工具:内置 Pi Agent 新增
media_search(关键词 / 类型 / 来源 / 最近 N 天检索素材库,默认 12 条上限 50,结果带日期、提示词与绝对路径,并附素材库总量与其中 Agent 生成数量)、media_export(把素材复制进工作区按相对路径引用,自动防重名、拒绝越界)、generate_image(1–8 张,支持宽高 / 比例 / 负向提示词 / 种子 / 以图改图,可复制进工作区)、generate_speech(audio.cpp → 三方 Provider → 免费 Edge 在线依次回退,单次上限 5000 字)与generate_video(提交后每 5 秒轮询,默认等 10 分钟、上限 30 分钟,超时或中断会明确告知产物稍后可被检索,不要在回答里假定已完成)。生成类工具会写文件且可能产生云端费用,只在 Agent / Goal 模式注入;media_search只读,Plan 模式也可用。 - Agent 生图「需要用户介入」弹窗:Agent 调
generate_image前检查生图后端是否就绪(缺 Base URL / ComfyUI 地址 / 未选模型 / MLX 引擎未装或权重未下载),不满足时弹出全局配置窗(任何页面都能弹):可切换 OpenAI 兼容 / MLX / ComfyUI 三个后端(各带就绪状态点)、填地址与 API Key、「扫描模型」拉候选(ComfyUI checkpoint 或/v1/models)、MLX 可直接装引擎并在窗内下载权重(带进度)。「确认并继续生图」后同一次工具调用继续跑且选择落盘到「图像」页配置;取消 / 关闭 / 超时 10 分钟 / 停止 / 会话重置都会立即收尾并明确告诉模型不要自行重试;同一时刻只保留一个弹窗,无界面监听(CLI / 无人值守)时按取消返回不挂起。 - 网关素材接口(只读)+ MCP
media_search:网关新增GET /v1/media(q/kind=image|video|audio/source=manual|agent/days/limit,返回含绝对路径与可播放 URL 的结构化列表),MCP 端tools/list在知识库与记忆之外新增media_search,Claude Code / Codex / Cursor 等外部智能体经网关即可查到并复用本机素材;与内置 Agent 共用同一份检索实现,鉴权与/v1/*一致,OpenAPI 已补端点说明。接口只读 —— 生成与导出仍只由界面或内置 Agent 触发。 - 素材来源标注(手工 vs Agent):
image_records/video_records/voice_records新增source列(迁移0024_media_source,默认manual),界面手工生成记为manual、内置 Agent 与经网关生成记为agent;图片与视频历史新增「全部 / 我生成 / Agent 生成」筛选,Agent 生成的卡片与侧栏记录显示「Agent 生成」徽标(手工生成不加标签,避免视觉噪音)。 - 设置 · 命令行手册页:设置 → 工具 → 命令行,把
omi的完整用法搬进应用——安装启用、启动应用与推理服务器、模型加载与切换、共享记忆(CLI / stdio MCP / HTTP MCP / REST)、编码工具(code)加载、引擎依赖与版本检查,每条命令与记忆接入片段都可一键复制(MCP / REST 片段里的网关地址取自当前设置);内容与omi guide、docs/omi-cli.md同源(src/shared/cli-docs.ts),中英双语跟随界面语言。 - 架构文档:新增
docs/architecture.md—— 面向维护者的结构说明:进程模型与五类进程边界、主进程各层(RPC / 推理运行时 / 模型库 / 智能层 / 媒体管线)、对外接口面(网关 / 图片服务 / 控制 socket 及端口与鉴权)、前端与 CLI 架构、数据层与目录布局、四条端到端数据流、不变量清单与已知架构债。
Changed / 变更
- 网关文档:OpenAPI 补充
/v1/memories、/v1/media端点与/mcp工具说明(总述改为「对话协议 + TTS / ASR + 共享记忆 + 本地素材库」);/v1/models聚合不变。 - 模型下载:下载面板每个任务都显示来源平台徽标(此前无法分辨字节从哪个站拉取);下载完成后把分类与来源平台写入仓库目录的
.vllm-meta.json,本地模型列表据此显示「从哪儿下的」(没有记录的老数据不显示来源)。 - 首次本地模型安装向导:列文件与下载统一走 ModelScope(此前列文件走 ModelScope、下载却写死 hf-mirror 镜像,两边文件名不一致时会出现「列表里有、下载 404」),向导中明确标注「文件与下载均来自 ModelScope(modelscope.cn)」。
- 模型分类识别:同时识别 ModelScope 的
task:*标签与 Hugging Face 直接放进 tags 的 pipeline tag(含 VLMimage-text-to-text归为对话),命名启发式补齐 deepseek / glm / mistral,减少落入「其它」;市场与详情页的格式徽标改为按平台元数据展示。 omi帮助体系与手册:新增omi guide(纯文本 /--md/--json/--lang en)打印完整手册(安装、启动、模型加载、记忆调用、编码工具加载),docs/omi-cli.md由同一份数据源生成(omi guide --md,scripts/omi-docs-smoke.ts校验命令表、帮助文本与文档三者同步);omi help支持子命令与工具级帮助(omi help memory add/omi help launch claude/omi server help logs),omi memory <子命令> -h等价;总览补齐此前遗漏的memory、guide与常用示例,omi launch --list与错误提示指向对应帮助。- 国际化:中英双语词条补齐模型市场 / 素材来源标注 / Agent 生图配置弹窗 / 本地模型目录管理约 450 行。
- 文档口径对齐:
ROADMAP.md完成度重估(生图闭环 / 视频生成 / 知识库 / 记忆 / MCP / Skills / 下载持久化 /omi launch等已落地项从"未启动"移入已完成,vLLM / SGLang 一键安装与实测、内存生命周期、平台支持改为按实际状态标注,并注明scripts/backlog.tsv是一次性导入载荷、看板状态以 GitHub Projects 为准);AGENTS.md补齐遗漏的memory/guide命令、Agent SDK 与 MLX 引擎,并新增「Hard Rules」一节固化跨进程边界约定;README 中英双份的技术栈表补上 Agent SDK 与 MLX、修正残留的omni提法,并挂上架构文档入口。
Fixed / 修复
- 备份恢复:归档可以把文件写到任意目录(安全):技能中央库是唯一不受数据目录约束的文件根,而恢复写回文件时按「恢复后的设置」重新解析它的落地目录 —— 那份设置来自归档本身。于是一个做过手脚的备份只要把
SKILLS_CENTRAL_PATH指向$HOME(或~/Library/LaunchAgents),再带上data/files/skills-repo/.zshrc,就能在用户从未授权的位置覆盖任意文件;而这正是文档推荐的「把备份发给别人排错」场景。现在落地目录只认恢复前本机设置里的值:归档只能决定写哪些文件,不能决定写到哪个根,两处路径不一致时给出提示。 - 备份恢复:离谱的 scrypt 参数能让进程吃光内存:KDF 的 N / r / p 写在归档头部(外部输入),
unlock()直接喂给scryptSync,而maxmem又是按 N×r 算出来的,内置护栏永远不会触发 —— 一个 147 字节的文件声明N=2^30就能让进程去申请 1 TiB,N=2^28直接把线程挂死。现在打开归档时就按「单次派生 ≤ 256 MB」校验参数并拒绝,明文报「密钥派生参数不合法」。 - 备份恢复:归档声明的条目长度能撑爆内存:读取长度来自 tar 头,一个 61 KB 的 gzip 声明清单有 64 MiB,预览就要 1.2 GiB 内存、3 秒(128 MiB → 1.9 GiB / 11 秒,而备份列表会对每个文件都做一次),原因是读取时逐块
Buffer.concat(O(n²))。现在单次读取有 8 MiB 上界,且攒够再拼一次。 - 备份恢复:数据库已提交后整次恢复仍可能失败:
rename失败一律退回复制,而目标是个目录(EISDIR)或源文件已被重复条目搬走(ENOENT)时复制同样失败,于是「数据库回来了、文件一个没写」。两个条目归一化到同一路径(a/../b与b)现在会在解包阶段识别并跳过后者,写回失败的单个文件改为记入警告继续(数据库事务此时已提交,不该让整次恢复失败)。 - 备份恢复:新机器上恢复"成功"但什么都没恢复:目标库不存在时,整表替换会对每张表判定「本机没有表」全部跳过,最后报告写回 0 条记录 0 个文件。恢复只做替换、不建表(内核刻意不依赖数据层,拿不到那批迁移),所以现在直接报错并提示「先启动一次应用让它建库,或改用应用内页面」,不再给出假成功。
omi backup restore无法用交互输入的密码恢复加密备份:提示输入的密码只用于重新inspect,传给restoreBackup的仍是原来的undefined(另有一行effectivePassword算了却从没用过),于是终端里只有--password/--password-file能用。现在输入的密码会回流到恢复调用。omi backup delete能删任意文件:目录白名单取自调用方同时传入的dir,把目标文件的父目录当dir传进来就绕过了守卫,且不校验扩展名。现在除目录边界外还要求「确实是备份文件」(.omnibackup扩展名 + 备份魔数),非备份文件一律拒绝并说明原因。- 远端下载中断会在最终文件名上留下半截备份:下载直接写目标路径、不校验长度,短包只会照常返回,流中断后列表里就多出一份「损坏的备份」(取消下载同样如此)。现在先写
.part、核对 content-length 后原子改名,失败即清理。 - S3 兼容存储列取备份必然 403:
ListObjectsV2的签名用了去掉尾部斜杠的路径,而真正发出的请求仍带斜杠 —— SigV4 下 canonical URI 必须与请求逐字节一致(AWS 不做路径归一化),真实 S3 会直接拒绝;只有测试用的假服务端不校验 canonical URI 才没暴露出来。现在签名与请求共用同一个 path,查询串也改用同一套编码(URLSearchParams会把空格编成+,而签名用%20,带空格的 prefix 同样对不上)。 - 「创建后自动上传」是死开关:
autoUpload会被保存、会渲染成开关,但没有任何代码读它,用户打开它之后备份并不会自动上传(文档还写着它会生效)。现在创建备份时真的会读它;恢复前自动生成的pre-restore-*回退点除外(就地兜底用,推远端既不符合预期,也会让恢复多受一次网络波动影响)。 - 备份清单缺字段会让恢复页白屏:预览只校验格式与版本,缺
db/tables/scopes的清单能通过预览,随后在恢复面板渲染时抛TypeError(整页空白),恢复本身也以Cannot read properties of undefined收场。现在这些字段在预览阶段就校验并提示「文件可能已损坏」。 - 创建备份时传入的文件名可以越出目标目录:
fileName来自 webview / CLI 且未净化,../../x会把归档写到所选目录之外;现在只取 basename(缺扩展名时仍自动补.omnibackup)。 - 手册漂移无人拦截:
scripts/omi-docs-smoke.ts校验命令表 ↔ 帮助文本 ↔ 数据源 ↔docs/omi-cli.md四者同步,但它此前既不在test:smoke列表里、CI 也不会执行,文档漂移事实上不会被发现;现已纳入test:smoke,随 CI 一起跑。 - 在线模型市场 · ModelScope 分页总数读错字段:检索接口返回的是
total_count,此前读total导致「共 N 条」永远等于当前页条数、加载更多判断错误;现显示真实命中总数并正确分页(Hugging Face 本就没有总数接口,改为如实显示「已加载 N 条」而不是编造总数)。 - 模型详情「已下载」误判:已安装列表登记的是文件名,而 HF 仓库常见
BF16/xxx.gguf这类子目录路径,此前用完整路径比对导致已下载的文件仍显示成可下载;现统一按 basename 比对。 - 删除本地模型静默失败:此前删除吞掉错误、只能删单个文件且无越界校验,用户看不到任何反馈;现在返回明确错误与原因并在行内展示,目录型条目按整目录删除、HF 缓存整条
models--org--repo删除(否则只删软链一个字节都不释放),非白名单路径一律拒绝并给出说明。 - 复制出的启动命令与实际启动不一致:仓库目录型模型实际是整目录加载,而复制命令仍按文件名猜引擎并把文件路径交给运行时;现在两条路径共用同一套运行时目标解析,"复制的命令"和"实际启动的"一致。
- Hugging Face 检索结果里的私有 / 受限仓库:此前 gated(需登录并接受协议)与 private 仓库也会列出,用户点了下载才撞 401;现在列表阶段直接过滤,且 401/403/404/451 立即抛出明确错误而不是换个域名再等一轮超时。
- 冒烟脚本不可重复运行:
memory-smoke/mcp-smoke/omi-docs-smoke用固定名字的临时目录且从不清理,第二次运行时memory-smoke的计数断言(列表 2 条 / 检索命中 / 删除后剩 2 条)会读到上一轮残留数据而失败,"重跑一遍 test:smoke 就红";现统一改为mkdtempSync建一次性目录并在结束时清理(与kb-*/video-gen冒烟脚本一致),调用方显式传OMNI_DATA_DIR时仍保留现场。
Internal / 内部
- 新增迁移:
0021_tense_dragon_man(补messages/agent_events/knowledge_*的会话与外键索引)、0022_memory_lifecycle(memory_events/memory_metrics与记忆状态 / 指纹 / 作用域索引)、0023_kb_governance(kb_events/kb_ingest_jobs与文档来源路径索引)、`0024_med...
v0.0.6-canary.0
[0.0.6-canary.0] - 2026-09-12
Added / 新增
- AI 视频生成(新应用):左侧图标栏新增「视频」应用,三种后端统一为「提交任务 + 轮询」异步模式——MiniMax(H3,云端,支持首帧图生视频)、Seedance(火山方舟内容生成任务 API)、ComfyUI(本地工作流);5 秒轮询任务状态,成片落盘后进历史库(新表
video_records,迁移0019_add_video_records),结果区可直接播放 / 下载 / 删除,参数面板支持提示词、负向提示词、分辨率、时长、种子与首帧图上传。 - Skills 管理(新应用):图标栏新增「Skills」应用,中央技能库(默认
~/.agents/skills)统一管理并同步到各编码工具;六区界面:技能市场(skillssh 榜单 + 一键安装 / 批量导入)、我的技能(启用 / 分组 / 标签 / 批量操作)、预设(技能集合一键套用到多个 Agent)、项目(按项目目录管理技能)、工具(53 个内置工具适配器 + 自定义工具 + 路径覆盖)、备份(Git 远端 + PAT、自动快照、快照列表);支持 symlink / copy 两种同步模式、技能文档查看、审计日志与元数据同步。 - 知识库 / 本地 RAG(新应用):图标栏新增「知识库」应用——数据源摄取(本地文件(文本直读,PDF / 图片走 VLM OCR)、手写笔记、网页抓取)、Markdown 感知切片(标题分节 + 段落贪心打包 + 超长硬切带重叠)、可选向量化(OpenAI 兼容
/v1/embeddings,Float32 base64 存在分块行)、混合检索(BM25 关键词与余弦向量各自排序后 RRF 融合,不依赖外部向量库或 FTS 扩展);四个标签页(召回测试、文档、访问、设置);对话界面挂载知识库后回答带 [n] 引用溯源(迁移0017_knowledge_base,引用随消息落库)。 - 知识库 · 重排序(Rerank):每个知识库可配置 Jina / SiliconFlow / Cohere 兼容的
/v1/rerank二次排序模型(模型 / Base URL / API Key 三项,可从服务端拉取模型列表);混合检索的候选按重排得分再次排序,召回落点标注「已重排」与相关性得分;未配置时保持原序,功能自动退化。 - 记忆层(新应用 + 全 Agent 共享):图标栏新增「记忆」应用;Agent 经
memory_search/memory_save/memory_list工具沉淀事实 / 偏好 / 经验 / 技能,与手工录入同库(迁移0018_strong_corsair);置顶与高热记忆作为「常驻核心记忆」注入 Agent 系统提示(MEMORY_ENABLED总开关);记忆对外三条通道——网关 REST/v1/memories(GET 检索 / POST 写入 / DELETE 删除)、网关 MCPmemory_*工具、omi memory add|search|listCLI;omi launch启动编码工具时自动刷新工具上下文文件(CLAUDE.md / AGENTS.md)的托管区块,并给 Claude Code / Codex / OpenCode 挂载omni-memoryMCP 服务器(在应用外用memory_save实时写回同一个库)。 - MCP 客户端与调试工作台:设置页新增「MCP」工具组,支持 stdio(换行分隔 JSON-RPC)/ Streamable HTTP / 旧版 SSE 三种传输的手写客户端(不引入 SDK,避免 Electrobun 自定义 Bun 运行时的 node 兼容层风险),服务增删改查、连通检测与工具枚举;已启用服务器的工具以
mcp_*注入 Agent(连接失败的服务器自动跳过,Plan 模式不注入有副作用的工具);网关同时提供 MCP 服务端——POST /mcp(Streamable HTTP,无状态),对外暴露知识库kb_search/kb_list与记忆memory_search/memory_save/memory_list,浏览器GET /mcp打开单文件调试工作台(连接 → 枚举工具 → 按 inputSchema 生成表单 → 调用 → 看原始 JSON-RPC)。 - 模型云服务重构:云端厂商配置从设置键迁移到
cloud_providers表(迁移0015_slippery_vulture)——多服务商配置并存、单一「激活」,激活行的 Base URL / API Key / 模型列表同步写回VLLM_API_BASE/VLLM_API_KEY/CLOUD_MODELS等旧槽位,网关、chat-model、omiCLI 与集成模型选择器零改动;设置页改为参照 Cherry Studio 的三栏面板(厂商列表 / 配置详情 / 模型),内置 20 家厂商预设(16 家彩色品牌 Logo,OpenAI / Anthropic / Gemini 用官方单色 path,未收录的回退字母徽章),并新增「默认模型」页集中指定各用途的默认模型;旧数据(CUSTOM_PROVIDERS/CLOUD_MODELS)首次读取时自动迁移入表。 - 实时仪表盘(重做):
server-stats替换为新的仪表盘页——吞吐 / 速度(tok/s)、请求数、活跃模型、内存 / CPU 负载、运行时长与模型磁盘占用(statfs读数据目录所在卷的可用 / 总容量),每 2 秒轮询。 - 主题与更新检查:新增
UI_THEME设置(system / light / dark,跟随系统并监听变化,作用于<html>的.dark类)与AUTO_UPDATE开关;「关于」页新增版本与 GitHub Release 检查(匿名 API 结果缓存 10 分钟避免限流,按通道比较版本并提示更新,可一键跳转下载)。 - OCR · PP-OCRv6 本地引擎(PaddleOCR):新增第三套本地 OCR 引擎,走 ONNX / PaddlePaddle CPU 装入独立 venv(
userData/engines/paddleocr),主进程启动常驻 Python worker(ppocr-worker.py,JSON-lines stdio 协议),模型加载一次常驻内存、识别不阻塞界面;内置 PP-OCRv6 medium 档(约 140 MB,34.5M 参数)一键安装与首载自动下载,安装日志与加载 / 识别阶段实时推送到界面,全程离线无需 API Key。 - OCR · 三引擎补全与模型详情:Tesseract(一键安装 + 多语言 LSTM 语言包)/ PaddleOCR / VLM 三个引擎页签补齐引擎状态、安装与下载进度、识别记录;模型详情改为原地打开(不再跳页)。
- 翻译 · 同传翻译:翻译页新增「同传翻译」——打开麦克风实时转写(复用 whisper.cpp / audio.cpp / OpenAI 兼容三套 ASR 引擎),并同步输出多种目标语言译文同屏滚动。
Changed / 变更
- 工具页布局统一:OCR / 图片 / 翻译 / 语音等工具页统一为「侧栏工具入口 + 左参数面板(引擎切换 / 配置 / 输入 / 主操作)+ 右结果区」,替换原先各自为政的页内切换方式。
- 设置页按组重构:单文件设置页拆分为偏好组(通用 / 外观)、工具组(MCP / 记忆 / 联网搜索 / 云服务 / 默认模型)与「关于」页,配套抽出共用表单组件(
setting-ui.tsx)与厂商图标表(provider-logos.ts)。 - 导航:应用图标栏新增视频 / Skills / 知识库 / 记忆四个入口,Agent 图标改为
CircuitBoardIcon;各应用按统一工作台布局(左侧参数面板 + 右侧结果区)排布。 - 对话:发送消息可挂载知识库(
kbIds)并在重新生成时复用检索;assistant 消息新增citations字段承载引用溯源。 - 网关文档:OpenAPI 补充
/v1/memories、/mcp端点说明;/v1/models聚合不变。 omiCLI:新增omi memory(add/search/list/mcp)——应用运行时走控制 socket(memoryAdd/memorySearch/memoryList),未运行时直连 SQLite;omi help memory有完整用法。- SQLite 并发:数据库启用 WAL、
busy_timeout=5000与synchronous=NORMAL,支撑omi memory/ MCP 桥接在应用之外直连同一个库读写。 - 媒体分发:图片服务器为视频容器补全 MIME(
.mp4/.webm/.mov/.mkv返回video/*,成片可用<video>播放)。 - 国际化:中英双语词条补齐新应用与设置页(
shared/i18n.ts新增 1255 行)。
Fixed / 修复
- 迁移 0013 在老库升级时被跳过:drizzle 以「库内已记录的最大
created_at」判断是否跳过迁移,而0013_uneven_lester的when小于前一条0012,导致从旧版本升级的用户(库内最大when已被后续迁移抬高)不会建出user_prompts表,「我的提示词」功能直接报错;现将其when调整为严格递增区间内,并把该迁移改写为幂等 DDL(CREATE TABLE IF NOT EXISTS/CREATE INDEX IF NOT EXISTS),使「已建表 / 曾被跳过 / 已升到最新」三种库都安全。 - 知识库向量补齐:
embedDocChunks内改为循环外复制一份配置对象(原写法在循环中展开累加,且可能污染调用方传入的对象)。
Internal / 内部
- 新增迁移:
0014_talented_network(Skills 预设工具开关preset_skill_tools)、0015_slippery_vulture(cloud_providers)、0016_lean_turbo(mcp_servers)、0017_knowledge_base(knowledge_bases/knowledge_docs/knowledge_chunks)、0018_strong_corsair(memories)、0019_add_video_records(video_records)、0020_kb_rerank(knowledge_bases增加重排模型 / Base / Key 三列)。 - 新增主进程模块:
video-gen.ts、cloud-providers.ts、mcp.ts、mcp-playground.ts、kb-mcp.ts、knowledge.ts、memory.ts、memory-api.ts、memory-sync.ts、release-check.ts、skills/(13 个文件:中央库 / 安装器 / 同步引擎 / 扫描 / 元数据 / 预设 / 项目 / 审计 / 备份等)。 - 新增前端:
video-screen.tsx、dashboard-screen.tsx、memory-screen.tsx、kb/(6 个文件)、skills/(9 个文件)、设置页各组面板与stores/{video,kb,memory-ui,skills}.ts。 - 新增脚本:
apps/studio/scripts/migrations-smoke.ts(journal 单调性 + 全新库建表 + 重复打开幂等;本次正是它先暴露出 0013 的when倒挂)。 - README 界面预览截图更新(模型云服务 / 编码工具集成 / 语音实时对话 / TTS / 模型选择向导),中英两份 README 同步重写。
- 依赖:无新增运行时依赖(MCP 客户端手写、向量检索纯 JS、调试工作台单文件无 CDN)。
Full Changelog: v0.0.5-canary.0...v0.0.6-canary.0