有些画面只有几秒,却让人想反复回看:海风吹起头发的那一刻,孩子突然笑起来的瞬间,或是旅行途中刚好遇见的一束光。视频记录了过程,照片留下了定格;而 Live Photo,让这两种记忆可以在同一张照片里相遇。
Vimemo 实刻是一款面向 iPhone 和 iPad 的视频转实况照片工具。挑选一段视频,选好想保留的片段和封面,就能把熟悉的动态瞬间整理成 Live Photo。

学习日志 LIUGUOFENG
有些画面只有几秒,却让人想反复回看:海风吹起头发的那一刻,孩子突然笑起来的瞬间,或是旅行途中刚好遇见的一束光。视频记录了过程,照片留下了定格;而 Live Photo,让这两种记忆可以在同一张照片里相遇。
Vimemo 实刻是一款面向 iPhone 和 iPad 的视频转实况照片工具。挑选一段视频,选好想保留的片段和封面,就能把熟悉的动态瞬间整理成 Live Photo。

环境:DeepSeek Harness Desk 0.3.37(运行时 dsh 0.1.5-rc.2,内置 Node 24.19.0),底层 LLM 库
@earendil-works/pi-ai0.85.1,接入 OpenCode Go(Console Go)网关。
一天之内踩了两个坑:先是所有请求返回 400MissingSessionID,修好之后又发现自定义接入的deepseek-v4.1-flash不能看图。两个问题的根因都不在报错指向的地方。这篇把排查路径、验证手法和最终配置完整记录下来。
OpenCode Go 是 OpenCode 提供的订阅制网关,端点统一在 https://opencode.ai/zen/go/v1/,按模型分三类协议:/chat/completions(DeepSeek、GLM、Kimi 等)、/messages(MiniMax、Qwen)、/responses(Grok、GPT 等)。
在 Harness 里,模型不由内置列表决定,而是写在用户配置 ~/.dsh/settings.yaml 的 llm-pi-ai.providers 段里。每个 provider 的路由键(下文的 opencode2)是配置字典的 key,模型列表是它的 models 数组。
因为 Harness 内置的 opencode-go catalog 里没有 deepseek-v4.1-flash(三个 DeepSeek 条目分别是 deepseek-v4-flash、deepseek-v4-flash-vision-exp、deepseek-v4-pro),所以我们用一个自定义路由键 opencode2 手动声明它。
这个「自定义路由键」的决定,是后面两个问题共同的伏笔。
对话请求全部失败:
{
"type": "MissingSessionID",
"message": "Error from provider (Console Go): Request is missing x-opencode-session and cannot be routed efficiently."
}
先去查官方文档,得到一个关键线索:OpenCode 把使用方分成 validated clients 和 known problematic 两类,而 DeepSeek Harness 被明确列在后者,备注是「session info missing on some model paths」——也就是说这是已知缺陷,不是我们配置错了。
社区讨论帖(deepseek-ai/deepseek-harness#5495)把机制讲得更清楚:网关从某日起要求每个推理请求带一个稳定的 x-opencode-session 头,用于把同一会话的请求路由到同一后端,以保持 prompt cache 热着。而 Harness 的请求链路
dsh-agent-loop → dsh-llm → dsh-llm-pi-ai
直接调用 pi-ai 的 streamSimple,绕过了上游 pi-coding-agent 里负责合并归因头的 mergeProviderAttributionHeaders,于是任何会话身份都没能到达 adapter,自然也没进请求头。
讨论帖里列了 6 种解法,按口碑排序,社区插件 dsh-opencode-session 是其中最干净的一档:它监听 Harness 的 llm/stream 事件,在匹配的 provider 上把一个进程内 AsyncLocalStorage 存起来,然后 patch 全局 fetch,在请求真正出网时把头合并进去,值复用 Harness 本来就带着的会话 ID。
检查本地发现插件其实已经装好了——但我们还是 400。原因出在插件的默认配置上:
// 插件源码
const DEFAULT_PROVIDERS = ['opencode', 'opencode-go']
插件默认只对路由键 opencode / opencode-go 注入头,而我们的路由键是自定义的 opencode2,根本不在匹配范围内。装了等于没装。
Harness 的插件树由 Cordis 加载器按 patch 层组合,每层是一个 YAML 数组。用户层在 ~/.dsh/profiles/web/cordis.patch.yml。我们在这一层按 id 定向覆盖插件那一行的配置,把路由键补进去:
- id: opencode-go-session-header
config:
providers:
- opencode
- opencode-go
- opencode2 # 我们自定义的路由键
mode: session-id
debug: false
这里有个容易踩的坑:读加载器源码(applyEntryPatches)后发现,id 定向 patch 的合并语义是
for (const [key, value] of Object.entries(overrides)) {
target[key] = value // 整字段替换,不是深合并
}
所以 config 是整块替换的。如果只写 providers,原本的 mode / debug 会连同整块 config 一起消失。必须写全。
这次没有靠「改完重启试试」,而是顺着数据流一路验证:
composeEntries(而非自己写个 YAML 合并)跑一遍「插件 bundle 层 + 我们的用户层」,确认最终行的 providers 里确实有 opencode2。dsh --dump-config --profile web,输出里能看到加载器自己标注的 # ... patched by /Users/.../cordis.patch.yml,并且 providers 已含 opencode2。debugFile,改动落盘后该文件出现了 36 条记录,全部是 provider: opencode2 的真实调用,每条都带上了 x-opencode-session,值就是会话 ID。顺带确认了一件省事的事:cordis.patch.yml 是被 Cordis HMR 热监听的(watchUserPatches),文件一改,加载器会重组整棵 patch 栈并事务性重新应用——不需要重启服务。
deepseek-v4.1-flash 接进来了,但传图片时模型看不到内容,附件也像是不可用。
Harness 判断一个模型能不能看图,靠配置里的 input 字段(PiAiModality,取值 text / image)。而模态的解析有三层优先级:
模型条目的 input → 该路由的 defaultInput → 兜底默认
关键在于:第一层和第二层的「继承」只对 pi-ai 内置 catalog 里的 provider 生效。我们的路由键 opencode2 不是 catalog provider,模型条目又没有声明 input,于是直接落到第三层兜底 defaultInput: [text]——视觉能力就这么被关掉了。
实测最能说明问题。我们用一个模块探针(后面细说)对同一份真实配置做前后对比:
声明 input 前:deepseek-v4.1-flash | input: ["text"]
声明 input 后:deepseek-v4.1-flash | input: ["text","image"]
动手改配置之前,先要回答一个前提问题:这个模型到底支不支持图片? 因为如果网关本身不支持,声明 input: image 只会让模型在拿到图之后中途报错,比看不到图更糟。
官方文档说 deepseek-v4.1-flash 是纯文本,四个 DeepSeek 模型里只有 deepseek-v4-flash-vision-exp 处理图片。但文档可能滞后,于是直接实测。
第一次测试是错的。我发了一张纯色图问「什么颜色」,三个模型(含公认的纯文本模型)全都答对了 "Red"。这说明这个测试没有区分度——纯色图靠猜都能中,所以结论无效。
于是设计了一个猜不中的对照实验:在白色背景上画一个具体的数字(用点阵字体渲染),问「图里是什么数字」,并加一组不带图的对照组。
结果非常明确:
deepseek-v4.1-flash 带图 → "7"
reasoning_content: "The image shows a pixelated digit that looks like a 7.
The top horizontal bar and the diagonal descending
stroke are characteristic of the digit 7."
换一个数字 4 再测一次,同样答对,推理过程还准确描述了「左侧竖笔画、横笔画」的结构。
结论:网关实际上是支持视觉的,官方文档过时了。 问题纯粹在 Harness 侧的配置声明。
在 ~/.dsh/settings.yaml 的模型条目下补上模态声明:
opencode2:
displayName: Opencode
apiKeyEnv: OPENCODE2_API_KEY
api: openai-completions
baseURL: https://opencode.ai/zen/go/v1
models:
- id: deepseek-v4.1-flash
name: deepseek-v4.1-flash
input: # 新增
- text # 新增
- image # 新增
改完后用 dsh-llm-pi-ai 导出的真实 Config schema 校验通过;再用适配器内部的 resolveRouteModels 对真实 settings 做物化,确认输出是 ["text","image"]。
settings.yaml 由 dsh-settings-file 用 chokidar 监听(watch 默认开启,源码注释写明「external edits hot-publish through the seam」),所以同样是热生效,不用重启。
复盘下来,两个 bug 本身都不复杂,但有几点思路值得单独拎出来。
400 是网关抛的,但根因在客户端的请求构造路径;「没有视觉」表现为功能缺失,但根因是一行配置声明。如果盯着报错的地方找,会一直在错误的方向上打转。
这次能快速收敛,很大程度是因为没有靠猜:
composeEntries,不是自己写的近似实现;resolveRouteModels,直接看物化结果。自己复刻一套逻辑去验证另一套逻辑,只会验证你的复刻。
内部函数没导出时,有个干净的办法:把模块复制到临时目录(软链运行时的 node_modules),在副本末尾追加一行导出,然后 import 这个副本。既拿到了内部函数,又不碰原始安装目录。
这是本次最有价值的一条教训。第一版视觉测试(纯色图)看起来通过了,但没有任何信息量——因为对照组(公认的纯文本模型)也「通过」了。
有效的测试必须满足:如果是阴性结果,它不可能碰巧通过。改成「读一个具体的数字」之后,纯文本模型无法靠猜满足条件,结论才立得住。
任何时候测试全绿都值得再问一句:这个测试有没有可能因为太简单而全绿?
官方文档白纸黑字说那是纯文本模型。实测证明它支持视觉。文档描述的是某一时刻的状态,而网关在持续迭代。涉及「某个端点到底行不行」这类事实问题时,一次实测胜过十页文档。
cordis.patch.yml 和 settings.yaml 都支持热更新,省去了重启。但反过来,没有重启这个明确的分界点,就需要更主动地去确认「改动真的被读进去了」。这次用的办法是:
debugFile 有没有新记录;--dump-config 看加载器的实际组合结果;lsof 检查进程持有的文件句柄 inode——如果磁盘上文件的 inode 和进程打开的 inode 不一致(原子写会换 inode),说明进程的缓存副本可能还是旧的,需要触发一次变更事件让它重读。input 字段的本质是声明,不是探测。源码注释把取舍说得很清楚:没有接口能问网关「你支持哪些模态」,所以只能靠配置声明。而两种猜错的代价不对等——
所以正确处理是:对确实验证过支持视觉的模型精确声明,而不是图省事给整个 provider 加宽泛的 defaultInput。
改动只涉及两个文件,都在用户目录下,没有碰 App 安装目录。
~/.dsh/profiles/web/cordis.patch.yml(修 400)
# 给 dsh-opencode-session 插件补上自定义路由键 opencode2。
# 注意:id 定向 patch 整块替换 config,mode/debug 必须重写。
- id: opencode-go-session-header
config:
providers:
- opencode
- opencode-go
- opencode2
mode: session-id
debug: false
~/.dsh/settings.yaml(修视觉)
llm-pi-ai:
providers:
opencode2:
displayName: Opencode
apiKeyEnv: OPENCODE2_API_KEY
api: openai-completions
baseURL: https://opencode.ai/zen/go/v1
models:
- id: deepseek-v4.1-flash
name: deepseek-v4.1-flash
input:
- text
- image
dsh plugin --profile web remove dsh-opencode-session 卸掉插件并清空 patch 文件。opencode2 这种自定义路由键依赖配置里显式声明,内置 catalog 一旦收录了 deepseek-v4.1-flash,就可以改用 catalog 默认值、省掉模态声明。grep -r "KEY" 配置文件 这类命令很容易把密钥整行带进终端历史或日志里。排查认证问题时用 grep -oE "^[A-Z_]+:" 之类只取字段名,或者干脆打码。如果已经发生了,记得轮换密钥。最近,我重新整理并更新了个人网站首页:www.liuguofeng.com。
如果你喜欢 DeepSeek Harness 的工作方式,却不想每次都打开终端、配置运行环境,DeepSeek Harness Desk 提供了一个更顺手的选择:把 Harness 放进一个简洁的桌面窗口里,并补上菜单栏/系统托盘、通知和运行状态管理等日常使用体验。
Continue reading DeepSeek Harness Desk:更轻松地在桌面上使用 DeepSeek Harness
把整套迁移流程封装成 Windows 一键程序:自动检查数据、导出 Markdown、清理网页外壳、压缩图片、创建目录、上传并核对云端结果。
5009 条为知笔记、44499 个图片资源:从 Markdown 导出、75% 图片压缩,到使用官方 YoudaoNote CLI 导入、回滚错误数据和最终云端对账。
Nacos 2.5.2 在 macOS M1 上的内存下限实测:500MB 降不动
Nacos 2.5.2 standalone 模式默认只设了堆 -Xms64m -Xmx128m,Metaspace 和 DirectMemory 完全没上限,实际 RSS 在 500MB 左右。想压更低,试了一圈,结论是:500MB 基本是硬下限。
踩过的坑
试过把堆降到 96m,运行 70 秒后 OOM 退出,没有 heapdump,静默挂掉。试过把线程栈压到 192k,JDK 11 在 macOS arm64 上直接拒绝启动——最低 208k。试过 Metaspace 上限设 128m,启动到 Derby 加载阶段就挂,因为 Nacos 光启动就要 140-150MB 的类元数据。
用 NMT 拆解 RSS 构成
开启 -XX:NativeMemoryTracking=summary 后用 jcmd VM.native_memory summary 逐项看,500MB 的去向很清楚:
| 区域 | 占用 | 说明 |
|---|---|---|
| Metaspace | 93MB | 16351 个类,Nacos 体积决定的硬需求 |
| Java Heap | 123MB | Xmx 128m 几乎满,96m 运行时不够 |
| 线程栈 | 67MB | 227 个线程,macOS 强制 208k/线程下限 |
| Symbol 表 | 19MB | 类加载的副产品,不可调 |
| Code Cache | 28MB | JIT 编译代码 |
| Direct Memory | ~70MB | gRPC/Netty 堆外内存 |
| JVM 自身 + 共享库 | ~50MB |
几乎全是 native 内存,JVM 参数设上限只能防止失控,降不了实际占用。
最终配置
startup.sh 第 95 行 standalone 分支改为:
JAVA_OPT="${JAVA_OPT} ${CUSTOM_NACOS_MEMORY:- -Xms64m -Xmx128m -Xmn32m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=192m -XX:MaxDirectMemorySize=96m -XX:+UseSerialGC -Xss256k -XX:ReservedCodeCacheSize=64m}"
每一项的作用:-XX:+UseSerialGC 替掉 G1GC 省 GC 元数据;-Xss256k 从默认 1m 砍到 1/4;MaxMetaspaceSize=192m 和 MaxDirectMemorySize=96m 封顶防失控;ReservedCodeCacheSize=64m 从默认 240m 降到 64m。
实测 RSS 仍在 500MB 左右,但这套参数封住了所有可膨胀的 native 内存区域,长期运行不会因为 Metaspace 或 DirectMemory 无上限而持续增长。
还想往下压的三条路(超出 JVM 调参范畴)
startup.sh -m standalone -f naming 只跑服务发现,跳过配置中心,实测 RSS 降到 485MB,省 50MB,代价是 config 不可用。底线:Nacos 这种 Spring Boot 重应用,光 JVM 自身结构 + 类元数据 + gRPC 堆外就要 400MB+,这是物理约束。想突破 500MB 得从减少加载模块入手,不是调参能解决的。
1. 备份原文件
cp nacos/bin/startup.sh nacos/bin/startup.sh.bak
2. 改 standalone 分支
编辑 nacos/bin/startup.sh,找到第 95 行(standalone 分支):
# 原始(只有堆)
JAVA_OPT="${JAVA_OPT} ${CUSTOM_NACOS_MEMORY:- -Xms64m -Xmx128m -Xmn32m}"
# 改为
JAVA_OPT="${JAVA_OPT} ${CUSTOM_NACOS_MEMORY:- -Xms64m -Xmx128m -Xmn32m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=192m -XX:MaxDirectMemorySize=96m -XX:+UseSerialGC -Xss256k -XX:ReservedCodeCacheSize=64m}"
只改 standalone 那行,集群分支(第 101 行)不动。
3. 重启验证
sh nacos/bin/shutdown.sh
sh nacos/bin/startup.sh -m standalone
# 等 1 分钟,确认 HTTP 200
curl -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8848/nacos/
4. 如果另一台不是 M1 而是 Intel Mac
参数完全一样,但线程栈下限不同,Intel 上 -Xss256k 可以更低。如果还想压,试 -Xss208k,不过省不了多少。
5. 如果另一台是 Linux/Windows
-Xss256k 可能可以降到更激进(某些 Linux 平台最低 160k),但也省不了几 MB,不值得折腾稳定性。
macOS chronod 占用 100% CPU 的排查与修复
现象:活动监视器中 chronod 长时间占用 100% 以上 CPU,重启系统后仍然存在。
进程路径:
/System/Library/PrivateFrameworks/ChronoCore.framework/Support/chronod
chronod 主要和 macOS 小组件、通知中心、App Intents、时间线数据同步有关。日志中如果反复出现这些关键词,通常说明它卡在小组件同步或缓存处理上:
com.apple.chrono
NotificationCenter
ReplicatorServices
remoteArchives
widget-relevance-cache
可先尝试关闭 iPhone 小组件:
系统设置 -> 桌面与程序坞 -> 小组件 -> 使用 iPhone 小组件
然后重启相关进程:
killall chronod 2>/dev/null
killall NotificationCenter 2>/dev/null
如果仍然高占用,清理 chronod 缓存和数据库,让系统自动重建:
mkdir -p ~/Desktop/chronod-backup
killall chronod 2>/dev/null
killall NotificationCenter 2>/dev/null
mv "$HOME/Library/Caches/com.apple.chrono" \
"$HOME/Desktop/chronod-backup/com.apple.chrono-$(date +%Y%m%d-%H%M%S)"
mv "$HOME/Library/Group Containers/group.com.apple.chronod/chronod" \
"$HOME/Desktop/chronod-backup/chronod-$(date +%Y%m%d-%H%M%S)"
killall chronod 2>/dev/null
killall NotificationCenter 2>/dev/null
如果执行时提示 Operation not permitted,需要先给终端完整磁盘访问权限:
系统设置 -> 隐私与安全性 -> 完全磁盘访问权限
给 Terminal 或 iTerm 授权后重新执行。
这个操作会重建小组件和通知中心相关缓存,一般不会影响应用数据。部分小组件可能需要重新加载或重新添加。
在 Windows 上,我们可以通过 NTFS 压缩功能让文件“原地变小”而不改变使用方式。macOS 虽然底层文件系统(APFS/HFS+)也支持透明压缩,但系统并没有提供图形界面开关。
applesauce 是一个命令行工具,可以调用 macOS 原生压缩能力,实现对文件的透明压缩——压缩后的文件仍在原位置,双击正常打开,但磁盘占用空间显著减小。
一键在 Chrome 侧边栏打开 DeepSeek,支持页面缩放并记忆缩放比例。
https://chromewebstore.google.com/detail/deepseek-sidebar/gakblhcadiegnapiajgolefjhhmicobi
git clone https://github.com/misswell/deepseek-sidebar.git
chrome://extensions├── manifest.json # MV3 扩展清单
├── background.js # Service Worker,处理图标点击
├── sidepanel.html # 侧边栏页面
├── sidepanel.js # 缩放控制逻辑
├── rules.json # 移除 X-Frame-Options 响应头
├── privacy-policy.html # 隐私政策
└── icons/ # 扩展图标
├── icon16.png
├── icon48.png
└── icon128.png
| 权限 | 用途 |
|---|---|
| sidePanel | 在 Chrome 侧边栏中展示 DeepSeek 页面 |
| activeTab | 点击扩展图标时获取当前标签页以打开侧边栏 |
| storage | 本地保存用户的缩放比例设置 |
| declarativeNetRequest | 移除 DeepSeek 的 X-Frame-Options 响应头,使其可在侧边栏中加载 |
| host_permissions | 访问 chat.deepseek.com 以实现上述头部修改 |
本扩展不收集任何用户数据。详见 隐私政策。
MIT