Commit Graph
80 Commits
Author SHA1 Message Date
AskaEth 1e0f2c9ec8 docs(api): v0.66 补充说明——chat.image 体积上限按形态写清(描述性更正,不升版本号) 2026-10-05 11:22:26 +08:00
AskaEth 0cbc9ff886 docs(api): 契约升 v0.66(帧层 + turn_id + 渠道配置 + 修正未实现清单 + 思考流口径更正)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 登记 turn_begin / steer_ack 帧壳 turnId / interrupt.turnId(仅 scope=current)/ 帧序按实现
  (session_busy → message_persisted(user) → turn_begin)/ activeTurns / cancelledTurns /
  pending_ack.reason=parallel_limit / session_idle 并行时收紧
- chat_messages.turn_id:历史 DTO、PATCH 响应、admin 渠道日志、message_persisted /
  channel_persisted / message_edited、握手回放均增可选 turnId(空则键省略)
- 渠道配置:open|close 可选 accountId + resolved/accountId;DELETE config 显式 empty=1 + removed/empty;
  GET /admin/channels 的 accounts[].connected
- 语音:VOICE_STT_TIMEOUT_SEC;IM 入站附件引用落库(行为变更)
- 修正 v0.65 的「未实现」清单(那 5 项都已实现并上线)
- 思考流口径更正:历史 thinking 恒缺席(全仓无写入者),思维链唯一出口是实时 thinking.* 帧
2026-10-05 11:15:25 +08:00
AskaEth e5cc9c6054 docs(api): 重写 api-client.md(61 KB / 701 行,比归档版缩小 85%)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
以代码为唯一真源重写:概览认证 / 用户侧 REST 表 / 管理侧表+权限矩阵 / WS 协议(握手+回放+上行+下行+角色门)/
契约细节(错误码·游标·幂等·消息 DTO·文档图片语音·忙时并行·interrupt)/ env 与模型配置 /
变更记录(只留 v0.60 起,更早指向 Docs/archive/)。

相对归档 v0.65 的修正(以 routes.go 等代码为准):
1. 管理侧权限矩阵计数错(归档 61 且自相矛盾)⇒ 实测 12 viewer + 11 owner/admin + 40 owner-only = 63
2. WS 握手实际也接受 ?token=<JWT>(仅不接受 X-API-Key)
3. limit>200 是钳到 200,不是 400(非数字/<1 才 400)
4. voice_output 的 sessionId 在 data 里(帧壳没有)
5. WS_MAX_CONNECTIONS 疑似解析未接线(标未确证)
6. 未确证清单 6 条已写入 §7.4
2026-10-05 09:34:08 +08:00
AskaEth 2ba1baf6d0 docs(api): 归档 v0.65 旧版 api-client / api-reference(准备以代码为真源重写干净版)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
旧版积了 v0.28→v0.65 的历史条目、「契约版本」标题并列、双变更记录、mtime 落后等结构性问题;
归档保留完整历史(含 §5 变更记录),新文件将从零重写,只留最近若干版并指向本归档。
2026-10-05 09:29:33 +08:00
AskaEth 879669cc59 docs(api): 契约 v0.65(忙时并行基础管线 + 六条登记)+ 修三条过期陈述
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
升 v0.65(依据 design/15 §2.5 与 commit 89e13cf),一并登记 679cee0(IM 入站附件引用落库)
/ 59a1ac4(llm_usage.session_id 接线)/ a918819(IM 各渠道语音可被读到),
cb564fe / 15181e7 / 7d2ff74 为纯内部修(无契约变化)。
三档:纯增量 = chat.parallel / activeTurns / cancelledTurns / pending_ack.reason /
两条 REST 应答形状 / VOICE_STT_TIMEOUT_SEC;行为变更 = IM 附件引用落库(无新字段)/
IM 语音进上下文 / session_idle 时点收紧(仅并行时);破坏性 = 无。
修正(客户端抓到的三条,属实):① session_id 「恒为 NULL」→ 已接线(端点仍不按会话过滤/分组)
② 「契约版本」标题并列三处 → v0.65 唯一,其余降为「上一版」
③ 按提交逐个核对补齐(mtime 落后问题)。
未实现项写明:turn_begin / steer_ack 帧壳 turnId / interrupt.turnId / chat_messages.turn_id /
graph_update 按路分桶 / 写类工具文件级互斥(tool/legfile.go 仅定死形状,无实现无接线)/ 第二路 system 摘要。
env CHAT_MAX_PARALLEL_TURNS 如实标注「未接线,生产恒为默认 2」。
2026-10-05 09:23:46 +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 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 c3339a5d07 docs(api): 契约 v0.61 —— 登记 .pdf 文本层与成对嗅探裁决(修 B1 落地后的契约漂移)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 漂移:B1(f82c76d)把白名单 22→23 项、嗅探改为 (扩展名, 嗅探) 成对裁决,而 Docs 仍写「22 项 / 仅放行 text/plain」。本切片只改 Docs 两个文件把口径改准。

- 登记要点:白名单 23 项(22 文本+ .pdf);成对裁决双向拒(HTML/文本改名 .pdf ⇒ 415;PDF 改名 .txt ⇒ 415);读侧真 PDF 下发 application/pdf 但仍 attachment+nosniff(不可内联);文本层抽正文、扫描件诚实拒绝(unreadable,文案含「需要 OCR,当前未做」)、加密/损坏可区分;安全阀 5000 页 / sidecar 8 MiB;ExtractorVersion documents/v3 ⇒ 旧 sidecar 一次性重抽(客户端可观察 chars/truncated 重算)。三档标注:行为变更(.pdf 从 415 变为可上传可读 + 裁决收紧)+ 纯增量(第 23 项)。

- 未发明任何未实现行为:OCR / PDF 写 / Office 明确写「未做」;v0.59 历史条目里的「22」加注「⚠️ 历史值;v0.61 起为 23 项」。

- 主会话复核:grep 22 项 仅剩历史 changelog 一处且已标注;v0.61 在 api-client 12 处、api-reference 13 处;给出「项 → 文档位置 → 代码证据(文件:行)」对照表逐条核对。

- 子代理如实报告的结构隐患(已登记):api-client 有**两套平行变更记录**(§1.2 末的大块 + §5 列表)易再漂移;api-reference 无自己的版本条目节。
2026-10-04 19:51:12 +08:00
AskaEth c4e09afd38 feat(admin): 面板「存储」卡片接真数据(含 PG 增强状态)+ 契约 v0.60
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 现状:overviewMetrics.ts:188 的「存储」卡是写死的 value:"SQLite",后端也没有对应读端点(/admin/system/* 只有 revoke-keys/rotate-jwt/backup/restore 四个动作端点)。

- 新增只读端点 GET /api/v1/admin/system/storage(ownerAuth):main{kind,path,fileBytes,walBytes,shmBytes,pageSize,pageCount,freelistPages,freeBytes} + documents{count,bytes} + vector{kind,enabled,ready};只读(SELECT/PRAGMA + os.Stat)、复用活连接不开新池、**绝不含凭据**(只回 kind,不回 host/DSN)、失败一律 500 storage_unavailable(不回全 0 假快照;表缺失也 500 而不是静默 0)。

- vector 真源 = 组合根 app.vectorState()(Configured = DB_PG_URL 非空;Live = 向量索引类型断言成功),零新探测 ⇒ 面板现在能区分「未启用(未配置 DB_PG_URL)/ 启用(pgvector·ready)/ 已配置但未就绪(连接失败,检索降级)」,正好解答主人对「面板只写 SQLite」的困惑。

- 前端:卡片显示真实体积 + WAL/页数/空闲页 + documents 件数与占用 + PG 那一行;取数走与已接线卡片同一条加载/错误态(失败显示 '—' + 端点报错,不静默显示 0)。

- 契约 v0.60(项目红线):api-client(版本段 + 端点表 + 存储状态契约小节 + changelog)与 api-reference(版本号 + 端点表 + 详情小节)同步;字段名三方(代码/Docs/前端)逐字对齐。

- 主会话独立验收:隔离 worktree(ST1 的 14 文件,排除 B1 在途的 chat_documents.go/documents_pdf_test.go)build/vet 绿、全树 59 包 0 FAIL、存储端点 7 用例 PASS、双平台交叉编译绿。

- ⚠️ 该切片自首一起事故:为通过 cmd/cyrene 类型检查跑了 go build ./cmd/cyrene/(单 main 包)⇒ 覆盖了仓库根 cyrene.exe(49,675,264 B / 19:32:19,脏树未 strip);上一个权威产物仍在 cyrene.exe~(39,507,968 B / 19:25:13)。运行中的 PID 477964 未受影响;主会话将在整树干净后重跑 ctrl.bat build 恢复权威产物。
2026-10-04 19:44:53 +08:00
AskaEth b19d798a2d docs(api): 契约 v0.59 —— 文档进对话 + 上下文预算按窗口推导 + 语音进编排 + thinking 口径
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- api-client 顶部 changelog 新增 v0.59(上一版指向 v0.58),三档标注齐全;新增 §1.2 文档存储与摄取契约、§3.15 语音回合、§2 上下文窗口与预算契约(v0.59);api-reference 端点表/帧表/错误码同步(+235/-33)。

- 登记要点:两条 documents 端点(含重复上传 200 + deduped:true、读侧必须 Bearer)、4 个 REST 错误码、chat.documents(≤5、只收存储名、三入口同形)、DTO document→documents 数组(单数形状从未发布 ⇒ 无兼容期)、chars/truncated 改为「本回合注入侧」、document-read 工具面、contextWindow + 3 个新 env 键 + CONTEXT_MAX_TOKENS 三态(1M ⇒ 699050)+ materialize 语义(欠账)、语音进编排/voice_input.clientMsgId/voice_output 归属键与降级信号、pending_full 两键 + 新增定向 error{code:chat_busy}、thinking 实时/回放剥离与历史可查的口径。

- 子代理如实报出并已就地更正的三处:白名单实为 22 项(design/24 §9.2 写 23,已改设计)、document_unreadable 本期无 REST 落点(只登记为工具错误形态,已改设计 §6.2)、§6.1 单数 DTO 作废。

- 主会话复核:Docs 里 v0.59 出现 22/32 处、四个新小节标题齐、引用的 6 个 commit 全为真实提交;另补一句「预算推导只在启动时算一次 ⇒ 改窗口后需重启重算」与面板文案对齐。
2026-10-04 18:11:44 +08:00
AskaEth 1728addcfe docs(api): api-client → v0.58 登记 LLM_MULTIMODAL(env-only 部署的主模型收图声明)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- §2 的 .env 键表:9 键 → **10 键**(v0.53 首登 9 键,v0.58 追加 LLM_MULTIMODAL),语义写全
  (默认 false;**仅供 .env 合成组**,有 models.json 时以组为准,与 LLM_REASONING_KIND 同口径;
  true = 图直进主模型上下文;非 bool 启动即报错,不静默回退;与两个工具模型键互不代表)
- §1 行 3(chat.image)补指针;§5 新增 v0.58 条(纯新增 env 键、默认 false ⇒ 老部署行为逐字不变)
- api-reference:版本指针 → v0.58,并在 §0.3 / §3.1 / §4.9 四处补明 env 等价键
- 表计数逐行核对一致(标题 10 键 ↔ 表体 10 行)
2026-10-04 16:33:09 +08:00
AskaEth b65fe05635 docs(api): §3.5 补 input 节点 props.steered(13.4.5 的图增量键,此前只登记在 v0.57 变更记录里)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- `input.steered: bool` = 本回合在**回合内边界**吃过新到达的用户消息时出现;纯增量、可选,
  标记只并入既有 input 节点(不新建节点、不改标签,图仍一条主干)
- 依据:`internal/runtime/loop/flow.go` 的 MarkInputSteered(nil/白名单外为空操作)
- 同步(DevDocs,不进 git):design/15 末节接口表改为实态 —— 转向源是**按回合挂在 ctx 上**的
  `loop.WithSteerSource`/`SteerSourceFrom`(不是构造期注入的 HistoryReader),理由=「哪些消息是本回合
  新到的」是按回合的状态、单例 loop 无法持有 ⇒ 组合根零改动;`steer_ack` 写法改为帧壳 + data 实态;
  并修 §2.4 的「按 created_at 去重」口径(实现只按转录行 id 去重 + (内容,图片) 配对,**不用时间戳**)
2026-10-04 16:30:57 +08:00
AskaEth 6c164f920b docs(api): api-client → v0.57 补登 steer_ack(13.4.5 漏登记,Android 端 diff 版本号时发现)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- §3 下行帧清单 + 角色门第 ② 档加入 steer_ack;新增 **§3.14**(触发/载荷/语义「已吃进·未正面回答 ⇒ 灰态」/
  回合内边界/与 pending_ack·session_busy 的分工/不发的四种情形/只吃本编排器排队过的消息/兼容性/
  **客户端现状=Web 前端尚无 handler** 的诚实边界/经 hub 广播不经 bus)
- §5 新增 v0.57 条目:I1/I2/I3 不变量、注入范围(只进本轮视图,不进 checkpoint 与记忆面)、失败让路、
  `graph_update` input 节点 `steered:true` 增量键、实现落点、兼容性与变异验证
- api-reference §6.4 角色门与下行表补 steer_ack(指向 api-client §3.14,不复制第二份契约);
  §6.5 补一句「回合内 steer」(否则 pending 语义会被读成「只能等下一个回合」)
- 核对方式:逐行「文档口径 ↔ 代码依据」表(帧名/载荷/atStep 1 基/角色门/不经 bus/注入不写回/未接线逐字不变)
- 登记待补:§3.5 的 props 清单缺 `steered`;design/15 的接口表写 HistoryReader 而实现是 SteerSource(已滞后)
2026-10-04 16:29:31 +08:00
AskaEth 8c6c796a29 docs(api): api-client → v0.56(协同 A2/G2/G3/G4 契约 + 三个环境变量 + 修正两处错误陈述)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- A2:interrupt 新增 scope({"","all","current"}),三态语义 + 非法值定向 bad_request;
  session_interrupted 形状零变化但 dropped 恒为真丢弃数
- G2:stream:true 直达编排器;响应面补 202 {queued} / 409 chat_busy / 503 chat_dispatch_failed;
  未接 loop 由 200 改 500 chat_unavailable(行为变更已写明兼容性)
- G3:image 解码后 5 MiB 上限 → 400 image_too_large(指路上传);**如实写明 WS 侧到不了这条判定**
  (wsReadLimit=4096 传输层直接断连,发真实图片必须「上传 + URL」)
- G4:新增可选 clientMsgId(两端)⇒ 幂等;REST 200 {duplicate:true,messageId}、WS 新帧 chat_duplicate
  (owner+admin,fail-closed);键回填进 message_persisted/channel_persisted/读接口;迁移 0013
- .env.example + api-client 新增三个环境变量:OWNER_IDENTITY_INJECTION(owner_only 默认) /
  CONTEXT_MAX_TOKENS(32768;0/负 = 关) / GUARDRAIL_KEYWORDS(留空 = 不挂)
- 修正两处错误陈述:api-reference §6.2 与 §6.6 的「OnStream 只填 seq、不带 turnId」(v0.36 起即带
  sessionId+turnId,v0.50 起带 streamId);上传上限 50MB → 5 MiB
2026-10-04 14:42:46 +08:00
AskaEth 20d452a51a fix(gateway)+docs(api): F5 区分「字段缺失/值为空白」+ E4/E5 契约文档治理
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- F5:PATCH /sessions/{id} 的 400 文案区分两类——都缺 → at least one of title or pinned is required;
  存在但空白 → title must not be blank(此前空白也回 title is required,误导排查方向)。
  「空串不能清空标题」的既有语义保持不变(用例显式钉住),新增 history_write_test.go 4 段断言
- E4:/auth/logout 的两种真实响应形状(单撤 {revoked} / 全撤 {revoked,revokedCount})+ 错误码;
  /auth/refresh 补 role;登录响应与一次性接入段补 refreshToken(两份文档一致)
- E5:api-reference §3.2 补 pinned/unreadCount/lastMessage/personaId 与准确排序规则
  (pinned DESC, (type='main') DESC, created_at ASC ⇒ 置顶的次级会话可排在主会话之前);
  新增 §3.6a PATCH /sessions/{id}(请求体/错误码/部分更新/title 不可清空)与 §3.6b POST /sessions/{id}/read;
  端点表补 read 行
- api-client 删掉「主会话恒在前」的错误断言(两处),PATCH 错误码文案同步
- DevDocs/02-development-constraints.md:上传上限 50MB → **硬 5 MiB**(常量、无配置项)
2026-10-04 14:13:27 +08:00
AskaEth 5fb0cf5e19 docs(api): api-client → v0.55(安全入口 / OneBot 四端 / 可观测 WS 推送 / 数据目录哨兵)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- ADMIN_PATH 安全入口整段口径(路径式前缀、命中即注入入口页、错前缀 404、API 不藏、用户端永不携带该串)
- DATA_DIR 新增 platform 哨兵(默认/显式路径/显式空值行为不变)
- OneBot v11 四端契约:配置面 transport/api_base_url/report_path + env 兜底 + 四端语义 + mode 取舍 +
  端口豁免边界;api-reference §5.24 同步
- 新增 §3.12 可观测实时推送:三个新帧(observability_log/trace/metrics)载荷-上限-触发、
  角色门 fail-closed(仅 owner+admin)、"读即订阅"语义、节拍、前端落点、兼容性
- 补记 13.4.5 面内三条与 MCP 换官方 SDK(REST/WS/env 零变更)
- 按实现纠正 5 处:api-reference 版本指针滞后两版、上行 chat 已直达编排器(不再经 bus)、
  http+server 的鉴权实为「配了 access_token 才校验」(未配则放行,与 ws+server 同口径,尚无 fail-closed 开关)、
  推送帧实际触达所有 owner/admin 连接(订阅只决定循环跑不跑)、remoteURL 回落口径
2026-10-04 14:08:10 +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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 153a9d6211 feat(runtime+admin): A2 LLM异步提取接线 + A4 Tracing接线 + A6通知后端聚合
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
A2 异步记忆提取:
- loop/memory.go: asyncExtractor接口(ExtractAsync)+extractMemoryAsync(60s超时,WithOutCancel,dispatch即返回,后台落库,失败仅Warn)+persistMemories共用
- run.go 三个回合落定点改异步提取; 删除run.go逐步提取(design/03'每轮后', 避免多轮工具N次LLM调用/重复写)
- cmd/cyrene/extractor.go buildMemoryExtractor: llmGroupUsable为真→LLMExtractor, 否则回落ManualExtractor(日志说明); keylessEndpoint放行loopback/私网

A4 可观测性 Tracing:
- cmd/cyrene/observability.go: newAppTraceWriter+traceFanout(多sink)+gatewaySpanSink(走gw.ObserveSpan,喂环形缓冲+LLM/subagent指标)
- wire.go 注入; 未改trace/gateway数据结构

A6 通知后端:
- internal/services/gateway/notifications.go(311行): GET /admin/notifications?type=&before=&limit= (adminAuth)
- 聚合已有源(obs.audit SQLite + s.backthink), createdAt倒序, id=source:id; 类型派生(channel/system/audit/backthink); 无新建表/无新Deps/wire零改动
- 诚实: backthink仅shouldPush计, 未注入源为空, 报错500, 未读游标后置
- 契约与前端Y一致: {id,type,title,body,source,createdAt}, before含游标(<=), type未知400
- Docs/api-client v0.33

全仓 build/vet/test绿(56包)+win/darwin/linux-arm64交叉编译绿; 新增测试(memory_async/extractor/observability/notifications)
2026-09-30 09:03:16 +08:00
AskaEth 1a85de555d feat(emotion): A1 情绪系统接线 + refactor: 拆分 channelbridge.go
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
A1 情绪接线:
- loop 回合边界钩子(TurnEnd/OnTurnEnd/settleTurn, Result.Mood); 每轮一次非每步
- persona/emotion: React(valence五档) + Machine.Move(BFS最短合法路径一步) + Observer(observe/decay/好感度,仅变化才emit); 好感度步长读角色配置(cyrene delta=10, 0..700)
- 落库 Mood(web session_persist.res.Mood + IM channelbridge/persist; user消息不带mood); WS mood事件(BroadcastMood,仅变化); per-persona隔离; 群聊/陌生人只读不写不发
- cmd/cyrene/emotion.go 生产接线 + wire.go 注入 loop.Options.OnTurnEnd
- 22 测试(状态机被loop调用/Mood非空落库/mood帧shape/只变化才发/群聊只读) + 真二进制冒烟
- Docs: api-client v0.32 §3.7 情绪事件 + 开源对照清单(companion 应用对照)
- 诚实: 本机无LLM凭据→未活体验证'模型返回后完整轮次'一环

拆分 channelbridge.go 816→257(纯搬移零逻辑): connect/inbound/outbound/blacklist/helpers.go; A1 的 IM mood 调用点落在新 inbound.go, 故与本提交同批

回滚 6 个 gofmt 顺手格式化文件(capacity_loop_test/flow/source_test/findchannel_test/admin_channels_platform_test/history) 缩 diff, 含 history.go 注释被转弯引号
2026-09-30 08:51:38 +08:00
AskaEth 2dee372fb2 feat(backthink/admin): ③b — rag/delete 真实现 + D1 推送出口钩子(自建优先→个推兜底) + api-reference 补 D2/D3/D5 补丁
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- B5: POST /admin/rag/delete 真做(DeleteDocument→DeleteProject 级联删章节+索引清理, owner+审计, 200/404/500, 500降级兜底); users/iot scenarios+rules 诚实501(无用户表/IoT场景规则模型, 消息写明缺什么, 不伪造), 锁住 501+not_implemented
- D1: backthink/push_outlet.go PushExporter{Export}; pushOutlet 自建HTTP(2xx=送达, token仅Authorization头+error脱敏)优先→Getui兜底(未接入, 诚实日志'getui not integrated'+(false,nil)); backthink.Deps.Exporter → pusher.withExporter, Push 在WS/IM后 exportAsync(detached+5s超时, 失败仅日志, 不破坏现有投递, 限流不上报); config PushOutletConfig(PUSH_EXPORTER_* + GETUI_*占位)
- 前端 Rag.tsx/api.ts 删除后 reload + 类型; Docs/api-client v0.31 + api-reference(#23 owner/200-404, §8.4, 端点表PATCH/DELETE messages + pinned + messages?q=)
- 测试(push_outlet/admin_rag_delete/admin_honest501/knowledge store), 后端+前端build全绿+3平台交叉编译
- 刻意: Getui SDK 未接入(无SDK不硬造), 主人自建服务填 PUSH_EXPORTER_URL/TOKEN 即可
2026-09-09 00:08:29 +08:00
AskaEth 844b2a74c1 feat(history): 消息搜索/编辑/删除 + 会话置顶(D2/D3/D5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- history/message_ops.go(新): Search(会话内, q/after互斥, LIKE参数化+%/_转义+大小写不敏感, 严格限定会话doc) / UpdateMessage(返回更新后Message) / DeleteMessage / messageByID / escapeLike
- history/session_pin.go(新): SetPinned; 迁移0009 chat_sessions加pinned INTEGER DEFAULT 0; store.go SessionMeta.Pinned + createSessionsTable加列
- session_preview.go: 查询加s.pinned + ORDER BY pinned DESC,(type=main) DESC,created_at ASC
- gateway history_write.go(新,保持history.go≤500): handlePatchSession(pinned+title) / handlePatchMessage / handleDeleteMessage; server.go加PATCH/DELETE /api/v1/messages/{id} + /sessions/{id}置顶
- history.go: handleListMessages读q分流Search; 契约PATCH title/置顶
- D2会话内搜索(不做跨会话,隔离会话不外泄红线); D3不做重发端点(客户端编辑后再发); D5用pinned bool
- 测试(message_ops/history_messages/migrate_pinned), build/vet/test绿+3平台交叉编译; Docs/api-client v0.30
- 未做: 编辑/删除WS实时广播(message_edited/deleted), 下批
2026-09-08 23:59:13 +08:00
AskaEth 18b2040c95 feat(admin): 面板补全 — 插件安装表单+已装列表 / LLM组maxTokens展示编辑 / POST admin/llm 部分编辑合并
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 前端: api.ts(adminPluginInstall带config/uninstall/listInstalledPlugins) + types + plugins/InstalledPlugins.tsx(已装列表+安装表单, Miuix) + llm/LlmProviderForm.tsx(maxTokens输入) ; Plugins.tsx去假安装按钮
- 后端 admin_write_llm_channels.go: llmProviderRequest字段改指针, mergeLlmProvider部分合并(省略保留原值), runtimeconfig/models.go Find
- 新增测试 admin_write_test TestAdminAddLlmProviderPartialEditPreservesFields
- 前端 build + 后端 build/vet/test 全绿; Docs/api-client v0.29
- (面板无法实机登录, 用 react-dom/server 冒烟渲染 LlmModule/PluginsModule)
2026-09-08 23:47:09 +08:00
AskaEth 70c9fba529 feat(plugin): 插件热安装(MCP server) — POST /admin/plugins/install 连接+发现+注册工具进registry+落盘plugins.json
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- capability/plugin/manager.go: Manager(Install/Uninstall/List/Restore/Close); Install=连接+发现+mcp:name注册工具,失败不落盘; holder持有*mcp.Registry; boot Restore重装
- config/runtimeconfig/plugins.go: PluginConfig(Normalize/Validate), LoadPlugins/SavePlugins(0600+tmp+rename)/Upsert/Delete/Masked(apiKey+敏感env掩码)
- mcp: MCPConfig加APIKey/Env, http/sse Bearer应用, stdio子进程env合并; registry.ToolNames()
- gateway: adminPluginInstall/Uninstall/ListInstalledPlugins; Deps.Plugins注入
- cmd/cyrene/plugins.go: wirePlugins(boot Restore+close)
- 契约: body{kind:mcp,name,config{transport, endpoint?/command?,args?,apiKey?,env?}} → {installed,name,kind,transport,toolCount}; 失败502不落盘/非法400/未注入501; 卸载连带unregister+删配置
- 测试(manager/plugins/admin/mcp_auth/组合根), build/vet/test绿+3平台交叉编译
- Docs/api-client v0.28 + api-reference install=501改真实; .gitignore加plugins.json
- 前端api.ts仍发旧body(无config)会400, 面板安装表单待后续
2026-09-08 23:20:12 +08:00
AskaEth 53002f1e11 feat(model): LLM token 预算可配置化 — models.json每模型maxTokens + LLM_FALLBACK_MAX_TOKENS回退兜底
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- config.go: LLMConfig.FallbackMaxTokens(LLM_FALLBACK_MAX_TOKENS, 默认8192)
- runtimeconfig/models.go: Group.MaxTokens(models.json每模型独立预算, 负值拒绝)
- model/fallback.go: Chain.WithTokenBudget + applyBudget; runtime.go buildSnapshot接入(active.MaxTokens, env.FallbackMaxTokens)
- 优先级: 显式Request.MaxTokens > 组maxTokens > 仅多步链末端的LLM_FALLBACK_MAX_TOKENS > 适配器默认8192; 单步链不动env兜底(避免OpenAI主模型行为回归)
- gateway admin: AdminLlmProvider.MaxTokens 透传/回显/负值400
- .env.example + .env(LLM_FALLBACK_MAX_TOKENS=8192); Docs/api-client v0.27 + api-reference + design/04
- 新增测试(config/runtimeconfig/budget_test/admin_write), build/vet/test绿+3平台交叉编译
2026-09-08 22:42:27 +08:00
AskaEth f43751fd07 feat(history): 消息正向增量拉取(A5) — GET /sessions/{id}/messages?after=
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- history/list_after.go: ListAfter(升序, created_at>after, 走覆盖索引, rowid兜底同毫秒)
- HistoryStore 新增 ListAfter(store+fakes同步)
- gateway history.go: handleListMessages解析after分流; after+before互斥400; 负数/非数字400
- 新增测试(list_after/history_after), build/vet/test绿+交叉编译
- Docs/api-client v0.26 + api-reference §3.4 同步
2026-09-08 21:49:12 +08:00
AskaEth 026b27b310 feat(auth): refresh token 续期(B2) — 不透明哈希+单次旋转+/auth/refresh+/auth/logout
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- auth/refresh.go: RefreshStore(Issue/Rotate/Revoke/RevokeAll/RevokeByUser), SHA-256哈希256-bit随机串, 单次旋转事务(并发重放fail-closed)
- 迁移0008_refresh_tokens(id/user_id/role/token_hash UNIQUE/expires_at/revoked_at + 索引)
- gateway: POST /v1/auth/refresh(旋转/续期) + /v1/auth/logout(单/全设备); login响应增refreshToken
- config: AUTH_REFRESH_TTL(720h)/AUTH_REFRESH_ROTATE_DISABLED; decode.go 支持time.Duration
- rotate-jwt成功后RevokeAll(响应refreshTokensRevoked), revoke-keys不动refresh
- 14测试+真实二进制e2e, build/vet/test绿+交叉编译
- Docs/api-client v0.25 + api-reference §2.3/2.4 同步
2026-09-08 21:45:00 +08:00
AskaEth 7ecb43bd5d feat(gateway): 图片上传端点(B1) — POST /v1/upload + GET /v1/files/{name}
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- upload.go: multipart上传(owner/admin+审计)→ {url,size,mime,name}; MaxBytesReader 5MB + DetectContentType嗅探 + 白名单raster(拒SVG/HTML) + crypto/rand 128-bit随机名 + 0700目录
- 静态服务: 同端口 GET /v1/files/{name}, 能力URL, 路径穿越全形态拒绝(.. / %2f / %2e%2e), nosniff, Range
- errors: ErrPayloadTooLarge(413)/ErrUnsupportedMediaType(415)
- server.go 路由+Deps.DataDir; wire.go resolveDataDir 注入
- 8测试+真实二进制E2E, build/vet/test绿+交叉编译
- Docs/api-client v0.24 + api-reference §0.3 同步
2026-09-08 21:36:03 +08:00