Repository navigation
Releases: NeonMedusa/SkyNet
Releases · NeonMedusa/SkyNet
Release list
v16
feat(ai): 思考回传(interleaved reasoning)全链路 + 估算取大 + 压缩降级阶梯 + 失败救援 ## 思考回传(能力表驱动) - 能力表 interleaved 字段(reasoning / reasoning_content / reasoning_text):assistant 消息有思考发内容、无思考发空串兜底(对齐 opencode "always set the field");未标记 模型不回传;模型表重新生成(含 interleaved 字段) - DB 加载与 worker 实时两路都带 reasoning;序列化测试覆盖 标记/空串/user-tool 不带 ## 估算与发送同口径 → 取大(防漏压自锁) - messageRequestBytes 带 count_reasoning:仅当前模型回传时计入 reasoning(压缩触发/ 保留区选择/tokens_before 同源) - 预检估算 = max(账本口径, 全量字节口径):账本 = 上次真实 usage + 增量(可能口径过期, 锚点带 provider+模型标签);字节口径永远按"当前要发什么"算——哪个大用哪个 - 修掉实测自锁:v15→新版迁移后锚点"模型相符"但口径过期(66.7 万 vs 实际 590 万)→ 不触发压缩 → 22.97MB 请求被网关掐流 → 4 连败 - 状态栏显示同口径(估算胜出加 ~ 前缀) ## 压缩(摘要)请求:默认带思考 + 阶梯降级 - 默认把思考作为 [thinking] 文本段喂给摘要器(对齐 pi/opencode;查证两家摘要提示词 均无"思考怎么处理"指引 → 不额外加) - 超预算阶梯:剥思考 → 工具加狠截断(2000→500 / 500→200)→ 从最老端丢消息 (上一轮摘要始终保留、至少保留最后一条);预算按"1 token ≥ 3 字节"保守换算 - 摘要请求仍失败(重试耗尽)→ 剥思考重试一次 ## 失败救援(P2 泛化) - 重试耗尽的失败(不管原因、不认错误码)→ 强制压缩一次并重发整请求;只救一次防死循环 - 实测本网关超限不给错误体(HTTP 200 后直接掐流)→ 旧版"只认错误体"永远不会触发 - 错误体可识别为"输入超限"时保留更具体的日志/提示 ## 体验与修复 - 发起前自动压缩改在 worker 首轮前异步执行:不再冻结界面 30~100 秒、消息不再滞留输入框 - ask 最终回答按 DB 行 id 兜底(发起前压缩重建历史后基线失效 → 曾 exit=4 无输出) - 诊断:cache 请求指纹 / endpoint 标识 / 首折日志 - 文档:development §2.3、reasoning-retention(调研 + 决议落地)、cache-miss-analysis ## 验证 - 231/231 测试(新增:思考序列化、估算取大、摘要阶梯五档、溢出识别等) - 卡死会话实测自愈:估算取大(5.56M)→ 触发压缩 → 带思考 20.8MB 超预算 → 剥思考 1.48MB(455k tokens)→ 摘要成功 → 重建 → 56 条消息小请求 → 回复落库 ✅
v15
feat(ai): 思考回传(interleaved reasoning)全链路 + 估算取大 + 压缩降级阶梯 + 失败救援 ## 思考回传(能力表驱动) - 能力表 interleaved 字段(reasoning / reasoning_content / reasoning_text):assistant 消息有思考发内容、无思考发空串兜底(对齐 opencode "always set the field");未标记 模型不回传;模型表重新生成(含 interleaved 字段) - DB 加载与 worker 实时两路都带 reasoning;序列化测试覆盖 标记/空串/user-tool 不带 ## 估算与发送同口径 → 取大(防漏压自锁) - messageRequestBytes 带 count_reasoning:仅当前模型回传时计入 reasoning(压缩触发/ 保留区选择/tokens_before 同源) - 预检估算 = max(账本口径, 全量字节口径):账本 = 上次真实 usage + 增量(可能口径过期, 锚点带 provider+模型标签);字节口径永远按"当前要发什么"算——哪个大用哪个 - 修掉实测自锁:v15→新版迁移后锚点"模型相符"但口径过期(66.7 万 vs 实际 590 万)→ 不触发压缩 → 22.97MB 请求被网关掐流 → 4 连败 - 状态栏显示同口径(估算胜出加 ~ 前缀) ## 压缩(摘要)请求:默认带思考 + 阶梯降级 - 默认把思考作为 [thinking] 文本段喂给摘要器(对齐 pi/opencode;查证两家摘要提示词 均无"思考怎么处理"指引 → 不额外加) - 超预算阶梯:剥思考 → 工具加狠截断(2000→500 / 500→200)→ 从最老端丢消息 (上一轮摘要始终保留、至少保留最后一条);预算按"1 token ≥ 3 字节"保守换算 - 摘要请求仍失败(重试耗尽)→ 剥思考重试一次 ## 失败救援(P2 泛化) - 重试耗尽的失败(不管原因、不认错误码)→ 强制压缩一次并重发整请求;只救一次防死循环 - 实测本网关超限不给错误体(HTTP 200 后直接掐流)→ 旧版"只认错误体"永远不会触发 - 错误体可识别为"输入超限"时保留更具体的日志/提示 ## 体验与修复 - 发起前自动压缩改在 worker 首轮前异步执行:不再冻结界面 30~100 秒、消息不再滞留输入框 - ask 最终回答按 DB 行 id 兜底(发起前压缩重建历史后基线失效 → 曾 exit=4 无输出) - 诊断:cache 请求指纹 / endpoint 标识 / 首折日志 - 文档:development §2.3、reasoning-retention(调研 + 决议落地)、cache-miss-analysis ## 验证 - 231/231 测试(新增:思考序列化、估算取大、摘要阶梯五档、溢出识别等) - 卡死会话实测自愈:估算取大(5.56M)→ 触发压缩 → 带思考 20.8MB 超预算 → 剥思考 1.48MB(455k tokens)→ 摘要成功 → 重建 → 56 条消息小请求 → 回复落库 ✅
v14
feat(model-table): 内置模型能力表 + 每模型思考档位 + junction 修复 + 新建会话误报修复 ## 模型能力表(src/model_table.zig,编译进程序) - 919 条(effort 362 / toggle 557):上下文窗口 + 思考档位 + off 表达方式 - 生成:tools/gen_model_table.zig(Zig 无依赖)+ tools/model_corrections.json (实测修正层,永远优先于 models.dev 数据;含预设自检——新预设忘了加映射会报错) - 表外模型:全档位可选 + 菜单灰色提示"不在能力表中,请自行尝试"(不拦截) ## 思考档位:表驱动 + 每模型记忆 + 钳制 - 删除旧硬编码档位表(只认 flash 的 needle 启发式) - 每模型档位记忆(config.json 的 thinking_by_model;顶层 thinking 保留为默认值) - 未选过强度的模型:全局档位解析到该模型支持的最高档(显示与发送一致) - 发送前钳制:档位不在可用集时降档(先往高找、再往低找;未知模型放行) - off 表达方式查表:qwen 系发 reasoning_effort:"none",其余不发该字段 - CLI -thinking:一次性覆盖(优先于每模型记忆);表外档位放行 + 提示 - 菜单动态高度(按当前模型档位数);状态栏 think: 显示走每模型取值 ## 上下文窗口 - 取值链:model_overrides > provider 配置 > 能力表 > 128k 兜底 - 删除旧 needle 硬编码表(mimo 曾兜底 128k 错值,现为精确 1,048,576) - config.json 新增 model_overrides(用户覆盖档位/窗口,最高优先) ## junction 修复(paths.zig,scoop persist 场景) - 探测先行:已存在(含 junction)直接采用;Zig createDirPath 对 junction 误报 NotDir 导致旧实现退回 cwd(scoop 安装后数据没落 persist 的根因) - 新增 junction 回归测试(mklink 真实场景) ## 新建会话误报"外部压缩"修复 - 根因:newSession 未复位 last_compaction_id → 新会话首轮 worker 落库引起 data_version 变化 → 轮询把 cp_id=0≠旧会话 id 误判为外部压缩 → 清屏重载+误报 - 修复:newSession 复位压缩/窗口化游标;轮询只认 cp_id > 上次(防御性加固) - 新增回归测试:残留游标不误报 + 真外部压缩仍识别 ## 诊断日志(流式观感问题定位用) - 流式消息创建 / 首块思考 / 首块正文到达(回合开始后 ms)——区分"上游 TTFT 慢" 与"客户端卡"(实测 mimo TTFT 3~7s,之后 1~2s 内涌完,属上游缓冲特性) - 外部压缩检测 WARN(带 cp_id 对比) ## 文档 - README/user-guide:数据目录、模型能力表、每模型档位、model_overrides 说明 - architecture:新增 model_table.zig / tools 条目 测试:221/221 通过(新增 config 往返、junction、档位解析、误报回归等测试)
v13
从此版本起,数据库和配置文件移动至 根目录/data/ 中,旧版本用户请手动移动文件完成迁移
v12
fix(stream): 空闲看门狗治半开连接卡死(全路径覆盖)+ 压缩重试 + 缓存 0% 显示 背景:TCP 半开(服务端不发也不关)时阻塞读永久挂住,生产实测会话卡死 92 分钟无恢复;截断检测/重试/Ctrl+Q 全部无法触发(Zig http.Client 只有 连接超时,无流式读超时)。 ## 空闲看门狗(netShutdown 唤醒阻塞读) - ai.zig 新增 watch_slot(句柄/打点/唤醒标记/互斥,宿主注入): streamMessage 与 listModels 连接建立/重定向后发布 socket 句柄, 读循环每块数据打点(首字节前等待也计入) - 唤醒用 netShutdown(.both):Windows AFD 的 receive-only 断开不完成 挂起的读(实验实测 .recv 挂起 47s+ 无效、.both 毫秒级唤醒) - 读失败归类:取消优先 → 看门狗唤醒按截断(可重试)→ 否则网络错误 - 发布/清除在锁内且先于连接关闭(defer 顺序),杜绝句柄复用误伤 ## 覆盖路径(阈值 stream_idle_timeout_ms,默认 180s,测试可注入) - 流式主请求:主循环每帧 + CLI ask 循环每轮泵 - 手动 /compact(异步):主循环泵(守卫扩到 isCompacting) - 工具轮中途压缩:主循环泵(守卫扩到 compact_live_running) - 同步压缩(阻塞主线程):runCompaction 临时起专用看门狗线程 - 模型列表拉取:独立槽位(菜单可在生成/压缩中打开,与流式并存)+ 专用线程 - Ctrl+Q:卡死时立即唤醒(此前完全无效);压缩取消同样生效 ## 压缩流重试(与主流同策略) - 手动/中途/同步三条路径均可重试网络错误/截断/瞬时故障;上限 3 次 (stream_retry_max 2→3,同步主流),指数退避 + 抖动 - 重试前握手:请主线程丢弃失败尝试的气泡与缓冲并回 ack(10s 兜底自清) - 压缩失败且无气泡时补一行失败提示(此前静默无提示); finalizeCompaction 补 drain(末段增量不丢) ## 状态栏:cached=0 显示完整段(方案 B) - 此前 cached=0 时 "缓存 X/Y" 段被隐藏,命中率与前段粘连为 "think:max0%" - 现在统一为 " · 缓存 0/141.5k 0%",与命中态格式一致(纯服务端真实值) ## 顺带修复两处潜伏 bug(此前 mock 未启动、相关测试处于 skip 未暴露) - createStreamJob:effectiveWorkDir 与 default_cwd 均空时回退进程当前目录 (否则 ls 工具拿到空路径触发断言崩溃) - 集成测试 usage 断言同步 89edfa6 新语义(保留最后一轮真实值而非回合累计) ## 测试 - 新增 test/mock_halfopen.ps1(发部分 SSE 后挂起不关;20s 读超时安全网, 看门狗失效时测试失败而非挂起) - 新增 6 个看门狗测试:半开唤醒、Ctrl+Q 立即生效、手动/中途/同步压缩、 模型列表拉取(均断言唤醒计数 >0 防假阳性;压缩断言 ≥2 证明重试链路) - 全量 216/216 pass(全部 mock 就绪,0 skip);ReleaseSafe 构建通过
v11
feat(usage): 上下文占用/缓存命中改为纯服务端真实值 + 每轮实时刷新
背景:此前启动显示 chars/4 估算(~ 前缀,低估中文约 18%),且只在回合
结束时更新一次;用户关心 90% 自动压缩窗口的判定准确性。
## 显示层去估算(数据源 = 服务端最后一次真实回传)
- seedUsageFromDb:加载/切换会话时读最后一条带 usage 的 assistant 行
(db.lastUsageRow 小查询),直接应用 input/cached/output 原样(无估算)
- 新会话/无数据:归零显示(0%),切换会话时先归零(不残留上一会话值)
- 删除状态栏 "~" 前缀与 "缓存 — 暂无数据" 占位;无数据时显示 "缓存 0%"
- refreshEstimatedUsage 删除(无生产调用点)
## 每轮请求实时刷新(不再等回合结束)
- 新增 usage_update 流事件:worker 每轮请求成功即推真实 usage
- 主线程收到即刷新 last_usage/context_usage(生成过程中状态栏持续更新)
- finalizeStream 不再用累计值覆盖(多轮求和会虚高),保留最后一轮真实值
## 压缩决策:每轮刷新锚点(精度提升,机制不变)
- 锚点从"回合开始设一次"改为"每轮请求成功后刷新":
此刻 job.history 即刚发出的请求前缀,anchor = {len, 真实 input}
- 估算 = 上一轮真实值 + 仅本轮新增(误差从整回合增量压缩到单轮增量)
- 结构原因:服务端只在处理请求时计数,本轮回复/工具结果天然滞后一轮,
该段只能估算(锚点机制必要;纯 chars/4 的全量估算误差 167k 会超过余量)
## 测试(198 pass)
- 更新:新会话归零、播种原样显示、显示与锚点解耦、缓存 0% 分支
- 新增:每轮刷新后锚点=全历史时估算=真实值本身;追加后仅加单轮增量
v10
fix(display): 长消息尾部锚点时视口跨界消息被误卸载——绘制空白 症状(用户报告):滚动到长消息(展开的大思考块 / 超长正文)附近时, 视口出现一大片空白(内容消失),继续滚动后"自己变好";伴随上下文/ 缓存统计短暂消失。用真实会话复现并定位。 根因:updateMessageWindow 的前向覆盖计算 keep_hi 用**整条消息的 span** (从消息开头算)去凑"4 屏 + 一屏",漏了 anchor_row。锚点位于长消息 (如 894 行的消息)尾部时: - keep_hi 被截断在 anchor+1(复现:anchor=354 → keep_hi=355) - 视口实际跨界到的后续消息被误判为"区间外"卸载 (复现:[REPRO3] 19370 在视口内但被卸载) - 绘制时按冻结行数占位却画不出内容 → 空白 - 锚点移开后重新覆盖 → "自己变好" 修复:keep_hi 从**锚点剩余行**起算(a_span - min(anchor_row, a_rows)), 再向后累计。修复后同场景 keep_hi=372(正确覆盖跨界消息)。 验证: - 新增回归测试(真实库 894 行消息):锚点放尾部,断言视口跨界消息 保持加载;修复前失败、修复后通过 - 帧转储:修复后在长消息 5 个位置渲染,非空行 32~39/40,无空白 - 179 pass(含新回归测试) 另注:样本中"上下文/缓存统计消失"是状态栏在 usage 重置后短暂走 "暂无数据"分支(预期行为),非同因。
v9
perf(display): 锚点滚动 + 窗口裁剪 + 加载常数化——会话规模与资源占用解耦 长会话(15.8k 行)的加载/滚动/内存全面常数化:从"全量建显示 + 全量驻留" 改为"锚点定位 + 窗口驻留 + 双向按需回拉"。 滚动模型(锚点取代偏移): - scroll_offset(距底部行数)→ (anchor_msg, anchor_row)(视口首行位置) + follow_tail(贴底跟随态);滚动不再依赖 total_lines(无需全量行数统计) - 删除全部偏移补偿逻辑(流式追加/工具块重建/物化行数变化/思考展开): 视口上方/下方内容变化不影响锚点指向的内容——"滚动不跳变"成为结构性保证 - 绘制从锚点起向下,凑满视口即停(不再全量扫描消息) 窗口裁剪 + 双向回拉(消息结构体也常数化): - trimWindow:以锚点为中心保留上下各 4 屏 + 500 条,超出部分连 Message 结构体一起丢弃;pullHistoryAbove/Below 在接近边界时按 db_id 回拉一批 - trimmed_above(下标→全局偏移)/ tail_has_more(尾部是否还有被裁历史) - 底部钳制:锚点下方不足一屏时回拉或吸附跟随态(修复上滚再下滚露白) 加载常数化: - loadSessionLazy 只读尾部 600 条元数据(不再全量读+建壳再裁); 上方条数用 COUNT(*) 记入 trimmed_above,滚动回拉时按需补 - 回拉到压缩摘要行时重建黄色标题(findCompactionBySummary) 清理: - 删除死代码 scrollOffsetCompat / addLoadedToolDisplay / addLoadedToolCallNote / atTail / preserveViewOnAppend / loadDisplayMeta(全量) 等,及 6 处无用行数计算 - display.zig 注释更新(disp_header 从未落库) 实测(真实 15.8k 行会话,ReleaseFast): - 启动内存 16.9MB → 13.8MB;加载 ~200ms(保留区行 ~117ms + 历史重建 ~74ms + 显示构建 ~23ms,前三者均不随会话总规模增长);首帧 ~1ms - 持续 PgUp 5 分钟 / 9367 次:内存 16.5 → 18.4MB(有界,不增长) - 上滚到顶(4539 帧):数组始终 ~500 条(显示层内存 ~80KB,常数) 测试:178 pass(含"上滚后再下滚不越过内容末尾"回归测试)。 文档:architecture.md / development.md / AGENTS.md 已同步(含踩坑教训)。
v8
perf(session): 长会话懒加载显示——加载/内存不随会话增长(schema v9) 背景:15.8k 行会话加载 ~2.4s(Debug),其中全量构建显示 ~1000ms + 首帧全量计行 ~410ms;42.8MB 思考内容 + 10MB 正文被完整物化后,98% 随即被窗口化卸载——白做。 核心改动(方案 B:真·按需加载): - schema v9:message 新增 disp_kind/disp_bytes/disp_lines/has_reasoning(由 insertMessage 经 computeDisplayMeta 统一计算)+ 覆盖索引 idx_message_meta (扫 15.8k 行元数据 12~15ms,完全不碰内容页);迁移 v8→v9 纯 SQL 回填 (先 DROP FTS UPDATE 触发器避免逐行重索引;LENGTH(CAST(... AS BLOB)) 取字节数) - src/display.zig:工具调用行/块标题/参数摘要/截断等显示文本纯函数抽出, 迁移回填与运行时共用(新增 toolRowHeader 统一入口) - 懒加载路径:TUI 加载只读骨架元数据 + 尾部 ~400 行物化,其余建壳 (行高按字节/行数估算,宽度 60~200 偏差 ≤3.4%);滚入视口经 reloadMessage 按 entry_kind(text/call_line/shell_block/diff_block/fail_line)物化 - 显示语义统一:调用行/块改由 tool 行生成(与流式渲染顺序一致,顺带修复 重启后显示顺序与流式不一致的旧问题) - 物化行数变化在视口下方自动补偿 scroll_offset(估算→真实自愈不跳变) - 回归保障:懒加载与全量加载的显示条目逐条等价测试 附带修复: - TUI 的 -db 参数此前被静默忽略(parseCliArgs 要求首参为子命令)→ 实际打开 默认 skynet.db、启动闸门误判版本;修复后暴露的路径悬挂段错误一并修复 - unloadMessage 卸载时释放 tool_header(原注释'重载取不到'已过时:reloadMessage 按 db_id 反查 assistant 行参数重算)——滚过的工具行曾永久驻留标题, 实测连续 PgUp 300s:24.7MB → 77MB 线性增长;修复后稳定 ~25MB 实测(真实 15.8k 行会话): - Debug:加载 ~1300ms → ~230ms;首帧 ~410ms → ~3ms;内存 53MB+ → 23.6MB - ReleaseSafe:加载 ~93ms;内存 16.9MB - 滚动 300s / 1.8 万次 PgUp:内存 24.7MB → 25.7MB(平稳) 文档:AGENTS.md(schema v9 / 显示元数据约定 / display.zig)、 docs/architecture.md(分区地图/数据流/连带影响)、docs/development.md (懒加载设计、滚动内存排查与教训)已同步。
v7
feat(tools): grep 长行截断提示、系统提示词修正、工具问题日志改为"只留未解决" grep 长行截断(AI 视角:grep 定位、read 阅读): - max_line_len 500 → 120:够判断"这行是不是我要找的",需要完整行时用 read - 截断行附提示 `… [truncated at N chars; use read to see it in full]` (N 动态显示;不写 limit——读几行由调用方判断;行号保留便于 read 定位) - 不加 full / max_line_len 参数:基于 331 次真实 grep 调用统计, "语义等价但更繁琐"的参数使用率极低(literal 仅 0.6%), 且截断 → read 的替代路径畅通 系统提示词(main.zig): - 删除过时条目:old_string / replace_all 已不存在于新 schema(硬错误, 会误导模型写错参数名) - 新增工具选择引导:grep 搜内容 / find 找文件 / ls 列目录 - 原则:提示词只讲"有什么、怎么用",截断/诊断/续读等行为让 AI 在使用中发现 grep 工具描述:删除过时的输出格式说明("path:line: text" → 现已是文件分组格式) docs/tool-issues.md(AI 维护的问题日志): - 新增"生命周期"规则:只保留未解决条目,修复/否决后直接删除, 结论写进 commit message(git log --grep 可搜,不占上下文) - 理由:该文件会被 AI 完整读入上下文,体积必须可控;历史条目价值随时间 衰减,而 git 历史是无限的 - 清理:删除"已解决"区(4 条)与状态字段;保留 2 条未解决 - 新增"记之前先核实"规则(下结论前穷举所有路径——曾因只看一条就断言 "工具参数不落库",实际经 tool_calls 列完整落库) README(作者本人):补充原生程序卖点(约 4MB / 内存 30MB 内 / 启动无感)、 平台支持现状、欢迎 AI 代码的 PR。 测试 185/185(新增"grep 长行截断 + 提示"用例;修正受阈值影响的旧用例)。 实测:短行/300字符/130字符/恰好120/121/多文件混合,6 项均符合预期; read offset=N 取回完整行的链路已验证。