AskaEth
|
213aff2e33
|
feat(backthink): 存档落「紧急度」精确档位(裁决 14 的精确化前置)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- backthink_records 自建表加 urgency TEXT NOT NULL DEFAULT ''(**不开迁移号、不动 internal/db**;对既有库幂等补列:PRAGMA 判存在 + 容忍 duplicate column,其它错误照报)
- 落库值 = 该轮**实际生效**的档位(escalatedImport 折叠后的 roundImport;她自报可上抬、事件自带高档不被她压低)——「生效值优先」而非「她的话优先」,避免与既有 escalateImport 口径打架
- Record.Urgency 的 JSON 键刻意是 urgency ⇒ WS 实时帧自然带上、前端防御性读取即刻显示(不必等重开页面)
- 思考报错行 / 未唤醒降级行 / 历史旧行 = **空串(未知)**,绝不反推(变异:【空串当 routine】⇒ 4 条测试红;【补列非幂等】⇒ 41 条红,新库启动即失败)
- 生产影响:下次启动对真实 data/cyrene.db 执行一次单列 ALTER,并记 source=services/backthink 的 Info「backthink records table upgraded」
|
2026-10-05 14:10:25 +08:00 |
|
AskaEth
|
afe9afb1b5
|
feat(events): 渠道事件桥接接线 + 组合根(design/26 E3+E4)
- E3:channelbridge/events.go 唯一插入点 recordEvent(两轴映射 Category→Tier、摘要恒过 SanitizeNoticeText、角色/账号只认白名单)+ Options.EventStore 注入
- 未接线 store=nil ⇒ 内存计数 + 首次 DEBUG,绝不中断入站(变异实测:改成直接调 Record ⇒ panic,测试红)
- 普通聊天消息零副作用(变异:去掉 Applicable 早退 ⇒ rows=1 红)
- T1 遥测只计数不落行(变异:心跳误判 T2 ⇒ 「逐条落行 1 行,want 0」红)
- E4:wire_channel_events.go(启动期清扫 + 双 ticker + 退出 flush,逐字对齐 wire_document_gc 的三条取舍)、wire_channels 注入、main.go 加一行 startChannelEvents
- internal/config/duration_env.go:新 env 键的读取口径(进程环境 > .env > 默认;「没写」与「写了空」都当没配;非法值回落 + 报错)——避免「只写在 .env 里被静默忽略」
- 三个新 env 键:CHANNEL_EVENT_RETENTION / GC_INTERVAL / COUNTER_FLUSH(模板同批更新,测试钉住键名一致)
- 插入点比设计示意图晚一步(放 preEntryReview 之后,只归一化一次且 NoticeClass 已有结论),「留存无条件、在任何早退之前」这条性质不变
|
2026-10-05 14:10:24 +08:00 |
|
AskaEth
|
7aaf7731dc
|
fix(gateway): directMsgPerMin 的写入路径 5 处透传(关掉「面板保存把 -1 静默重置为 0」)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 缺口:后端解析与面板都已就绪,但 gateway 没让该字段通过 ⇒ 面板保存一次渠道配置就把主人手配的「关闭流控(-1)」重置为 0=保护性默认 6 条/分
- 5 处:AdminChannelAccount DTO / channelConfigRequest / config.ChannelAccount 拷贝 / adminChannelAccountOf 映射 / 微信扫码重建账号
- 语义零改动(只透传);变异实测:删拷贝 ⇒ channels.json 变 0(want -1)、删微信重建拷贝 ⇒ 同红、删 GET 映射 ⇒ 回显缺失
- 顺带登记一条负载诱发 flake(capability/plugin 的 TestManagerInstall_ConnectFailureDoesNotPersist,靠「端口被拒」断言,满载时端口被抢)⇒ 非本切片引入,已进 00-索引 §四
|
2026-10-05 14:01:52 +08:00 |
|
AskaEth
|
321cc3b763
|
feat(backthink): 思考轮工具面补齐(裁决 15)—— 能力面接入 + 只读 capability_list + 本轮状态快照
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 缺口 d:状态在**思考前**读一次,同一份快照既喂思考轮又喂 Decide(读不到 = Known=false,绝不伪装成中性状态)
- 缺口 c:新增只读工具 capability_list(owner-only、三重输出边界、读写隔离),她可自查当下真实可用面
- 缺口 b:内置允许集补 transcode / capability_list(此前 CAPACITY_ENABLED=true 时新工具会从模型可见面消失)
- 缺口 e:guidancePrompt 旧文案承诺的「IoT/日历/提醒」在全仓根本没有对应工具(chat 侧也没有)⇒ 按诚实化改为「与主人对话同等工具面 + 指名自查入口」,不再有空头支票
- (a) 未做且**不自创**:那三把工具无任何设计规格(名字/schema/读写边界/输出上限)⇒ 已报回并登记进 design/20(裁决:走能力/插件/MCP 暴露路线)
- 变异实测:允许集去掉新工具 ⇒ TestDecide_AllowedSets + 组合根跨层测试红;丢掉状态快照 ⇒ TestLoopThinker_Think_InjectsRoundStateBlock 红
- 已知弱项(如实登记):组合根那行 wireCapabilitySurface() 调用本身无测试覆盖(与既有 wireTranscode 同惯例)
|
2026-10-05 13:54:57 +08:00 |
|
AskaEth
|
34cef95e17
|
feat(gateway): 配置只读事实面 + 后台思考行「档位/穿透」(契约 v0.68)
- 新增 GET /api/v1/admin/config-facts:记忆整理 7 键 + 安静时段 2 键的**进程生效值** + 声明默认值(反射读 default 标签,不读 .env)
- 面板:记忆整理卡 + 安静时段卡(Miuix 只读),文案按新语义「只服务主动搭话,不静默回复」
- 后台思考行加 urgency(**只能是下限**,存档不可分 high/critical)+ quietHoursBypassed(与既有 bypassed 是两件事)
- 红线实测:往响应里塞 JWT ⇒ TestAdminConfigFacts_NeverLeaksSecretValues 打红(连断言消息都把整个响应打出来了)
- 前端:configFacts 24 checks / ConfigFactsCards 29 / BackthinkUrgencyTags 19 全过
- 契约 v0.68(纯增量:B12 新端点 + B8 两字段 + §4.10 形状与语义 + §7.4-17 下限限定)
|
2026-10-05 13:54:56 +08:00 |
|
AskaEth
|
a057a0f61f
|
feat(channels): 流控可配 —— 陌生人私聊额度走账号配置 directMsgPerMin(裁决 13)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 后端:账号顶层 directMsgPerMin(缺失/0 = 保护性默认 6 条/分;正值 = 该值;负值 = 关闭并留带 source 的 WARN)
- 日志如实报出**实际生效**额度(adapter.(*FlowGate).DirectPerMin()),告警文案不再写死 6
- 面板:渠道配置表单加字段(含帮助文案)+ 回显/写回链路(LIMIT_KEYS 四键)+ 账号卡片摘要
- 文案纠错:AdapterConfigSummary 的 msgPerMin 由「私聊每分钟上限」改为「非主人消息每分钟总上限」(原描述是误导),并列补 directMsgPerMin 行
- 变异实测:缺键丢默认 / 负值当 1 条每分 / 前端吞负值 —— 三个都精确打红并还原
- ⚠️ 已知缺口(下一片补,gateway 面):GET/PUT /admin/channels 尚未下发/解码该键 ⇒ 面板暂时回显空、保存会把 -1 重置为 0
|
2026-10-05 13:50:29 +08:00 |
|
AskaEth
|
ba17820040
|
feat(channels): QQ 非消息事件全枚举投递 + systemnotice 补 request/interaction/unknown 类
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 适配器不再整类丢弃:notice/request/meta_event/message_sent/未知 post_type/JSON 解析失败( decode_error ) 一律归一化投递并留痕
- notice 映射补 group_ban/sub_type 全枚举,未知类型走 NoticeUnknown 兜底(fail-safe 只登记、绝不猜、绝不放行)
- 不投影 flag/comment/file/duration;归一化 DEBUG 只记结构(脱敏,变异实测可打红)
- 传输层端到端已覆盖(真实 http+server 上报路径:加好友请求/心跳/未知类型)
- ⚠️ 主人 2026-10-05 决定「先不连 QQ」(风控)⇒ 本片为通用红线(绝不静默丢弃)+ 现成证据链,QQ 侧后续增量工作暂停
|
2026-10-05 13:47:27 +08:00 |
|
AskaEth
|
ec3f6e810a
|
feat(events): 渠道事件存储地基(Event/Store + SQLite 实现 + 有界计数聚合 + 保留期 GC + 迁移 0021)
- 新包 internal/runtime/events:Tier/Category/Platform/ChannelType/ActorRole 具名常量 + Event/Filter/Counter + Store 六方法
- 留存口径:T2/T3 逐条落行;T1 只计数;unknown / decode_error / link 三例外逐条落行(链路是否活着是事故定案唯一证据)
- 计数:内存聚合 + 上限 512 的有界缓冲 + 满即整批 upsert,落盘失败回填而不丢;GC:保留期 720h/间隔 1h,≤0 = 关闭
- 迁移 0021_channel_events(两表 + 6 索引,含计数 upsert 唯一键);Event 无 flag/正文/URL 字段(design/26 §4.2 红线)
|
2026-10-05 13:47:25 +08:00 |
|
AskaEth
|
52dac4575f
|
feat(gateway): 账号在线三态(后端 AccountStatus 可选接口 + 面板第二枚标签 + 契约账目更正)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- known=false ⇒ accountOnline 键**缺席**(未知 ≠ 离线),与既有 connected 语义并列不合并
- 面板:账号在线/账号离线/账号状态未知 三态 + 中性灰 tone + 「这不等于离线」悬停说明
- 测试:gateway 跨层锁「键缺席」(用真 qq_obv11.Adapter)+ 面板 61/40 断言
- 变异:known 当离线 ⇒ 两条测试同时红(已复跑确认)
- Docs/api-client.md:§7.2 那行由未实现改已实现(留表,注明下版移入 §7.1)
|
2026-10-05 13:29:53 +08:00 |
|
AskaEth
|
7ae4298871
|
feat(channels): 陌生人私聊正常聊天(跳概率门)+ 强制流控 6条/分与回合槽位 + 安静时段不再硬静默 + 受限车道边界行前置
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 陌生人私聊:不再走「值不值得回」的概率门,唯一闸门 = 每发送者 6 条/分滑窗 + 同会话 1 个在跑回合
- 被限流只登记不回,且留带 source 的可见告警(不静默)
- 安静时段在回复侧只作信号(规则层不再静默),群聊取消夜间静默
- 锁定流控常量:FlowDirectDefault 加字面量钉桩测试(放大它 = 静默关流控,且会让额度循环跑百万轮)
|
2026-10-05 13:26:15 +08:00 |
|
AskaEth
|
f91a83ff81
|
feat(urgency): 通用紧急度通道(Declarer/Level + 只抬不压折算)+ IoT 首个生产者 + 她自己的结论走结构化标记穿透安静时段
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 13:11:04 +08:00 |
|
AskaEth
|
4a2d276881
|
feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 13:02:28 +08:00 |
|
AskaEth
|
89715d1813
|
feat(channels): 受限车道回合补自身会话历史(群聊/陌生人能连贯理解一段对话;复用共享 transcript,拒绝主会话 id)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 12:58:01 +08:00 |
|
AskaEth
|
8f6a75b7d7
|
feat(channels): IM 车道生成并写入真实 turn_id(user/assistant 成对 + 同源 WithTurnID;群聊隔离与静默语义不变)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 12:22:44 +08:00 |
|
AskaEth
|
ba1974038a
|
feat(gateway): 并行提示接线(第二路 + 串行续跑路;OtherMessageID/Queued 取自另一在途路,无其它在途路则不挂)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 12:21:24 +08:00 |
|
AskaEth
|
20160c66d8
|
feat(security): 代审口径 A 落地(仅子代理发起的敏感调用做实质判定 + fail-closed + 理由进审计与小字)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 12:12:31 +08:00 |
|
AskaEth
|
fa850f9599
|
feat(loop): 落地 design/15 §2.5.6 并行提示(结构化 ParallelNotice + 独立 system 行,杜绝一题多答/跨端串话)
|
2026-10-05 12:12:23 +08:00 |
|
AskaEth
|
5139945bfe
|
feat(channels): 非消息事件接入(notice 归一化 + 传入前审查,fail-safe 静默)与入站脱敏帧日志/日志轮转
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- notice:adapter 归一化 seam + 结构化分类 + 净化(ANSI/控制/零宽/双向字符、200 runes 截断)
+ fail-safe 写死为「静默」(未装配审查/超时/失败 ⇒ 仅登记、不驱动 loop)
- 身份面零改动:notice 发送者恒 RoleSystem + SenderID=system
- 复用策略四的同一个审查模型(Options.Review),未另造第二套
- 入站帧脱敏 DEBUG(post_type/message_type/sub_type/self_id/user_id/sender_id/group_id/段类型/at 目标)
- ctrl.bat 启动前归档轮转(保留 20 份)+ bash 侧改追加(不再重启即截断)
|
2026-10-05 11:55:05 +08:00 |
|
AskaEth
|
85abb90067
|
feat(security): 敏感清单补齐 design/11 §4 的 7 类(写数据/对外发消息/访问密钥/改配置,结构化判据)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 11:53:17 +08:00 |
|
AskaEth
|
509dc412eb
|
feat(memory): 记忆 P2(项目索引物化 / decay 回写 / 作用域写规则 / 静默吞点 + loop 级只读红线测试)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 11:47:39 +08:00 |
|
AskaEth
|
9b903a89bc
|
feat(runtime): IM 主人私聊补历史转录(与 Web 主会话同源:抽共享 transcript 包 + OwnerEnvFor 单入口)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 11:36:18 +08:00 |
|
AskaEth
|
eeb5f26668
|
fix(memory): 堵住 knowledge 工具绕过群聊/陌生人过滤的 P0 / owner 召回 hits 如实上报 / 静默吞点补来源日志
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 11:30:51 +08:00 |
|
AskaEth
|
91a3209c9b
|
feat(memory): 记忆整理 v1(design/23)——低峰定期扫描 + 事实包含判定 + 规则否决 + 只产建议队列(不自动应用)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-05 11:29:36 +08:00 |
|
AskaEth
|
b73a1276c6
|
feat(channels): IM 入站图片接线(纯图片不再被丢 / data URI 进模型视图 / 诚实提示 / 来源白名单)
|
2026-10-05 11:22:26 +08:00 |
|
AskaEth
|
6a545badbd
|
feat(channels): IM 入站图片采集层(图片白名单 + 微信图片来源 + 复用 vision 内联入口)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- mediafetch 新增**图片白名单**(扩展名 + **字节嗅探兜底**;与文档/音频**三份名单互不污染**),
取回/裁决/上限全部走既有 Fetch(**来源白名单 deny-by-default** + 逐跳重定向校验 + 边读边限 + 超时)
⇒ 没有新造第四份取回实现
- 微信:给图片 URL 配来源白名单(沿用该渠道 CDN 根口径)
- vision 的 `data:` URI 内联入口为**只读复用**(未改 vision 一行)
- **行为变更**:图片现在**会**被尝试取回(**字节嗅探是唯一裁决权** —— 不是图片字节就不写 `Data`);
其它渠道尚无图片来源白名单 ⇒ 由 deny-by-default 拒绝(**安全**,只是暂读不到,走诚实降级)——
故同步更新了 kook 的既有测试(原断言「图片保持既有路径 = 不取回」已随口径演进)
- ⚠️ **`channelbridge` 一个字没碰**(接线点在切片报告 §7),等监工接线 + 补其它渠道白名单
- 测试:3 个新文件;变异 5 条
|
2026-10-05 11:04:41 +08:00 |
|
AskaEth
|
5de708a560
|
fix(gateway,channelbridge,turnreply): p 标签半包写入侧规整(修「整条塌成一段」)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
裁定(已发安卓):**能修**,走**写入侧规整** —— 不指望模型输出、不动客户端。
- 新增 internal/runtime/turnreply/paragraph.go 的 NormalizeParagraphs:
把未被 <p> 包裹的段落补上 <p>(按空行分段;<action>/<narration> 段也包进 <p>,
与正确形态 <p><action>…</action>…</p> 一致)
- **幂等**守门断言:已完全包裹的文本逐字不变(防二次加工)
- 落点覆盖 Web 与 IM 两条落库路径(gateway/session_turn_reply.go、channelbridge/turnreply.go、
proactive.go)⇒ 两边共用同一份规整文本 ⇒ **历史一次性变好、两个客户端零改动**、渲染口径不分叉
(测试与变异详见切片报告)
|
2026-10-05 10:56:39 +08:00 |
|
AskaEth
|
d532eb3fcd
|
fix(session,qq-obv11): 修「判主好了、会话车道没跟上」(QQ 私聊没镜像进主会话)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真因(agent-221 用三条独立证据推翻了「SenderID 为空」的假设):ev.UserID 有值;
owner.yaml 的 qq 条目是 10:37:57 才加的(此前那几条判陌生人属**正确的 fail-closed**);
而热重载成功后仍不对 —— 因为 internal/channel/session/manager.go 的 GetOrCreate
对**已绑定的渠道直接返回缓存会话**:判主门槛用新 actor 会过(所以她会回话),
但**车道用的是缓存的旧会话**(Owner=false ⇒ 不镜像进 Web 主会话、MemWritable=false、
落盘 actor_role 永不更新)。
修:缓存命中但 ActorRole 与本次解析不一致 ⇒ rebindLocked 刷新车道 + 同步落盘
(updateActorRoleSQL);persist 的 INSERT OR IGNORE 后读回落盘车道、不一致就地纠正
(覆盖上一轮进程留下的旧行);落盘失败只 WARN(不因记账失败丢主人消息);
**身份变化不搬家、不换 session id**;车道只来自 ResolveActor 的结构化白名单判定。
QQ 适配器(防御加固,非本次真机 bug 的修复):senderIDOf 兜底链 顶层 user_id →
sender.user_id;并用完整论证钉住「**群聊绝不能用 ChannelID 兜底**」(否则群里任何人
可顶群号冒充主人);顺手体检:自己发的消息(post_type=message_sent)与 meta_event
已被 handlePayload 过滤。为守 ≤500 行把 id/字符串小工具拆到 ids.go。
测试:session/ownerlane_test + qq/ownerlane_test 新增用例(含「群号等于主人号时
陌生人不得借群号冒充」「群聊发送者 id 缺失时 SenderID 必须为空而非群号」「白名单
热重载后车道立刻刷新且 session id 不变」「白名单移除后立即退回,不是单向提权」
「跨进程继承的旧行被就地纠正」)。
变异 5 条(去 sender.user_id 兜底 / 群聊用群号当 SenderID / 命中却判 unknown /
还原缓存直返 / 去掉旧行纠正),每条先确认可编译、贴红行、还原复绿(md5 逐字节校验)。
|
2026-10-05 10:53:05 +08:00 |
|
AskaEth
|
42279b7124
|
feat(channels): 按账号连接/断开 + 可删空 id 条目(修主人真机踩的两个缺口)
缺口①:「连接」是按平台连、且恒打第一条账号(channelActionRequest 只有 name)⇒ 主人有两条
qq-obv11 时点到的是 mode=server 的历史条目,报 server mode requires listenAddr,而正确的
client 条目轮不到。缺口②:空 id 条目既删不掉(DELETE 要求 id 非空)也改不了(UpsertChannel
空 id = 新建),越改越多。
后端:
- POST /admin/channels/open|close 请求体新增可选 accountId(缺省逐字兼容,老客户端零影响);
按该账号驱动;未知 id ⇒ 404 且零连接(不静默回落第一条);已配置未注册 ⇒ 409
- DELETE /admin/channels/{platform}/config 新增显式 empty=1(id 与 empty=1 互斥 ⇒ 400;
无可删 ⇒ 404);语义严格 —— 只删空 id 条目,绝不把「id 为空」默认解释成删空 id
- 响应新增:open/close 的 resolved(实际驱动的适配器名)与 accountId;delete 的 removed/empty;
GET /admin/channels 的 accounts[].connected(可选 bool,缺省 = 未知)
- 审计动作名不变(channel.open/close/delete),日志行新增 accountId 字段
前端:每条账号一行「连接/断开」(带 accountId);按钮旁明确「会连哪一条」;空 id 行显著标注
+ 删除入口;channelActionBody 携带 accountId;新增 action 测试 33 checks + 空 id 行渲染 31 checks。
测试:后端 14 个用例(真实 HTTP 路由 + JWT + 真注册表 + spy 适配器,断言「哪一条被驱动、
另一条一次都没被碰」)。
变异 6 条(accountId 被忽略 / id 为空当删空 id / 找不到静默回落 / 前端丢 accountId /
删空 id 退化成 ?id= / 空 id 行按普通行渲染)。
|
2026-10-05 10:37:56 +08:00 |
|
AskaEth
|
d804c4e675
|
feat(gateway): 接线 chat_messages.turn_id 写入(A–D 四点齐)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
A 新起一路:publishLegStart 在 persistUserMessages 之前 withTurnID(ctx, leg.turnID)
(该行随 ef3dcb6 已进 HEAD)。
B 队首续跑路:completeTurn 在 o.mu.Unlock() 之后 assignTurnID(sessionID, runLeg.turnID, next.persisted)。
C 被 steer 吃进:consumePending 认领成功后 assignTurnID(sessionID, s.turnID, steerRows(candidates))。
D 助手回复:persistTurnReply 增 turnID 形参,completeTurn 传 leg.turnID —— 刻意**不用** turnIDOfLeg:
interrupt scope=all 会清空 st.legs,而被打断那一路已产出的正文仍要照原样落库(内容是真的),
按 gen 回查只能拿到空串 ⇒ 配对丢失。
删掉 history_turnid_test.go 的 t.Skip:TestSubmitWiresTurnIDForBothRows 真跑并通过。
新增 session_turnid_wiring_test.go(真 SQLite 内存存储 + 真编排器 + 可逐路放行 runner):
单路两条同值 / 并行两路各自同值且两两不同 / 续跑补写(先钉「补写前为空」)/ steer 补写(用该路自己的转向源)/
TestAssistantTurnIDSurvivesInterruptAll(钉住 D 的取值来源)。
变异 5 条(A/B/C/D 各自 + D′ 改回 turnIDOfLeg —— 只有那条守门用例红,证明它守的正是这个取舍)。
诚实边界:turnIDOfLeg 现无生产调用方(保留为兜底);先到先得竞态未构造对抗用例;
IM 渠道桥不走本编排器 ⇒ 其行 turn_id 留空属预期(契约里键缺席)。
|
2026-10-05 10:00:17 +08:00 |
|
AskaEth
|
ef3dcb699b
|
fix(gateway,loop,tool): 解耦「工具互斥身份」与「checkpoint 归属」(修普通回合不受保护)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
缺口:「1 普通回合 + 1 并行路」时普通回合既不登记也不被检查 —— 因为互斥身份取自
loop.ParallelLegFrom,而网关只给 parallel=true 那一路盖它。直接给普通回合盖 WithParallelLeg
会把它的 checkpoint 键从 sess 变成 sess#turn-x(改崩溃恢复语义),故改为两条独立通道:
- 工具互斥身份:loop.WithToolLegID / ToolLegIDFrom(新增),网关在 beginLegLocked 内【无条件】盖,
取值 = 本路 turnID ⇒ 普通回合 / 队首续跑路 / 并行路 三条路都覆盖
- checkpoint 归属:WithParallelLeg 一行未改,仍只有真并行路 ⇒ 恢复语义零变化
守门断言 TestCheckpointOwnerIgnoresToolLegID:普通回合 ctx 下 AgentID 必须逐字仍是 sess。
变异 3 条:互斥身份回退只认 ParallelLegFrom(判忙失效)/ 顺手把 checkpoint 改读新通道
(守门断言红,证明确实解耦)/ 普通回合不盖身份(判忙失效)。
另修 race:-race -count=2 三包全绿;诚实边界含一个既有竞态(同一路并发登记共享,建议后续小切片)。
|
2026-10-05 09:53:08 +08:00 |
|
AskaEth
|
d64b26d499
|
feat(chat): chat_messages.turn_id 列 + DTO(写入接线留待下一步)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
迁移 0018:ALTER TABLE chat_messages ADD COLUMN turn_id TEXT NOT NULL DEFAULT ''
(与 persona_id / client_msg_id / document 同族:不用 NULL,空串 = 不属于任何已知回合 ⇒ 旧行零迁移、键省略)。
存储层全链路带 turn_id(列清单唯一一份 ⇒ List/ListAfter/Recent/Search/FindByClientMsgID 自动同源);
新增 history/turn_id.go:ensureTurnIDColumn(既有库幂等前滚)+ SetMessagesTurnID
(第二段补写:单事务参数化、先到先得 WHERE turn_id = ''、幂等、未知 id 忽略)。
DTO:historyMessage.TurnID(json turnId,omitempty)⇒ REST 列表 / admin 渠道日志 / WS 握手回放 /
message_persisted / channel_persisted / message_edited 一次全覆盖。
写入面:persistUserMessages 读 ctx 回合标识;persistAssistantReply 增可选尾参(既有调用点零改动);
withTurnID / turnIDFrom / assignTurnID / turnIDOfLeg。
⚠️ 编排器把 turnId 交给落库的 4 处(新起一路 / 续跑路 / 被 steer 吃进 / 助手回复)尚未接线
(那些文件当时被另一路占用);当前生产行为 turn_id 仍为 ''。清单已在切片报告里精确到文件:行。
13 新用例 + 5 条变异(INSERT 不带列 / DTO 不回填 / 去掉 omitempty / 去掉先到先得 / 不回滚前滚)。
为守 session_persist.go ≤500 行,turnId 一节拆到新文件 session_turn_id.go。
|
2026-10-05 09:46:10 +08:00 |
|
AskaEth
|
354b08a45f
|
feat(tool): 写类工具文件级互斥(读共享 / 写独占)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
新增 tool/fileclaims.go:FileTargeter 可选接口,由工具声明本次调用要碰的文件
(动态声明、不猜正文 —— 同一工具的 path 在读/写语义不同)。
语义:读共享 / 写独占;两个读允许并行(主人「优先性能」口径);未知操作/裸路径按写处理(保守)。
占用键含操作类别(read/write,NUL 分隔),路径 Abs+Clean+Windows 大小写折叠;随最后占用者离场删键(有界)。
Dispatcher 默认装配进程内占用表(无需 cmd 侧接线);Invoke 登记 + defer 释放(成功/报错/超时/panic 四条路径)。
loop/tools.go 唯一接线:tool.WithLegID(ParallelLegFrom(ctx)) 桥接网关路标识。
判忙不阻塞、不排队死等;显式文案回灌(含文件名与状态)让 LLM 稍后重试。
测试 3 个新文件(占用表语义 / 接线确定性并发 / loop 桥与端到端);变异 5 条,另揪出并修一个
真隐患(空串既当「无人占用」哨兵又是合法路标识 ⇒ 改 (leg,busy) 返回)。
诚实边界:①「1 普通回合 + 1 并行路」时普通回合不受保护(网关只给 parallel 路盖 legID;
修法须与 checkpoint 归属一起裁决);② 目前无真实工具实现 FileTargeter ⇒ 机制就绪但占用表恒空,
真实保护待下一片给 file 等工具加声明。
|
2026-10-05 09:38:12 +08:00 |
|
AskaEth
|
c0dc435770
|
feat(gateway): 忙时并行帧层(turn_begin + steer_ack.turnId + interrupt.turnId)
新下行帧 turn_begin:编排器每启动一路恰好一帧(单路 kind:"turn" / 并行路 "parallel"),
data{kind, messageIds, leg, activeTurns};角色门 fail-closed 仅 owner+admin,未进 frameReachesAll。
steer_ack 增帧壳 turnId(与该路 turn_begin 同源)—— 并行下没有它配不上正确的 turn.end。
上行 interrupt.turnId(仅 scope:"current" 生效):精确停某一路;目标不存在 ⇒ 定向
{type:"error",data:{code:"bad_request"}},不广播、不动任何会话;省略 turnId = v0.65 原口径(全部在途路)。
帧序按实现:session_busy / pending_drain → message_persisted(user) → turn_begin
(规范草案 §2.9 的时序图需按实现更正:messageIds 必须是已落库的权威 id,故只能在落库之后)。
测试:编排器侧 + 真 WS/JWT 端到端;变异 5 条(每路一帧 / 帧壳 turnId / 目标化打断 / 取消筛选)。
Docs 待升 v0.66(由监工登记)。
|
2026-10-05 09:38:11 +08:00 |
|
AskaEth
|
89e13cf53d
|
feat(gateway,loop): 忙时并行回合基础管线(I1′ 有条件放宽)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- turnId 由编排器生成、loop Run 复用(硬前提:否则 turn_begin 与流帧各铸一个,客户端两级分桶立即失配)
- StateSink 增带 turnId 的变体(未接线时退化为现签名,不丢状态帧)
- 上行 chat.parallel(WS chat 帧 + REST 三入口同形;省略 = 逐字不变)
- 单会话额度默认 2 / 硬上限 3 / <2 关闭;额度满或未声明 ⇒ 落回既有排队语义
- I1′:checkpoint 归属、历史配对、忙态计数三处从会话级单例升为路级
- session_idle 收紧为「所有在途路收尾且 pending 空」(仅并行时生效;单路逐字不变)
- 预留接口(形状定死、无实现无接线):tool/legfile.go 的 FileClaimer / ErrFileBusy / WithLegID
- 顺手修一个真 bug:并行路 ctx 派生自提交请求作用域,REST 同步入口一写响应就把它连坐杀死(改 context.WithoutCancel 派生)
- 23 个新用例(含 -race -count=3);变异 6 条(turnId 复用 / idle 时点 / 并行升级 / checkpoint 键 / 编排器交 id / pending_ack reason)
- 契约变更点 10 条待监工升 v0.65(本人未改 Docs/**)
|
2026-10-05 09:18:50 +08:00 |
|
AskaEth
|
a918819878
|
feat(channels,voice): IM 各渠道语音消息可被读到(全渠道云端 ASR + 临时熔断)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
采集面:mediafetch 新增 inline_voice.go(音频白名单,与 documents.ExtAllowed 互不污染;
复用既有来源白名单 / 逐跳校验 / 边读边限 / 超时);四个适配器零改动。
消费面:channelbridge 在流水线最前端把「语音字节 → 文本」归一并写回 msg.Content;
inbound.go 的丢空判据放宽为「无文本且无可用媒体」。
微信优先平台自带转写(Content 非空 ⇒ 不调 ASR,不重复付费)。
降级(主人定的两层):新增 voice/outcome.go 的 STTOutcome 识破静默档位;
errors.go 分类鉴权失败/欠费/限流(CircuitWorthy);voice_breaker.go 临时熔断 + 退避(TTL 恢复);
无论是否熔断,消息面都落一行中文诚实提示(绝不静默、绝不留空消息)。
独立短超时:VOICE_STT_TIMEOUT_SEC(默认 20s,同步 .env.example)。
kook 不再把 CDN 地址写进 Content(地址仍在 Media[].URL)。
设计先行:design/06 §1.1/§1.2、design/08 §4.1、design/24 §1.3/§4.2、开源对照清单。
变异 5 条(静默档位不带 Cause / Transcribed 误判 / 熔断恒放行 / ChatText 退化 / 平台文字判据取反)。
诚实边界:实施时官方音频规格尚未抓到 ⇒ MaxVoiceBytes 自定 5 MiB(保守)、采样率仍 16 kHz;
官方规格随后已抓到(10 MB / 5 min;格式含 amr、无 silk),对齐留待下次改动。
|
2026-10-05 08:32:37 +08:00 |
|
AskaEth
|
679cee04f7
|
fix(channelbridge): IM 入站附件引用写进落库的用户消息(安卓报的 P0)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
根因:摄取结果只挂到「内存里的模型可见消息」(inbound.go 的 attachInboundAttachments),
落库时只传了 text、history.Message 没有 Document 字段 ⇒ 该列恒空 ⇒ 读侧 omitempty
导致 documents 键整个缺席 ⇒ 任何客户端只看到一条空消息。Web 侧不这样,是因为
gateway/session_persist.go 有 messageTextImageDocument 把 ContentTypeDocument 部件编码进该列
—— IM 路径缺的就是同一步。
修法:inboundAttachments.documentColumn() 复用 history.EncodeDocuments 同一口径写出该列;
persistToMain/persistIsolated 增 document 参数并返回刚落库的消息;新增两段式第二段
persistTurnDocumentStatus 按 id 回写抽取结论(只补结论、按存储名匹配、匹配不到原样留),
与 gateway/session_document_status.go 逐字对齐。
契约零扩张:无新端点/帧/字段;0 附件 ⇒ 列空 ⇒ 键省略;形状即 web 的同一结构体。
测试 6 条(含元素 JSON 逐字断言、unreadable 带 reason、被拒不编造芯片、群聊隔离分支、
真循环端到端);变异两条(还原挂载 / 关闭结论回写)⇒ 各 4 条红。
|
2026-10-05 08:10:21 +08:00 |
|
AskaEth
|
59a1ac4056
|
feat(model): llm_usage.session_id 真正落库(补完 U1 缺口)
通道:新增 model/session_ctx.go 的 WithSessionID / SessionIDFrom(与 tool.WithTurnPersona
同族的 ctx 范式,避开 model 反向 import loop 的环),loop 在 Run 顶部播种;适配器
record(ctx,...) 接会话;usageSessionArg 空串映射为 NULL。
兼容:sink 未注入或会话未播种时行为逐字不变;既有 model 测试零断言改动即全绿。
文件:model/{session_ctx.go(新), usage.go, usage_store.go, openai.go, anthropic.go,
usage_session_test.go(新)} + loop/{run.go, usage_session_test.go(新)}。
变异三处:去 loop 打标 / 写侧不绑会话 / 适配器不读 ctx ⇒ 各自红行,还原复绿。
契约与日志字段集未动;读侧不按 session 过滤,面板汇总口径不变。
|
2026-10-05 08:04:49 +08:00 |
|
AskaEth
|
cb564fe981
|
fix(memory): 空 query 不再触发语义回退 + 回退有界(修真机 39 秒召回)
真因(agent-197,文件:行):只有附件的消息正文为空 ⇒ queryFromMessages 返回空串 ⇒
retrieve.go 的 vectorIndexScores 在问索引之前就 return ⇒ 上层分不清「没问过索引」与
「索引无命中」⇒ 打出误导的 no-candidate-hit ⇒ 转内存回退,把全库 302 条候选正文塞进
一个 embedding 请求(302 × 约 130ms = 39.3 秒,与实测 duration_ns 吻合)。
索引/维度/表/写入其实全是好的(PG vec_items 302 行全 dim=1024,与 SQLite 302 个 id 集合相同)。
修法:空/纯空白 query 整条语义路径跳过(不问索引、不 embed);回退改为有界
(分批 32 + 1 秒时间预算 + 正文到向量 FIFO 缓存 1024,不再每回合重算)。
文件:retrieve.go / retrieve_semantic.go + 新增 retrieve_fallback.go / retrieve_keyword.go /
retrieve_fallback_test.go(retrieve.go 曾达 501 行,按 ≤500 拆分)。
变异:去掉空 query 门 ⇒ 向量化 43 条文本(want 0);整库单请求 ⇒ 69 条超上限 32;停用缓存 ⇒ 重复嵌入。
|
2026-10-05 08:04:47 +08:00 |
|
AskaEth
|
15181e7063
|
fix(model): Anthropic 并行工具结果合并为同一条 user 消息(修真机 400)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
根因(agent-193,文件:行):loop 的 tool_history 按调用逐条产出 RoleTool 消息,
而 anthropic_encode 把每条各自编码成一条独立 user 消息 ⇒ 上游只认紧邻 assistant
的那一条,第 2..N 个 tool_use id 被判「没有 tool_result」⇒
`messages.1: tool_use ids were found without tool_result blocks immediately after`。
单调用回合只有 1 条结果消息,所以长期潜伏;KB1 让 knowledge 真正成功后它才被暴露。
修法:编码器在**线格式**层把相邻的 RoleTool 合并进同一条 user 消息(块序 = 调用序),
遇到任何非工具结果回合即复位(不同 assistant 回合的结果绝不混入同一条)。
未碰 openai_encode(OpenAI 要求每个 tool_call_id 一条独立 tool 消息)。
测试 3 个新文件:编码器线格式(非流式 + 流式)/ loop 端到端(真适配器打假上游)/
回填侧契约;含配对断言(每个 tool_use 在下一条消息里有同 id 的 tool_result)。
变异:还原旧编码 ⇒ 4 条红,端到端逐字复现真机 400。
是否本轮引入:否(编码与回填两文件在本轮零改动,属基线潜伏缺陷)。
|
2026-10-04 22:30:04 +08:00 |
|
AskaEth
|
fd13b49b24
|
fix(channelbridge): 出站气泡不再携带入站载荷(真机:发文件后回复全丢)
根因(bubbles.go:43):`out := target` 把【整条入站消息】复制成出站气泡,
入站 Media 被一起带出 ⇒ 适配器去上传那个附件 ⇒ weixin_ilink 的 getUploadURL
拿不到 url ⇒ sendToOne 连已组装好的文本 item 一起丢掉 ⇒ 用户收不到任何回复。
修法:新增 outboundBubble(白名单只带路由字段 + 本气泡文本),一次清掉
Media / RawData / Mentions / ReplyToID / ReplyToText / MessageID / SenderName
六类入站专属字段;将来新加进 UnifiedMessage 的字段默认不外发(失败方向安全)。
测试 2 条:入站带附件 ⇒ 出站 Media 为空;带附件 vs 不带 ⇒ 出站除时间戳逐字节相同。
变异:破坏白名单把 Media 带出 ⇒ 2 条红。
影响面:仅「入站带附件」的消息;纯文本聊天不受影响(已真机确认)。
|
2026-10-04 22:30:03 +08:00 |
|
AskaEth
|
fbbbb4ea64
|
fix(channelbridge): IM 渠道也走「回合必落可见回复」(与网关同口径)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
根因(agent-194,文件:行):
- inbound.go 的 deliverReply 对空回复直接 return(静默)⇒ 只剩 user 消息;
- runInboundTurn 报错直接 return ⇒ 失败回合连说明都没有。
修法:抽共享**纯函数**包 internal/runtime/turnreply(从网关逐字搬运、零语义改动)+
channelbridge/turnreply.go 的 turnVisibleReply(与网关 persistTurnReply 同构);
取消边界在 IM 用 context.Canceled / ctx 取消表达(渠道桥无代次概念,如实登记差异)。
测试 6 条:空回复/纯空白/轮次用尽/安全网/回合报错/取消不落库/正常逐字不变。
变异:还原旧静默行为 ⇒ 4 条红(含「出站 0 条,want 1」与英文内部诊断外泄)。
|
2026-10-04 22:29:54 +08:00 |
|
AskaEth
|
be38651ba4
|
feat(llm): 用量落库 + 只读汇总端点 + 面板用量小卡(契约 v0.64)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- model 层新增可选 UsageSink(nil ⇒ 逐字不变,既有用例零改动)
- 组合根注入 UsageWriter:有界队列 1024 + 单写者 + 批量事务(128/批、2s 兜底),
队列满丢弃 + 限流 WARN,绝不阻塞模型调用;sink 挂 RuntimeLLM(热重载不断)
- 迁移 0017:表 llm_usage(13 列,session_id 可空)+ idx_created_at + idx_session_id
- 新端点 GET /api/v1/admin/llm/usage(owner|admin 读;缺省近 24h、上限 90 天、
超限 400 不静默裁剪;hitRate = cacheHit/(cacheHit+cacheMiss),分母 0 ⇒ 0;
绝不含凭据与正文;未接线/查库失败 ⇒ 500 usage_unavailable)
- LLM_USAGE_RETENTION(默认 720h,≤0 = 关闭清理):启动清一次 + 每小时
- 面板 LLM 页「用量(近 24h)」小卡 + Docs 两份同步 v0.64
- 已知缺口(如实登记):session_id 恒 NULL,需 loop 侧在请求上打标,留列后接
|
2026-10-04 22:16:26 +08:00 |
|
AskaEth
|
18381f65fd
|
fix(gateway,loop,tools): 失败回合必落可见回复 + knowledge 角色可信
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
根因(agent-191,均给出文件:行):
1) knowledge 的 persona 在 schema 里是 optional,模型省略时工具把空串透给
memory.Retriever → 硬校验 ErrMissingPersona → 模型只能重复调用同一堵墙。
修:新增 tool/turncontext.go(loop 播种本回合角色),knowledge 依次取
「参数 → 本回合角色 → 可消化拒绝」,拒绝文案明确「重试不会成功」。
2) persistAssistantReply 对空文本直接 return,而三条正常收尾出口(空回复 /
轮次用尽 / 回合内部报错)都会给空文本 ⇒ 历史只剩 user 消息、客户端静默。
修:新增 session_turn_reply.go 的 persistTurnReply(唯一落库入口 + 按原因
的中文诚实说明);中断 / 陈旧代次 / 静默内部流程(backthink、子代理调度)
仍不落库,真实文本行为逐字未改。
测试:knowledge_test 改断言 + turn_persona_test(新)+ session_turn_reply_test(新,7 条)
变异:去掉 loop 角色发布 ⇒ loop×2 + gateway「工具执行次数 = 0, want 2」红
|
2026-10-04 22:14:42 +08:00 |
|
AskaEth
|
3be70f8232
|
feat(channels): 适配器入站内联附件字节(telegram/kook/qq_obv11/weixin_ilink)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增 internal/channel/mediafetch:来源白名单(deny-by-default,支持 *.suffix)
+ 逐跳重定向校验 + 边读边限 + 超时;只回来源主机,绝不回完整 URL
- 四渠道各自 media.go 接通入站内联:只从平台载荷给出的引用取回字节
(telegram file_id→getFile→file_path 形态校验;kook CDN;qq 文件段 url;
weixin cdnURL + AES-ECB 解密)
- 常量全部复用既有来源:documents.ExtAllowed / DefaultMaxBytes / MaxPerMessage
- channelbridge 诚实提示文案改为对「渠道只给远程引用」与「代取失败」两种成因都成立
- 18 个测试函数(含四渠道「正文里的 URL ⇒ 0 次命中」红线反证、端到端进模型可见视图)
- 变异验证:去掉尺寸上限 ⇒ 四渠道红(含修正后不再是假绿的 weixin 超量用例)
|
2026-10-04 22:13:17 +08:00 |
|
AskaEth
|
902b6eecfd
|
feat(api): documents[].reason + messages 对不存在会话回 404(安卓回执九两条;契约 v0.63)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- ① documents[] 元素增可选 reason(omitempty):装配期在给出 status 的**同一处**填充,取的**就是注入模型清单里那份原因文案**(同一字符串两处用,服务端不为客户端另编一句),只在 unreadable 时下发;随 S3.5 的回合后回写落进 document 列 ⇒ 历史接口 / 握手回放都有。文案只含原因、不含落盘路径/存储名以外内容/凭据(有独特目录名反证断言)。
- ② GET /sessions/{id}/messages:不存在/已删除 ⇒ 404 session_not_found;**存在但无消息仍 200 {data:[]}**(空会话绝不能被答成 404);参数形状校验仍先于 404;存在性判定复用既有窄视图(history.Store.SessionPersona,未知 id 报 ErrSessionNotFound),**未新增 SQL/存储方法**;判不出来(未接该视图)保持旧语义(判不出 ≠ 不存在),判定本身失败回 500 不静默。
- 契约 v0.63(三档:reason 纯增量 / messages 404 行为变更 / 无破坏性):api-client §1 行 6 + §1.2④ 的 10 条逐字文案 + §4.4 错误码 + changelog + banner;api-reference §1.2 行 6 + §3.4 + §3.8 DTO + §8.1 错误码表。
- 主会话独立核对的副作用(隔离会话 sess-* 现在也 404):查活库 chat_sessions 里**根本没有 sess-* 行**(只有 web 与 web:<uuid>),sess-backthink 消息数 0,前端唯一提到 sess-backthink 的是 graph_update 的内存桶测试 ⇒ **没有客户端按 id 读隔离会话的 messages**;且这与 POST /sessions/{id}/read(v0.37 起即 404)一致 ⇒ 接受不加 sess- 例外,并在回执里问安卓确认。
- 主会话独立验收:隔离 worktree(S9 的 13 文件)build/vet 绿、整树连跑 2 次 59 ok / 0 FAIL、reason/404 用例 8 PASS、双平台交叉编译绿;**变异亲自复现**(在调用点短路 404 守卫 ⇒ UnknownSessionIs404 与 DeletedSessionIs404 双红:status = 200, want 404)。
- 如实登记:验收过程中出现过**一次不可复现的子集 FAIL**(restore 之后那次 Session|Reason 子集跑;未记到用例名)—— 随后同一子集、那三条用例 ×5、以及整树 ×2 全绿,判为瞬时;已记入账本,后续留意。
|
2026-10-04 21:50:08 +08:00 |
|
AskaEth
|
c1fa1e9b9a
|
feat(admin): 面板「重置为 env 默认」动作(materialize 的反方向)+ 契约 v0.62
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 语义:只把 .env **确实声明过**的键覆盖回 env 值;env 未声明的键(fallback/multimodal/maxTokens/reasoning/personaModels)原样保留;**组集合不增减**(不复活被面板删的组、不删面板新增的组);无键可改时不写盘。成功即热重载 LLM 栈。返回 {reset:[{group,keys}], skipped:[{group,reason}], path},**只回键名、不回显凭据值**。
- 护栏:必须 confirm:true(缺失/false/非法 body ⇒ 400,且**先于任何读写**);**owner-only**(与同页 POST /admin/llm、/admin/llm/test 同门 —— 这是破坏性写,门不比同族更宽);审计动作 llm.resetFromEnv(camelCase,全仓多词动作的既有约定)。失败路径明确报错(env_defaults_unreadable / models_unreadable / llm_unavailable),不假成功。
- 组合根注入 Deps.EnvPath(网关不 fopen .env):请求时按既有解析入口重读,语义 =「退回 .env 现在声明的默认值」;.env 层里的 provider 是同义反复,不计入「env 为该组声明了什么」的判定(有专门用例)。
- 前端:LLM 页「重置为 env 默认」卡片(中文二次确认 → 提交 → 按 reset/skipped 渲染 → 成功后重拉配置),标签 owner 专属(与 System.tsx 同款)。
- 契约 v0.62:api-client(权限矩阵改回 12+10+38=60、端点表 9b、重置契约小节、写操作契约、changelog、banner)与 api-reference(端点表 #70、§5.13a 详情含 curl、§8.2/§8.3/§8.4)同步;字段名三方逐字对齐。
- 主会话独立验收:隔离 worktree(R1 的 19 文件)build/vet 绿、gateway+config 三包绿、R1 用例 10 PASS、reset_from_env 残留 0;**变异亲自复现**(去掉 confirm 校验 ⇒ RequiresConfirm 红:status = 200, want 400,且响应体显示面板配置真的被改)。
- 子代理自报并已处理:测试文件触 500 行上限 ⇒ 拆成语义侧 + 护栏侧两份;门改回 owner-only 后前端标签会误导 admin ⇒ 改「owner 专属」并重建 bundle。任务书本身仍是旧口径(owner|admin / snake_case)—— 已由主会话登记修正说明,避免压缩后有人按任务书回退。
- 未做(诚实边界):不支持按组重置;不纳入 channels.json/plugins.json;不自动重启(contextWindow 的预算推导仍只在启动时算一次)。新端点需下一次 build+restart 才在 :10942 生效。
|
2026-10-04 21:33:11 +08:00 |
|
AskaEth
|
6066165394
|
test(flake): 清掉最后三条墙钟/条件竞态(mcp 订阅门控 + vision 注入时钟 + voice 兜底期限)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- #6 capability/mcp TestRegistry_ToolListChangedRefreshes:真因**不是慢,而是条件竞态** —— 本机协商到协议 2026-07-28(SEP-2575)时服务器**只**把 notifications/tools/list_changed 投给已登记 subscriptions/listen 的会话,而客户端订阅是 fire-and-forget(Connect 返回 ≠ 服务器已登记),服务器又在变更后 10ms 发一次、丢了不补 ⇒ 订阅登记晚于那个定时器,该次变更**永远**不生效(旧 waitFor(5s) 只能超时)。修法:显式门控 —— 先等服务器 notifications/subscriptions/acknowledged(服务器先登记再回确认)→ 再触发变更 → 再断言刷新由 tools/list_changed 驱动(既有断言一条未少、另加一条更强的驱动断言)。协议回落旧版时门控自动跳过。
- #7 multimodal/vision TestImageCache_RefetchesAfterTTLWhenContentChanges:真因是把**墙钟当判据**(ttl=20ms 下「TTL 内」成立与否只由调度抖动决定,实测空载≈0、并行负载下 >20ms 会同时打红两条断言)。修法:注入可手动推进的 testClock(生产本就有 cache.now 字段、默认 time.Now,零生产改动),断言一条未少。
- 附带:修掉 G5 批次遗留的第三条墙钟兜底 —— gateway/voice_turn_test.go 的 waitFrame 3s → 15s。**放宽的是兜底期限、不是断言**:它断言的是「帧确实到达」,帧没到照样 Fatal;实测单跑 -count=20 全绿、只在整树并行下偶红(语音回合要过编排器 → 门控假 runner → 假 TTS → 定向回连接)。
- 主会话独立验收:隔离 worktree(F2 三个测试文件)build/vet 绿、两条目标用例各 -count=20 绿;加本条兜底修复后整树连跑 2 次均 59 ok / 0 FAIL(修前那次整树抓到 voice 那条红 → 归因见上)。
- 子代理自报(已核实并清理):它在加压时用 30 个 yes 做负载、第一次 kill 没杀干净 ⇒ 整机 12 核被占满约 15 分钟(期间所有 go 命令近乎停滞,也解释了本轮测试为何异常慢);已全部终止(Get-Process yes = 0),此后证据均在干净环境重跑。它同时**如实说明**:在纯加压下没能直接撞出这两条原始 flake(窗口极窄),确定性论证建立在「可控对抗条件复现 + 根因」上。
|
2026-10-04 21:11:17 +08:00 |
|
AskaEth
|
3ba31b58ff
|
feat(documents): 引用计数 + 文档/侧车 GC(主人 2.2 裁决;S7)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 引用计数真实维护(documents 表唯一写入面):增减点只有四处 —— history 的 insert / UpdateMessageDocument / DeleteMessage / DeleteSession,且**全在同一事务**内(计数写失败 ⇒ 消息写入整体回滚,有钉住用例);UpdateMessageDocument 按新旧名字**集合差**增减(三阶段回写是常态,重算会重复计数 ⇒ 文件永远回收不掉)。
- GC:候选 = ref_count = 0;判据 = last_referenced_at(NULL 回落 created_at)早于 now − 保留期;动作 = 删 sidecar + 原件 + 元数据行(删行带 AND ref_count = 0 原子再确认);路径双检;汇总日志(含实占字节);单件失败 WARN 并继续;删除顺序 = 先文件后行(失败则行留着下轮重试,不留孤儿)。
- 组合根:启动期清扫一次(不阻塞启动)+ ticker 常驻(可停,close 顺序在关库之前);env DOCUMENT_GC_INTERVAL 默认 1h / DOCUMENT_RETENTION 默认 168h,**任一 ≤0 = 关闭 GC**(RETENTION=0 不解释成立即回收,关闭时记 WARN);.env.example 已同步(AGENTS §10)。
- 主会话独立验收:隔离 worktree(S7 的 18 文件,排除 F2 在途的 mcp/vision)build/vet 绿、全树 59 包 0 FAIL、GC/引用用例 20 PASS、双平台交叉编译绿;**变异亲自复现**(候选查询去掉 ref_count = 0 ⇒ TestGCDoesNotDeleteReferencedDocument 红:candidates = 1, want 0)。
- 未做(任务书允许的「只做后端」):面板手动「清理未引用附件」入口 —— 后端能力已就绪(documents.NewGC + GC.Sweep),接成端点属小切片(含 admin 路由 + 审计 document.gc + 契约登记)。
- 已知窄窗口(已在 gc.go 文件头登记):一轮 GC 与一次落库几乎同时发生时可能删掉刚被引用的文件(行不删、记 WARN);坏 document 列只记 WARN 不阻塞消息写入 ⇒ 计数可能偏低(只会让文件**晚**回收,绝不早删)。
|
2026-10-04 21:01:30 +08:00 |
|
AskaEth
|
79b1ec36aa
|
fix(auth): API Key 落盘(修「每次重启 key 全失效」)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 缺陷:internal/auth/apikey.go 的 key 表是纯内存 map(注释自认 production should persist)⇒ 一次 restart 后所有 key 一律 401;实测挡住了大附件真机 e2e,也与「key 用于生产救火」的长凭据语义矛盾。
- 迁移 0016_api_keys:表 api_keys(id/name/key_hash/created_at/last_used_at/revoked_at)+ 唯一索引 idx_api_keys_key_hash;**只存 SHA-256 hash,无明文入口**。
- 纪律:Generate 先落库后改内存(失败 ⇒ 内存不变 + 返回错误);AttachKeyStore 启动时把库中未吊销 hash 载入内存(Load 失败仍接上 store ⇒ Generate 会响亮报错,而不是静默退回「不落盘」);Revoke/RevokeAll/RevokeByMask 先落库再移出内存(落库失败 ⇒ WARN + 内存不变,fail-closed:绝不让被吊销的 key 重启后复活)。
- 内存表也改为存 hash(不再存明文)⇒ 面板 id/掩码由 hash 派生:不再泄露明文首尾、且跨重启稳定(重启前记下的 id 重启后仍可吊销)。
- 契约零变化:HTTP 请求/响应形状、网关代码、既有内存用例断言全部未动(网关构造注入点只在组合根 wireAPIKeys)。
- 主会话独立验收:隔离 worktree(K1 的 7 文件)build/vet 绿、全树 59 包 0 FAIL、key 用例 24 PASS、双平台交叉编译绿;变异独立复现(跳过落库 ⇒ 重启存活用例红)。
- 取舍与遗留:吊销落库失败在 HTTP 层仍表现为 404/revoked:0(签名零改动的代价,日志可辨);last_used_at 列已建但恒 NULL(避免把读路径变写路径);旧内存 key 已被此前重启消灭,需重签一次后即长期有效。
|
2026-10-04 19:55:18 +08:00 |
|
AskaEth
|
f82c76df27
|
feat(documents): PDF 文本层抽取(S6 第一段;扫描件仍诚实拒绝)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 依赖:github.com/ledongthuc/pdf(**BSD-3-Clause**,非 MIT —— 子代理核了 LICENSE 并纠正了任务书的写法;零传递依赖;无 cgo/无外部进程;体积 strip 后 +285.5 KiB / +0.74%,在 detached worktree 里量取,产出写到仓库外)。被否候选:pdfcpu 无文本抽取、rsc.io/pdf 停更、pdfium/poppler/mupdf 需 cgo 撞红线。
- 实现:魔数 %PDF- 是唯一入口判定(名字/声明指向 PDF 而字节无魔数 ⇒ ErrUnsupported,绝不退回纯文本抽);逐页 GetPlainText(页间仅去首尾空白);ExtractorVersion v2→v3(所有 sidecar 一次性重抽);页数上限 5000 + 字节安全上限复用;新增哨兵 ErrPDFEncrypted / ErrPDFDamaged。
- 诚实边界:无文本层(扫描件)⇒ ErrNoTextLayer 且文案含「图片型/无文本层 PDF,需要 OCR(当前未做)」;加密/损坏各自可判、绝不 panic、不回空正文假装成功。
- 白名单与成对裁决:.pdf 纳入(22→23),且 ContentAllowed 做双向裁决(.pdf 只收嗅探为 application/pdf;PDF 字节只收在 .pdf 名下);读侧真 PDF 如实下发 application/pdf,仍 attachment + nosniff(不可内联)。
- 1 处 panic 兜底已如实登记:该库对「xref 指向文件外」畸形件会在 NumPage() 直接 panic(已用仓库外程序复现),单二进制常驻后端不能因 587 字节上传件崩掉 ⇒ defer recover 折成 ErrPDFDamaged;损坏类主用例仍走库的真出错路径。
- 主会话独立验收:隔离 worktree(B1 的 14 文件)build/vet 绿、全树 59 包 0 FAIL、PDF 用例 13 PASS、双平台交叉编译绿;变异独立复现(魔数裁决短路 ⇒ 魔数/成对裁决用例红)。
- ⚠️ 契约漂移(已排 C2):Docs 仍写「22 项白名单 / 嗅探仅放行 text/plain」,与本切片的 23 项 + 成对裁决不一致 ⇒ 需升 v0.61 登记 .pdf 与新的边界。
|
2026-10-04 19:46:36 +08:00 |
|