Commit Graph
100 Commits
Author SHA1 Message Date
AskaEth 852c327360 refactor(web): 随路径式入口清理失效的管理链接(设计:用户端不提供管理入口)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- TopBar / Nav 的 hash 形式管理入口(#/admin、#/admin/system)**删除**(design/07 §0.2:用户端不提供任何
  管理入口、bundle 不含该路径串);面板只经 ADMIN_PATH 路径直入
- AdminApp 三处 window.location.hash = "" 改调 router 导出的 navigateUserShell()
- 复核:tsc 0 错 + vite build 成功;用户端源码 grep "#/admin" 0 命中(仅在 channels/useAdapterNav.ts
  尚有模块内闭环的 hash 字面量,属另一决策,仅报告未改)
- 局限:无 playwright/puppeteer 且不许加依赖 ⇒ 「布局未塌」为结构+静态样式依赖推断(无子元素数量相关样式)
2026-10-04 14:01:43 +08:00
AskaEth a398dc9b92 feat(config,gateway,web): 路径式安全入口 ADMIN_PATH + 数据目录 platform 哨兵(裁决 1.6/16.4.7/12.4.2 + 1.5/12.4.3 核对)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 1.6 + 16.4.7:安全入口=路径式 `/<ADMIN_PATH>`(env `ADMIN_PATH`,默认 `admin`;`xxx/admin` 亦可,
  非法值回退 `/admin`)。命中前缀 → 送注入了 `<base href="/">` + `window.__CYRENE_ADMIN_ENTRY__` 的面板入口页
  (深链直达);**形如面板入口但前缀不符 → 404**(不回落用户端聊天壳);`/api/*`、`/ws`、webhook 先放行
  ⇒ **API 不藏**。前端路由改**路径式**(`router.ts` 只读注入,无硬编兜底;移除 hash 入口)
- 1.5 / 12.4.3 核对:暴露面默认 `0.0.0.0` 与裁决一致,无需改代码(仅补注释)
- 12.4.2:`DATA_DIR` 支持 `platform` 哨兵(大小写不敏感)→ 平台标准目录;默认 `./data` 与显式空值行为不变
  (显式空值保持原兜底,避免正在运行实例突然换库)
- 复核:build/vet 绿 + config/gateway 包测试绿 + linux/darwin 交叉编译绿;新增 7 条用例,
  并用真实构建产物(`webui.Assets()`)跑通「浏览器直开 /admin 能启动面板」(`<base>` 必须在资源引用之前)
- 变异 2 条(关掉错前缀 404 / 前端默认前缀兜底)已还原
- ⚠️ 运行时序:前端已生效而后端 hatch 未生效期间**面板打不开** ⇒ 需紧接一次「build 到位 + restart」
2026-10-04 14:00:25 +08:00
AskaEth 9253fef92a feat(qq_obv11): OneBot v11 四端齐备(裁决 1.4 / 12.4.1)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 四端=ws×client / ws×server(原有,补语义注释)+ **http×client**(新增 http_client.go:
  调 OneBot HTTP API、请求-响应、retcode 判成败、token 只走 Authorization 头)+
  **http×server**(新增 http_server.go:监听 listen_addr 收上报、fail-closed 鉴权、10MiB 体上限)
- 配置面:适配器新增 transport / api_base_url / report_path(json tag 沿用既有口径);
  env 兜底 CHANNEL_QQ_OBV11_{TRANSPORT,API_BASE_URL,REPORT_PATH};**默认值=现状**(ws+server)
- 出站分流:session.go 发送路径按 transport 走 WS 写帧或 HTTP POST(不改它则 HTTP 端无法出站)
- 端口豁免边界:http+server 沿用 AGENTS §9 已登记的同一豁免(design/13 §0 原文即含 HTTP server),
  且 listen_addr 为空时报错、**绝不开端口**(与 ws 侧同名校验成对)
- 关键取舍(已上报):OneBot 的 HTTP 是「动作 + 上报」两半,mode 决定**哪一半必需**、
  另一半给齐字段就一并启用 ⇒ 单个适配器即可「收上报 + 回消息」,四端仍各自可独立选择与测试
- 复核:build/vet 绿 + qq_obv11/cmd 包测试绿 + `-race` 干净;10 条新用例(HTTP 两端全部用本地
  httptest 假 OneBot,未连真实 QQ);变异 3 条(伪造 retcode 成功 / 去掉上报鉴权门 / 默认值翻 http)已还原
- 未接:get_group_list(后续)、无 token 时放行(与既有 ws_server 一致,如需 fail-closed 请裁决)
2026-10-04 13:58:18 +08:00
AskaEth 0e1f163ef9 feat(gateway,admin-web): 可观测面从轮询改为 WS 帧推送(裁决 05.4.3 + 16.4.3-WS)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增三个下行帧(纯新增,既有帧名/形状零改动):observability_log / observability_trace /
  observability_metrics,各带 ≤1s 节拍、无新数据不发帧、单帧上限(日志 200 / span 100)
- 帧壳 {type,data} 不带 sessionId;**不进 frameReachesAll 白名单** ⇒ fail-closed 只发 owner+admin
  (变异验证两次:加进白名单 → viewer 用例必红)
- 推送开启条件=owner/admin 凭据「读过一次」对应 REST(引导数据仍由该次读取返回),
  连接全断即退订并停循环(无常驻 goroutine);既有 REST 端点路径/RBAC/形状完全不变
- 前端:删除 logs/metrics/trace 三处 useAutoReload 轮询;新增 observabilityStream(引导+去重合并+窗口)
  与 observabilityViews(三个面板 + WS 徽标),长列表统一走已落地的 VirtualList
- 复核:build/vet 绿 + gateway 全包测试绿(含真 bus→真 /ws/chat 三角色帧断言)+ `-race`;
  前端 tsc 0 错 + vite build 成功
- 待登记契约:api-client 需新增「可观测实时推送」一节
2026-10-04 13:58:03 +08:00
AskaEth 74b12d87ca fix(history,loop,gateway): 上下文续接的最新性/一致性三条(裁决 13.4.5 的面内部分)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
本组只落地「在允许文件面内、且能用 design/15 既有规格推出」的部分;13.4.5 的「真并行 + 动态旧上下文」
与 design/15 §2.1/§2.2 的既有串行续接规格存在读法冲突,按规程**停下上报**(未自创语义)。

- history.Recent(续接窗口)并列打破:created_at DESC, rowid DESC —— Windows time.Now() 步长 ~0.44ms,
  用户连发必然同毫秒并列;修复前并列时取**更早**的 ⇒ 丢掉刚发出的最新 n 条且同毫秒内反插入序
- 上行 chat 不再经**可丢弃**的 bus:直达会话编排器的非阻塞 Submit(design/15 §2.1「会话消息不能被 drop」
  + §4「不阻塞是硬约束」);orchestrator 为 nil 时回退 bus,旧断言仍绿
- 被**取消**(中断)的回合把该会话 checkpoint 落定为不可恢复(用 context.WithoutCancel 写,快照保留可查询,
  仅在回合未落定时触发)⇒ 不再顶掉下一条消息的上下文
- 复核:build/vet 绿 + loop/gateway/history 三包测试与 -race 全绿;新增 5 条用例(含一条真 WS+真编排器+
  真 loop+真 persist 的集成用例);**3 条变异反证**精确复现「新消息被丢弃只见旧窗口」「窗口取早不取新」
  「bus 饱和时会话消息静默消失」,均已还原
- 待裁决(已上报主人):① 「同一会话多回合真并行」需改 checkpoint 键(sessionID:step 并发互相覆盖)、
  flow 图按轮键控、前端单槽 busy —— 均在本组文件面外;② 「动态吸收新到消息」在 design/15 **无规格**,
  属新语义,须先写设计;③ 非取消的错误退出同样留可恢复快照(同机制,未扩范围,因 resume_test 正钉住旧行为)
2026-10-04 13:56:35 +08:00
AskaEth a1083a322f feat(mcp): 协议层换成官方 Model Context Protocol SDK,删自研实现(裁决 10.4.6 + AGENTS §9.5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增依赖:github.com/modelcontextprotocol/go-sdk v1.8.0(direct)+ github.com/google/jsonschema-go v0.4.3
  (SDK 自带依赖,直接用于 schema 校验/默认值,避免再造一份);go.sum 纯新增
- 删除自研协议层:rpc.go / stdio.go / http.go / sse.go 及其测试
- 用 SDK 的成熟逻辑:命令传输(stdio 子进程)/ SSE / Streamable HTTP、协议版本协商与
  -32022 回退、keepalive(含失败阈值)、tools/list_changed 订阅、能力协商(InitializeResult.Capabilities)
- 保留自研的四件事(其余零自研):配置→传输、发现→registry 注册、能力族推断、schema 缓存(Prepare 命中不重复解析)
- 能力闸诚实化:服务器声明 capabilities 但不含 tools 时 ListTools/DiscoverTools 返回
  ErrCapabilityNotDeclared(不静默空集);完全不声明 capabilities 的老式服务器仍按 legacy 处理
- 复核:build 绿 + capability 全包测试绿(mcp 42 用例,含真子进程 stdio / httptest Streamable HTTP /
  内存传输三类假 server,不依赖外网)+ `-race` 干净;变异 2 条(featuresFrom 恒 true / 删 applySchema 调用)已还原
- 无 REST/WS/env/数据目录变更;包内 API 有增删(见回报),仓外调用方签名未变(未改 cmd/cyrene 与 plugin/manager)
2026-10-04 13:53:47 +08:00
AskaEth 7b05b4bfa9 docs(api): api-client → v0.54(渠道配置 patch 语义 + 阶段 6 四项)+ api-reference 同步
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 渠道写路径语义变更:config 从「整条替换」改为**三态补丁**(省略=保留 / 非空=覆盖 / 显式 null 或 ""=清除),
  并写明四类数据安全后果与破坏性提示;api-reference §5.24 同步
- 阶段 6 四项登记:16.4.1 静默/活动态与预约切换、16.4.5 会话级消息缓存(非激活帧按 sessionId 入桶、
  不再丢弃)、16.4.3/16.4.6 虚拟滚动与专属页只读卡、16.4.4 双档 UI 模式(性能档=现状,持久化 cyrene.ui-mode)
- 修正 4 类过时陈述:① 渠道写路径整条替换;②「忽略非激活会话的帧 / 按激活会话过滤」(api-client 四处
  + api-reference 一处);③ graph_update 无会话归属;④ api-reference 漏记 GET /admin/llm/catalog(补 §4.9.1)
- 自证:旧陈述 0 命中;新词 PatchChannel/补丁/ui-mode-fancy/sessionCache/sess-backthink 均命中
2026-10-04 13:48:24 +08:00
AskaEth 4ae714883d fix(gateway,runtimeconfig): 渠道配置写路径改为 patch 语义,修凭据静默丢失(P0 级数据安全)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新问题(本轮前端组发现):config 是整条替换,而面板只发送用户本次碰过的平台字段 ⇒
  ① 保存微信(iLink)账号卡(其平台字段只有 cdn_base_url,甚至可为空)会把
  bot_token/bot_id/owner_id/get_updates_buf **全部抹掉**(iLink token 是长期凭据);
  ② Telegram 填 webhook_url 而不重填 token 会因 cleanAccounts「token 空即丢账号」把**整个账号**删掉
- 修法:新增 runtimeconfig.PatchChannel + mergeChannelConfig + isEmptyConfigValue(三态,与 personaId 同构)
  · 键省略 / config 整体缺席 / null = **保留已存值**(未给出的键绝不被抹掉)
  · 给出非空值 = 覆盖 · 显式 null 或 "" = **清除该键**(0/false 不算空)
  · 写路径 UpsertChannel → PatchChannel;非 config 字段(名单/限流/personaId)仍整值替换(不变)
- 回归(真断言落盘内容):微信卡片只带 cdn_base_url / 空卡片三种形态 → 四个凭据键全在;
  Telegram 只带 webhook_url → 账号存活且 token 不变;显式 null/"" → 键真被清除;新账号正常写入;
  既有 TestAdminSaveChannelConfigPersistsAndReloads 保持绿
- 变异反证 3 条(改回 UpsertChannel → 精确复现两缺陷;去掉清除分支;"" 不再算空)均已还原
- 边界未改:token 要求型平台**显式**清 token 仍按 cleanAccounts 丢账号(主人明确清除凭据的语义);
  「本次一个 config 键都没给出」在非 weixin-ilink 平台仍回 400(既有行为,不在授权内放宽)
2026-10-04 13:42:37 +08:00
AskaEth 8d70f6d331 feat(web): 双档 UI 模式(性能档 = 现状 / Smooth Corner 华丽档)+ 开关挂载(裁决 16.4.4)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- design/uiMode.ts:档位纯逻辑(localStorage `cyrene.ui-mode` 持久化、跨标签页同步、未知值回落默认档),
  只改 <html> 类名与 data 属性 ⇒ 不碰 React 树、不重挂载、几何尺寸不变
- 性能档=**现状**:tokens.css / styles/index.css 的改动**全为新增(0 删除)**,且全部限定在
  `:root.ui-mode-fancy` 下;75 元素签名比对证明性能档渲染与改动前逐元素一致
- 华丽档对齐六大核心:连续曲率 corner-shape / 多层 Surface 环境光 / 弹簧动效 / 深色优先(浅色单列补偿)/
  毛玻璃升级 / Liquid Glass(4 折射 + RGB 色差 + 鼠标跟踪 + 双层动态边框)
- 开关挂载:main.tsx 在 createRoot 之前 initUiMode(零闪烁);TopBar/Nav/AdminTopBar 三处挂载,
  窄屏(<640px)按既有约定收起(实测否则 375px 角色块被压到 27px、面板溢出 73px),移动端由侧栏抽屉覆盖
- 复核:tsc 0 错 + vite build 成功;uiMode 38 项 node 测试 + headless Chrome 真渲染 30 项(真点击/真持久化/
  真指针事件);变异反证(去掉读回 → 持久化往返必红)
2026-10-04 13:41:09 +08:00
AskaEth c1a98d7c01 perf(admin-web): 长列表虚拟滚动 + 专属页只读配置卡(裁决 16.4.3/16.4.6)+ 修限流读写成对的数据丢失
- 16.4.3(效率最高):AdminTable 超阈值走窗口化(DOM 行数与数据量解耦,上下占位行撑滚动条);
  新增可复用 VirtualList;适配器消息日志换成窗口化版
- 16.4.6(操作更便利):保留列表页内联编辑,同时把「专属页只读展示该平台完整字段」真正实现
  (AdapterConfigSummary,此前专属页一个配置字段都不显示)
- 🔴 顺带修一条静默数据丢失:列表页限流三项原从 acct.config.* 读,而后端把它们存**账号顶层**
  ⇒ 表单永远回显空、保存时 undefined→0 会把已设限流清空。现改从顶层读、留空则不发该键、非法输入报错中止;
  并顺手改正用户可见的错误文案(0 = 保护性默认 6条/分,负值才是关闭)
- 复核:tsc 0 错 + vite build 成功;channel limits 27 / admin virtual scroll 8023 项断言;3 条变异反证
2026-10-04 13:41:08 +08:00
AskaEth b480b04dd0 feat(web,ws): 会话级消息缓存 + 图桶 + 忙态按会话过滤(裁决 16.4.5/16.4.2 + 修 16.4.1 的帧缺口)
- 16.4.5:非激活会话的帧**不再丢弃**,按自己的 sessionId 落进该会话桶(新增 store/sessionCache.ts 纯函数 +
  useAppStore.sessionMessages/flowGraphs);切会话改为「存回/取出」而非清空;流式增量 O(1) 定位桶、
  只延长已存在的助手气泡(不凭一帧 delta 凭空造消息)
- 16.4.2:图按会话入桶(上限 12 个),Backthink 页取 sess-backthink 的图而不再被用户会话图顶替;
  空态区分「实时通道未连接」与「尚未产生链路图」;wire_runtime 的图源白名单注释按裁决口径重写(零行为改动)
- 修 16.4.1 的缺口:handlers/session.ts 补 `session_idle` / `session_interrupted`(此前落到未知帧兜底 ⇒
  sessionBusy 可能卡 true ⇒ 预约永不落地);忙态**置忙与置闲两侧**统一按 frameIsActive 过滤(只过滤置闲
  会让后台会话的忙帧点亮徽标而它自己的 idle 被丢 ⇒ 永久卡忙)
- 清理:删除 lib/streamScope.ts(口径已被 16.4.5 推翻)与只剩测试在用的 messageEventApplies
- 复核:tsc 0 错 + vite build 成功;messageSync 69 / session 19 项逻辑测试;4 条变异反证
2026-10-04 13:41:02 +08:00
AskaEth f454273c5a feat(web): 静默/活动态切换 + 预约生效(裁决 16.4.1)
- 唯一逻辑入口 components/modeSwitch.ts(纯函数判定 + 预约态 + 落地/撤销)+ ModeSelector 组件
- 活动态:她自己切(下行 mode_switch)直接生效,用户此时切换只登记**预约**、不偷偷改、不上行,
  且不清除预约(两层不打架)
- 静默态:用户可即时切 + 上行 mode_switch
- **用户提前切 → 下次静默态生效**:`active` 由 true→false 时落地;徽章写「已预约 · X(她静默后生效)」,
  不会让人误以为立刻生效
- 「活动/静默」判据=她是否在忙(sessionBusy || 屏上 streaming || sending);若口径要改成 capacity 三态,
  只需改 modeSwitch.ts 的一个谓词(已登记)
- 复核:tsc 0 错 + vite build 成功 + node 逻辑测试 28 项;2 条变异反证(去掉活动态分叉 / 去掉 active 守卫)
2026-10-04 13:41:00 +08:00
AskaEth 093f38d986 docs(comments): 注释中文化 · internal/services/channelbridge(纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态)
- 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避)
- 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
2026-10-04 13:33:06 +08:00
AskaEth 61cf80d5ee docs(comments): 注释中文化 · internal/runtime/memory(31 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串:820 增 / 820 删 严格 1:1;剥掉整行注释与行尾注释尾巴后,
  逐文件与 HEAD 比对「代码部分差异 = 0」;裁决/design 编号计数逐项与 HEAD 一致
- 保留可执行 SQL 样例、SQLite 报错原文、pgvector/HNSW/ANN/BLOB 等技术词原文
- 复核:go build 绿 + memory/projectindex/vector 三包测试绿;gofmt 仅剩 2 个既有未格式化文件(改动前即如此,未顺手修)
2026-10-04 13:32:54 +08:00
AskaEth eb1828ee7a docs(comments): 注释中文化 · internal/runtime/orchestration(29 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态)
- 裁决编号与 design 节号(02.4.3 / 11.4.2 / design/05 §9 等)逐字保留
- 复核:go build 绿;orchestration 及其子包(audit/comm)测试绿;gofmt 干净
2026-10-04 13:30:04 +08:00
AskaEth fe90f64a56 docs(agents): 登记主人新红线「后端常驻」——下一次构建产物就绪前不准关后端
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- AGENTS.md §9 新增一条:运行中的后端(cyrene.exe,:10942)在**下一次构建产物就绪之前不准关**,
  除非出现恶性 Bug(进程致命错误/必须阻断的严重故障)
- 推论写入铁律:① 跑 go test/build/git/验收都不需要停后端;② 换二进制只允许「先 build 到位、
  再一次 restart」,不留空窗;③ 启动不许用前台阻塞式包装(ctrl.bat start/restart 会守着进程轮询健康,
  工具超时会连带杀进程树)——必须确认进程脱离工具;④ 压缩/中断后先探活(netstat + /api/v1/health)再判断
- 同步记进 DevDocs/工作小结/00-当前态.md 的红线区(含本次实测:TaskStop 掉包装后后端存活、PID 文件已写)
2026-10-04 13:18:26 +08:00
AskaEth 7afef8d037 fix(loop,gateway,persona): awakening 逐 turn 按角色生效 + 修「面板保存角色抹掉 5 个块」的数据丢失(裁决 14.4.3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- per-role 注入补齐:Options.PersonaAwakening(personaID) → Loop.personaAwakening → l.awakeningFor(ctx)
  → capacity.Input.Awakening(toolSchemas 是全仓唯一构造该 Input 处)
  · 按 turn 解析(不绑 Loop 构造期,避免并发会话数据竞争),与既有 personaFor/knobsFor 同构
  · 未知角色/空角色/档案无该块 ⇒ 零值 ⇒ 回落 Config.Awakening(不声明者行为与今天构造性一致)
- **数据丢失修复**:persona PUT 是整块替换,此前只保留 thinking ⇒ 保存一次会同时抹掉
  awakening / visual(头像·背景) / triggerWords(群聊提醒词) / emotion(情绪机参数) / affection(好感度曲线)。
  新增 applyAwakeningEdit + applyPersonaReadOnlyBlocks 逐一保留(cyrene.yaml 五块全写,属必现故障)
- 文件守 500 行:主人档案段原样搬移到 admin_owner_profile.go(零逻辑变更)
- 复核:build/vet 绿 + 全树 `go test ./... -count=1` 0 FAIL;端到端=真 YAML→loader→buildCapacity→真 loop
  流式请求→断言工具面(tasky 声明 force_state 唤醒 → 含 web-search;cyrene 不声明 → 与无解析器对照逐字相同);
  **8 条变异反证**先红后还原(含逐块删除各自的保留逻辑)
- 残留缺口(登记):wire_runtime.go:254 那一行本身无用例覆盖;loader 非 strict(未知 YAML 键静默忽略,
  需主人裁决是否开 KnownFields);POST /admin/personas 仍写不出这五个块
2026-10-04 13:07:52 +08:00
AskaEth 58cba0b8d2 docs(api): api-client → v0.53(阶段 5 全部契约)+ api-reference 同步 + .env.example 补 9 键
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新端点 GET /admin/llm/catalog 全契约(200/gzip/503 catalog_unavailable/快照副作用/不进热路径/审计 llm.catalog)
- models.json 组级三键:reasoning.kind(形状)+ personaModels/personaProviders(角色级模型/协议路由),
  含「缺省=保留、给出=整块/整表替换」语义、Group.Validate 的 400 口径、回落与 WARN 规则
- GET /admin/llm 新增恒下发的 reasoning 与两个取值域(reasoningKinds/personaProviderOptions)
- 语义修正(此前文档写反):multimodal = 主模型能否收图;LLM_VISION_MODEL/LLM_OCR_MODEL 只配视觉/OCR 工具
- 新工具 image-crop(x/y/width/height、50MiB/16MP/8MiB 上限、越界报错不 clamp、策略默认 owner-only)
- 推理开关按形状下发:替换 v0.41「Qwen 只 WARN 不发」;effort 非法值改为丢弃+WARN(不降档)、effort:none 显式关思考
- .env.example 新增 9 键(LLM_REASONING_KIND / LLM_OCR_MODEL / LLM_LOCAL_* / CAPACITY_L3_*),默认均为保守值
- 权限矩阵计数按 routes.go 校正 56→59(owner/admin 只读档 9→10,含 /admin/llm/catalog)
2026-10-04 13:07:09 +08:00
AskaEth 1ac02bd77e feat(gateway,admin-web): 面板可编辑 provider 推理形状 / 角色级路由 / multimodal 真语义(裁决 03.4.2/03.4.1)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 后端:AdminLlmProvider 暴露 reasoning{kind}(恒下发,""=未声明=auto)、personaModels、personaProviders;
  顶层新增 reasoningKinds / personaProviderOptions 两个**取值域**(唯一来源= runtimeconfig 导出枚举,
  校验错误文案也由枚举拼接 ⇒ 不可能漂移)
- 读写共用同一映射(admin_llm_provider.go),避免「读侧少一个键 ⇒ 面板一保存就抹掉」
- 写侧语义:缺失=保留、给出=整块/整表替换;非法值一律 runtimeconfig.Group.Validate() ⇒ 400 bad_request
  (实测非法 kind 返回 400 且 models.json 完全不动,而非 500 静默失败)
- 前端:新增 LlmProviderRouting(推理形状下拉 + multimodal 开关 + 按角色路由编辑器);
  目录能力位改三态(true/false/未标注),「未标注」不再谎称「不支持」;目录/角色清单不可用时
  **不阻塞编辑**且与「字段为空」是不同状态;绝无 mock/sample 回退
- 复核:build/vet 绿 + gateway/config 包测试绿 + tsc 0 错 + vite build 成功;2 条变异反证(删 reasoning
  补丁 → 2 例红;删 Group.Validate → 该 400 用例红)已还原
- 注意(主会话已知):npm run build 把新 SPA 写进 frontend/web/dist,而运行中的二进制优先从磁盘读该目录
  ⇒ :10942 目前是「新面板 + 旧后端」,等阶段 5 全绿后按红线「先 build 到位、再一次 restart」切换
2026-10-04 13:06:54 +08:00
AskaEth 8890c30bb5 feat(model): 按 provider 形状独立适配推理开关 + per-persona 提供商链真接线(裁决 03.4.2/08.4.1/03.4.1 余项)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增 reasoning_shape.go:**每个 provider 形状一个 resolver,只发自己形状的字段**
  · deepseek:thinking{type} + reasoning_effort(依据 DevDocs/reference/deepseek 官方本地副本)
  · vllm:chat_template_kwargs.enable_thinking · dashscope:顶层 enable_thinking(**只允许显式声明**)
  · openai:仅 reasoning_effort · anthropic:自己的 thinking{budget_tokens}(未改)
  · 未知 kind → WARN 且一个字段都不发(不许静默失效)
- 形状载体:models.json 组级 `reasoning.kind`(可热更新,未声明则不写该键)+ env `LLM_REASONING_KIND`
- 红线:`effort:none` 在只有布尔开关的形状上翻成**显式关思考**,绝不降成任何强度档
- 03.4.1 余项:`Request.PersonaID` + `RuntimeLLM.ForPersona(pid)` 视图;
  每 persona 协议一条链并消费 `RouteProviderForPersona`;`agent/request.go` 非流式路径同一钩子
- **生产装配(主会话补)**:默认 agent 与 personaAgentPool 都改用 `ForPersona(pid)` 视图——
  否则角色级模型/提供商路由在生产里永不被选中(形同虚设)
- 复核:build/vet 绿 + model/agent/config/cmd 包测试绿;5 条变异反证先红后还原
  (其中一条「红但没钉住」已改用例:通道读取加 2s 超时,避免整包 panic 掩盖信号)
- 未落:DashScope 的 auto 推断(缺官方依据,只允许显式声明);Qwen `/think` 前导消息
  (design/01 §2.1 与 design/11 §3 口径冲突,停手报告)
2026-10-04 12:57:55 +08:00
AskaEth 71fefe5fc3 feat(persona,capacity): persona awakening 档位真生效 + 当前主角色已填(裁决 14.4.3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- loader 新增可选 awakening 块(default_state/overrides{min_confidence,force_state}),载入期校验 + 归一化,
  非法值显式报错(拒绝「写了却静默无效」);缺失 = nil = 无该档位,不补默认
- 组合根把当前主角色档案的档位填进 capacity.Config.Awakening(未知档位名告警后按无档位处理),
  真的参与 Decide → applyAwakening(MinConfidence 优先于全局 0.6;force_state 钉死态)
- 当前主角色 cyrene.yaml 填 default_state=静息 + min_confidence=0.7(设计 §3 陪伴型取静息;
  0.7 压掉 L2 趋势(0.66)误唤醒、仍低于 L1 强信号(0.9),温柔陪伴不误打扰)
- 复核:build/vet 绿 + persona/capacity/cmd 包测试绿;端到端 4 例锁定「同回合有无档位判定相反」;
  变异两轮(yaml tag 改错 → 8 处红;解析但不接线 → 端到端红)均已还原
- 已知盲区(另派):per-role 注入未接线(loop/request.go 的 Input.Awakening);
  面板整块替换 persona 会抹掉 awakening 块(需 applyAwakeningEdit)
2026-10-04 12:49:40 +08:00
AskaEth 38b4527949 feat(tool,loop): 工具调用软判并回(裁决 14.4.2)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 落点=Dispatcher.Invoke 入口(唯一执行口,覆盖主循环/子代理/编排全部调用方)
- 语义=**不硬阻断**:越界调用照常执行,把 not_allowed 提示与软判理由挂到真实结果上回灌模型
  (符合 design/14 §1「状态改变不构成能力边界、紧急时也能调」与 §4「入口前置软判」)
- 允许集单一真源:loop 发布 capacity 过滤后的 schema 名单(= 模型本轮实际看到的工具面),
  分发器不在本地另算一套判定
- 审计:复用既有 AuditEvent/OnAudit 通道(what=tool.reject.not_allowed,design/14 §4 原文),
  前端小字与审计日志同源;日志带 source=runtime/tool
- ctx 载体:Run 入口每 Run 播种允许集持有者(run.go +6 行,越界但必需——ctx 只能由 Run 或
  request.go 播种,后者归 G5-A)
- 与既有硬闸门零重叠:restrict(群聊/陌生人)与 ToolWhitelist(子代理)仍在更早处硬拦 + 各自审计
- 复核:`go build`/`go vet`/tool+loop 包测试绿,`-race` 下 SoftJudge/AllowSet 无竞态;
  变异 M1(软判恒空)4 例红、M2(不发布允许集)仅端到端 1 例红 ⇒ 两侧各被独立覆盖
2026-10-04 12:45:48 +08:00
AskaEth 3972ea4007 refactor(admin-web): 模型目录面板改走既有 API 层(adminGetLlmCatalog)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增 lib/api/admin.ts 的 adminGetLlmCatalog({refresh}) + 目录响应类型(lib/types/admin.ts),并由 lib/api.ts 再导出
- LlmCatalogPanel 不再直连 lib/api/core(它曾是全仓唯一这样做的 admin 模块),行为不变:
  五种状态判定、gzip 响应、离线粘贴导入均保持
- 复核:grep lib/api/core 全仓前端 0 命中;npx tsc --noEmit 0 错;npm run build 成功
2026-10-04 12:45:36 +08:00
AskaEth fa33c79e11 feat(model,capacity,vision,io): per-persona 路由 + 本地三级(默认关) + L3 分类 + OCR 独立模型 + image-crop 工具(裁决 03.4.1/03.4.3/14.4.1/14.4.4/14.4.5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 03.4.1:per-persona 路由载体选 models.json(组内新增 personaModels/personaProviders + 校验 + PersonaRoute()),
  Router/RuntimeLLM 快照绑定、loop 流式路径按该 turn 的 persona 路由;主步用路由结果、降级步钉自己的模型
- 03.4.3:本地第三级降级链(LLM_LOCAL_*,Configured() 含 Enabled ⇒ **默认关**),warn 标注来源
- 14.4.4:capacity 接入 L3(分类器接口 + TTL 缓存 + 严格 JSON 解析),CAPACITY_L3_ENABLED **默认 false**
- 14.4.1:语义修正 —— group.multimodal 才是「主模型能否收图」(env 路径不再臆断),LLM_VISION_MODEL 只配视觉工具;
  vision/OCR 可各用不同模型;runtime 不再因有图抢路由(vision.Router 是唯一分层权威)
- 14.4.5:新增 image-crop 工具(工具数 5→6,策略默认 owner-only),schema 支持按区域裁切
- 组合根:wire_runtime 读 group.Multimodal;capacity 装配拆出 wire_capacity.go(守 500 行)
- 复核:`go build`/`go vet`/各包 `go test -count=1` 全绿;**6 条变异反证**先红后还原(含作者自查出的 2 条假阳性变异)
- 未落:按 provider 的 thinking 开关(需 openai_encode.go/openai.go 文件面)、persona awakening(需 loader 文件面)
2026-10-04 12:44:52 +08:00
AskaEth 63e5b70e3d feat(gateway,admin-web): 模型目录自动化 —— GET /admin/llm/catalog(裁决 03.4.2 次件)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增 GET /api/v1/admin/llm/catalog(owner|admin,?refresh=1 强制刷新):抓 models.dev api.json 并裁剪
  (226 供应商 / 8392 模型 → 1.31 MB),写落盘快照 data/llm_catalog.json(无凭据,TTL 24h,gitignore)
- 降级诚实:快照优先 → 在线刷新 → stale 快照兜底(标 stale+warning)→ 都不行回 503 catalog_unavailable,
  绝不编造;抓取只发生在该端点内,**不进运行时热路径**
- 前端:新增 llm/LlmCatalogPanel(在线目录 + 粘贴导入落同一份候选 → 一键带出 baseURL/model id/multimodal/
  maxTokens);Llm.tsx 挂载面板、删掉硬编码的「主→备→本地」假状态、表单按 id#seq 重挂
- 诚实性:面板把「加载中 / 端点未实现 / 抓取失败或离线 / 响应结构不认识 / 目录为空」分成五种独立状态,
  全程无 mock/sample 回退
- 复核:新增 9 条零外网用例全绿;**变异**(Stale:true→false / TS 只认 camelCase)各自红后已还原;
  Go 真产出与 TS 解析器双向实测 8392 模型能力位零差异
- 越界说明:routes.go 仅 +3 行(注册该路由);llm/LlmCatalogPanel.tsx 为拆出以守住 Llm.tsx ≤500 行
2026-10-04 12:43:04 +08:00
AskaEth b8fa53e43c docs(api): api-client → v0.52(approval_notice 帧 + 策略落盘 + graph props 新键)+ api-reference 去假陈述
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增下行帧 approval_notice(§3.11,主代理代审小字,owner+admin,fail-closed 默认档);
  审计口径 who=角色名 / what=approval.<工具> / result=granted|denied
- 工具策略落盘口径写入端点表(载体 tool_policies,迁移 0012,先落库后改内存)
- graph_update 节点 props 新增四类可选键(tool args_keys/args_summary、guardrail、memory、orchestration),
  纯增量 + 发布前脱敏;帧名/事件名/顶层字段集未动
- api-reference:439 的「仅内存标记,重启复位」改为「落盘 tool_policies,重启保留」;§4.17 补旁路节点 span
- 按实现修正 7 处表述:args_keys 是「全部参数名」(只有 args_summary 限放行表)、explain 带「」引号、
  detail 只截目标部分、allowed=false 还覆盖无代审者/代审报错两条 fail-closed 路径等
2026-10-04 12:34:14 +08:00
AskaEth b99268d019 feat(trace,loop,orchestration): memory/guardrail/orchestration 真 span + 参数摘要脱敏 + Guardrail 输出侧异步(裁决 11.4.2/11.4.3/13.4.1)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 11.4.2:三类节点首次真进图 —— memory.recall/memory.write(loop/memory.go 召回与写入点)、
  guardrail.input/output(流式主路径两侧 + agent.RunStep 静音路径)、orchestration.spawn
  (scheduler 每次派发/收束);新增 trace.ChildSpan 让不持有 Tracer 的叶子包也能开子 span
- 11.4.3(设计先行,规格先写进 design/19 §1.1):tool 节点参数摘要 —— 放行表 argValueKeys +
  trace.IsSecretKey 统一判据(密钥类键不给值)+ 单值 80 rune / ≤24 条 + 出图前再清洗;
  前端 NodeDetail 新增「参数摘要」区,未放行键显示「值未展示」
- 13.4.1:流式主路径输出侧护栏改后台 goroutine(脱离 context、30s 兜底、不阻塞回合),
  输入侧仍在模型调用前同步拦下;拦截闭环到 guardrail 节点 + 审计事件
- 拆文件:scheduler.go 加 span 后 546 行 → 编排 span 助手拆出 scheduler_spans.go(回到 492)
- 契约:无帧名/事件形状变化;graph_update 节点 props 新增**可选**键(tool args_keys/args_summary、
  guardrail action/allow/elapsed_ms、memory action/scope/hits/…、orchestration subagent/persona),
  文档同步由主会话统一落
2026-10-04 12:31:20 +08:00
AskaEth 0dfad675b8 fix(admin-web): 删掉面板硬编码假数据,改成诚实的空态/错误态(P0-24,审计 §4.3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- Automations.tsx / Llm.tsx 原有的 sample 回退("后端在线但列表为空 → 渲样例数据")整段删除
- 后端不可达 → 明确错误态;后端可达但没有数据 → 空列表 + 引导文案(对齐 Users.tsx 已修做法)
- 复核:grep sample 零命中;npx tsc --noEmit 0 错误;npm run build 成功
2026-10-04 12:30:38 +08:00
AskaEth 63236c9b00 feat(capability,gateway): 工具策略落盘 + 主代理代审真接线(裁决 1.1 核对/1.3/10.4.2/13.4.3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 1.1(核对):registry 默认两布尔 false 已一致,零代码改动(设计文档已按裁决改写)
- 1.3 + 10.4.2:新增迁移 0012_tool_policy(tool_policies 表,表名复数 + idx_tool_policies_name);
  新增 registry.PolicyStore/SQLitePolicyStore,启动 Load 覆盖注册初值、SetPolicy 先落库后改内存
  (落盘失败保持旧值并报错);面板保存路径不变但从此重启仍在
- 13.4.3:代审真接线 —— restrict.SensitiveCall 结构化判定(terminal / file write / http POST)、
  restrict.Gate/Reviewer/Resolver(无代审者或代审报错一律 fail-closed 拦截),
  组合根装进 loop.Options.Hooks 的 CallAdmit;gateway.RecordApproval 落 audit_logs
  (who=角色名 / what=approval.<工具> / result=granted|denied)并广播 approval_notice 帧
- 前端:代审小字「xxx 想要干什么,我审查后批准了」进聊天流顶部(帧→store→组件,文案单一来源
  复用 audit.Approval.Explain()),附纯 node 逻辑测试
- 复核:迁移在 data/cyrene.db **副本**上 up/down/up 各一次通过;TestRegistry_PolicyStoreSurvivesRestart
  与 TestAdminSetPolicySurvivesRestart 证明「重启后策略仍在」;terminal 触发代审 + 审计有行有端到端断言
2026-10-04 12:30:16 +08:00
AskaEth ed5bd92e1b fix(exec): 沙箱子进程不再继承宿主完整环境(P0-7,审计 §4.3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- buildEnv 不再以 os.Environ() 全集为基,改为「平台最小必需键白名单」裁剪后再叠加 req.Env
  · Windows:PATH/PATHEXT/SYSTEMROOT/WINDIR/COMSPEC/TEMP/TMP/USERPROFILE/HOMEDRIVE/HOMEPATH/APPDATA/LOCALAPPDATA/NUMBER_OF_PROCESSORS/OS/PROCESSOR_ARCHITECTURE
  · Unix:PATH/HOME/TMPDIR/LANG/LC_ALL/USER/SHELL
- 键名匹配在 Windows 上大小写不敏感(避免宿主写 Path 时漏掉),overlay 覆盖 base 时同口径去重
- envAllowed 语义不变:调用方能显式放行的键照旧,仅改变 base 来源
- 新增 TestTerminal_Env_HostNotInherited(非并行,t.Setenv):父进程设非白名单变量,断言子进程读不到;反证=把该键加进白名单立刻 FAIL
2026-10-04 12:29:43 +08:00
AskaEth 09ee561aa7 refactor(exec,io): 文件沙箱单入口收进 io 工具、删 exec.FileSandbox(裁决 13.4.4)+ 修一条假阳性安全断言
- 删除未接线的 exec.FileSandbox(含 5 个用例):文件沙箱从此只有 plugins/tools/io/file.go 一处实现
- io 侧取更严一侧:NUL 拒绝(原 io 缺)、写前按最深存在祖先判定落点(避免越界建目录)、roots 归化到真实路径(修 roots 自身是 symlink 时的全误拒)
- exec/sandbox.go 保留并注释说明:那是**终端工作目录守卫**的原语(terminal.go 在用),不是文件沙箱
- 修假阳性:原 NUL 用例断言错误文本含 "NUL",而 t.TempDir() 目录名自带测试函数名 "…RejectsNULBytePath…"、错误又内嵌整条临时路径 ⇒ 删掉安全检查用例照样绿。改为哨兵错误 ErrNullByte + errors.Is(错误文本逐字节不变),修复后同一变异必红
- 同类排查:file_test.go 三处文本断言判定无风险(含空格的串不可能出现在临时目录名里),保留
2026-10-04 12:29:39 +08:00
AskaEth 95f97f5df3 docs(comments): 注释中文化 · plugins/channels/weixin_ilink(20 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串:剥掉行尾注释后,每条「-」都有逐字节相同的「+」对应行(8 组,代码零改动)
- 保留协议/线上标识符原文:errcode、bot_token、context_token、getupdates、binded_redirect、PKCS#7、ECB 等
- 术语对齐既有译法:owner→主人、peer→对端、long poll→长轮询、backoff→退避、sentinel error→哨兵错误
- 已知既有欠债(未顺手修):login_reuse_test.go 在 HEAD 上就未 gofmt 干净;login_test.go 的 TestStartLogin_ScanStatusTransition 91 行 >80(HEAD 同为 91 行)
2026-10-04 12:29:05 +08:00
AskaEth 09fba77f87 docs(comments): 注释中文化 · internal/runtime/history(15 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩 4 条行尾注释的翻译,代码前缀全等)
- 交叉引用(design/16 §4.6、D4/B3、0010 迁移文件名)逐字保留
- 保留未译技术词:DDL/rowid/LIKE/LEFT JOIN/DTO/rune/WS/actor 及全部标识符与表名
- 顺带副作用:store.go、session_persona_test.go 由注释文字改变而变得 gofmt 干净(未运行 gofmt -w)
2026-10-04 12:28:48 +08:00
AskaEth b0fbb8bc0d docs(comments): 注释中文化 · internal/runtime/multimodal(21 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串,代码零变化:go/scanner 词法流比对 21 文件 token+字面量全等,增删行数 1:1(467/467)
- 刻意保留非散文英文:JSON Schema description、工具 Description() 返回值、adapter prompt、API 路径与命令示例、环境变量名
- 逐行确认代码前缀未变,未触碰其他模块
2026-10-04 12:28:16 +08:00
AskaEth b9e042d07d docs(api): api-client v0.51 补管理面契约 + api-reference 去假陈述 + 帧名口径统一(裁决 15.4.2/15.4.3/10.4.5/08.4.2)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- api-client.md v0.51 同版补记 D 组:X-API-Key 已接线(管理面与用户侧已认证端点同时接受该头,恒 owner 级、fail-closed)、新增 owner-profile GET/PUT(owner-only,写带审计)、persona systemPrompt 改必填;§2.0 owner-only 档 35 → 37 条
- api-client.md 修正 /admin/users 条(签发/吊销 key 已实现,POST 创建用户仍诚实 501)与 M10 待办(主人档案保存已落地)
- api-reference.md §0.1 与 §8.4 两处「X-API-Key 未接入」假陈述改为指针(契约以 api-client 为准),版本指针 v0.49 → v0.51
- gateway 帧可见性测试的虚项改为真实帧名常量 wsSubagentResult(此前写的是 bus 主题拼法 subagent.result),测试说明帧名与 topic 的分工
2026-10-04 12:10:21 +08:00
AskaEth adef36538f feat(auth,gateway,user): X-API-Key 真接线 + key 一律超管 + owner 档案真落盘(裁决 15.4.2/15.4.3/10.4.5/08.4.2)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 15.4.2:管理 API 与 Bearer 共用同一 Authorize 路径,X-API-Key 命中签发表即过门;未知/已吊销/空值 401 且不回落 Bearer(fail-closed)
- 15.4.3:key 表去掉角色维度,任一有效 key 鉴权后即 owner;签发接口取消 role 分级(不再读请求体)
- 10.4.5:新增 GET/PUT /api/v1/admin/owner-profile(owner-only,PUT 带审计 owner.profile.update),0600 原子落盘 + 活档案热替换;前端主人档案改接真实 API,读不到即禁用保存,删掉硬编码默认档案
- 08.4.2:persona 的 identity.systemPrompt 必填,缺失/空白 400 显式报错,不再派生回落
2026-10-04 12:07:51 +08:00
AskaEth 8c6466cb00 feat(orchestration,gateway): 子代理结果登 WS 帧 subagent_result(裁决 02.4.3)
- 编排层改为在 subagent.result 上发布带父会话归属的 SubagentResultEvent,后台批次结果不再消失在进程内总线
- gateway 中继:新帧常量 wsSubagentResult="subagent_result",eventBridge/bridgePatterns 登记该 topic,normalizeEvent 专用成形(帧壳 sessionId = 派生该批次的父会话)
- 角色门 fail-closed:未登记进 frameReachesAll,owner/admin 可达、viewer 不可达,真 JWT 三角色 WS 断言钉住
- 前景(2s 内跑完)批次仍只回工具结果,不发此帧
2026-10-04 12:07:46 +08:00
AskaEth 0ce498754a fix(memory,loop): 记忆作用域读写成对 + source_conversation 结构化(裁决 1.9/04.4.2/04.4.3)
- 04.4.2/1.9 读侧:Retrieve 候选集并集 = 本人 persona 域 ∪ user 共享域 ∪ project 共享域(他角色 persona 域仍排除),群聊/陌生人取受限收窄
- 写侧:群聊/陌生人整段跳过记忆提取(可查不可写),persona 域仅属主可写
- 04.4.3:source_conversation 不再由 LLM 自由生成(prompt 不索要、候选结构不含该键),落库时无条件盖真实 {session_id,message_id},编解码集中在 memory/source.go
- 新增跨角色种子用例(原用例即使关掉并集也会通过)+ 成对用例「同群聊上下文:读得到共享域、写不进 store」
- 同步 store.go/retrieve.go 文档注释,与 design/03 §3/§4 口径一致
2026-10-04 12:07:44 +08:00
AskaEth af8591b78a fix(channel,user): owner 车道只认白名单,堵住群主/伪造 role 冒充主人(裁决 06.4.2/06.4.3/06.4.4)
- 适配器只描述平台身份:qq_obv11 的 owner/admin 一律上报 RoleMember,不再上报 RoleOwner
- session 层唯一判主点只认白名单(owner.yaml Accounts ∪ 每渠道 adminIds),nil 档案 fail-closed 拒绝
- 新增「每渠道管理员层」:channels.json ChannelAccount.adminIds[] → runtimeconfig.AdminIDs → OwnerProfile.SetPlatformAdmins(整体替换语义,热重载后移除的 id 立即失效)
- 组合根接线:启动即播种、面板保存先灌白名单再重载适配器(cmd/cyrene/wire_channels.go + 4 条端到端单测)
- 06.4.3 节流与参与度状态按会话独立计数(ConversationKey 三处统一,空会话 GC);06.4.4 未知平台主动发言保守默认 1 条/分钟
2026-10-04 12:07:40 +08:00
AskaEth 7e7f53bb41 fix(orchestration,loop): 子代理现在真的收到任务(P0)+ 工具白名单 + 超时可配(裁决 02.4.2/02.4.1/02.4.4)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
执行方案阶段 2 · P2-1。**02.4.2 是最伤的一条 P0**:`spawntool` 从不填 `AgentSpec.Context`、
`scheduler` 把 nil 交给 loop ⇒ 子代理模型上下文 = 角色 system + 记忆召回 + **0 条消息**,
goal/attach_info/data 一个字都到不了模型(此前测试用 mock adapter 恒答固定文本,测不出)。

- **02.4.2**:新增 `taskContext/taskUserMessage`(`spec.go`)把 **goal 正文 + attach_info + data
  渲染成同一条 user 消息**(`[任务目标]/[附加信息]/[任务数据]` 独立段落);`Spawn()` 设
  `Context: taskContext(spec)`;**system 只保留角色指令**,任务绝不进 system。
  真断言(捕获请求体的 adapter + 真 loop/agent):首请求恰好 **1 条 user 消息**、同时含 goal 与
  attach_info,且**所有 system 消息不含 goal 文本**。
- **02.4.1**:`AgentSpec.ToolWhitelist`(nil/空 = 继承全量);schema 增 `tool_whitelist`
  (顶层 + 每 task 条目,task 未带时继承批次级);`scheduler.run()` 经 ctx 传递 +
  `scopedRunner` 派生;`loop/options.go` **纯增量(+75 / −0)**:`WithToolWhitelist`/
  `ToolWhitelistFrom` + `(*Loop).WithToolWhitelist` 派生副本(浅拷贝仅替换 `hooks.CallAdmit`,
  链式保留原钩子),名单外调用否决为**软结果**回灌模型。
- **02.4.4**:`SUBAGENT_TIMEOUT_SEC`(默认 600)入 `internal/config` + `.env.example`;
  优先级 = 按调用 `timeout_seconds`(1s–24h 校验)> `WithSpawnTimeout`(生产由 cfg 填充)>
  调度器 `PerAgentTimeout`(`wire*.go` 仍是 10min 常量,未动);迟到 span 续根由测试锁定。

验证:**全树 `go build`/`go vet`/`go test ./... -count=1` = 58 包 ok / 0 FAIL**;
`GOOS=linux/amd64` + `GOOS=darwin/arm64` 交叉编译绿;前端 `tsc --noEmit` + `npm run build` 绿;
本批文件全部 ≤500 行、函数 ≤80 行。
变异验证(四条,均带还原证明):① `Context` 改回 `cloneMessages(spec.Context)` →
「1 条 user 消息」断言 FAIL(`user messages = 0`,即 P0 症状);② `WithToolWhitelist` 原样返回 →
显式白名单测试 FAIL(`alpha executed 1 times despite not being whitelisted`);
③ `subCtx` 改回全局默认 → 3 个超时测试 FAIL;④ `foldSpanFinish` 丢掉 `SpanFinished` →
迟到 span 状态断言 FAIL。

⚠️ 已知缺口(**登记,未假装已解决**):① 无真实模型端到端(全部捕获请求体 mock);
② 白名单**只挡执行、不过滤工具广告面**(模型仍能看见未列工具,调用即被拒并拿到可读原因;
过滤需改 `loop/request.go`);③ **迟到 span 的"新一轮已开始"窗口未修**(`loop/flow.go` 的
`StartRound` 会整体替换 `graphs[session]` ⇒ 新轮开始后迟到的 span 仍落进新轮图;需按轮键控图,
属后续);④ per-role 超时覆盖未实现(需 persona 旋钮管道)。
2026-10-04 11:30:39 +08:00
AskaEth 6e7ec56f9a feat(loop,gateway): 事件批 — tool_execution 三事件 + streamId + turn.start/end(裁决 01.4.1/01.4.2/01.4.3/01.4.5/03.4.4/1.12)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
执行方案阶段 2 · P2-3(`DevDocs/audit/_plan.md`)。契约 `Docs/api-client.md` → **v0.50**。

- **01.4.1 `tool_execution` 拆三事件**:`model/stream.go` 删旧 `StreamKindToolExecution`,新增
  `tool_execution.start/update/end` + `ToolPhaseRunning/Done/Error`(与 design/19 status 同值);
  `StreamEvent` 增 `Tool`/`Phase`;`loop/tools.go` 的 `emitToolExecution` 重写为结构化字段、
  **Delta 恒空**(断言锁死),`ID` 复用 provider tool call id(可与 `tool_call.*` 关联)。
  ⚠️ **诚实边界**:start/end 有真实生产者;`update` 的 wire 映射已实现并单测锁定,但**工具分发器
  无中途进度上报源 ⇒ loop 暂不产出 update 帧**(已在 api-client §3.1 如实登记)。
- **03.4.4 phase 驱动图节点状态**:`invokeOne` 用**单一 phase 决策**同时决定 end 帧 phase、
  span 的 `phase` prop 与终态;`trace/graph.go` 新增 `FlowStatusForPhase`,`graph_build.go` 的
  `AddSpan` 对 tool 节点**优先消费 phase prop**(缺省回落 span 终态 ⇒ 两路不可能矛盾)。
- **01.4.2 `streamId`**:`StreamEvent` 增 `StreamID`;`loop/stream.go` 每次 ChatStream 生成
  `stream-<hex>`(crypto/rand,降级时间戳),该流所有帧共享、多步工具轮取新 id;`ws_bridge`
  把它并进**模型流帧** `data`(`tool_execution.*`/`turn.*` 不带)。语义区分:`turnId` = 哪轮回答,
  `streamId` = 这条流式消息。
- **01.4.3 `turn.start`/`turn.end`**:`loop/run.go` 入口发 start、defer 发 end,**覆盖全部出口**
  (最终答案/安全网终止/预算耗尽/步骤错误);帧**无 data 载荷**;静音的内部 run(backthink/子代理)
  不发。顺带把 91 行的 `Run` 拆成 `driveTurn` + `settleAndReturn`(清掉一处既有 >80 行违规)。
- **01.4.5**:仅 `Docs/api-client.md` §3.5 补 `graph_update` 三范围(`web`/`web:<uuid>`/`sess-backthink`),
  与 `wire_runtime.go` 的 allow 谓词一致,**零代码变更**。

验证:`go build ./...` 绿;本批四包(loop/model/trace/gateway)vet + 测试绿(含 `-count=1`);
linux/amd64 + darwin/arm64 交叉编译绿;本批 16 文件 ≤500 行、函数 ≤80 行。
**真 WS 帧断言 8 条**:帧序 `turn.start → message.delta → tool_execution.start →
tool_execution.end → message.delta → turn.end`(+`stream_end`)、turn 边界无载荷且整轮 turnId 一致、
两条模型流 `streamId` 互异、tool 帧 `{tool,phase,id}` 且无 `delta`/`streamId` 键、
`turn.end` 在步骤出错时也发出、内部静音 run 不发边界帧。
**变异验证**:把 `ev.StreamID = streamID` 改为赋空 → 两条断言 FAIL(`streamId` 为空/两流同空),
还原后复绿(直接删行会先被编译期 `declared and not used` 拦截)。

未验证:前端未消费这些帧(本轮未动 `frontend/**`,「前端能渲染」待前端批);无真实模型端到端。
2026-10-04 11:26:35 +08:00
AskaEth afef75f640 feat(backthink): 安全域穿透安静时段 + 长静默不拦心跳 + 启动即思考(裁决 1.8/07.4.2/07.4.3/07.4.4/07.4.5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
执行方案阶段 2 · P2-2(`DevDocs/audit/_plan.md`)。四条裁决:
- 1.8/07.4.2:`think()` 不再走忽略 importance 的 `disturbAllowed`(已删),**接回 `Decide` 级联**;
  安静时段只挡常规推送、**critical 安全域穿透**;`DecisionInput` 增 `ConclusionPush`/`Heartbeat`。
  睡眠仍绝对不打扰(裁决只豁免安静时段,此点已在设计与报告写明)。
- 07.4.3:长静默(>2h)分支对**心跳类**放行(常规事件仍需 ≥high),刚聊过天 10min 内扣留不变。
- 07.4.4:`tickLoop` **启动立即先思考一轮** + 拟人化启动消息;启动轮与心跳**同门同限频**
  (重启风暴不会刷屏),ctx 已取消则跳过。
- 07.4.5:核对一致(`THINK_INTERVAL_SEC` 默认 900s 与 design/20 §0 权威口径一致)⇒ **代码零改动**。

验证:go build 绿;backthink 包 vet/测试绿;`-count=20`(抗 flake)+ `-race` 绿;
linux/amd64 + darwin/arm64 交叉编译绿;gofmt 干净;文件 ≤500 行、无 >80 行函数。
变异验证(三条,均带还原证明):① 安静时段改回"全拦" → 安全域穿透断言 FAIL(首次只删 critical
分支时仍绿——`critical >= high` 会从 high 分支漏过,故真实变异必须是"全拦");
② 删心跳豁免 → 长静默心跳断言 FAIL;③ 删启动思考 → 启动即思考断言 FAIL。

未验证:无真实模型凭据 ⇒ 启动轮问候与 `[我决定:…]` 解析仅经 mock + 真 loop 验证,端到端 LLM 未验。
2026-10-04 11:21:44 +08:00
AskaEth a35bcb8aa9 docs(agents): 锚点路径统一为 DevDocs/工作小结/00-当前态.md(目录规范化:入口文件按本目录 NN- 编号约定改名)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
配合工作小结目录规范化(agent-79):当前态文件由自造名 当前态.md 改为 00-当前态.md,
与 00-索引.md 同属入口类编号;本提交只改 AGENTS.md 里 5 处路径文本,其它一字未动。
DevDocs 侧引用(恢复提示词/开发时间线/开发习惯与约定/02-constraints/00-索引)同批更新;
DevDocs/audit/** 与重构前归档里的旧文件名是历史证据,故意保留。
2026-10-04 11:17:32 +08:00
AskaEth e3ae0129c9 docs(agents): 锚点路径更新为 DevDocs/工作小结/当前态.md(工作小结已整体收进其目录)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-04 指出:工作小结应当整体住在 `DevDocs/工作小结/` 目录里,不该有一个入口文件留在目录外。
据此把 `DevDocs/工作小结.md` 搬进目录、改名为 `当前态.md`(该目录因此自洽:
`当前态.md`(当前态)+ `00-索引.md`(历史索引)+ 按时间分片的历史文档 + 重构前全量归档)。

本提交只更新 AGENTS.md 里 5 处引用路径(§2 先读锚点 / §5 每步更新 / §7 关键决策位置 /
§8 监工动作 / §8 压缩后恢复);DevDocs 侧的引用由并行任务统一修正;
`DevDocs/audit/**` 里出现的旧文件名是当时的现场证据快照,故意保留不改。
2026-10-04 11:08:35 +08:00
AskaEth 5a9f161fe3 docs(agents): 登记两处已裁决例外(OneBot 反连端口 / 表名复数命名)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-04 裁决后按执行方案(DevDocs/audit/_plan.md P1-G1F)登记,两处均为
「按例外处理并写清边界」,不放松其它情形:

- §9 单端口红线 ♢ 例外:仅 `qq-obv11` 渠道的 OneBot **反连(server)模式**可另开
  监听端口,且必须由配置显式给出(`listen_addr`)、**不得默认开启**;其余渠道/模式
  一律不得新开端口。(对应裁决 1.4 / 12.4.1;规格见 design/08 §4、design/13 §0)
- §10 命名条款 ♢ 例外:表名**统一用复数**、以既有迁移为准——原「表名单数」**不再作为
  硬约束**,命名口径以 design/13 / design/16 为准(对应裁决 1.11 / 09.4.2)。
2026-10-04 11:01:12 +08:00
AskaEth 89434e4f99 fix(contract): enums 的 MODES 漂移守卫改按目录收集,抗前端类型拆分
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
TestModeTableMatchesFrontendModes 原先写死读 frontend/web/src/lib/types.ts;阶段 0 把该
文件拆成 barrel + lib/types/*.ts 后 MODES 住进了 types/chat.ts ⇒ 测试红。

改为读 barrel + 目录下全部 .ts(新增 frontendTypesDir 常量与 readFrontendTypes 辅助):
「MODES 住哪个文件」不再是本测试的耦合点,以后再搬也不用改它;barrel 缺失
(backend-only checkout)仍按 fs.ErrNotExist 跳过。

验证:go test ./internal/contract/enums/ 绿;全树 go test ./... = 58 包 ok / 0 FAIL;
gofmt -l 干净。
2026-10-04 10:54:07 +08:00
AskaEth 6e0323b446 refactor(frontend): 拆 types.ts(871) / ws.ts::dispatchFrame(266)(纯搬移 + barrel)
执行方案 DevDocs/audit/_plan.md 阶段 0 · G0-D:
- lib/types.ts(871) → lib/types/ 11 个域文件 + 14 行 `export *` barrel(仿 lib/api.ts 先例)
  chat / ws / session / agent / flow / persona / api / admin(269) / adminWrite(176) / kook /
  notification
- lib/ws.ts(494) → 203 行入口 + lib/ws/handlers/ 8 文件;dispatchFrame(266 行) 改为按组
  分发链(7 个 handler 各持一组 case,命中 return true,未命中走原 default 兜底),
  33 个 case 标签一一守恒

机械证明:type 导出集 86 个守恒(拆后并集 diff 为空);去掉空行与 import/export 语句后
**784=784 行、排序 diff 为空**;dispatchFrame 的 case 标签集 33=33 守恒;ws.ts 的
scheduleReconnect 至文件尾与 HEAD 字节级一致。
65 个 lib/types 消费方 + 3 个 lib/ws 消费方**零改动**通过 —— tsc 0 错误正是这一点的强判据。

验证:npx tsc --noEmit 0 错误;npm run build 成功;9 个前端逻辑测试全绿(含真实驱动
拆后 dispatchFrame 的 messageSync 54 checks 与 personaSession 23 checks);
最大文件 admin.ts 269 行、最大函数 openSocket 72 行(均达标)。
2026-10-04 10:54:06 +08:00
AskaEth e9afca9b29 refactor(cyrene): 拆 wire.go 926 行 → 5 文件(纯搬移,零行为变化)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
执行方案阶段 0 · G0-A:组合根 926 行是全仓第一大超限文件,且被 10+ 条裁决争用
(1.4/12.4.1、03.4.3、06.4.2、07.4.4、10.4.5、12.4.2、13.4.3、14.4.1、14.4.3、16.4.2)
⇒ 不先拆,后续全部被迫串行且必然破 500 行红线。

- wire.go(181):入口与共享 —— const 块、app struct、newApp
- wire_runtime.go(362):loop/agent/model/memory/capacity/trace(buildPersonas、wireMemory、
  wireRuntime、buildCapacity、onCapacity、onToolAudit、defaultPersonaID、defaultInstructions…)
- wire_services.go(158):exec/task/iot/session/history/automations/backthink
  (wireAuxiliary、pushOutletConfig、close、resolveDataDir、resolveSQLitePath)
- wire_channels.go(172):渠道/桥(ownerRegistrar、wireChannels、runtimeChannels、reviewFor…)
- wire_frontend.go(117):gateway/webui/观测扇出(capacityEmitter、wireFrontend)

纯搬移机械证明:去 package/import/空行后两侧 **811=811 行、排序后 diff 为空、md5 相同**;
顶层声明集合 30 条守恒;唯一新增 = 各文件的 package 行与 import 块(原 import 块按声明归属
重分配给新文件)。未重命名/重排/改注释。

验证:go build/vet 绿;go test ./cmd/... 绿;linux/amd64 + darwin/arm64 交叉编译绿;
五文件 181/362/158/172/117 行均 ≤500;gofmt -l cmd/cyrene/ 无输出。
注:全量 go test 当前有 1 处红 —— internal/contract/enums 的
TestModeTableMatchesFrontendModes 读 frontend/web/src/lib/types.ts 找 MODES,而该文件
正被并行的前端拆分任务搬走;与本提交无关,随后修正该测试的读取路径。
2026-10-04 10:50:35 +08:00
AskaEth deb0a3cb97 refactor(gateway): 拆 admin.go 666 行 → admin.go / admin_query.go / admin_mutate.go(纯搬移)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
执行方案 DevDocs/audit/_plan.md 阶段 0 · G0-C:该文件是全仓第二大超限文件(666 行),
且后续多条裁决要改它(工具策略落盘、主人档案 GET/PUT、admin API 补推送结果、模型配置
自动化)⇒ 先拆。

- admin.go(169):共享地基 —— backthinkListLimit 常量、全部 DTO 类型、
  collectSessions/collectMemories 收集器、iotDeviceSummary
- admin_query.go(467):只读 GET 聚合(personas/sessions/memories/plugins/channels/iot/
  llm/automations/users/backthink/rag 的列表与查询 handler + 渠道账号脱敏等私有辅助)
- admin_mutate.go(41):写操作(adminSetPolicy,带审计的 owner 写)

纯搬移机械证明:拆前与拆后(非空、非 package、非 import 块行)排序后 **md5 相同**
(605=605 行);顶层声明集合 43 条守恒;唯一新增 = 两个新文件的 package 行与 import 块。
未触碰 admin_write_*.go / admin_channels_*.go 及任何 _test.go。

验证:go build/vet 绿;internal/services/gateway 测试绿(含 -run Admin 定向);
linux/amd64 + darwin/arm64 交叉编译绿;三文件 169/467/41 行均 ≤500;gofmt -l 无输出。
2026-10-04 10:46:51 +08:00
AskaEth ec292081b2 refactor(trace): 拆 graph.go 471 行 → graph.go / graph_build.go / graph_props.go(纯搬移)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
执行方案 DevDocs/audit/_plan.md 阶段 0 · G0-B:该文件距 500 行红线仅 29 行,而后续
11.4.2(memory/guardrail/orchestration 三类 span 生产者)等改动必然推进它 ⇒ 先拆。

- graph.go(159):对外类型与快照 API(FlowStatus* / FlowNode / FlowEdge / FlowGraph /
  NodeSpec / RunGraph;NewRunGraph / Start / SeedInput / SeedOutput / Snapshot)
- graph_build.go(245):构图与折叠(Upsert / rememberParent / laneOf / merge / addEdge /
  AddSpan / AddSpanStart / kindForSpan / labelForSpan / statusForFinished)
- graph_props.go(77):prop 白名单与脱敏(safeSpanProps / safePropKeys / summarize / truncate)

纯搬移机械证明:拆前与拆后(取非空、非 package、非 import 块行)排序后 **md5 相同、
diff 为空**;声明集合 28 条守恒;唯一新增文本 = 两个新文件的 package 行与 import 块。
`safePropKeys`(含 4bf1f7c 刚加入的 "persona")原样落在 graph_props.go,逐字节未动。

验证:go build/vet 绿;internal/runtime/trace + internal/services/gateway 测试绿(含
graph_persona_test 与 flow_update_ws_test 的 graph_update 线上帧成对契约回归);
linux/amd64 + darwin/arm64 交叉编译绿;三文件 159/245/77 行均 ≤500;gofmt -l 无输出。
2026-10-04 10:46:13 +08:00
AskaEth 32634feefa docs(api-reference): 修正与实现相反/滞后的陈述 + 明确权威归属(契约以 api-client 为准)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
审计(DevDocs/audit/00-总览.md §4④ 及 02/05/09/10/11/15/16 报告)多路独立发现:本文件
自称「唯一详细参考 + 每变更必同步」,实际系统性滞后于实现,且有整段与实现逐条相反。

修正 15 类(每处都回代码核对行号):
- §0.1 与 §6(:986/:992/:1030/:1052/:1130/:1246):「WS 上行不鉴权 / history 回放对任何
  已认证连接」→ 角色门口径(`route` 首行默认拒;`replayHistory` 首行门;下行仅 7 个状态机
  信号全发),指向 design/11 §7.1/§7.2 与 api-client §3
- §5.26/§5.27 plugins install:501 → 真实 200 {installed,name,kind,transport,toolCount}
- §1.3 鉴权列:9 行 owner|admin → owner|admin|viewer(routes.go 实为 viewerAuth)
- §3.5 POST /sessions:「无 body」→ 可选 {personaId?}(未知角色 400;角色创建后不可变)
- §3.3 PersonaSummary:补 mood/thinking、修正 memoryNamespace(读侧恒 "")与示例
- §3.1 chat:补可选 mode;stream 非必填 | §4.4 kind 枚举改为 tool|skill|channel|mcp|iot
- §5.8/§5.9 IoT 501 文案、§5.29 personaId 缺省与 semantic 原因、§5.32 rotate-jwt 响应
- §7:「无 /upload 端点 / 无 REST 语音端点」→ 两者都存在并给形状(原与本文 §0.3 自相矛盾)
- §8.1/§8.4:移除已实现项;observability ×3 → ×4

结构性(根因治理):文件头新增**权威归属声明** —— 契约以 api-client 为准,本文件是内部/
运维参考,凡重复的契约描述改为引用 api-client(复述就是两份真相);并如实降级其自封的
「唯一详细参考」称号,注明"字段示例可能滞后"。

未补(留给后续「文档补全」批次,已登记):session_read、message_edited/deleted、
/admin/plugins/installed、/admin/observability/logs、/admin/notifications、
/sessions/{id}/read、upload/files/audio 概览行,以及若干字段示例滞后。

验证:git diff --stat -- Docs/ 仅本文件(+65/−46);未触碰任何 .go/.ts。
2026-10-04 10:37:45 +08:00
AskaEth 4bf1f7cac6 fix(trace): 让 graph_update 的分支根节点 props.persona 真的前送(白名单漏登记)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
审计(DevDocs/audit/11-trace-observability.md 发现 #1):scheduler 用
span.SetProp("persona", sub.PersonaID) 设了这个 prop,api-client v0.47 也已把它写进
对外承诺,但 trace/graph.go 的 safePropKeys 白名单不含 "persona",而 safeSpanProps
只遍历白名单 ⇒ 该键在到达客户端前被静默丢弃(实测:整个 props 键都消失)。

修法:把 "persona" 加进 safePropKeys,并在同处写清它为什么可以前送 ——
① 它必然是注册表校验过的角色 id(scheduler.checkPersona 未知 id 一律 ErrUnknownPersona
拒绝整批派发);② 它本来就随 GET /personas 与 message_persisted.personaId 对外可见;
③ 值仍过 redactValue → RedactText(凭据形状清洗 + 80 rune 截断)。

测试:新增 trace/graph_persona_test.go(真 Tracer → 真 SetProp → 真 Snapshot,两条断言:
有 persona / 不指定角色时键不出现 = 钉住 v0.47 的"纯增量")+ 网关 flow_update_ws_test.go
追加 TestFlowGraphUpdateRelaysPersonaProp(真 bus Publish → 真 WS 帧 → 断言帧内节点
props.persona=="yuki")。

验证:go build/vet 绿;trace + gateway 包测试绿;全树 go test ./... = 58 包 ok / 0 FAIL;
linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净;三文件 471/57/370 行、最长函数 64 行。
变异验证:把 persona 从白名单摘掉 → 两条断言分别 FAIL(trace 层 props 为空;
WS 帧里 span-sub 节点的 props 键整个消失),还原后复绿。

遗留(登记):前端尚未消费该键(flow 节点未显示"以谁的身份替班")。
2026-10-04 10:37:22 +08:00
AskaEth 074ffc4ebf docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批:
已推库 16 个文件 + 本批剩余文件)。
校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0);
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
2026-10-03 22:28:25 +08:00
AskaEth 91e8d7a029 docs(runtime): internal/runtime/loop 44 个文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;本模块按长期分批协议(一个模块一次派工)执行。
注释行数 1329 → 1329 逐行 1:1(含中文 33 → 1304);行尾注释 5 条一并译。
校验:机械校验(非注释改动行)= 空;独立 Go AST 程序(剥注释复渲)证明 44/44 文件代码全等;
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
gofmt:capacity_loop_test.go / flow.go 改动前即未格式化,按铁律保留原样未动。
2026-10-03 22:28:21 +08:00
AskaEth 355f81d04a docs(gateway): gateway 第 2 批 5 个文件注释改中文(只改注释,代码零字节改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-03 拍板「注释一律中文」。本批 = WS 上行门与握手回放那批文件:
ws_hub.go / ws_history.go / ws_route.go / ws_role_gate_test.go / voice_signal_test.go
含中文的整行注释 6 → 148;无行尾注释。

校验(判据 = "只改注释、零代码字节改动"):
- git diff -U0 | 排除 +++/--- / 注释行 / 空行 / 行尾注释 → 为空
  (含"把纯空行变动也视为违规"的更严变体,同样为空)
- 独立实现:两侧剥掉注释后逐行比对代码文本 → 5/5 全等
- gofmt -l 干净(改动前同样干净,不存在"顺手格式化")
- go test ./... 全绿;go build/vet 绿;linux/amd64 + darwin/arm64 交叉编译绿
安全约束强度逐句保留:ws_route.go 的「默认拒(非黑名单)/ 删掉它等于放开一切」、
ws_history.go 的「门在 replayHistory 第一条语句 / 扩角色须先登记 design/11 §7.2 /
回放不含 thinking」、ws_role_gate_test.go 的测试名与断言字面量一字符未改。
2026-10-03 22:10:33 +08:00
AskaEth fe1ff33115 docs(gateway): WS 帧门相关 11 个文件的注释改中文(只改注释,代码零字节改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-03 拍板「注释一律中文」。本次先把本会话动过的文件就地翻;
不做全仓一次性翻译,存量按包分批排期(见 工作小结.md 待办)。

范围:ws_frame_scope.go / ws_frame_scope_test.go / broadcast.go / ws_hub_turn.go /
ws_hub_message_event.go / session_read.go / server.go / ws_hub_stream_scope_test.go /
history_write_broadcast_test.go / mood_test.go / extra_test.go
含中文的整行注释 18 → 382。

校验(判据 = "只改注释、零代码字节改动"):
- `git diff -U0 | 排除 +++/--- | 排除注释行/空行/行尾注释` → 为空
- 独立实现:两侧剥掉注释后逐行比对代码文本 → 11/11 文件全等,不一致数 0
- 行尾注释 7 行逐条列出,代码列与原文逐字节相同(gofmt 未要求重排)
- gofmt -l 干净;全树 go build/vet 绿;go test ./... = 58 包 ok / 0 FAIL;
  linux/amd64 + darwin/arm64 交叉编译绿
语义未删减:部分折行英文长句合并为更少行,故整行注释数 438 → 399。
2026-10-03 22:07:56 +08:00
AskaEth 770a83c3fb feat(gateway,security): WS 上行帧角色门 + 握手回放仅 owner/admin(design/11 §7.2)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-03 拍板"这一类要收紧"后,补齐 §7.1 下行门之外的两处同类缺口:
viewer 是只读监控档,却能通过 WS 拿到内容、甚至发起写入。

A. 握手 history 回放仅 owner/admin(ws_history.go)
   连接建立时回放主会话最近 50 条消息正文,此前对任意角色照发 ⇒ viewer 一连上就
   拿走主人对话内容。门放在 hub.replayHistory 的第一行 —— 它是唯一发出 history 帧的
   函数,将来多一个调用者也绕不过去。

B. 上行 JSON 帧按角色授权(ws_route.go,fail-closed)
   此前 route 完全没有角色检查。现在 route 的第一条语句即:非 owner/admin → 回定向
   {type:"error",data:{code:"forbidden"}} 并 return,因此"不广播、不驱动模型、不落库、
   不返回任何内容"是结构性的,而不是每个 handler 各自记得检查。写成默认拒而非黑名单 ——
   chat / interrupt / subagent_ask / task_output_query / voice_input / mode_switch 以及
   将来任何新帧都自动被拒。协议级帧(ping/pong/close)由 gorilla 在 ReadMessage 内处理,
   不受影响(已实测:被拒后 ping→pong 正常、close 后服务端注销连接)。
   被修掉的真实越权:viewer 发 chat 会真的驱动模型并把消息落库;subagent_ask /
   task_output_query 的定向应答会绕过 §7.1 下行门,把子代理上下文/任务输出回给 viewer。

契约:Docs/api-client.md → v0.49(对 viewer 收紧;owner/admin 逐帧形状不变)

验证:go build/vet 绿;go test ./... = 58 包 ok / 0 FAIL;-race ./internal/services/gateway/...
ok;linux/amd64 + darwin/arm64 交叉编译绿;新测试 -count=3 稳定。
真 httptest WS + 真 JWT 逐帧实测:viewer 收不到 history 回放、上行一律定向 forbidden
且 runner 计数 = 0 / 落库 = 0;owner 侧 chat 驱动 runner = 1 且落库 2 条。
变异验证:① 去掉 A 的门 → viewer 收到 {"type":"history",...,"content":"主人的私密提问"},FAIL;
② 去掉 B 的门 → viewer 帧驱动 runner 1 次并落库 2 条、且直接收到 subagent_event / task_output,
FAIL。均已还原;且撤掉 B 时 A 的测试仍独立 PASS(两把门不互相代偿)。

遗留(如实登记):非 JSON 的上行字节不经过 route 的角色门(零副作用:不带 type、不触发
handler、不落库);admin 上行未单独测一格(与 owner 共用同一分支)。
2026-10-03 22:02:16 +08:00
AskaEth bc0047b449 feat(gateway,security): WS 下行帧角色门 — 内容帧仅 owner+admin,viewer 只收状态机信号
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
design/11 §7.1(主人 2026-10-03 拍板)。此前 hub.broadcast 是全连接广播,而任意角色
(含 viewer)都能连 /ws/chat ⇒ viewer 能收到角色回复正文(message.*)、推理内容
(thinking.*)、消息增量、graph_update、mood 等。这与 design/19 §1/§3「flow 可视化仅
owner/admin 可见」冲突,也与 GET /sessions/{id}/messages 的 owner/admin 档位不一致。

收口:唯一分类点 internal/services/gateway/ws_frame_scope.go 的 frameReachesAll(typ)
(fail-closed switch,默认 false);hub.broadcast 投递前查表,白名单外对非 owner/admin
连接直接跳过。**不给 broadcast 加 bool 参数、不让调用点自己判断** —— 那正是"漏一处就是
泄漏"的形状。全包只有一个 fan-out 循环,14 个 .broadcast( 调用点全部落进该入口。

- 白名单只有 7 个状态机信号帧(不含任何内容):session_busy / pending_ack / pending_drain /
  session_idle / session_interrupted / pending_full / session_read
- 其余全部(流式 message/thinking/tool_call、message_persisted/edited/deleted、graph_update、
  subagent_event/result、task_output/task_done、automation_result、tool_used、capacity_awaken、
  mode_switch、mood、device_update、reminder)仅 owner+admin
- 语义澄清:本门管「谁被主动推送」,数据可见性仍由 REST 决定(viewer 的 12 条只读监控端点
  不变,含 GET /personas 的 mood);/ws/chat 连接鉴权与 voice_output 定向帧不动

契约:Docs/api-client.md → v0.48(对 viewer 是收紧;owner/admin 零变化;无端点/字段形状变更)

验证:go build/vet 绿;go test ./... = 58 包 ok / 0 FAIL;-race ./internal/services/gateway/... ok;
linux/amd64 + darwin/arm64 交叉编译绿。真 httptest WS + 真 JWT 按角色逐帧实测 37 帧:
viewer 只收到 7 个状态信号,owner/admin 37/37。
变异验证:① graph_update 加回白名单 → 真 WS 抓到 viewer 收到 graph_update,3 个测试 FAIL;
② session_idle 移出白名单 → 3 个测试 FAIL(viewer 等不到哨兵帧而超时)。均已还原。

遗留(已登记,待主人裁决):① 握手 history 回放对任意角色照发(viewer 连上即得主会话最近
50 条正文);② /ws/chat 上行未按角色授权(viewer 可发 chat = 真驱动模型并落库;
subagent_ask / task_output_query 的定向应答绕过本门)。
2026-10-03 21:50:29 +08:00
AskaEth 28b663231e test(gateway): 补 graph_update 的 WS 中继与 wire 契约测试(此前零覆盖)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
`#/admin/flow` 报障调查发现:flow 链路每一环都是对的(loop 的 FlowPublisher 三入口
在真实回合里都被调用 / wire.go 的 allow 白名单正确 / parseGraph 与 Snapshot 字段逐条对得上),
但**「bus 的 ws.graph_update 经 ws_bridge 中继成 WS 帧」这一跳此前完全没有测试**——
逐环人工核对永远核不到它。补两个契约测试把它钉死:

- TestFlowGraphUpdateRelayedToWS:真 bus Publish("ws.graph_update", <真 trace.RunGraph.Snapshot()>)
  → 断言 WS 收到 graph_update 且数据逐字节未被改写
- TestFlowGraphUpdateWireFields:精确断言 graph/node/edge/log 四层的 key 集合
  = 前端 parseGraph 读取的那一套(改名/多字段/少必填都 FAIL)

验证:go build/vet 绿;-race 网关 ok;全量 58 包 ok / 0 FAIL;交叉编译绿。
变异验证:① 改 trace.FlowNode.Label 的 json tag → 键集断言 FAIL;
② 让 ws_bridge 的 normalizeEvent 丢掉 ws.graph_update → 中继测试 i/o timeout FAIL。
2026-10-03 21:39:03 +08:00
AskaEth 5847705e35 fix(gateway): 修 Snapshot 交出活 map 导致进程被 fatal 打死(并发 map 迭代+写入)
现场:cy.log:560,2026-10-03 21:00:28.403,栈顶
encoding/json.mapEncoder.encode ← writeJSON(rest.go:383) ← adminObservabilityMetrics(observability.go:89),
不可 recover,网关 recover 中间件接不住 ⇒ 单二进制进程整体退出(端口 10942 零监听、
所有后台服务含 backthink 心跳一起没;比第一次心跳 tick 早 32.6 秒)。

根因:MetricsCollector.Snapshot 在锁内只新造了 StatusCounts,却把 collector 自己的
活 map m.perMethod / m.perPath 直接交出去;锁外(handler 的 writeJSON)才开始遍历,
而每个请求的 logging 中间件(middleware.go:79)在别的 goroutine 里 Record 写这两个 map
⇒ 写有锁、迭代没锁。面板并行轮询十几个端点,/admin/observability/metrics 被打了 29 次
且常见同毫秒两个请求,必踩。

修法:Snapshot 改为交出副本(新增 copyCounts / copyStatusCounts,都在锁内遍历),
并把「必须在锁外可安全编码」的原因写进文档注释。对外读侧语义不变(仍是全量累计)。

测试(新增 observability_metrics_test.go,242 行):① 快照与 collector 双向隔离(确定性)
② 并发「写 + 编码快照」(-race 下复现生产竞态)③ 真 HTTP + 中间件 + 并行轮询
/configmetric 端点(= 21:00:28 现场)。

验证:go build/vet 绿;-race ./internal/services/gateway/... ok;全量 go test ./... 58 包 ok / 0 FAIL;
linux/amd64 + darwin/arm64 交叉编译绿。
变异验证:把 Snapshot 改回交活 map → 确定性测试 FAIL("snapshot shares the collector's perPath map")
+ -race 报出与生产同一处的 DATA RACE(Record 写在 observability_metrics.go:64)。
2026-10-03 21:38:59 +08:00
AskaEth 08b3a01752 feat(runtime,ai): session_read — 跨会话只读「主动读取」能力(design/22 §8-3/§8.1)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-03 过目 design/22 §8.1 草案并点头后实现。

- 新包 internal/runtime/sessionread:工具壳 + 只读取数 + 归属排版(按职责拆 8 文件)
- 只读由类型保证:注入的 HistoryReader 只声明 List/ListSessions —— 本包结构上无法写入
  任何会话、也推进不了目标会话的 read_at
- fail-closed 授权,顺序 = 环境 → 白名单 → 存在性:
  非 owner 环境拒(ErrNotOwner)→ 白名单只许 web / web:<uuid>(ErrSessionNotAllowed)
  → 会话不存在是硬错误(ErrSessionNotFound,绝不返回空材料,防"读不到"被当成"对方没说")
- 排版:头部三行恒定(外部会话材料 · 只读 · 不属于你的记忆 / 来源角色 / 来源会话)
  + 时间跨度与翻页游标 + 逐条「[时间] 说话人:」归属
- 说话人取消息自身 persona_id;'' → 全局默认角色;未知 id 原样显示;user → SenderName(空=主人)
- Execute 返回 string:loop 的 formatToolResult 对 string 原样透传,换行不被 json 转义
- 接线 cmd/cyrene/wire.go 一行 + wireSessionReadTool():能力默认标记(双 false)= owner-only,
  不新增任何 bypass 路径;审计仍走 loop 的 gate
- 日志 source=runtime/sessionread,只记 actor/target/count/limit/before/has_older,不含正文

验证:go build/vet ./... 绿;internal/runtime/sessionread(25 测试)+ cmd/cyrene 绿;
      linux/amd64 + darwin/arm64 交叉编译绿;
      TestWireSessionReadToolRegistersOwnerOnly 断言 owner 放行 / group_member / stranger 拒绝

遗留(登记,未解决):§8.1 约束 4 要求"每次调用落审计",但 loop 的 gate 只在拒绝时发
AuditEvent ⇒ 成功读取目前只有日志可追溯,未进审计库;补 loop 侧成功路径钩子需先定口径。
2026-10-03 21:33:40 +08:00
AskaEth 05d34853f0 feat(orchestration,runtime): subagent_spawn 支持指定目标角色(以该角色 instructions + 记忆域运行)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
design/22 §8-2(主人 2026-10-03 拍板):跨角色协作 = 把其他角色以子代理形式派出去,
该子代理以目标角色的 instructions 运行、并按目标角色的记忆作用域读写。

- AgentSpec / SubAgent / SubAgentResult 新增 PersonaID;Options.PersonaExists 作角色解析器
- scheduler:派发前校验角色(ErrUnknownPersona),批量 all-or-nothing;
  run ctx 经 withPersonaEnv 只覆盖 SessionEnv.PersonaID —— ActorEnv/MemWritable 原样复制
  (身份识别靠 channel 元数据,persona 决定她怎么答、不决定她能碰什么:项目红线)
- 记忆归属键改为 PersonaID 优先(回落 RoleID)→ 替班者写的是目标角色的记忆域
- spawntool:schema 顶层与 tasks[] 新增可选 persona;省略 = 与旧行为逐字节一致
- 分支根节点新增 props.persona(可观测性);SinkMemory 移入 memory.go(守 500 行红线)
- 组合根:personaExists(agent 池);cmd/cyrene/wire.go +5 行

契约:Docs/api-client.md -> v0.47(graph_update 新增可选 props.persona,纯增量)

验证:go build/vet ./... 绿;internal/runtime/orchestration + cmd/cyrene 测试绿;
      go test -race ./internal/runtime/orchestration -run Persona 绿;
      linux/amd64 + darwin/arm64 交叉编译绿
变异验证:① 目标角色换回父/默认角色(3 处断言 FAIL)② 关调度器侧角色校验 ③ 关工具侧预检
2026-10-03 21:30:45 +08:00
AskaEth c7ecc3a249 feat(session,web): 会话角色不可变 — PATCH 拒 personaId + 移除前端换角色入口
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
design/22 §8-1(主人 2026-10-03 拍板):会话角色在创建时确定、之后不可变,
要换角色必须新开会话。

后端:
- PATCH /api/v1/sessions/{id} 的 personaId 改为 json.RawMessage 判「键出现」,
  出现即 400(null/""/未知 id 一律拒),且先于会话存在性检查;错误消息点名 personaId
- 删除 Server.setSessionPersona 与 PATCH 的 501 分支;title/pinned 行为逐字不变
- sessionPersonaWriter 接口与 history.Store.SetSessionPersona 保留(存储能力)

前端:
- 移除会话行「切换角色」入口与面板「挂角色」下拉(含 setSessionPersona action、
  lib/sessionPersona.ts);保留「新建会话选角色」;面板「角色」列改为只读展示

契约:Docs/api-client.md -> v0.46(破坏性:依赖 PATCH 换角色的客户端须改为新建会话)

验证:go build/vet ./... 绿;gateway+history 包测试绿(-count=1);前端 npm run build
(tsc --noEmit + vite build)绿;9 个前端逻辑测试全绿。
变异验证:改回「静默忽略」/「写绑定」两种反向实现,新断言均 FAIL。
2026-10-03 21:24:51 +08:00
AskaEth f9430dee81 fix(admin): 渠道 personaId 改为三态语义(省略=保留 / ""=解绑 / id=绑定)——修「保存其它配置即静默清空绑定」
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
跨端语义相反:后端 5969bba 实现的是「省略/\"\"=解绑」,而管理面板只在用户改动下拉时才发该键 ⇒ owner 打开已绑账号、不动下拉、保存其它配置就会静默清空渠道角色绑定。
- 请求 DTO 改 *string 区分「省略」与「显式空串」:省略 → 保留现状;显式 "" → 解绑;非空 id → 绑定(未知角色 400);沿用读-改-写 + bridge.Reload 热更新 + 审计
- GET /admin/channels 继续回显 account.personaId(已确认)
- Docs/api-client 三态语义写清(省略=保留是本次的关键修正)
- 测试:省略保留 / 显式空串解绑 / id 绑定 / 未知 id 400 + 变异验证(把省略改回解绑 → 断言 FAIL)
2026-10-03 21:06:22 +08:00
AskaEth 5969bba3bd feat(channel,runtime): 批次2 后端 — 消息级角色归属进上下文 + IM 私聊随主会话角色 + 渠道级绑定
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 design/22 §2/§3/§4.4/§7(3/4/5)。
- ①「消息级角色归属」进入模型上下文(批次 1 只做存储+DTO):历史消息的发送角色现在对模型可见,且**不把系统元数据塞进用户正文**(design/11 §10 红线)
- ② IM owner 私聊镜像解析出的角色 = **主会话绑定的角色**(§7-3)
- ③ 渠道级绑定:ChannelAccount 加 personaId(channels.json 读写/校验/脱敏沿用既有约定)+ 角色解析优先级 **会话 > 渠道 > 默认** + **群聊/陌生人强制默认角色**(§7-4);admin 渠道配置端点支持 personaId
- Docs/api-client 版本 +1
- 验收:go build/vet/test 全绿 + linux/darwin 交叉编译
2026-10-03 21:01:33 +08:00
AskaEth 3732c538cc feat(admin): 人格页「绑定会话数」+ 渠道账号绑定角色(B 批次 2 前端)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 design/22 §4.4/§4.6 + §7-4/7-7(契约先行,后端由并行代理实现)。
- 人格页:展示每个角色绑定的会话数——面板侧用 GET /admin/sessions 的 personaId 聚合(personaId 为空 = 继承默认角色,已计入默认角色并在 UI 写明口径),随既有 useAutoReload 刷新;**未新增后端字段**
- 渠道级绑定 UI:每个渠道账号加「角色」下拉(选项取 GET /admin/personas),保存走 PUT /admin/channels/{platform}/config 的 personaId;已绑角色回填、未绑定显示「跟随默认角色」(不冒充某个角色);提示语写明**群聊/陌生人会话始终用默认角色**,避免暗示可覆盖隔离会话
- 验收:npm run build 绿 + 逻辑测试(personaSession/messageSync)通过
2026-10-03 20:50:07 +08:00
AskaEth 2aa3233f84 feat(persona,web): 人格读侧(thinking 三态 + 角色级 mood)+ 面板表单 + mood 播种
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 design/04 §3.2(thinking 语义,showToUser 三态)+ design/01 §2.1 + 主人两条定论(心情跟角色不跟会话;mood 初值走口径 ①:由角色读端点暴露、前端加载播种)。
- 管理侧 GET /admin/personas 的 PersonaSummary 增 thinking(budgetTokens/effort/mode/showToUser)——**showToUser 保持三态**(未配置 ≠ false)
- 用户侧 GET /personas 增**角色级 mood**(取自 emotion 层该角色当前状态,非从消息推断)→ 前端加载播种 currentMood,消除「刷新后心情徽标短暂为空」
- 前端 Personas 页加 thinking 四控件(预算/effort/模式/showToUser 三态):只做输入校验,**provider 是否真支持由后端 WARN 决定,前端不假装能力**
- 先拆 useAppStore 选择器(原 494/500)到独立模块再做,避免越线
- Docs/api-client 版本号 +1(两处契约 + 三态与角色级 mood 语义)
- 验收:go build/vet/test 全绿 + linux/darwin 交叉编译 + 前端 build 绿 + personaSession/messageSync 逻辑测试通过
2026-10-03 20:40:44 +08:00
AskaEth 6dc1f218d5 test(gateway): 修既有 flake — 根因在测试替身的取消契约,非产品 bug
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
TestInterruptCancelsTurnAndClearsPending 在纯 HEAD 与新代码上都约 7–15/50 失败(既有 flake,污染每次验收)。
- **根因(测试侧)**:测试替身 gatedRunner 阻塞在 select{tickets | ctx.Done()};A 轮被 interrupt 取消时它已 park,ticket 释放后**同一调度步内两个 case 同时就绪**,Go 的 select 随机选 → 有时把**已被取消**的 turn 记成执行过(诊断重放 300 次:18 次失败,且每次被记执行的 ctx 都已 canceled)
- **产品侧复核无 bug**:interruptLocked 在锁内 cancel + 清 pending + gen++;被取消/顶替的轮 completeTurn 命中 stale gen 直接返回,不碰状态、不续排 pending
- **修法(仅测试)**:替身忠于真 runner 的取消契约(入口先查 ctx.Err()、两条取消路径都 recordCanceled、新增 waitCanceled);测试在 Interrupt 后、释放 ticket 前**先等被取消轮确实停下**,断言不再依赖调度巧合;另加 waitIdle / run.count()==1 / session_idle 事件数==1(原有 4 处断言一字未删)
- **稳定性**:修前 -count=50 四次 15/9/7/10 失败 → 修后 -count=50×8 + -count=100 + -count=300 **0/750**;-race 目标测试与整包均 ok
- **变异验证**(防改断言变绿):去掉 interrupt 的 cancel → 10/10 FAIL;不清 pending → FAIL;去掉 completeTurn 的 stale-gen 判断 → 10/10 FAIL
2026-10-03 20:30:38 +08:00
AskaEth b74de9fd86 docs(readme): 示例里的 DeepSeek 模型名更新为当前名(deepseek-chat 已于 2026-07-24 停用)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 DevDocs/reference/deepseek/facts-for-cyrene.md 的时效性事实:旧名 deepseek-chat/deepseek-reasoner 已停用,且 claude-* 指向 DeepSeek 会被静默映射成 deepseek-flash。仅文档示例,未改任何真实 .env。
2026-10-03 20:22:41 +08:00
AskaEth 0de68bc960 fix(model): DeepSeek 推理参数语义反转 + thinking 开关真发(按官方文档核对结果)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
依据 DevDocs/design/04 §3.2「官方核对后的修正」9 条 + 本地 DevDocs/reference/deepseek/facts-for-cyrene.md(官方原句/URL)。
- 🔴 reasoning_effort 白名单改为官方集合 [none, low, high, max] + 别名映射(minimal→low, medium→high, xhigh→high, ultra→max);**删除就近降级**,非法值一律丢弃+WARN。此前 none(关思考) 被降级成 low(开思考) 属语义反转
- 🔴 DeepSeek thinking 开关真发顶层 {"thinking":{"type":"enabled|disabled"}}(官方默认 enabled,不传≠关);此前只打 WARN 导致 mode:off 静默无效
- DeepSeek 的 /anthropic 端点忽略 budget_tokens → 不再对其抬 max_tokens(会误导预算/日志),改为按官方用 output_config.effort
- 注释纠正:penalty 属接口级 deprecated(非仅思考模式);top_p 仅思考模式生效且下限 0.95(不加该字段)
- tool_choice 雷登记:思考模式下 required/指定 tool 会 400(思考默认开)
- 旧模型名清理(deepseek-chat/reasoner 已于 2026-07-24 停用)
- 请求体级测试 + 变异验证(把 none 改回 low / 把 thinking 改回只 WARN → 对应断言 FAIL)
2026-10-03 20:22:31 +08:00
AskaEth 0624d9ee36 feat(session,runtime): 多角色并存—按会话选角(迁移0011 + 按回合解析 + agent池 + 提取器修正)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 DevDocs/design/22 批次 1 后端(§2/§3/§4.1-4.5/§4.7/§5 验收 1-6)。
- 迁移 0011:chat_sessions.persona_id + chat_messages.persona_id(均 DEFAULT '',无回填 → 升级零风险);同步包内幂等 DDL;**真实库升级验证**(拷贝 data/cyrene.db 到临时目录跑 MigrateUp:既有 1 会话/109 消息 persona 仍为 '',原库未被触碰)
- history:SessionMeta/Message 加 PersonaID + SetSessionPersona;ListSessions/List/scan/Append 带上
- runtime:SessionEnv.PersonaID + loop.personaFor(ctx) 替换固定 l.personaID(memory/request/turn)+ **agent 池**(Options.Agents,按角色取 instructions,未命中回落)+ **提取器不再把 persona 烤死在构造期**(多角色下记忆不再永远记在 cyrene 名下)+ **thinking 旋钮改为按会话角色解析**(否则角色 Y 会用角色 X 的预算/展示)
- gateway:会话 DTO/POST/PATCH 支持 personaId;ws/rest/voice 的 OwnerEnv 硬编改为**按会话解析角色**(actor 元数据语义不变);admin memories 搜索去掉硬编 cyrene
- Docs/api-client v0.42;**两处有意的行为收紧**已写明:POST /sessions body 非法 JSON → 400(原先完全忽略 body;空/null/{} 仍 201);POST /admin/memories/search 省略 personaId 且无默认角色 → 400(原先默认查 cyrene)
- 测试:design/22 §5 验收 1-6(记忆隔离/提示词首条 system/情绪按角色/提取 persona 归属/迁移兼容)+ 进程内 e2e(断言 adapter 收到的 model.Request.Messages[0] 即该角色的 systemPrompt)
- **已知未做(留批次 2 / 待确认)**:① IM 与渠道级未做 → **§7-3「owner 私聊镜像随主会话角色」尚未生效**;② 请求级覆盖未实现(本批只做会话级);③ **§7-5 的「上下文发送角色标记」只完成存储+DTO 半边**——消息 persona 已落库且 DTO 已带,但模型上下文不从 chat_messages 重建,「第 N 条是谁说的」尚未进上下文(需确认是否属批次 2);④「切角色重建摘要」当前是空操作(压缩未接线);⑤ thinking 旋钮仅在流式路径生效(非流式 agent.RunStep 不带,A 批次既有性质)
- 验收:go build/vet/test 全绿 + linux/amd64 与 darwin/arm64 交叉编译(既有 flake 本轮未触发;同批 session_orchestrator.go 一度 501 行 → 抽出 session_tail.go)
2026-10-03 20:12:29 +08:00
AskaEth 66a96d2f93 feat(web): 会话级角色 — 角色跟随激活会话 + 选角入口 + 面板挂角色(B 批次 1 前端)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 DevDocs/design/22 §4.6/§5(7) 实现(后端由并行代理在做,按已定契约先行)。
- currentPersona 由「全局单值」改为「跟随激活会话」:新增 lib/sessionPersona.ts 解析(personaId 空 → 回落默认角色),App/TopBar/chat/lib/persona.ts 全部改为按激活会话取角色(头像/背景/名字/心情随会话切换)
- 交互:新建次级会话可选角色;会话换角色入口;管理面板 Sessions 页加「挂角色」下拉(走 PATCH /sessions/{id})
- 组件拆分:Nav.tsx 228→143(抽出 NavItem.tsx 53)、新增 SessionRail.tsx 275(会话侧栏)
- 契约:types.ts / lib/api/sessions.ts 加 personaId?(建会话/改会话支持)
- mood 改为**按角色分桶**(同一角色的多个会话共享该角色心情);升级后旧 mood 值不保留(刷新即空)——属行为变化,已在报告注明
- 测试:新增 personaSession.test.ts 40 checks(角色解析/回落/乐观更新与回滚)+ 既有 messageSync 54 checks 回归
- 验收:npm run build 绿;**未做浏览器端到端**(无 jsdom/渲染测试底座),UI 交互与失败路径提示未渲染验证
- 已知余量:useAppStore.ts 494/500 行(下次改动前应先拆 selectCurrentPersona/useCurrentPersona/useCurrentMood)
2026-10-03 20:05:40 +08:00
AskaEth 38a403afca feat(persona,model,loop): 人格 thinking 配置 + 5 个死字段接线 + 推理旋钮(按 provider 映射)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
按 DevDocs/design/01 §2.1/§2.2 与 design/04 §3.2 实现(规格含联网核过的官方依据:思考内容刻意不角色化、不写分步思考指令、思考控制只走参数级)。
- 人格 schema 新增 thinking:budgetTokens / effort / showToUser(*bool,省略=nil=保持现行为,显式 false=只落库) / mode(default|on|off→Enable 三态);非法 mode/负预算 → ErrInvalidThinking(不静默)
- 5 个死字段接上注入:personality→values→speechStyle→catchphrase→background 拼为独立段落(带【】小标题,空值不拼);**全空时输出与旧实现逐字节一致**(有专门测试)
- model.Request 新增 Thinking{ BudgetTokens, Enable } 与 ReasoningEffort;新增 model/reasoning.go:厂商识别(模型名→baseURL 兜底)、取值白名单(OpenAI 系 none…max / DeepSeek low|high|max)、非法值降级+WARN、**不支持一律 WARN 而非静默**;Anthropic 映射 thinking{budget_tokens} 并**保证 budget<max_tokens**(不足时抬高 max_tokens+WARN,正是历史上思考吃光预算→空回复那个坑)
- loop:新增 Options.ThinkingShowToUser(*bool) 仅门控 thinking_* 帧(正文/工具帧不受影响);场景级 mute 语义不变
- 补一处任务清单外但必需的接线:persona→model.Request 的 thinking 映射(否则 budget/effort/mode 会变成新的死字段);showToUser 刻意不产生 Thinking(纯呈现开关)
- 管理端:personaUpsertRequest 补 thinking DTO + 校验(400);PUT 省略时**保留已存块**(防旧客户端抹配置);写侧 yaml round-trip 已验证
- Docs/api-client v0.40→v0.41(可选请求字段)
- 测试:26 个新用例(loader 解析/拼接逐字节/映射、anthropic 非流式+流式预算约束、openai 透传/降级/丢弃、Qwen 诚实 WARN、loop 门控、DTO round-trip);3 次变异验证(省略与 false 混同 / 去掉预算约束 / 短路门控 各自 FAIL)
- 验收:go build/vet/test 全绿(含 57 包)+ linux/darwin 交叉编译 + 前端 build;唯一 FAIL 是既有 flake TestInterruptCancelsTurnAndClearsPending(3/20 新代码 vs 3/20 纯 HEAD,已归因非本次引入)
2026-10-03 19:54:22 +08:00
AskaEth e4e18ce2a2 chore(gitignore): 按「类」忽略文件管理器/编辑器备份与临时文件
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人说明:.env.bak 是其远程文件管理 App 修改文件时自动生成的(会重复出现)。故除已有 .env.* 规则外,再统一忽略 *.bak / *.bak-* / *.orig / *.tmp / *.swp / *~。
- 含密钥的 channels.json / models.json / plugins.json 与 cyrene.db* 本就在被忽略的 data/ 下,已安全
- `git ls-files` 确认无任何已跟踪文件命中这些模式(不会误伤)
- .env.bak 本身未删除/未读取(属主人资产)
2026-10-03 19:40:23 +08:00
AskaEth 0519bb3c2e chore(gitignore): 忽略 .env.* 备份(保留模板)—— 防密钥误提交
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
仓库根出现未跟踪的 .env.bak(编辑器/工具生成的密钥备份,3876B),而 .gitignore 只忽略 .env,git add -A 会把它提交上去。现加 `.env.*` + 放行 `.env.example/.sample/.template`(当前被跟踪的 .env* 只有模板,故无副作用)。文件本身未删除/未读取(属主人资产)。
2026-10-03 19:39:39 +08:00
AskaEth de1fa83b79 feat(orchestration,loop,flow): 接线子代理编排 — 派发入口 + 分支归父会话 + 节点日志(含脱敏)
修的是「文档说 ✅、生产不可达」:此前 Scheduler.Spawn 只有测试调用方、无 bus 入口、无派发工具;Span.Record 无生产调用方 → 泳道画不出、节点日志恒空。
- 入口:subagent_spawn 工具(internal/runtime/orchestration/spawntool.go,同包便于用真 Scheduler/loop/Tracer/Flow 做集成测试;注册在 cmd/cyrene/subagent_tool.go 照 terminal_tool 范式)。默认 owner-only,不设 AllowInGroup/AllowInStranger;安全仍由 restrictSchemas + applyToolSecurityGate 双层保证,未绕代审
- 父会话归属:AgentSpec.ParentSession(请求语义)→ Scheduler 转运行期 ctx 标记 loop.WithFlowBranch → 分支 span 以父 session 发布;**allow 谓词一行未改**;单图语义靠「分支 Run 不调 StartRound/EndRound」+ FlowPublisher 不新建图;未归属 spawn 一张图都不发(有测试锁)
- 前台最多等 2s(对齐 task.autoBackgroundThreshold)→ 合并结果作工具结果;转后台则广播 bus subagent.result(design/05 §9 回填 hook);新增 Scheduler.Wait(ids) 按批等待(Join 会串并发批次,故不重用)
- 子代理跑在 WithoutCancel(ctx):保留 span 归属、不被工具超时/回合结束顺手杀掉;生命周期由 PerAgentTimeout + CancelAll 约束;内部 WithMutedBroadcast 静音(不进前台 stream)
- 节点日志:loop/spanlog.go 新增 llm.done / tool.done / subagent.done;工具参数**只记键名不记值**;仅步骤真正结束才追加(跳过/被拒路径不发,不伪造)
- 脱敏(红线):新增 trace/redact.go(形状规则 sk-/Bearer/JWT/key:value + 键名规则 api_key/token/authorization/password…,递归嵌套、不改调用方事件),graph.go 发布前必经;**顺带修掉一个既有泄漏**:result_summary prop 原先未脱敏(工具结果里的密钥会进图),由图级变异测试抓出
- 测试:新增 22 个(工具级 / 图级 / 日志级),含 4 次变异验证(去掉 WithFlowBranch、去掉 record*、还原未脱敏、去掉静音 各自 FAIL);-race 过 orchestration+trace+loop
- 已知限制:真实模型端到端未验(无凭据/不起服务);工具白名单是 design gap 未做;subagent.result 暂无消费者(design/15 未实现);后台子代理跨轮次边界迟到 span 会丢泳道根;PerAgentTimeout=10min 待确认
2026-10-03 19:39:38 +08:00
AskaEth 3f79159fbd feat(auth,gateway): 接线 viewer 只读门 — 12 条监控 GET 对 viewer 开放,敏感档保持 owner/admin
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
背景:auth.go 注释写着 RoleViewer 是只读监控档,但实现从未接线 —— 管理侧 24 个 GET 全是 owner/admin,viewer 全 403。本批把它接上(手机端「全功能管理」的前置之一)。
- routes.go:新增第四道门 viewerAuth = Authorize(owner, admin, viewer);registerAdminRoutes 传入。**只**把 12 条只读监控 GET 从 writeAuth 换过来:personas / sessions / plugins / plugins-installed / channels / iot / automations / rag / observability{metrics,trace,logs} / notifications;其余 12 条读与 32 条写**一行不动**
- 保持 owner/admin(viewer 403):memories / llm / users / backthink / audit / sessions-export / channels-messages ×2;owner-only(35 条)含凭据获取(login-qr/login-status)、channels view/friends、全部写操作
- 三处「编码旧策略」的既有测试按**新权限矩阵**改写(不是放宽):gateway_test 的 /admin/plugins viewer 403→200 且补一条 memories 403;observability_logs 的 ViewerForbidden→ViewerReadsTail;notifications 的 viewer 403→200
- 新增 rbac_viewer_test.go:5 个顶层 / 185 个子测试(12 条×3 角色 200、9+35 条 viewer 403、admin 在读档非 401/403 且在 owner-only 403、56 条匿名 401、写路由 viewer 403);含 3 次变异验证(含**反向变异**:把 memories 误放开为 viewerAuth 会 FAIL)
- auth.go 的 RoleViewer 注释改为**实际**可见面;Docs/api-client 新增 §2.0 管理侧权限矩阵 + 变更记录 v0.40;顺带修 §0 一处不实陈述(原写「管理端另支持 X-API-Key」→ 标注尚未接线)
- 验收:**隔离 worktree(HEAD + 本片)** go build/vet/test 全绿 + linux/amd64 与 darwin/arm64 交叉编译 + gofmt 干净(主工作树此时被另一代理的在途改动占用,见下条提交)
- 已知遗留(未改前端,按范围只记录):管理面板未按角色隐藏分区 → viewer 打开 memories/users/llm/审计等页会吃 403(应做能力降级)
2026-10-03 19:27:25 +08:00
AskaEth 2c100fe3d9 feat(flow,web): 全链路可视化 P2 — 泳道/并行分支 + 节点活动状态 + 节点实时日志
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 后端 trace/graph.go(395→470):泳道感知游标 —— parents 记住被跳过容器(state_transition)的父链、laneLast 按泳道分别的末节点游标;泳道首节点挂 subagent 根、后续步骤在本泳道内串链;**子代理根不再推进主干游标**(否则 spawn 之后的主干节点会被算成最后一条分支的步骤);node kind 抽具名常量。+ graph_lane_test.go(并行双泳道断言精确边集/嵌套泳道/主干游标不回归/laneOf 环保护)
- 前端新增 flow/{tone,lanes,layout,NodeDetail,LaneSummary}:分组=沿入边反向 BFS 取最近 kind=subagent 祖先(纯 edges,故无需新增 wire 字段);布局=主干沿用 P1 最长路径分层 + 每泳道一条独立横带(带内最长路径深度、逐带下推 y 不重叠、横向滚动);Flow.tsx/FlowGraph.tsx 加泳道框/虚线泳道边/运行闪烁(既有 mu-blink)/选中高亮/图例
- 实时日志:选中态按 node id 保存,随每帧 graph_update 重新 find → 自动显示追加后的 log[];空时区分「运行中(事件随 span 结束回填)」与「未记录事件」,不合成事件
- 未新增 wire 字段 → 无契约变化(因此未动 lib/types.ts / wsParse.ts);<FlowGraphView> 旧签名兼容,Agents/Backthink 页不受影响
- 验收:go build/vet/test 全绿 + linux/darwin 交叉编译 + npm run build(177 模块) + 逻辑测试 lanes 40 / channelStatus 45 / enums 47 / messageSync 54
- ⚠️ 已核实、未伪造的缺口:生产路径当前产不出子代理数据 —— Scheduler.Spawn 全仓仅测试调用方(0 生产)、编排包无 bus 订阅入口、无「派子代理」工具/capability → 泳道要真的亮需先接线;Span.Record 同样无生产调用方 → log[] 恒空(UI 已诚实显示空态)
2026-10-03 19:21:10 +08:00
AskaEth 49d12d9a64 fix(admin): 修上一提交的类型错误 — channelListState 改为泛型;仪表盘三态化
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
上一提交(52ee8e5)带着 tsc 错误被推上去了(我的校验顺序写错:set -e 在 build 之后)。本提交修掉并把「先绿后提」做对。
- channelStatus.ts:channelListState 改为泛型 <T>(它只判 null/error,与元素类型无关),使仪表盘能对任意列表复用同一套「读不到 ≠ 0」判定;既有调用点行为不变
- Dashboard.tsx:六个 HTTP 卡片(人格/会话/记忆/插件/渠道/IoT)由 data ?? [] + ?? 0 改为复用该三态:unknown → 「—」+「读取失败(与后端失联)」(warn)、pending → 「—」+「读取中…」;WS 驱动的 Agent/后台思考卡片不变
- 验收:npm run build 绿(先跑后提)+ channelStatus 45 断言 + messageSync 54 断言
2026-10-03 19:17:26 +08:00
AskaEth 52ee8e53aa fix(admin): 仪表盘读不到数据时不再画成 0/「全部在线」
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
与渠道页同一类误导(主人本次误读的同源问题):六个 HTTP 卡片(人格/会话/记忆/插件/渠道/IoT)原用 `data ?? []` + `?? 0`,后端不可达时显示 0 且「全部在线」(tone=ok),把「读不到」画成好消息。
- 改为复用 channels/channelStatus 的列表三态:unknown → 「—」+「读取失败(与后端失联)」(warn);pending → 「—」+「读取中…」(neutral)
- WS 驱动的 Agent / 后台思考卡片不依赖 HTTP,保持不变
- 验收:npm run build 绿 + channelStatus 45 断言 + messageSync 54 断言回归
2026-10-03 19:17:04 +08:00
AskaEth 37fb3cd265 feat(admin): 渠道状态三态化 + 顶栏后端失联指示 — 不再把「读不到」画成「未连接」
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
起因:面板把「取数失败(后端不可达)」渲染成红色「未连接」,主人因此把「后端没在跑」误读成「微信适配器挂了」(AdapterDetailPage 的 record?.connected ?? false)。
- 新增 channels/channelStatus.ts(纯逻辑三态:ready/pending/unknown + 记录级 connected/disconnected/unconfigured/unknown/pending;局部类型,未动 lib/types.ts)
- 新增 channelStatusView.tsx:ChannelStatusTag(三态 Tag + 「数据可能过期」)+ ChannelLoadError(失败/过期横幅 + 重试)
- 覆盖 13 处渲染点(channels/ 全目录 + AdminApp):未知态一律灰色「状态未知」+ 原因,绝不落红色「未连接」;未知态禁用连接/断开与「新建/编辑配置」(避免 prefill 空列表误建/覆盖);读取失败不再冒充「尚未保存任何渠道配置」「暂无消息」「未声明能力」等事实性谎言
- 顺带修:WeixinLogin 首次 login-status 读取失败被 .catch 静默吞成「未登录」,现如实报「读取失败(与后端失联)」
- AdminApp 顶栏新增 useBackendHealth + BackendStatusTag:HTTP /api/v1/health 探活(15s 循环),**探测成功前不显示「在线」**;WS 状态作为独立 Tag 展示,两者不混算
- 验收:npm run build 绿(177 模块)+ 新增 45 断言 + 既有 54 断言回归 + renderToStaticMarkup 真渲染冒烟(unknown 渲染不含「未连接」也不含 err 色;未探测成功不显示在线)
- ListPage 主函数 125→71 行(改前已超 80 行红线,行为等价)
2026-10-03 19:16:22 +08:00
AskaEth a51aadeef4 refactor(weixin-ilink): 措辞纠正 —「会话过期」→「会话被 iLink 拒绝」(零逻辑改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
事实(主人 2026-10-03 确认):iLink 的 bot token 是长期凭据(一个月前扫码的仍可用);errcode=-14 是 iLink 侧拒绝/作废该会话,不是时间性过期;且至今未观测到真实 -14(当日全部日志 0 次)。此前代码把这些写成「过期」,属沿用的旧命名,现纠正。
- 纯改名/文案:ErrSessionExpired→ErrSessionRejected、errSessionExpired→errcodeSessionRejected、expired/expiredToken→rejected/rejectedToken、markExpired/expiredLocked/expiredState→markRejected/rejectedLocked/rejectedState;409 错误码 session_expired→session_rejected
- 行为一字未改:显式标记 / Connect 不假 OK / 面板 needsRescan+lastError / 扫码或换新 token 即清除 全部保留
- 微信协议自身的「二维码过期」与 auth 的 JWT/refresh 过期一律未动(那些是真·时间性过期)
- Docs/api-client v0.39 补事实说明;顺手收尾 3 处残留注释(admin.go / channelbridge/inbound.go / readloop_test.go)
2026-10-03 18:26:27 +08:00
AskaEth cdc370dc82 feat(weixin-ilink,gateway): 会话过期显式化 — 不再静默变哑/假 OK 重连,面板可见「需重扫」
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 适配器新增过期状态机(plugins/channels/weixin_ilink/session_state.go):getupdates/sendmessage/getuploadurl 收到 errcode=-14 即 markExpired(ERROR 仅一次,只带 bot_id,token 永不入日志),扫码登录拿到新 token 或配置换新 token 时清除(惰性比对)
- Connect/HealthCheck 在过期时返回 ErrSessionExpired(可与 ErrNotConnected 区分):不再对死 token 设 connected=true 报假成功;发送路径同样返回该错误而不是笼统 ErrNotConnected
- 网关:POST /admin/channels/open 遇过期 → 409 session_expired + 明确文案(不再吞错回 200);GET /admin/channels 摘要新增 needsRescan/lastError(经可选接口类型断言读取,取不到不伪造)
- channelbridge:读循环非取消退出时把自身记账的 connected 置 false(热重载被替换者退出不抹掉新实例)
- login/login_admin:过期时 StartLogin 允许重新出码、LoginStatus 如实报 failed(原先可能把死 token 当 confirmed 重新落盘)
- Docs/api-client v0.39;新增 5 组测试 + 变异验证(去掉标记/恢复假成功/网关不填字段 均 FAIL)
2026-10-03 18:00:01 +08:00
AskaEth 2092094d0a fix(platform): 内嵌 IANA tzdata,Windows 也能解析 TZ;新增本机 embedding 服务脚本
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- cmd/cyrene/main.go: import _ "time/tzdata" —— Windows 无系统 tzdata,导致 config.TZ(默认 Asia/Shanghai) 解析失败、loop 时间注入被静默禁用;内嵌后单二进制自洽(约 +450KB)
- 实测:迁移到 Windows 后启动日志不再出现 "timezone load failed; time injection disabled"
- scripts/embed.bat(新):本机 llama.cpp embedding 服务启停(start/stop/status),OpenAI 兼容 /v1/embeddings,跑 Qwen3-Embedding-0.6B-Q8_0(dim 1024)
- 实测:start→Ready→status running→POST /v1/embeddings 200 dim=1024;配合 docker compose 的 cyrene-pg(pgvector) 后端日志变为 pgvector index ready dim=1024(不再回落内存语义)
2026-10-03 17:58:45 +08:00
AskaEth 17c9fb7545 chore(scripts): 新增 Windows 等价控制脚本 ctrl.bat
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 与 scripts/ctrl.sh 同语义:start(默认)/stop/restart/status + 额外 build
- 端口读 .env 的 PORT(回退 10080);日志 %USERPROFILE%\cy.log;PID %USERPROFILE%\.cyrene.pid;Start-Process 隐藏窗口脱离控制台(等价 setsid nohup & disown);停止先优雅后强杀
- 刻意 ASCII + CRLF(cmd 解析对编码/换行敏感),并显式走 %SystemRoot%\System32 绝对路径,避免 MSYS(Git Bash) 的同名工具抢占
- 实测:status/stop/start/restart 全流程通过(后端 10942 health ok)
2026-10-03 17:44:35 +08:00
AskaEth f42ea1e41c feat(web): B4 枚举标签映射 — labelFor 未知值原样显示
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增 lib/enums.ts:platform/mood 标签 + labelFor(kind,value),未知值原样返回(display-raw 红线),注释标明权威来源是 GET /api/v1/meta/enums
- 4 处 mood 渲染改用标签(Nav/TopBar/chat×2);未动 types.ts 的 MODES/Mode
- 新增纯逻辑测试 enums.test.ts(47 项,esbuild+node,零新依赖)
- 未动 admin channelFields 的第二份 PLATFORM_LABEL(记入待办)
2026-10-03 16:55:22 +08:00
AskaEth 22e8398110 feat(contract,gateway): B4 枚举权威表 — GET /api/v1/meta/enums + 未知值回落约定
- 新增 internal/contract/enums 叶子包:platform/mood/mode 三表 + UnknownFallback="display-raw";每次调用返回新切片,无包级可变全局
- GET /api/v1/meta/enums(anyAuth 只读、纯投影、无 store/无错误路径)
- 反漂移 4 处:真构造内置适配器断言 Name() 与表双向相等、真工厂 buildAccountAdapter 逐项可建、mood 对齐 emotion 常量 + 默认转移表、mode 对齐前端 MODES(跨语言,文件缺失则 skip)
- Docs/api-client §1.1 枚举权威表 + 行15 + v0.38;api-reference §3.7 + 行12
2026-10-03 16:55:22 +08:00
AskaEth a828edacc5 feat(web): D4 会话未读徽标 — 打开即已读 + 消费 session_read
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- SessionMeta.unreadCount(可选,兼容旧后端)+ api/sessions.markSessionRead
- lib/sessionRead.ts:会话历史落地后再推游标(best-effort,失败静默,下次 GET /sessions 收敛)+ 帧归属判定(ws.ts 因此留在 500 行内)
- store.clearUnread 无变化时返回同一 state,避免多余重渲染;Nav 徽标沿用 Miuix token(mu-radius-pill/mu-err/mu-rise-in)
- 逻辑测试 35 → 54 项,含变异校验(改成 clearUnread(activeSessionId) 即 FAIL)
2026-10-03 16:47:56 +08:00
AskaEth e8eccd2bb2 feat(history,gateway): D4 会话未读/已读游标 — unreadCount + POST /sessions/{id}/read + session_read 广播
- chat_sessions 增 read_at(毫秒,与 chat_messages.created_at 同单位);迁移 0010 并把既有会话回填为各自最新消息时间(升级当天不误报)
- ListSessions 单查询派生 unreadCount(无 N+1,复用 idx_chat_msgs_session_created 覆盖索引)
- MarkSessionRead 只前进 MAX(read_at, ?);未知会话 ErrSessionNotFound;游标方法挂在网关消费侧窄接口(沿用 GetMessage/messageLookup 先例,避免污染 runtime 接口与其 fake)
- POST /api/v1/sessions/{id}/read:writeAuth + 400/404/501,成功广播 session_read、失败路径不广播
- Docs/api-client v0.37(§1 unreadCount / 新行 8a / §3.9 session_read / §4.2 用法)
- 含迁移回填测试、store/handler 测试与变异验证;顺带把 migrate_pinned_test 的「回退一步=0009」硬编码改为对迁移数量免疫
2026-10-03 16:47:55 +08:00
AskaEth 04a8ee0727 feat(web): 流式帧按会话过滤,非激活会话不再串入当前气泡
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- WsDownEvent 增 sessionId;流式分支(message/thinking/tool_call/tool_execution/stream_chunk/stream_end 及 legacy thinking/tool_used)统一过 streamFrameApplies
- 会话列表未解析(activeSessionId 为空)时保持改前行为,避免误丢主人自己的流
- 抽出 lib/streamScope.ts 承载该判断,ws.ts 回到 500 行红线内
- messageSync.test.ts 增 21 项断言(跨会话丢弃/激活追加/无 session 兼容/未解析回退)
2026-10-03 16:08:53 +08:00
AskaEth 61cc32c44b feat(gateway,loop): A1 流式帧附加 sessionId/turnId — 多会话并发流式可路由
- model.StreamEvent 增 SessionID/TurnID(适配器在 loop 之下,恒留零值)
- loop 在回合边界注入;覆盖 stream.go 与 tools.go 的 tool_execution 发射点
- 网关 runTurn 每回合生成 turnID 并经 loop.WithTurnID 挂进运行上下文(编排层与 legacy 直连的唯一收口点);broadcastStream 填充 wsEvent
- Docs/api-client v0.36 契约与变更日志;v0.10 条目标注为已被补齐
- 新增 loop/gateway 测试,含变异验证
2026-10-03 16:08:52 +08:00
AskaEth 29aa60d0eb test(platform): 修 Windows 上平台脆弱的断言(不削弱测试意图)
- trace/task: 亚 tick 耗时合法读到 0(本机时钟步长约 0.44ms),改为断言非负且 Duration == EndedAt-StartedAt
- user/runtimeconfig: Windows 无法表示普通文件的 POSIX 权限位(Stat 恒 0666),0600 断言仅在非 Windows 生效,注释写明实现确实请求 0600
- exec: MSYS pwd 把同一目录渲染成 /tmp/...;改用「标记文件相对名解析」+「与显式 root 的 pwd 输出逐字相等」两条跨平台断言 + 变异验证
- 只读排查全仓同类脆弱断言,结果记入 DevDocs/工作小结
2026-10-03 16:08:52 +08:00
AskaEth fbbfcc95ee fix(persist,channel): 同类定宽时间戳修复 — checkpoint 恢复与渠道会话列表排序
- persist: formatTime 定宽;loadLatestSQL 补 rowid 兜底;New 时归一化历史行(避免 LoadLatest 挑到更旧检查点)
- channel/session: 写入定宽;listSessionSQL 补 rowid 兜底;核实无 INSERT 吃 DDL 默认值,故只加注释不改默认值
- 新增回归测试并做变异验证(改回 RFC3339Nano 即 FAIL)
2026-10-03 16:08:51 +08:00
AskaEth 31086ed1ab fix(memory): 定宽时间戳存储 + rowid 确定性排序 + 遗留数据归一化
- formatTime 改用固定 30 字节布局(9 位小数秒),让 TEXT 列的字典序与时间序一致(RFC3339Nano 会去掉尾随零产生变宽字符串,秒内会反序)
- memory_items / project / project_chapter 的 ORDER BY 全部补 rowid 兜底,消除同 tick 打平时的顺序不确定
- 开库时幂等归一化变宽的历史值;无法解析的行原样保留,不阻塞开库
- 回归测试:变宽排序反例、同一时间戳插入序、归一化幂等与跳过
2026-10-03 16:08:50 +08:00
AskaEth 1ac126fc9e feat(web): D 消息编辑/删除前端入口 + 日志聚合前端(Observability后端tail) + 修api-client契约
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- D前端: lib/api/messages.ts(patchMessage/deleteMessage,兼容裸DTO与{message}) + MessageActions.tsx(仅user消息; 单击/右键/450ms长按开Miuix玻璃菜单; 编辑弹窗预填+字数→乐观updateMessageById→patchMessage→失败回滚; 删除确认→deleteMessage→removeMessageById; 乐观气泡禁用) + chat.tsx挂载 + Icon加edit/trash/more
- 日志前端: types(AdminLogLine time:string|number) + adminSystem.adminObservabilityLogs + Observability.tsx 后端分级tail(level筛选chip注明'该级及以上'+等宽行HH:mm:ss.SSS·level着色·source·msg+30s刷新) 下半保留WS回显
- Docs/api-client §6a/§4.2 修正: PATCH /messages/{id} 实际返回 {data:<DTO>} 非 {data:{message}}(前端已双向兼容,避免误导Android)
- 前端 build 绿(167 modules); SSR冒烟11 checks
- 遗留: 未实机登录; 菜单absolute理论上边缘可裁切(可改portal)
2026-10-03 15:23:27 +08:00
AskaEth 025dd1796b feat(observability): 日志聚合后端 — logger环形缓冲 + GET /admin/observability/logs
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- internal/logger/ring.go: Entry{time,level,source,msg} + RingBuffer(2000,满覆盖最旧,不阻塞)+ringHandler(slog旁路,Enabled委托内层,sourceOf提取source=再回落Scoped); Options.Ring非nil才包
- gateway/observability_logs.go: GET /admin/observability/logs?level=&limit= (writeAuth owner/admin) → {data:[{time(RFC3339 UTC),level,source,msg}]} 最新在前; level阈值过滤(warn=warn+error,已标注取舍); limit缺省200上限1000; 未知level400/未注入501
- routes.go注册 + observability.go SetLogBuffer(atomic.Pointer) + cmd/cyrene main.go/wireGatewayLogBuffer接线(logger先于gateway)
- 控制台/文件输出零变化; 记录只源自已产出日志,不额外打印敏感
- Docs/api-client v0.35; 测试(ring 10 + observability_logs 8 + cmd端到端2); build/vet/test绿(56)+win/darwin交叉编译
2026-10-03 15:22:03 +08:00
AskaEth 79589b5c9f refactor(gateway): 拆 ws_hub.go 589→182(纯搬移零逻辑) — conn/turn/origin/message_event 四文件
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- ws_hub.go 182(hub核心/常量/wsEvent/newHub/broadcast) + ws_hub_conn.go 165(连接层handleWS/read·writePump) + ws_hub_turn.go 168(relay/streamChat/runTurn/interrupt/broadcastStream) + ws_hub_origin.go 73(Origin校验) + ws_hub_message_event.go 30(message_edited/deleted广播)
- 代码行多重集 identical + 顶层声明diff空; 每文件≤500/函数≤80(最大handleWS 28/streamChat 25)
- gateway测试绿 + build/vet + win/darwin交叉编译绿
2026-10-03 15:19:57 +08:00
AskaEth 2720d77331 feat(gateway+web): D 消息编辑/删除 WS 实时广播 — message_edited/message_deleted
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 后端: ws_hub.go broadcastMessageEdited(完整DTO,thinking剥离)/broadcastMessageDeleted(全广播+sessionId,前端过滤); history_write.go PATCH/DELETE成功后触发(404/400/501不广播); history/message_ops.go GetMessage(导出壳,取删除前sessionId)
- 前端: useAppStore.updateMessageById/removeMessageById + messageEventApplies(同会话过滤); ws.ts 消费两事件(缺content丢弃); dispatchFrame导出(可测)
- 真机e2e: PATCH/DELETE → WS收到 message_edited/message_deleted, 二次删404无帧
- Docs/api-client v0.34 §3.8; 测试 history_write_broadcast_test + messageSync.test(14 checks)
- 遗留: 前端编辑/删除UI入口+api客户端函数未接(lib/api 无 PATCH/DELETE messages); ws_hub.go 589行>500(既有)
2026-09-30 11:58:49 +08:00
AskaEth 4ea68d17f3 refactor(gateway+model): 拆 gateway/server.go 719→204 + 重构 scanSSE/processFrom 到 ≤80行
- server.go 204 + 新 routes.go(193,路由注册/Handler/withMiddleware)/deps.go(108)/contracts.go(231,17依赖接口)/broadcast.go(78,BroadcastPersisted/CapacityAwaken/Mood); 导出面不变; 74条路由pattern+RBAC门逐条比对一致
- model/openai_stream.go: scanSSE 138→25(parse/apply/finalize 对齐 anthropic_stream); openaiStreamState结构体
- channelbridge/inbound.go: processFrom 112→46(blacklistDrop/inboundTargetFor/runInboundTurn/deliverReply)
- 纯行为等价; build/vet/test绿(56包)+win/darwin交叉编译
2026-09-30 11:58:49 +08:00
AskaEth 4c3c98d5d4 refactor(web): 拆 lib/api.ts 789→109 barrel + lib/api/ 8域文件(纯搬移零逻辑)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- lib/api/{core,auth,chat,sessions,personas,admin,adminChannels,adminSystem}.ts
- api.ts 改纯 barrel 显式 re-export(export type for types, isolatedModules); 导出名 77→77, 36处导入点零改动
- 代码行多重集比对一致(仅 request/limitQuery 各加一 export 供兄弟模块); 每文件≤500/函数≤80
- 前端 build 绿(165 modules)
2026-09-30 11:52:35 +08:00