AskaEth
|
21b906ffb0
|
feat(memory): D3 第五态 defer(入库审查)+ 提取留存规则闸 + 临时 TTL 配置面
主人原话:「收窄提取器,确实要给他一个主动式的记忆留存规则,不能想到什么都
往记忆里写」「.env 只做兜底用 …这些键优先从面板配置上取」「临时记忆到期删除」。
按 design/23 §18(D3 第五态 defer)、§19(到期处置)、design/03 §9.6
(tempTtlSec 默认 7 天 + 坏值口径)、§10(提取留存规则)落地。
- IngestDecision 闭集加第五态 defer:照常 active 落库 + 写短 expires_at 落
临时层(不删行、不改正文,回滚 = 清空该列);IngestRuleVersion d3.2 → d3.3。
- 落点:ingestGatePlan 增 ExpiresAt + expiresAt()(走 expiryText/formatTime 的
定宽 30 字节 UTC,与读侧时效闸同一套文本口径),由 finishIngest 在 defer 一态
补写 expires_at(INSERT 与补写之间的短暂窗口如实登记,方向 = 宁可多留)。
非 defer 三态一笔都不多写。
- T0 提取留存规则闸(extract_retention.go):design/03 §10.5 的 N1~N5 纯规则,
接进 judgeIngest 的候选比对之前,命中即 defer + reason=t0_low_value,不进 T1;
两道护栏(显式「记住」/ 持久信号)一票放行,fail-safe 方向 = 宁可多留。
- T1 闭集扩为 merge/modify/keep/defer(同一个 prompt、同一次调用,绝不新增
第二次模型调用):只有关系判为真新时才问价值;defer 要求 target=0,否则判
非法回落 create(reason=t1_reply_invalid)。
- 提取器提示词加固(design/03 §10.3 B):正向清单 + 反例清单 + 「允许返回 []」。
- 临时记忆 TTL:代码默认 tempTtlSec = 604800(7 天);面板
$DATA_DIR/config/memory-tidy.json 的 tempTtlSec 优先(ApplyReviewTuning 逐键
解析,坏值回落默认 + WARN,绝不写进 cfg)。该键在 .env 里没有出处且不新增
(.env 已冻结)⇒ 实际生效链是「面板 > 代码默认」。启动播种同步该键(*int +
omitempty,nil 省略、绝不写 null),保证播种文件与生效值逐字一致。
- preinsert_store.go 的 listReviewCandidatesSQL 原位注释:这条内部召回 SQL
刻意不过时效闸的裁定与理由(design/03 §11.2 第 3 条)。
- 测试:defer 落库带短 expires_at(闸 + T1 两条路径)、写入口径与时效闸同源、
TTL 坏值与回落、规则版本 d3.3、整理候选幂等键不含 IngestRuleVersion、
提取规则正例 11 条 / 反例 11 条。
验收:go build ./... + go vet ./... + go test ./... 全绿;windows/darwin 交叉
编译通过(GOOS=darwin GOARCH=arm64 / GOOS=windows GOARCH=amd64)。
|
2026-10-13 20:22:48 +08:00 |
|
AskaEth
|
68ba85389b
|
Merge branch 'feat/memgate-read-side-expiry-gate-for-memory' into dev
|
2026-10-13 19:59:40 +08:00 |
|
AskaEth
|
0c5fa7b2ae
|
docs(memory): timegate.go 登记「审查留痕读侧不过闸」—— 留痕表结构上无该三列,无可加之闸
|
2026-10-13 19:54:36 +08:00 |
|
AskaEth
|
9a3b3cff8b
|
feat(memory): 读侧时效闸(四条列表读路径)+ Add 落 expires_at 列
主人裁定「临时记忆到期删除 / 任何调用记忆的地方都要正常调用到」的读侧
第 ① 步(隐藏):design/03 §9.6 / §11.2 的时效闸下沉到共享底层,不写在
任何调用点。
- timegate.go(新):timeGateClause = (expires_at = '' OR expires_at > ?)
AND (valid_to = '' OR valid_to > ?);timeGateNow() 与列值同源(同一个
formatTime 的定宽 30 字节 UTC 文本),expiryText() 负责写入侧编码。
- Store.ListByPersona / List / ListScope / ListProject 四条列表读路径
的 WHERE 在既有 status 闸之后拼上时效闸;Retriever.visibleCandidates
组合前两条,检索入口因此自动继承同一判据(无第二处判据)。
- Store.Get 与整理/审查的内部召回 SQL 刻意不过闸,理由写在各自注释里
(listTidyPartitionItemsSQL 原位注释在本切片;listReviewCandidatesSQL
的原位注释按塔裁定改派 M2,timegate.go 头注登记了这条拆分)。
- Store.Add 的 INSERT 补上 expires_at 列(MemoryItem.ExpiresAt):Add 是
全仓唯一 INSERT 路径,时效列的真生产者只能在这里,否则闸退化成空壳。
- timegate_test.go(新):过期/失效行在四条路径与 visibleCandidates 上
都不可见、空值与未来值可见、Get 仍能读过期行、Add 真的落库该列;
外加时间口径同源钉(文本序==时间序 + 跨格式 'T' vs ' ' 陷阱实证)。
验收:go build ./internal/runtime/... 通过;go vet 通过;go test
./internal/runtime/memory/... 全绿;internal 全包 62 个 ok / 0 FAIL;
GOOS=darwin GOARCH=arm64 交叉构建通过。
|
2026-10-13 19:54:19 +08:00 |
|
AskaEth
|
ae68bb0241
|
docs(api): 契约 v0.90 —— 渠道连接态三态(可选 pending/ready/reason + connected 语义收紧)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
登记 POST /admin/channels/open|close 响应与 GET /admin/channels 平台行/账号行新增的三个可选键,并写明 connected 由「动作被受理」纠正为「真实连接状态」这一行为变更(老客户端若只看 2xx 会继续骗人)。新增 §2.4(d) 字段级说明;C19/C20 两行标注;计数不变(14 + 25 + 40 = 79)。
|
2026-10-13 19:41:14 +08:00 |
|
AskaEth
|
b4d55f412e
|
fix(channel): 连接动作不再乐观 —— 适配器自报连接态三态 + 面板如实呈现
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
根因:adminChannelOpen 在 ch.Connect 返回 nil 后硬编码 connected=true。而 Connect 返回 nil 只说明「这次调用没有致命失败」—— ws+client 首连失败刻意非致命(OneBot 实现可能比 Cyrene 后启动),ws+server 则只需监听就绪,两种情况 IsConnected() 都是 false。前端又只按「请求成功」写死「已连接」,于是管理面把「后端受理了」说成「渠道连上了」。
改法:三态口径落在适配器自己身上(connected/pending/ready,internal/channel/adapter 的纯函数 ConnectStateOf 判定 + ConnectSignals 结构化入参,避免两个 bool 接反),qq_obv11 实现 ConnectOutcome 并记住脱敏后的失败原因;网关经类型断言探测(与 accountStatusAware/rescanAware 同套路,核心 sdk.Channel 契约刻意不扩展),只做映射不猜 transport/mode。
线上投影:open/close 响应与 GET /admin/channels 的平台行/账号行各新增 pending/ready/reason 三个可选键(零值即整键缺席,同 NeedsRescan/LastError 口径);既有四键逐字不变,未实现该状态面的平台输出逐字节不变。close 报真实 IsConnected(),断开也不乐观。
凭据边界:reason 三层脱敏(形状级 → 已知明文替换 → 剥 access_token= 查询参数),日志同样只用脱敏串。
验收:主会话亲自复跑 go build/vet 全绿、全树 go test 无 FAIL、目标三包 test 全绿、前端 tsc --noEmit 干净、esbuild 逻辑测试 34+46 checks 全过;主会话亲自变异摘掉第 3 层脱敏实现,发现原两条脱敏用例其实钉不住它(值恰好等于已知明文/被形状级顺带吃掉)⇒ 补一条钉住它独有窗口的用例(短值+紧邻空白),变异下必红、还原后绿。
|
2026-10-13 19:38:56 +08:00 |
|
AskaEth
|
d81635672e
|
fix(channel): 热重载把新适配器写回能力注册表
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
面板保存渠道配置后 Bridge.Reload 只重建了桥接器自己的适配器表,能力注册表里的条目仍指向保存前的旧实例;而管理面的渠道列表、每账号连接状态、open/close 的目标解析全部读注册表 —— 于是卡片永远「未连接」,点「连接」驱动的是旧实例(按旧 URL 拨号)。
生产日志实证:保存后 bridge 侧新实例连上 ws://localhost:3001 且心跳正常,而点连接时被驱动的适配器拨的是旧地址 ws://localhost:10301。
改法:注册表新增 upsert 语义的 ReplaceChannel(覆盖 Value、保留运行期 Policy;Register 遇重名会 ErrDuplicate,热重载必然同名);桥接器经窄端口在适配器上线后同步,账号被移除时删除条目;组合根注入 a.caps 包装。同步失败只记日志、不中断 Reload;未接线时保持旧行为并只 WARN 一次。
验收:go build ./... + go vet 全绿;registry/channelbridge/gateway 三包 test 全绿;主会话亲自变异(摘掉 Policy 沿用 ⇒ TestReplaceChannelSwapsValueAndKeepsPolicy 红)。linux/darwin 交叉编译通过。
|
2026-10-13 19:21:49 +08:00 |
|
AskaEth
|
23b6e3fe1e
|
feat(memory): 启动时自动播种面板配置文件(缺失才写、绝不覆盖)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07:「后端自动创建独立的面板配置文件要做」。
- 启动路径(wireMemoryTidy → applyMemoryTidyTuning 之后)若
$DATA_DIR/config/memory-tidy.json 不存在或全空白 => 写一份「当时生效值」
(十键:既有六键 + 频率四键),此后面板接管
- 已存在(非空)=> 跳过,该分支没有任何写盘语句;损坏 => WARN + 按 .env 跑、
一个字不写盘(不「修好它」)
- 幂等(第二次启动必落到跳过分支);原子写(tmp 0600 + rename)
- 落盘前完整性闸:缺键 / 非正周期 / 钟点越界 => WARN + 不落盘
- 值来自 TidyTuningSnapshot(先跑两个归一化再翻回正向键)=> 播种那一刻运行
行为零变化,有强断言(归一化后配置逐字相等)
- 零新增依赖、零 env 键变更;5 个变异(含「让已存在走写盘」=> 红)
|
2026-10-07 20:38:37 +08:00 |
|
AskaEth
|
d680390300
|
feat(memory): 整理频率走面板配置(面板 > env > 默认)+ 分时段动态间隔
主人 2026-10-07:「.env 只做兜底…优先从面板配置上取」+「频率必须调…或者动态
分配,比如白天间隔短一点 晚上到了静默时期稍微长一点」。
- memory-tidy.json 新增四键:intervalSec / quietIntervalSec / quietStartHour /
quietEndHour(*int + 指针判定 => 显式 0 是合法值、缺位才回落)
- 优先级逐键三档:面板 > env(既有键,未增删)> 代码默认;坏值不覆盖 + WARN,
且不启动失败
- 动态间隔由纯函数 intervalAt(at) 决定,调度侧每一拍现算 => 跑完按当时时段重算
- 🔴 语义变更(有意,代码注释已登记,待补设计修订):安静时段从「唯一可跑时段」
变成「降频时段」—— 旧的「安静窗外一律 continue」被删,否则 intervalSec 永远
是空壳(白天唤醒必被跳过);窗口仍决定档位与补跑判据
- 6 个变异(含「加回旧的那道门」=> 端到端用例红)
|
2026-10-07 19:21:32 +08:00 |
|
AskaEth
|
6deab2e836
|
fix(io): knowledge 工具不再接受「以他角色身份检索」(红线越权读)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
design/03 §7.1 写死:跨角色读只有一条通路(把 B 当子代理派出去查);
「自己的检索能拿到 B 的角色日常记忆 = 已经越权(不是灰区)」。
缺陷:resolvePersona 让显式 persona 参数优先且零校验 ⇒ 模型(或提示词注入)
传 persona=B 即走 ListByPersona(B),读到 B 的角色日常记忆。
- 角色改为只能来自当前回合(tool.TurnPersonaFrom(ctx));显式 persona 非空
且不等于本回合角色 ⇒ 明确拒绝(可消化:原因 + 改道 + 不许重复调用)
- 选「严格校验」而非「删参数」:删参数会让模型静默改用本回合角色,把 A 的
记忆当成 B 的(归属错认)且毫无信号;删键亦属对外可见的 schema 变化
- 工具 schema 键集合与必填项不变(只改 description 文案)⇒ 契约零变化
- 子代理通路未破(子代理回合的 TurnPersonaFrom 本来就是 B,已加用例钉住)
- 顺带核对:FilterSharedVisible 仍在,受限环境过滤未因地塌陷
点名改掉两条「依赖缺陷行为」的既有用例:
- TestKnowledge_ExplicitPersonaWins(原断言把「模型点名即换角色」写成规格)
- TestKnowledge_ReturnsResults(机械依赖,只改调用形状,断言一字未动)
|
2026-10-07 19:17:07 +08:00 |
|
AskaEth
|
811e463cea
|
refactor(web): chat.tsx 551 行按职责拆成 5 片(守 500 行铁律,零行为变化)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- chat/index.tsx 272 行(编排层:SplitChatBubble + MessageBubble + 各气泡)
- chat/toolviz.tsx 112(工具卡 / 思维链可视件)
- chat/labels.tsx 70(元数据小字)
- chat/chrome.tsx 60(外壳判定与注记)
- chat/split.ts 51(纯函数:br 分隔与段落切分,最易测)
- 旧 chat.tsx 删除;调用方一行未动(沿用既有目录索引解析,与 messages/ 同一模式)
纯搬家证据:把旧文件与新 5 文件做行多重集 diff,差异只有 export 前缀、
一行 re-export(ThinkingBlock / ToolCard)与 import 重写 —— 没有一行
JSX / 样式类名 / 逻辑变化;测试文件一字未改。
对外导出名与形状逐字不变(仍是 ThinkingBlock / ToolCard / MessageBubble / SplitChatBubble)。
验收:tsc 0、render 189 checks、bubble 27 checks、四个邻接套件全绿、
vite build 294 模块;变异(拆后去掉 br 空段过滤 => ⑦ 红)证明测试仍在真测那些逻辑。
|
2026-10-07 18:56:30 +08:00 |
|
AskaEth
|
789e3af74e
|
fix(multimodal): 视觉工具的空/畸形 data: URI 明确拒绝 + 入参嗅探与可行动报错
根因(从 checkpoints 取到的那一轮 tool_call 实际 ARGS 逐字节核实):
模型交给工具的 image 参数是 "data:image/png;base64," —— 只有前缀、
载荷为空(全长 22 字符)。两个加载器早就会剥 data: 前缀,但把空载荷
解码成零字节后照样挂上合法 MIME 放行:
image-crop → 零字节进 image.Decode ⇒ image: unknown format
vision → DataURI("image/png", nil) 发给上游 ⇒ 400 unsupported image
(模型复述不出入参里那条 58583 字符的 data: URI,只抄下了前缀。)
- 新增 checkInlinePayload:data: URI 解码后做内容嗅探(复用既有裁决)
- 空载荷 / 缺 ;base64 段 / 缺逗号 / 非法 base64 ⇒ 明确拒绝
- 报错含「不是图片」+ 嗅到的类型 + 支持格式 + 十六进制物证 + 可行动提示,
且不回显正文或 base64
- 裸 base64、http(s) URL、工具 schema、安全策略一律未动
- 测试:新增 16 个用例;全树 61 包 ok;4 个变异(含停用嗅探 ⇒ 5 用例红)
|
2026-10-07 18:50:45 +08:00 |
|
AskaEth
|
da1d23dd23
|
docs(契约): 补两条口径 —— 非 chat 模式(单卡正文)与 br 不分隔的位置
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 非 chat 模式(工作/代码/文档/首页/视觉/语音/智能体/设置):br 是真换行、
不切分;行内白名单照常出样式;白名单外逐字原样;不许出现字面标签。
各模式既有较强排版约定优先(工作模式按行切清单、代码模式 pre 块)
- br 只在普通文本层分隔:角色注记内部与白名单行内元素(尤其 code)内部
都不分隔 —— 前者会把注记正文漏成聊天气泡,后者会破坏成对元素平衡、
触发整段降级反而露出裸标签
|
2026-10-07 18:45:08 +08:00 |
|
AskaEth
|
f01fd7db20
|
feat(web): u 加回白名单 + 空气泡丢弃 + 非 chat 模式不再显示字面标签
主人 2026-10-07 三点裁定:「下划线确实可以保留」「多余的空气泡确实可以丢掉」
「非聊天模式 按照应当有的专业卡片和格式来显示」。
- 白名单回到六项(u 加回,s 仍在外)
- 空 p 块也丢(与 br 碎片同一口径);纯注记回复 ⇒ 零气泡
- 非 chat 模式(8 个单卡正文)给它自己的渲染口径 CARD_RENDER:
br ⇒ 真换行元素(那些容器没有 whitespace-pre-wrap,文本换行会被 CSS 折叠)、
行内白名单正常出样式;WorkMessageBody 的按行切清单、CodeMessageBody 的 pre
等既有较强约定一律保留
- 🔴 发现并修了一个会炸的下游损失:空气泡原本是承载头像/思维链/工具卡/心情/
「生成中…」的那一行,丢气泡会把这些用户可见内容一起丢 ⇒ 加 hasBubbleChrome,
补一行无正文外壳
- 测试 99 -> 189 checks;6 个变异(含「外壳行摘掉 ⇒ 工具卡丢失」)
- 改掉一条既有断言:三个 p 中首段被注记剥空 ⇒ 气泡数由 3 改为 2(并补按行数断言)
|
2026-10-07 18:44:45 +08:00 |
|
AskaEth
|
1107b2a717
|
docs(契约): 行内白名单定为六项(加回 u,s 仍不在内)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-07 18:37:42 +08:00 |
|
AskaEth
|
74c9f2d8df
|
feat(channelbridge): u 加回 IM 出站的行内白名单(改为六项)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07 裁定「下划线确实可以保留」。
- 行内白名单:b / strong / i / em / code / u(六项)
- s 仍在外(逐字原样保留,含大写 <S>)—— 有一个守门变异专门锁这条
- 行为改动只有一行(case 列表加 "u"),其余是注释与测试同步
|
2026-10-07 18:37:35 +08:00 |
|
AskaEth
|
a19d12b228
|
feat(markup): br 改为气泡/消息分隔符 + 行内白名单收窄到五项(Web + IM 两侧)
主人 2026-10-07:「对消息内的 <br> 始终处理为多条消息隔断」。
Web 侧(frontend/web):
- chat.tsx 的 splitParagraphs(SplitChatBubble 的切分层)新增 BR_SEPARATOR_RE
与 splitBubbleBreaks:<br>/<br/>/</br> 等价、属性不解析、<brow>/<br2> 挡掉、
流式半截 <br 不切;不含 <br> 的段逐字返回原样(既有 p 口径一字节不动)
- inlineMarkup.ts 删掉 break 节点(br 不再是行内元素),白名单收窄为
b/strong/i/em/code 五项;renderContent.tsx 同步;测试 76 -> 99 checks
- 40 条基线逐字节对照:28 IDENTICAL,12 个 DIFF 恰是含 br/u/s/<BR> 的样例
IM 侧(internal/services/channelbridge):
- 新增 inline_tags.go:成对平衡的白名单行内标签剥标签留文本、白名单外与
结构不平衡整段原样保留;br 作为第二级分隔符交给 splitBubbles
- 两级切分:p 块为第一级,块内按 br 再切为第二级;空气泡丢弃
- br 切分放在角色注记摘除之后:否则 action 里的 br 会把注记正文漏成聊天气泡
(变异专门钉住这条顺序)
两路各 4 个自带变异均「改坏 -> 编译过 -> 变红 -> 还原 -> 复绿」。
|
2026-10-07 17:32:05 +08:00 |
|
AskaEth
|
6ac214366e
|
docs(契约): br 改为「气泡/消息分隔符」(主人口径),行内白名单收窄到五项
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07:「对消息内的 <br> 始终处理为多条消息隔断」。
- br 语义与 p 同级:两边文本成为两条独立消息(IM)/ 两个独立气泡(Web),
而不是同一气泡内的换行;<br> / <br/> / </br> 三种写法等价
- 行内白名单据此收窄为五项(b/strong/i/em/code),br 移出白名单
- 明确影响面:含 br 的回复在 IM 上会变成多条消息(受既有气泡间隔节流)
- 标注「实现跟进中」:Web 端 2dae469 当时把 br 当行内换行、IM 出站当时
连行内标签都还没剥,两侧都在改;以契约为准,不以当日实现为准
- IM 出口那节补一句:br 不按行内标签处理,按切分走
|
2026-10-07 17:20:47 +08:00 |
|
AskaEth
|
d27c1d4e33
|
docs(契约): v0.89 修订 —— 白名单收窄六项 + 边界行为降为实现细节 + 补 IM 出口口径
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
与安卓(客户端)会话对齐后的三处修订:
- 白名单八项 -> 六项(去掉 u / s):它们在她 100 条输出里 0 命中,
不值得让各端为它们返工;各端可额外容错,但契约只承诺六项
- 结构不平衡的粒度(整段降级 vs 只坏那一对保持字面)降为实现细节:
契约只承诺「不产生行内渲染 + 字符一个不丢」;这类输入极罕见,
为它让任一端返工不划算
- 新增 IM 出口口径:成对且平衡的白名单标签 -> 剥标签留文本;
br -> 换行;白名单外与不平衡 -> 原样保留(逐字不丢)。
与 action / narration 的「整体剥除」语义不同(那是角色扮演标记)
大小写口径(Web 不敏感 / 安卓只认小写)留待各端自定,已与安卓协商。
|
2026-10-07 17:19:48 +08:00 |
|
AskaEth
|
b989f0bb0c
|
docs(契约): v0.89 登记助手消息的行内标签白名单
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
依据:主人 2026-10-07 要求前端支持这类标签的基本渲染(Web 端实现已推库
2dae469,真机产物已一致)。
- 白名单:b/strong、i/em、code、u、s(成对)+ br(void),大小写不敏感
- 三条硬口径:白名单外按纯文本原样显示(字符一个不丢)/ 属性一律不解析 /
结构不平衡(未闭合、交错)整段降级为原文
- 安全红线:助手文本是不可信内容,任何端都不许整体当 HTML 注入;
正确姿势是解析成结构化节点、交给各自的安全渲染原语
- 范围克制:不做块级(段落仍走 §4.16 的 p 机制)、不做 a、不做 Markdown
- 服务端零变更:无新端点 / 帧 / 字段,计数仍 14 + 25 + 40 = 79
|
2026-10-07 17:15:32 +08:00 |
|
AskaEth
|
2dae4693be
|
feat(web): 消息渲染支持行内标签白名单(修 <b> 等标签裸露显示)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07:「她在聊天消息中插入了 html/md 标签,能否增加网页前端
对此类标签的基本渲染支持?」
实测现场(最近 12 条 assistant 消息):<p> 96 次、<action> 34 次
(这两个前端已支持),<b> 6 次(未支持 => 裸标签直接显示给用户);
Markdown 记号 0 个 => 她不用 Markdown,所以不引 Markdown 引擎。
- 新增 inlineMarkup.ts(纯逻辑,111 行):把文本切成「文本 / br / 行内元素」
的节点树;渲染与解析分离,不拼 HTML
- renderContent.tsx 沿用既有出入口,把节点树映射成 React 元素;
既有 <p> / <action> 机制原样不动
- 白名单:b / strong / i / em / code / u / s(成对)+ br(void),
标签名大小写不敏感;不做块级、不做 a(独立安全面)、不做 Markdown
- 安全:全程无 dangerouslySetInnerHTML(文本走 React 子节点,默认转义);
属性一律丢弃(节点类型里没有属性位);白名单外 => 纯文本字面量;
结构不平衡(未闭合/交错)=> 整段降级为原文,字符一个不丢
- 测试 76 条断言(esbuild + node,零新依赖);含 3 条安全性质
- 无回归:改动前后 11 个消息样例过真实 SplitChatBubble 链路逐字节对照,
7 个 SAME,4 个 DIFF 恰是含白名单标签的样例
|
2026-10-07 17:11:37 +08:00 |
|
AskaEth
|
a684328b76
|
feat(memory): T1 加保守全局速率限流(防每天数百到上千次调用失控)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07 裁定:按最优解加全局限流。
- 新增 t1RateGate(滑动窗口、可注入时钟,~70 行);默认
120 次 / 60 分钟(现网 700-1000 条新记忆/天 ≈ 25-42/小时,
取 120 留约 3-5 倍余量)
- 过闸摆在「t1Enabled + 有灰区候选」两个守卫之后、发请求之前
⇒ 不调模型就不占额度;放行才记账
- 超限语义与既有失败路径同源:跳过 T1、决定仍是 create、零改写,
只多一个 reason=t1_throttled 进留痕 + ReviewStats.T1Throttled 计数
- 配置键 t1PerWindow / t1WindowSec 放 $DATA_DIR/config/memory-tidy.json
(零新 env 键,启动期读一次);坏配置回落默认 + WARN,不启动失败
- 每窗口只记一条 WARN(超限是预期行为,一天几百条会淹掉真故障)
- IngestRuleVersion 仍为 d3.2:判定规则集与解析契约没变,变的只是
额度状态,reason 列已可分辨(升版会有 §17.6 记过的幂等键重复行副作用)
自研而非复用 session.NewRateLimiter 的理由(design/12 §N C.5 自己登记过
这条取舍):既有件 Allow() 内部写死 time.Now(),没有时钟注入缝,
而本切片必须能注入可控时钟;且复用会把 channel 层概念拖进 memory 包。
|
2026-10-07 16:25:54 +08:00 |
|
AskaEth
|
987cefdf28
|
fix(embedding): 端点地址规范化 + 独立短超时(治入库审查整条超时)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
背景:入库前审查链路一直整条超时(日志每 ~20s 一条 WARN),异步那层
(含刚装好的 T1)从未成功跑过。
- 新增 endpoint.go:把「连接语义上不可靠」的回环别名规范化成 127.0.0.1
(localhost 大小写不敏感 / 0.0.0.0 / 空主机),只替换 Host,不碰
path / query / fragment / userinfo;显式 [::1] 与真实主机名逐字不动
- Embed 加独立短超时 DefaultRequestTimeout = 5s(远小于审查 20s 预算):
卡住的调用快速失败并走既有「降级到词项余弦」的路径,把预算留给 T1
- .env.example 示例地址 localhost -> 127.0.0.1(未注释行键数 69 -> 69,无增删)
- 跨包测试钉住 5s 严格小于审查总预算
诚实边界(子代理实测,推翻了主代理原先的 IPv6 根因推断):
- Go 客户端走 localhost 实测约 57ms、curl 约 0.2s ⇒「localhost -> ::1
等满 20s 预算」这条链不成立;更可能是单槽 llama.cpp 的吞吐/争用
(一批 64 条约 2.1~2.9s,审查 201 条约 8~10s,再叠 T1 8s 顶满预算)
- 规范化在任何环境下都无害,5s 快速失败对这个真因对症;但真因
(容量/争用)需要另排切片,已记入工作小结
|
2026-10-07 16:22:28 +08:00 |
|
AskaEth
|
a4efbffe60
|
docs(架构): 插件开端口的四条护栏转为「已确认采纳」
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07:「其他几个选项按你的最优解来」⇒ 四条护栏正式采纳,
去掉 AGENTS.md §9 与契约 v0.88 里的「待主人逐条确认」字样。
护栏内容不变:显式配置 / 默认绑 127.0.0.1 / 日志与审计 / 不得绕过身份判定。
|
2026-10-07 16:16:29 +08:00 |
|
AskaEth
|
cc566354fa
|
chore(scripts): embedding 服务改回只绑 127.0.0.1(0.0.0.0 实测无益且有暴露面)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07 最终口径:改回 127.0.0.1。
一个来回的结论登记进脚本注释,免得以后有人再试一遍:
- 0.0.0.0 是 IPv4 通配、不覆盖 IPv6(实测 netstat 只有
TCP 0.0.0.0:11434 LISTENING,http://[::1]:11434 仍失败)
⇒ 它修不了「客户端把 localhost 解析成 ::1 而卡住」那个故障
- llama-server 没有鉴权 ⇒ 绑 0.0.0.0 等于向整个局域网暴露本机 embedding 算力
- EMBED_HOST 保留为可配置项(默认 127.0.0.1),将来真需要对外再打开
|
2026-10-07 16:16:07 +08:00 |
|
AskaEth
|
71438f666c
|
chore(scripts): embedding 服务改绑 0.0.0.0(主人口径),host 提成 EMBED_HOST
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07:让服务监听 0.0.0.0。
- --host 由写死 127.0.0.1 改为可配置 EMBED_HOST,默认 0.0.0.0
- 实测结论登记进脚本注释:0.0.0.0 是 IPv4 通配、不覆盖 IPv6
(netstat 只有 TCP 0.0.0.0:11434 LISTENING;http://[::1]:11434 仍失败)
⇒ 它修不了「客户端把 localhost 解析成 ::1 而卡住」那个故障;
客户端侧仍必须用 127.0.0.1(见 internal/runtime/embedding)
- 同时登记暴露面事实:llama-server 没有鉴权,绑 0.0.0.0 等于让局域网内
任何设备都能白嫖这台机器的 embedding 算力;设 EMBED_HOST=127.0.0.1
即可收回(脚本已留这条退路)
|
2026-10-07 16:14:17 +08:00 |
|
AskaEth
|
1b4d0a552f
|
feat(memory): T1 灰区模型判定 —— D3 入库前审查补上 LLM 那一半
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07 裁定「可以做 T1」。此前 D3 只有纯规则 T0,凡灰区(相似但
互不包含)一律保守 create,真机上「同一事实换措辞」类重复结构性治不住
(清完 92 条字面级重复后,active 里含「喵喵七号」的仍有 101 条)。
- 插入点:T0 判定内核只多吐一份结构化 Gray(惰性收集,恒零模型调用);
T1 挂在异步审查层 Review,同步入库闸不碰它 ⇒ 写入路径零模型调用是
**数据流结构**而不是承诺
- 只许回 merge / modify / keep + 一句理由;坏 JSON、非法枚举、编号越界
一律回落 create(绝不猜、绝不挑最近的一条)
- 禁产新内容:模型只给「动作 + 候选编号 + 一句理由」,正文一律取自库里
既有的两行 ⇒ T1 结构性地不可能把幻觉写进记忆库
- 不越硬门与红线:灰区已滤掉跨角色/作用域/项目/只读来源与时效/数值/冲突/
低置信;「规则否决一票否决」不可走样,T1 不提供翻案入口
- 有界:每次审查至多一次调用(灰区打包,上限 8 条),子超时 8s 且不超过
审查总预算;失败只改 reason、决定恒 create、零 UPDATE
- 开关 t1Judge(放 $DATA_DIR/config/memory-tidy.json,零新 env 键),默认开;
模型出口用闭包晚绑定既有模型栈(零新客户端、零新依赖)
- 留痕 reason 结构化(t1_merge / t1_modify / t1_keep / t1_reply_invalid /
t1_unavailable),rule_version d3.1 → d3.2;模型的自由文本理由一律丢弃
- preinsert_config.go 为纯搬移(为守 500 行上限,无新逻辑)
|
2026-10-07 14:10:33 +08:00 |
|
AskaEth
|
5b8c7a3ef3
|
docs(架构): 澄清单端口红线只约束框架自身,扩展插件可开端口
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07 口径补充:原先把这条红线读成「任何组件都不许开端口」过宽了。
- AGENTS.md §9:红线加限定语(只约束 Cyrene 框架自身);qq-obv11 那条已登记
例外升格为一般规则的实例;新增插件开端口的四条护栏(显式配置 / 默认绑
127.0.0.1 / 日志与审计 / 不得绕过身份判定)—— 护栏由主代理拟,待主人逐条确认
- Docs/api-client.md v0.88:澄清「插件监听端口不在契约范围内」;零破坏性变更,
无新端点 / 无新帧 / 无字段形状变化,路由计数不变(14 + 25 + 40 = 79)
- README.md:用户侧表述同步
DevDocs 侧同步(不进 git):design/13 §0、design/09 §5、design/08 §4、恢复提示词红线节。
|
2026-10-07 14:09:01 +08:00 |
|
AskaEth
|
33ea7a830f
|
style(search): 修 zhipu.go 的 gofmt 对齐(改动前即存在的格式债)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
spec 字面量的三行对齐空格多余,gofmt -l 一直报它。零语义变更:
只删多余空格,gofmt -l 现已全干净。
|
2026-10-07 13:42:58 +08:00 |
|
AskaEth
|
ce9af42945
|
feat(search): 新增腾讯云·元宝搜索与秘塔搜索两家 provider
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
接入位表六家扩到八家,端点形状与错误码一字未改(契约 v0.87,仅两处名单)。
- tencent_wsa:POST {base}/SearchPro + Authorization Bearer,零签名。
两条官方口径决定实现:HTTP 状态码恒 200(判失败只能读 Response.Error.Code);
Pages 是「JSON 字符串数组」需二次解析。错误码按官方表分类
(UnauthorizedOperation/AuthFailure.* = 凭据、ResourceUnavailable = 欠费、
RequestLimitExceeded* = 限流),文案只带 code 不带上游 Message。
- metaso:POST {base}/api/v1/search,响应 schema 仅第三方来源且互相打架,
实现走宽松解析(数组候选与字段名回退),逐处标「未实测」。
- 零新 env 键:两家只走 $DATA_DIR/config/plugins/web-search.json,
.env / .env.example 未动;组合根 websearch_providers.go 零功能改动。
- 契约 v0.87:values.provider 合法闭集与 plugin.providers 候选各加两条,
计数不变(14 + 25 + 40 = 79)。
|
2026-10-07 13:42:41 +08:00 |
|
AskaEth
|
8e3311cea2
|
test(cyrene): 补同步入库闸的接线断言(切片连带的漏提交)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 断言组合根真的接了闸的开关:a.memoryIngestGateOff() 与 DisableIngestGate: gateOff
- 断言 store 侧唯一写路径先过闸(不是只投异步队列)
(前一片 90f4302 提交时漏了这个测试文件,工作树因此挂着一处未提交改动;现补齐。)
|
2026-10-07 13:25:35 +08:00 |
|
AskaEth
|
90f430216a
|
feat(memory): D3 从「异步后置」改成「同步入库闸」—— 由整理层决定入库形态
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人 2026-10-07 裁定:「把记忆整理功能接到昔涟和记忆库中间做审查,由整理功能
进行入库」。现状是**异步 + 入库后软合并** ⇒ 行已经落库才审,面板/召回先脏后清,
而写入速率实测 ~25 条/小时(≈600/天)⇒ 清理永远追不上。
落法(三条硬规格,全部落地):
① **同步闸但有界**:闸在 `store.go:247` 的 `planIngest`(INSERT **之前**),
本体 `preinsert_gate.go`。墙钟预算 **150ms**(实测最坏 p95 仅 4~7ms ⇒ 20~50 倍
余量)、候选扫描上限 **100**(比异步侧 200 更紧,因为它在写路径上)。
🔴 **零远端 embedding**:闸恒用词项余弦(`deps.Embedder` 根本不参与)——
远端 embedding 一次 2.3~3.0s 且对同文本返回不同向量(不可复现),不许摆在同步
路径上拍板。
② **fail-open,绝不丢记忆**:候选源报错 ⇒ `gate:candidates_unavailable`;超预算 ⇒
`gate:budget_exceeded` ⇒ **照常 insert(active) + 投既有队列 + 留痕 + WARN**。
③ **三态落库(落 merged,而不是"不插入")**:确定性命中(字面重复 / 归一化等价 /
真包含)⇒ INSERT **直接带 `status='merged'` + `merged_into=<存活行>`**,存活行
`merged_from` 追加、`merge_count`+1 ⇒ **新行从不进 active、从不建向量索引**,
而**行与原文都在表里 ⇒ revert 无损**。若"干脆不插",那条文本从未存在过行,
revert 无从下手,违反 design/23 §8 的红线。
- **不与异步 D3 重复审**:闸做决定的那条 `Async=false`(留痕由闸自己落,
审计动作名 `memory.ingest.gate`);真新/拿不准的走原有队列(留痕仍归异步层)。
- **开关**:`$DATA_DIR/config/memory-tidy.json` 的 `{"ingestGate": false}` ⇒ 一键回到
「只插不审」(= 改动前的异步行为)。**零新 env 键**(`.env` 未碰)。
- 顺手把 `store.go` 的行映射拆到 `store_rows.go`(守 §10 的 ≤500 行)。
验收(主会话复跑 + 亲自变异):`go build ./...` + `go vet` 绿;memory 包测试绿;
我用 -overlay 摘掉闸的预算 ⇒ `TestIngestGate_Add墙钟有界` 与
`…failOpen_超预算绝不丢记忆` 变红("Add 等了 2.0s,越过闸预算 150ms")。
子代理另做 2 个变异(去掉预算 / 把 fail-open 改成 fail-closed)全红,并实测
**闸开 vs 关的 `Add` 额外墙钟:p50 ≈2.5~3.1ms、p95 ≈4~7ms**(基线批均值 35.8µs)。
⚠️ 未部署(要一次 build + restart);⚠️ 已知边界(如实):闸只用词项余弦 ⇒
「同义但字面差得远」拦不住(有意为之,由异步层/夜间整理兜);闸的候选是**最新
100 条**(更老的重复闸看不到);**并发同内容双写**仍可能两条都落 active(既有竞态,
SELECT 与 INSERT 不在同一事务,非本次引入,第二层会清)。
|
2026-10-07 11:41:44 +08:00 |
|
AskaEth
|
f310173b4c
|
feat(memory): reclassify 纳入自动档(主人 2026-10-07「全自动挡吧」)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
自动落地现在三类:merge(字面重复 ∨ 事实等价 ∧ ≥0.95)· archive(one_off_event
且不像习惯)· **reclassify(新增)**。reclassify 只改 `kind` 一列
(`reclassifyTidyItemSQL` 只写 kind+updated_at),**不动正文、不动作用域、不碰第二条行**。
判据(纯函数 `tidyAutoReclassifyKind`,四个条件全中才自动):
① 恰 1 个 source 且无 target;② 理由 = `kind_mismatch`;③ 建议 kind 合法;
④ **改前 kind 合法且 ≠ 建议值** —— ③④ 是 **revert 抓手**:没有它自动落地就不可回滚。
**边界**:只读 ProposedKind / KindA,**一概不读** ProposedContent / MergedContent /
ContentA/B / 分区 / 时间字段。
六条护栏逐条覆盖它:软动作(同一条 apply 路径)/ 每轮上限(三类同占一个池子,
默认 50 硬顶 500,混合时截断如实)/ 审计 `memory.tidy.apply`(`auto=true`,
`reclassified=%d` + stats + 日志 + run 审计,**只带计数与理由、不带正文**)/
**可 revert**(`revertTidyReclassify` 按 KindA 写回,且只在当前 kind 仍等于建议值时生效)/
幂等(候选条件更新 + 行 `AND status='active'` 双闸)/ 开关关闭 ⇒ 连查询都不发。
另补一道本片新增的门:**受限来源**(群聊/陌生人)与跨分区的 reclassify **skipped**
(原先只有 merge/archive 有这道门)。
验收(主会话复跑 + 亲自变异):整树 `go build ./...` + vet 绿;memory/gateway 两包
测试绿;我用 -overlay 禁掉「必须真的变了」那一格 ⇒ `…EligibilityBoundary` **3 条子例变红**
(改前 kind 缺失/非法/与建议值相同 ⇒ 都"没法回滚")。子代理另做 4 个变异(连正文一起写 /
摘受限来源门 / 去掉 reclassified 计数 / 不要求可回滚抓手)全红。
🔴 **未动的红线(一行未改)**:`archive` 的「习惯类不归档」守卫(design/23 §5.2-1)
—— `looksLikeHabit` / `tidyHabitMarkers` / `tidyReasonOneOffEvent` 零改动,
`TestTidyAutoNeverArchivesHabitMisjudgedAsOneOff` 仍绿。要连它也自动请主人明说。
⚠️ 未部署(要一次 build + restart);⚠️ design/23 的 §7「reclassify 交人工」与
§13 开放点 5「全部人工应用」**现已过期**,主会话同步。
⚠️ 已知遗留(需新授权):历史积压里指向**已被吸收行**的 reclassify 候选会每轮被选中
又永远 `source_not_active` 跳过,白占一次每轮名额;彻底解法是「永久不可用的候选自动
标 rejected」(新行为)。
|
2026-10-07 11:26:34 +08:00 |
|
AskaEth
|
6fe68ecfac
|
feat(browser): 方案 A 落地 —— 显式端口 + 去掉 stealth flag(主人 2026-10-07 拍板)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
依据 `DevDocs/reference/chromium-cdp/`(9 份实测+官方源码):旧姿势传 `--remote-debugging-port=0`
会被 Chromium 当成 ChromeDriver 式自动化启动 ⇒ 主动打开 `AutomationControlled`
⇒ `navigator.webdriver=true`;那个 bad-flag 是承重覆盖,但代价是**每次有头弹横幅**。
改用显式端口后 `webdriver` **本来就是 false** ⇒ flag 变纯冗余 ⇒ 去掉它,**两者兼得**。
- `launch.go`:`--remote-debugging-port=<我们选的端口>` 替掉 `=0`;**删掉**
`--disable-blink-features=AutomationControlled`(注释写明理由)。
- 新增 `portrecord.go`:端口来源与落点的**唯一出处** —— 取空闲端口(`net.Listen`
取号后立刻关闭,**本进程不常驻监听**)、写/读/清**我们自己的记录**
`<profile>/cyrene-debug-port`(选它与 Chrome 的 `DevToolsActivePort` 同目录:
天然按 profile 隔离、profile 换掉记录自动消失、内容只有端口号不含凭据);
**兼容老记录**(换二进制时上一版起的浏览器可能还在跑 ⇒ 认不出会把"自己的浏览器"
误报成"profile 被别人占了");就绪判据换成「我们选的那个端口答得上 `/json/version`」;
TOCTOU 处理 = 端口被占**换号重试一次**,重试仍失败则如实报错且不留记录。
- 三条认领路径(复用/清陈记录/如实报错保留现场)与三条失败分类(进程立刻退出 /
等端口超时 / 预算耗尽)**全部保留**;`ErrLaunch` + `errors.Is` 口径未变;
**绝不杀进程**红线未变。
真机自证(子代理跑,默认 Skip 的 `realchrome_test.go`,`CYRENE_BROWSER_CDP_E2E=1` 开闸):
`webdriver=false` · 命令行**无** flag/`--enable-automation`/`--headless` · UA 无
`HeadlessChrome`、`plugins=5`、`languages=zh-CN,zh`、`window.chrome` 在 ·
**UI Automation 控件树 17 个命名节点里没有「信息栏容器/信息栏」⇒ 无横幅** ·
第二次 Dial **复用同端口、浏览器主进程仍是同一个 PID** · 收尾按 profile 路径筛残留 = 0。
验收(主会话复跑 + 亲自变异):整树 `go build ./...` + 两包 vet 绿;browser 包测试绿;
我用 -overlay 把该 flag 加回参数表 ⇒ `TestLaunchArgs_HeadfulCarriesNoAutomationTraces`
变红(逐字报"不许传的 flag")。
⚠️ 未验(如实):无头档的横幅(无窗口判不了)、`chrome-headless-shell`、风控侧是否把
"端口写法"本身当信号、生产 profile 整链路(未碰)。
|
2026-10-07 11:18:25 +08:00 |
|
AskaEth
|
74e171bc04
|
docs(contract): v0.86 —— 搜索条数上限可在面板配(字段 + values 键 + 回显键;无新端点)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-07 11:11:24 +08:00 |
|
AskaEth
|
c68387a69a
|
feat(search): 面板上终于能改「结果条数上限」(原来改了会被静默丢弃)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真缺陷(上一片登记):`maxResults` 在面板上**根本改不了** —— ① 字段表里没有这个
字段(UI 不显示输入框);② `saveProvider` 只并 provider/apiKey/baseUrl/enabled;
③ `mergeSelectionPatch` 只处理 primary/fallback ⇒ **手工塞进 values 也会被静默丢弃**
(文件不变、也不报错)。「我改了它却没变」正是要消灭的那种假话。
- `panel.go`:表单加 `{Key: FieldMaxResults, Type: sdk.FieldNumber, Min: 1}`(**SDK 本就有
数字类型**,未自造;前端本就渲染 number ⇒ **不需要改前端**)。刻意不设 `Default`
(设了会让一次「只改 key」的保存把默认值固化进文件、还可能盖掉 `.env`)。
- `websearch_selection.go`:`mergeSelectionPatch` 里按**三态**落 `maxResults` ——
缺位/null ⇒ **不改动**;正整数(含整数字符串)⇒ 采用;0/负数/小数/空串/非数/
布尔/数组 ⇒ **400 `invalid_payload`** 且**盘上一字节不动**(绝不静默忽略)。
- `reauth` 回填该值(否则一保存就把上限清掉)。
- `.env.example`:该键的示例值与「默认 5」注释同步为 **10**(代码默认已改;⚠️ `.env` 未动)。
验收(主会话复跑 + 亲自变异):**整树 `go build ./...` 通过**;三包 vet/测试绿;
端到端「面板存 20 ⇒ 模型要 50 也只给 20、要 3 给 3」(走真 action hub → 落盘 →
热重载 → WithSearchMaxResults → 真 dispatcher,未重启后端);
我用 -overlay 把 `f.MaxResults = maxResults` 摘掉 ⇒ 两条用例变红
(「面板给的值必须真的落盘」/「回填的 maxResults = 5, want 20」)。
⚠️ 已知取舍(如实登记):**清空输入框 = 400**(后端按「非数 ⇒ 报错」拒),因此面板上
没有「去掉上限」的手势 —— 要回到 `.env` 兜底得改文件;要支持「清空 = 去掉覆盖」
需另定哨兵语义,未自行发挥。
⚠️ 契约面已变更(面板声明多一个 number 字段 + saveProvider 的 values 多一个可写键 +
响应多一个回显键)⇒ 主会话登记。
|
2026-10-07 11:10:56 +08:00 |
|
AskaEth
|
8325d1bb8c
|
docs(contract): v0.85 —— B25 按动作批量拒绝候选 + 轮末自动落地(行为变更)+ link 候选停产
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-07 11:08:07 +08:00 |
|
AskaEth
|
6d92536276
|
feat(memory): 整理不再只产建议 —— 轮末自动落地高置信 + 停产 link 候选
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人原话:「那么多 link 是干什么的?该删就删,怎么这个记忆整理机制这么保守呢」。
① **link 停产**(依据 design/23 §14 把它列为「未细化」⇒ 既无落点也无数据模型;
`applyable()` 也只认 merge/archive/reclassify ⇒ 应用 link 恒 `skipped`,是纯噪音):
- 规则层 `containNone` 判定改为 `keep`(不再产出 501 条不可应用的待办);
- `TidyLink` 常量保留,只为**读历史行**与「不可应用」判定;
- 存量出路:新端点 `POST /api/v1/admin/memories/candidates/reject?action=&limit=`
(**幂等、只改候选 status、不碰记忆**),主会话用它清掉存量 501 条。
② **轮末自动落地**(默认开,仅两类,判据是纯函数):
- `merge`:**字面重复** ∨「事实集合等价 ∧ 相似度 ≥ 0.95」;
- `archive`:理由 = `one_off_event` **且不像习惯**(`design/23 §5.2-1` 红线:
习惯类会因正文带钟点被误判成一次性事件,归档它是错的)。
- **不自动**:`reclassify`、低置信 merge、跨分区/跨角色、任何改正文、受限来源
(群聊/陌生人)—— 新增执行面守卫 `tidySkipSourceRestricted`。
③ **六条护栏**(缺一不可):软动作(复用既有 apply 路径)/ 每轮上限 50(硬顶 500,
超限如实 `Truncated` + 真总数)/ 审计 `memory.tidy.apply`(`auto=true`,不带正文)/
**可 revert**(复用既有回滚)/ 幂等(复用候选状态条件更新)/ **一键关回只产建议**
(`$DATA_DIR/config/memory-tidy.json` 的 `autoApply:false`,**零新 env 键**)。
验收(主会话复跑 + 亲自变异):memory 与 gateway 两包 build/vet/test 全绿;我用
-overlay 短路「习惯不归档」守卫 ⇒ TestTidyAutoNeverArchivesHabitMisjudgedAsOneOff
变红(逐字报「作息习惯被自动归档了 …design §5.2-1 红线」)。
⚠️ 未部署(要 build+restart 才生效);⚠️ 契约欠登记(新端点 B25 + §7.2),主会话补;
⚠️ 真机端到端未跑(不能重启后端)。
|
2026-10-07 11:07:00 +08:00 |
|
AskaEth
|
353293e807
|
feat(im): 主动消息「没送达微信」现在说得清(原因码 + 三处可见)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人原话:「可以,这有必要,不然我老是收不到」。真机日志:
`backthink IM push failed … weixin_ilink: proactive push outside 24h window`
—— 她的话**送进了 Web 活跃客户端,但没送进微信**,原因是**平台规则**(微信 iLink
只允许用户最后一条消息后 24h 窗口内主动推送),不是崩溃。
本片只做「如实说清」,不绕规则:
- **原因码**:新增契约层分类件 `internal/channel/adapter/delivery.go`
(`DeliveryCategory` + 4 个码 + 可选自描述接口 `DeliveryFailure` / 读取函数),
**沿用**既有 voice `Category(err)` 与 `SystemNoticeNormalizer` 的范式,**不造第二套枚举**;
平台码由产生错误的层声明:`weixin_ilink` 的 `ErrProactiveWindow` 自报
`proactive_window_closed`(哨兵文本与 `errors.Is` 判定**逐字未变**)。
- **三处可见**:① 结构化 WARN(补齐 category/channel/platform/route);
② 审计 `backthink.push.im_undelivered`(与 `backthink.self_wake.*` 同族);
③ 推送结果 `Detail` 追加稳定标签 `im_undelivered=<渠道>:<原因码>` —— 面板**已在
渲染 Detail**(`Backthink.tsx:185`)⇒ 主人现在就能看到,**无需改前端**。
- **不丢消息**:IM 路失败照旧走活跃客户端路,且**只在 Web 真送达时** `pushed=true`;
有测试钉住「IM 失败不影响另一条路」(同一条消息没被丢)。
- **不做的**:不重试刷屏、不换渠道冒充微信、不伪造送达、不新增 `.env` 键。
验收(主会话复跑 + 亲自变异):**整树 `go build ./...` 通过**;4 个包 vet/测试全绿;
我用 -overlay 把 `ErrProactiveWindow` 退成裸 `errors.New` ⇒ weixin 包两条用例变红
(`category = "failed", want proactive_window_closed`),channelbridge/backthink 亦红。
⚠️ 未落地的建议(已登记,未硬改):给她的"推送回执"(`backthink_records` 加
`push_category`/`push_channel` 两列 + 下一轮注入只读一行)与面板结构化徽标 ——
红线:回执只能用来换渠道/换措辞/等窗口,**不得演变成绕平台规则**。
|
2026-10-07 10:59:22 +08:00 |
|
AskaEth
|
bff57bd1ae
|
feat(search): 结果条数上限 5 → 10,并让「上限」真的是上限
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人原话:「返回五条结果会不会太少了?」。裁定:**上限抬高到 10,且它是真上限**
(模型要 50 也只给 10);模型要更少(3)时**尊重模型**;缺省/非法(≤0/非数)⇒ 上限。
- plugins/tools/io/web_search.go:`n := resultLimit(s.maxResults, m)` 一处收口
(原来是 `if count>0 { n = count }` ⇒ 模型的 count 能顶掉配置上限,与
.env.example 写的「条数上限」不符 —— 那是一条真口径缺陷,本次一并修正)。
- internal/config/websearch.go:`WEB_SEARCH_MAX_RESULTS` 代码默认值 5 → 10
(三处口径现已一致:env 默认 10 / io 包 defaultMaxResults 10 / 各家发给上游的
请求区间是另一回事,未动)。
- 生产:`data/config/plugins/web-search.json` 的 `maxResults` 5 → 10 + 一次
saveProvider 触发**热重载**(applied+registered),盘上已生效。
验收(主会话复跑 + 亲自打两个变异):
- io 包 / internal/config / cmd/cyrene 相关用例全绿;
- 变异 A(拆掉上限、恒取 50)⇒ **4 条用例变红**(含「默认上限 10」「count 非数值
⇒ 上限」「count=0 ⇒ 上限」「既有 MaxResultsOption」);
- 变异 B(默认值退回 5)⇒ internal/config 的默认值断言变红。
🔴 顺带挖出一条真缺陷(**本片只报不修,等派**):`maxResults` **在面板 UI 上
根本改不了** —— ① `search/panel.go` 的字段表里没有这个字段(输入框不显示);
② `saveProvider` 只并 provider/apiKey/baseUrl/enabled;③ `mergeSelectionPatch`
只处理 primary/fallback ⇒ **手工塞进 values 也会被静默丢弃**(「我改了它却没变」
成立)。修它要给面板加一个数字字段 + 三态落 `f.MaxResults`,属**管理面板对外契约
变更**(须同步 Docs/api-client.md),留给主会话。
|
2026-10-07 10:58:40 +08:00 |
|
AskaEth
|
7f63c5bec4
|
fix(browser): 同 profile 的浏览器还活着时不再必然启动超时
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真机复现(我做的):同 profile 再起一次 chrome.exe ⇒ Chrome **把命令委托给已有
实例并 exit 0**,stderr 逐字「正在现有的浏览器会话中打开。」⇒ **不产生新的
DevToolsActivePort** ⇒ 我们的等待只能超时 ⇒ 浏览器降级档整体不可用。
修法:启动前加一跳**探测**,三档各自出路 ——
- 端口记录缺席 / 端口没人听 ⇒ 正常启动(后者先把**陈记录**清掉:那是启动时写下
的记录、不是锁,删一条指向死端口的记录不会伤到任何活实例);
- 端口在听 + `/json/version` 有合法响应 ⇒ **复用它**(INFO `browser debug
endpoint reused`),**不再起第二个**;
- 端口在听但握不上手 ⇒ **如实报错**(保留现场,既不起进程也不删记录);
- 真被占用且拿不到端点 ⇒ 报错带 **Chrome 原话**(`withChromeOutput`)+ 可操作处置。
🔴 红线(写进错误文案与注释):**绝不杀别人的浏览器**、不发信号抢 profile、
不新开监听端口;`ErrLaunch` / `errors.Is` 口径未破。
验收(主会话复跑 + 亲自变异):browser 包与 plugins/tools/io 两包 build/vet/test
全绿;我用 -overlay 摘掉「复用」那一支 ⇒ TestLocalChromeAdoptsLiveEndpoint 与
TestDial_ReusesLiveEndpointAndDoesNotSpawnSecondProcess 变红。子代理另跑了真机
端到端(临时 profile):会话 1 拿端口 51732 → 裸起第二次 chrome(exit 0)→
会话 2 **复用同一端口、未起第二个实例**。
⚠️ 已知未修(如实):Chrome 那句委托提示是**控制台代码页(GBK)字节**,我们的
UTF-8 日志里会显示成乱码(字节保真、人看不了);彻底解决要引编码转换,另定。
⚠️ 判据③的取舍:陈记录 +端口恰被别的程序复用时,会持续报错直到人工删
`DevToolsActivePort`(错误文案已写明处置);**未加"报错后自动清理重试"**。
|
2026-10-07 10:56:16 +08:00 |
|
AskaEth
|
93c5a4c46f
|
fix(browser): 启动失败要说出原因;预算耗尽 ≠ 浏览器坏了
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真机现象:主人看到 Chrome 窗口内有横幅(他读作「不支持接受的参数」),日志只有
一句「等待调试端口时被取消: context deadline exceeded」,看不出是谁的错。
三件(本片):
- 接住子进程 stdout/stderr(有界:前 4 KiB / 上报 12 行,只缓存不输出)⇒ 失败时
打一条带 exe / argv / chrome_output / chrome_output_bytes 的 WARN;成功只记字节数
(不刷屏)。
- 三种启动失败在错误里分开:① 进程立刻退出(profile 被占用)② 等端口超时
③ 调用方预算耗尽(ctx 已结束 ⇒ **根本不 spawn**)。③ 与 ② 用两个截止时间比大小
区分 —— 顺带修掉一个老缺陷:原来 ctx 一结束就报「等待调试端口时被取消」,而那
几乎总是我们自己的 30s 用满,现被正确归到 ②。
- 降级档不再被**已耗尽**的轮预算连坐:如实报「预算耗尽 ⇒ 未尝试浏览器渲染,
这不是浏览器故障」,**不许把"没试"说成"试了失败"**。plugins/tools/io/bodyfetch.go
一字未动(fetchedBy / browserError 语义原样),只在**发起方**决定要不要发起。
对主人那条横幅的结论(有据,非推测):那是 Chrome 的 **bad-flags 警告栏**,触发者
是 `--disable-blink-features=AutomationControlled` —— 它明确在 Chromium 的 kBadFlags
名单里(bad_flags_prompt.cc 列了 kDisableBlinkFeatures/kEnableBlinkFeatures),本机
Chrome 154 zh-CN 的文案是「您使用的是不受支持的命令行标记:$1。稳定性和安全性会
有所下降。」;而「不支持接受的参数」在 Chrome 的 zh-CN/en-US 资源与二进制里**零命中**
⇒ 是意译。⚠️ 该参数是 design/28 §2 要求的(掩盖 navigator.webdriver)⇒ 横幅是
**设计取舍的视觉副作用**,去掉它才消失(但会暴露自动化特征)。登记待主人裁决。
验收(主会话复跑 + 亲自变异):go build ./... + 两包 vet 绿;browser 与 cmd/cyrene
两包测试绿;我用 -overlay 去掉 cmd.Stderr 接线 ⇒ TestDial_ReportsChromeOutputAndArgvOnLaunchFailure
变红(chrome_output="" chrome_output_bytes=0)。
⚠️ 未部署:跑着的产物不含本片,要等下次 build + restart。
|
2026-10-07 10:37:33 +08:00 |
|
AskaEth
|
23dc9a3e5e
|
docs(contract): 修回 §5 开头被吃掉的 blockquote 首行(v0.84 登记时的编辑失误)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
|
2026-10-07 10:29:49 +08:00 |
|
AskaEth
|
77dbf9b1f7
|
docs(contract): v0.84 —— 显示名形状变更 + 段落标签硬约束 + 浏览器降级档登记
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
① plugin.providers 候选:裸字符串数组 → 对象数组 {value,label};两条硬口径
(label 仅供显示、value 才是落盘值;前端三形状兼容 ⇒ 其他插件零影响)。
② 新增 §4.16:助手文本不得出现未配对段落标签(一个 <p> = 一段、段落不嵌套),
含实测根因两条 + 修后处置口径 + 「客户端仍应保留容错」与护栏清单。
③ 模型面登记:web-fetch 浏览器降级档的 fetchedBy(direct|browser)如实标注。
④ provider 配置面去 env 化(对客户端零形状影响)一并登记。
⑤ 计数不变:无新端点 ⇒ 14 + 24 + 40 = 78。
顺带修一处我自己的失误:上一版登记 §4.15 时把 `## 5. env 与模型配置` 标题
误删,已补回(§5 表格内容未受影响)。
诚实边界:web-search 真机工具路径仍有一处未部署的修复(linkless)已登记;
显示名真机点击未跑 + dist 是 go:embed(前端改动必须重新 build);
viewer 角色表格仍显示裸 id;腾讯/秘塔适配未实现。
|
2026-10-07 10:29:39 +08:00 |
|
AskaEth
|
2c56bc9704
|
fix(search): 「上游没给链接」不再被当成「没有结果」(真机 no results 的根因)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真机现象:她自主思考的 4 次 web-search 全部报
`search: no results (provider=zhipu)`,而同一查询直连上游返回 10 条。
根因:`Finalize` 把「link 为空」当成「不是一条结果」整条丢弃。而智谱
`search_std` 的 `search_result[].link` 在真机上**经常整批是空串**(实测同一
payload 连打 6 次:10 条全空 link,title/content 齐全;换查询也见过 6/10 空)。
⇒ 10 条被整批丢光 ⇒ 工具层如实报「一家都没查到」——**那是谎报**。
为什么面板探针是 ok:探针走 `search.Probe → p.Search`,**不过 Finalize**;
工具走 `Execute → p.Search → Finalize`。同一份上游、同一份解析代码,两条路
给出相反结论 —— 探针对这一类缺陷**结构性失明**,以后不能拿探针通不通推断
工具通不通。
修法:无链接条目**照原样保留**(title/content 对模型仍有用,链接空着就空着,
**绝不编一个出来**);判据改为「link/title/content/snippet **全空**的空壳行才
不算结果」;无链接条目不参与按 link 的去重(没有链接就没有判同身份,宁可重复
也不静默吞)。「空结果 ≠ 成功」**未放宽**:真·空 search_result 仍报 ErrNoResults。
验收(主会话复跑 + 亲自变异):go build ./... + vet 绿;io 与 search 两包绿;
我用 -overlay 把判据退回缺陷形态 ⇒ TestWebSearch_ZhipuLinklessResultsAreNotNoResults
变红,报错串与真机逐字相同。子代理另用真 key 走 in-process 工具路径实测:
三条当时失败的查询现在各回 10 条。
⚠️ 本修复要等下次 build + restart 才真机生效(现在跑的还是老产物)。
⚠️ 其他四家是否也有空 link 形态未验(无 key);修复是通用的。
|
2026-10-07 10:28:36 +08:00 |
|
AskaEth
|
bc1dee3f56
|
refactor(search): provider 配置面去 env 化 —— 加新家不再需要动 .env
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
动因:`.env` 已冻结(基础键不加/不改/不删),而「加一家 provider」原本会逼出
新的 env 键(一家一个 struct 字段 + searchProviderVector 逐家读 + 逐家写死的
switch)⇒ 想接腾讯云元宝/秘塔就只能破红线。面板本来就该是插件配置的一等来源
($DATA_DIR/config/plugins/web-search.json),故让**文件成为权威**。
- plugins/tools/io/search/registry.go:接入位表加 defaultBaseURL(「没配 baseUrl
回落代码默认值」从此只有一个出处);新增 Descriptor(s)/DefaultBaseURL。
- cmd/cyrene/websearch_providers.go(新):按接入位表逐家算出生效投影 ——
加一家时这里**零改动**,这正是本片的核心;顺手把 wire_io_tools.go 从 430 行
降到 342(它逼近 §10 的 500 行上限)。
- cmd/cyrene/wire_plugin_actions.go / websearch_action_data.go /
websearch_selection.go:基地址判定统一走「文件值 → 接入位默认值」的进位链;
checkPrimaryUsable 判据不放松(真没配齐仍 400 primary_not_configured)。
🔴 **一处对我任务书的纠正(子代理提出、我采纳)**:我要求「既有 6 家在文件里
没有该条目时仍回落 .env」。仓库现状**没有**这层回落,且有回归用例把它钉死
(TestWireIOTools_PanelConfigClearedProviderDoesNotReviveFromEnv:「文件里没有
的家不得从 .env 复活」)。本片按「不改变既有行为」实现 —— 我的要求才是错的:
文件一旦存在就是权威,env 只在文件缺失/为空时播种。
验收(主会话复跑 + 亲自变异):go build ./... + 两包 vet 绿;search 包与
cmd/cyrene 全绿;我用 -overlay 让文件里的 apiKey 被忽略 ⇒ 4 条用例变红
(含「无 env 字段的家只认文件也能进注册表」这条核心判据)。
⚠️ 新家仍未接:腾讯云元宝(tencent_wsa)/ 秘塔(metaso)两片随后串行落地。
|
2026-10-07 10:18:58 +08:00 |
|
AskaEth
|
e5e5207c94
|
test(channelbridge): 补跨「渠道桥 → 网关」的 IM 附件整链回归
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
安卓端的 P0(「IM 入站的文件要挂到消息的 documents 上」)其实早已修复
(679cee0):附件引用在落库那一刻写进 chat_messages.document 列,DTO 的
documents 由该列解出。但既有两个用例各据一半(渠道桥侧只验落库、网关侧只验
投影),**没有一条真正跨过这条边界** —— 于是「哪一段断了」只能靠人读代码。
本片补一条真跨边界的端到端:同一个真实 history.Store(:memory: SQLite)
同时喂真渠道桥与真网关,走完整 IM 入站回合(真抽取器 + 真循环 + 假模型),
最后从 HTTP 原始 JSON 逐字断言该消息带 documents。
验收(主会话复跑 + 亲自变异):channelbridge 包测试绿;我把
inboundDocumentColumn 变异成恒回空串(模拟 P0 根因)⇒ 该用例变红,报错文案
正是「带附件的用户消息缺少 documents 键(这正是安卓报的 P0)」;还原后绿。
⚠️ 真渠道/真设备未跑:要主人发一条新的真文件消息才能做设备级复验。
|
2026-10-07 10:11:19 +08:00 |
|
AskaEth
|
c46053e276
|
feat(admin): 搜索提供商下拉/多选/表格显示中文名(值仍是 ASCII id)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人报障:「为什么中文搜索接口……下拉菜单里全给的英文名?你甚至不愿意带个括号
把中文名写上去」。根因不是漏写文案,而是整条链路没有「显示名」的位置:字段
声明只有 optionsFrom,候选动作回的就是 KnownNames() 的纯 id 数组。
- 后端:候选改为对象数组 {value,label};label 表与接入位表同源(键用
NameSearXNG 等常量),未知 id 原样回。value 逐字等于 KnownNames()。
- 前端:下拉/多选「显示 label、写 value」;表格单元格按 value→label 索引显示,
查不到回落显示原值。多选从裸输入框改成「候选开关 + 落盘值输入框」。
解码器本就兼容裸字符串/{value,label}/{id} 三形状 ⇒ 其他插件零影响。
中文名逐字:SearXNG 本地兜底(searxng)/ 智谱 BigModel(zhipu)/
阿里云 IQS 信息查询(aliyun_iqs)/ 博查 Bocha(bocha)/
火山引擎·豆包搜索(volcengine)/ 百度千帆 AI 搜索(baidu_qianfan)。
验收(主会话复跑 + 亲自变异):go build ./... + vet 绿;search 包测试绿;
tsc --noEmit 0 错;新渲染测试 26 checks + 既有 plugin panel 55 / data 90 /
schema values 40 / channel config 70 全绿。我用 sed 把 values.ts 的
option.value 全换成 option.label ⇒ 渲染测试红(「点选把候选项的 id 追加到
降级链末尾」got="bocha, 智谱 BigModel(zhipu)")—— 这正是会把配置文件写坏的
错法;还原后 md5 一致且绿。
⚠️ 契约面变更(我在 Docs/api-client.md 登记):plugin.providers 候选响应形状
字符串数组 → 对象数组;label 仅供显示、value 才是落盘值。
⚠️ 已知边界:viewer 角色看不到 form 区块 ⇒ 表格对 viewer 仍显示裸 id(未做)。
⚠️ 真机浏览器点击未跑(项目无 jsdom)⇒ 建议刷新管理页点一次确认观感。
|
2026-10-07 10:08:19 +08:00 |
|
AskaEth
|
8051906afa
|
fix(turnreply): 产出侧不再产出未配对的段落标签(安卓报障的回执)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
安卓客户端登记(他们文档 02 末节):一条助手消息的 content 是
`<p><p>正文</p>`,按他们的解析契约(非贪婪)必然把 `<p>正文` 当内容 ⇒ 用户
直接看到字面 `<p>`。任何按该契约定实现的消费方都会踩。
根因(两半都证实,都在同一处):
- (a) 规整器**原样放过**模型吐出的 `<p><p>X</p>`:段块正则非贪婪,把它当成
一个"已完整包裹"的块逐字保留(旧的覆盖检查用同一个正则 ⇒ 护栏本身是盲的);
- (b) 规整器**亲手制造**该形状:末段漏 `</p>` 时无条件套一层 ⇒ `<p><p>X</p>`。
实证:模型吐 `<p>那现在…?`(无闭标签)经旧代码规整后与上报串**逐字节相同**;
- (c) 顺带修一个幂等漏洞:落单 `</p>` 旧行为会越跑越多(`<p></p></p>`)。
修法(新增 paragraph_repair.go:配对修复 + 接线):
`<p><p>X</p>`⇒`<p>X</p>`;`<p>X`⇒补 `</p>`;`<p>正文<p>`⇒合并;`<p>A</p><p>`⇒
丢空开标签;`<p></p>`⇒整对丢;落单 `</p>`⇒丢标签正文不动。不吞正文护栏:
`<p`(无 `>`)/`<p2>`/`1 < 2`/`a<b>`/代码围栏/`<action>` 内 `<p>`/自闭合
`<p/>` 一律原样(每条都有用例)。`normalizePiece` 改为"补闭不补开"——在造出
畸形的那一点上根治。
验收(主会话复跑 + 亲自变异):go build ./... + vet 绿;turnreply /
gateway 全量 / channelbridge 三包绿;我用 -overlay 摘掉配对修复接线 ⇒
TestNormalizeParagraphs_* 变红。生产库只读核查:177 行 assistant 里**仅 1 行**
畸形(各消费方已容错)⇒ 不做一次性修复脚本。
⚠️ 未部署:跑着的 :10942 仍是旧产物 ⇒ 畸形仍会产出,安卓端的容错请继续保留。
|
2026-10-07 10:07:13 +08:00 |
|
AskaEth
|
378faf212e
|
feat(browser): 取正文能力 + web-fetch 浏览器降级档接线(解封 Z5/B5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
Z5(c2e413e)把 web-fetch 的降级链与注入点做完了,但「取正文」入口缺失 ⇒
组合根拿不到 BodyRenderer、这一档实际关着(当时如实登记)。本片补上:
- internal/capability/browser/render.go:新增 Manager.RenderText(ctx,url)
(导出的进程内能力)—— 先过 GuardURL(与全仓同一份 SSRF 判定源)、复用
withSession/session、等 readyState=complete 之后才取 body.innerText;
渲染失败 = 分类错误,渲染成功但正文为空 = 空串+无错(不冒充成功/失败)。
- cmd/cyrene/wire_body_fallback.go:适配器 + 挂载点,把 Manager 适配成
io.BodyRenderer 并用 WithBrowserFallback 挂上;web_runtime 里把浏览器
接线**上移到 io 工具集之前**(顺序颠倒会静默关死这一档)。
- plugins/tools/browser/tool.go:新增 Tool.Manager(),供组合根取驱动句柄。
开关口径(裁决,依据 design/28 §1 M5 / §7 判据 4 / §8 B5):**有可用浏览器
就挂,没有就不挂**,不新增开关 ——
① 设计把 M5/B5 写成要落地生效的降级档,不是永远关着的空壳;
② 设计 §8 的可调项清单里没有这条降级档的开关,`.env` 已冻结 ⇒ 自造开关属
「自行发挥」;
③ io 库层「不传 = 默认关」的语义一字未改,本片只把组合根从「永远关」改成
「没来源就关」,并留带来源的 WARN(reason=no-usable-browser)。
⚠️ 若日后要「有浏览器也需显式开启」,得先给设计定开关落点,代码侧一行改回。
验收(主会话复跑 + 亲自变异):go build ./... + 三包 vet 绿;browser/io 两包
测试绿(cmd/cyrene 由子代理跑过全绿);我用 -overlay 把 GuardURL 的判定对象
换成常量 URL(绕过被拒目标)⇒ TestManager_RenderTextBlockedURLRejectedBeforeAnyProcess
变红,证明判定确实**在起任何进程之前**跑。
⚠️ 生产端到端未验:按红线没重启后端、没起真 Chrome ⇒「真页面 → browser 取到
正文」这条链只有假 CDP + 组合根接线级证据。
⚠️ design/28 有 6 处表述与代码现状冲突(归属 web-fetch vs web-search、extract
动作归属、B5 排期、状态过期等),已登记待我同步文档。
|
2026-10-07 09:59:56 +08:00 |
|
AskaEth
|
25aa50e532
|
feat(search): 智谱 provider 按官方 schema 加固(请求补确证字段 + 错误码分档)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
官方文档逐页确证后落地的四件(分册 11 的「官方 schema 确证(2026-10-07)」):
- 请求补 search_intent:false(官方标 required,缺发可能触发 1213)与
content_size:medium;count 仅 1–50 透传,越界不发该字段;
search_query >70 rune 直接报错且**不发请求**;
- 响应按官方收窄:摘要只认 snippet/summary/abstract 候选(官方响应**没有**
snippet),publish_date 入 Published;
- 错误码分档:1000/1001/1003⇒unauthorized、1113⇒quota_exceeded、
1302/1305⇒rate_limited。🔴 1113 与 1302/1305 **同为 HTTP 429** ⇒ 只看状态码
分不开「该充值」与「该退避」,必须读 error.code(码同时进日志文案);
- media/icon/refer 当前一律不投影(Result 没有「来源站点」位;要投影应给
Result 加字段 + 同步契约,不许塞进 Snippet —— 否则摘要位会被站点名恒填满)。
另修两处过期注释(providers_test.go 仍写「只发 search_engine/search_query」)。
验收(主会话复跑 + 亲自变异):go vet 绿;search 包与调用方 io 包全绿;
我用 -overlay 删掉 1113⇒quota_exceeded 映射 ⇒ TestZhipuErrorCodeMapping 变红
(欠费被误报成限流、探针码 rate_limited)。
⚠️ 候选键容错暂未删除:官方已排除的键保留到**真机实测通过后**再收窄(现无 key)。
⚠️ 真机未跑:仍只打 httptest,官方真实响应形态未实测。
|
2026-10-07 09:59:24 +08:00 |
|
AskaEth
|
c512609c63
|
feat(admin): 记忆整理候选的应用/回滚操作面(切片 TIDY-FE)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人报障「面板上点不到『应用』」→ 后端 TIDY-1 的五条端点(契约 v0.83 B20–B24)
此前只能 curl 驱动。本片补齐界面:候选表(动作/状态/摘要/理由/时间 + 行内
应用/回滚,回滚仅在 status=applied 时可点)、status 筛选、批量应用全部待定、
手动真跑一轮(两个写动作都走二次确认)。
三条契约硬口径落到界面:
- 幂等不是错误:skipped 用中性色,绝不渲染成失败;
- 超限必须如实:truncated=true 时黄牌「还有 N 条待定未处理」,且该分支绝不
渲染「已全部处理」;未处理量只由后端权威字段算得(total - 已处理三项);
- 权威键只读:状态与计数全部取自响应,不做「应该成功了」的推断。
一个刻意的偏离(有测试钉住):截断提示不用共用 Notice tone=warn —— 那个组件
的固定前缀是「待实现」,贴在「还有 N 条未处理」上会被读成「功能还没做」。
验收(主会话复跑 + 亲自变异):tsc --noEmit 0 错;vite build 通过;
渲染测试 43 checks、传输层测试 28 checks 全绿;我把 truncated 判断取反 ⇒
渲染测试红在「截断时必须显式点明命中数大于本轮处理数」,还原后 md5 一致且绿。
⚠️ 未跑真机浏览器交互(需 owner 凭据 + 真后端),证据止于渲染层与传输层。
|
2026-10-07 09:58:21 +08:00 |
|
AskaEth
|
aa0bf8b5a0
|
fix(websearch): 面板「填 key + 同一次选它当主 provider」不再被自己的校验回滚
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真机报障(2026-10-07 09:50):面板上给 zhipu 填了 API Key,保存却一直回
400 primary_not_configured;盘上配置文件 mtime 一动不动 —— 连刚填的 key
一起被回滚,界面表现为「怎么填都存不进去」。
根因(顺序错误):saveProvider 先 applySelectionPatch(内含「主 provider 必须
已配齐」的校验),后 UpsertProviderEntry。校验看的是**盘上的旧文件**,而这次
提交里的 key 还没落盘 ⇒ 校验判它没配齐,整笔拒绝。
修法:把校验从 applySelectionPatch 里摘出来,改名 mergeSelectionPatch(只合并
文件级字段 + 回报有没有给 primary),由 saveProvider 在 UpsertProviderEntry
**之后**对最终文件校验,再写盘。判据本身一字未改 —— 「真的没配齐的主家照样拒」。
验证:cmd/cyrene 整包测试绿;新增回归 TestWebSearchSaveProviderAndPrimaryInOneSubmit
(同一次提交填 key + 选主家 ⇒ 200 且盘上 key 与 primary 都在;反向:真没配齐仍 400
且文件字节不变);用 -overlay 把校验改回「拿旧文件校验」⇒ 该用例变红且报错文案
与主人看到的逐字相同。
|
2026-10-07 09:56:37 +08:00 |
|
AskaEth
|
ef8fc66e54
|
docs(contract): v0.83 —— 记忆整理应用面五条端点 + 读侧 status 闸 + 两处过时口径改正
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
新增 §2.2 B20–B24(候选列表 / 单条应用 / 批量应用 / 单条回滚 / 手动真跑)、
§4.15 详解(三条硬约束:幂等不是错误 / 超限必须如实 / 软动作永不物理删除)、
错误码表补 404 tidy_run_not_found、409 tidy_disabled、500 六个 tidy_*、503 tidy_timeout;
计数 writeAuth 19 → 24、总数 73 → 78(按 routes.go 核:writeAuth 挂载 admin 26 条,
减去历史口径的 channels/events 与 counters ⇒ 24)。
改正两处已成假话的 §7.2 条目:
- 「整理候选 apply/revert/列表/运行均未实现」→ 已实现五条,仍缺 reject / 运行历史读口 / 按 run 筛候选
- 「面板记忆页无法过滤」→ 补一句:v0.83 起服务端已按 status 过闸,字段面仍未补齐
诚实边界写进契约:生产库零动作 / 面板真机未跑 / 旧候选 run_id 为空串 /
run_id 列只在存储包前滚 / 语义召回不可复现。
|
2026-10-07 09:51:57 +08:00 |
|
AskaEth
|
f847769698
|
test(gateway): 面板记忆列表过 status 闸的端到端回归(TIDY-1 补证)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
真实 sql.DB + 真实 memory.Store + 真实 HTTP handler:两条在册 ⇒ 面板 2 条;
软合并一条 ⇒ 只剩被保留那条;再归档另一条 ⇒ 200 + 非 null 空数组。
复核(主会话亲自):目标测试绿;用 overlay 把 store.List 的 status 闸
变异成恒真 ⇒ 该用例变红(证明它真的在测那条闸,而不是在测夹具)。
顺带确证一件口径:GET /admin/memories/reviews(审查留痕)**不该**过 status 闸
—— 留痕表本身无 status 概念(preinsert_schema.go:29-46),且它是软删行的
查看与撤销入口,加闸等于审计自我抹除。
|
2026-10-07 09:49:57 +08:00 |
|
AskaEth
|
9126d653e5
|
fix(memory): 读侧过 status 闸 —— 软删行不再被检索、不再进面板
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
整理(tidy)与入库前审查(D3)都只做软动作:status 改 merged/archived,
正文一个字不改、行一行不删。因此只要有一条「能看到什么记忆」的读路径
漏了 status 闸,清理就等于白清 —— 生产库上那 10 条 merged 行就是反例。
- store.go:ListByPersona / List 加 status='active'
- rules.go:ListScope / ListProject 加 status='active'
- Get 刻意不过闸(撤销软合并要先读得到被吸收的行),并在注释里写明
- gateway/admin.go collectMemories 只加注释:口径只有一处,不重复过滤
验证:go build ./... + go vet 绿;memory 包全量测试绿;
新增 status_gate_test.go(4 条读路径逐条断言 + Retriever 端到端
「内容逐字相同的两条,软合并后只召回一条」+ Get 反向护栏);
用 -overlay 把闸门变异成恒真 ⇒ 5 个读路径用例与检索端到端用例全红。
|
2026-10-07 09:48:37 +08:00 |
|
AskaEth
|
c2e413ec38
|
feat(io): web-fetch 直连→浏览器渲染降级档 + fetchedBy 如实标注(Z5 机制)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 降级链:直连失败 / 判定为动态页 ⇒ 浏览器渲染取正文;返回体带 fetchedBy=direct|browser,
两条档的失败各自留痕(directError/browserError/browserSource/dynamicSuspect),**绝不静默替换**
- 被 SSRF 拒绝的目标**不换路**(红线);渲染器缺位时如实报双失败
- 3 个变异自检(去掉动态页判据 / 去掉 SSRF 短路 / 掩盖双失败)
⚠️ **生产接不通**:internal/capability/browser 没有「取正文」入口(不在本片文件面)⇒
开关默认关、不假装可用;解封形状已在报告里给出(加 RenderText ≈40 行)
|
2026-10-07 09:34:47 +08:00 |
|
AskaEth
|
20f82c9679
|
docs(contract): v0.82 —— resultSummary(失败也带)+ 自排成本可见面 + browser 面板(Z4)
- tool_execution.end 的 resultSummary(含失败口径、≤500 rune、空 ⇒ 缺席)
- GET /admin/backthink 加 ?trigger=(闭集 + 未知/空 400 invalid_trigger + 下推 SQL)+ 新端点
GET /api/v1/admin/backthink/self-wake(writeAuth、三态、gate 快照、*IsDefault 语义)
- GET /admin/panels 新增 browser/settings + 六个动作(3 读 3 写)+ 错误码 + turnAttachment
- request_think 可达性改正(已上线);计数 72→73(writeAuth 18→19)
|
2026-10-07 09:34:46 +08:00 |
|
AskaEth
|
9fa7e65278
|
feat(loop): tool_execution.end 失败也带 resultSummary(Z3,主会话裁定)
与回看面对齐:失败调用给该次错误文本的脱敏渲染(仍 ≤500 rune、仍走 trace.RedactText),
缺席只剩「真的没有结果」(空结果 / 空错误文本)。2 变异(失败仍缺席 / 绕过脱敏)
|
2026-10-07 09:34:45 +08:00 |
|
AskaEth
|
8e2441c183
|
feat(browser): 补上 B3/B4 的两行接线(Z2)—— 面板页与截图进上下文真生效
- wire_runtime.go 加 TransformContext: a.browserShotTransform(visionResolver)(只改本轮所见)
- wire_frontend.go 在 declareBuiltinPanels 之后接 wireBrowserPanel(带 a.cfg != nil 守卫,
否则 6 处既有夹具 panic;生产路径 cfg 恒非 nil ⇒ 行为与字面写法一致)
- 3 条盯梢用例(1 条 Skip→转正 + 2 条新增),2 变异打红
- 真机(隔离实例):panels 出现 browser/settings、三个只读动作真回执、**真回合内联 image 部件
61,410 字符** ⇒ design/28 判据 2 在真产物里成立
|
2026-10-07 09:34:44 +08:00 |
|
AskaEth
|
93fc24cade
|
docs(readme): 用户侧文档按代码重写(Z1)
修正 6 条「本该 ✅ 却标 ⚠️/❌」的能力(checkpoint 恢复/DashScope ASR/异步记忆提取/知识库删除/
全链路 P2/面板 18 模块)+ 快速开始与 .env 冻结口径 + 目录结构 + **新增「已知缺口与路线图」如实列未做**
|
2026-10-07 09:34:44 +08:00 |
|
AskaEth
|
454ddb0b70
|
feat(memory): 记忆整理**应用路径 + 调度修复 + 手动真跑**(TIDY-1,主人投诉的根因)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
主人实测:memory_items 1191 行、候选 77 条全 pending、memory_tidy_runs 仅 1 行、两晚定时没跑。
根因两条(都在这片里修掉):
① **以前根本没有执行面** —— 只有只读试算,候选永远 pending
② **调度被重启饿死** —— interval=24h 的 ticker 从进程启动算起,两天重启十几次 ⇒ 24h 永远走不完
- 应用路径:逐条 / 按 run 批量(默认 100、硬顶 500、超限 truncated)/ revert;**软合并**
(status + merged_into/merged_from/merge_count,**绝不改 content、行不删**)+ 审计
(memory.tidy.apply / memory.tidy.revert)+ **幂等**(候选状态条件更新 + 行 status 闸两道)
- 调度:改成**到点即跑**(catchUp:无历史+窗口内 / 距上次 run ≥ interval / 窗口起点晚于上次 run),
「上次 run」以 memory_tidy_runs 为**持久事实**;注入时钟用例证明**重启不饿死**
- 手动真跑端点 POST /admin/memories/tidy/run(同档鉴权 + 审计 + 回执:新增/合并/软删/耗时)
- 候选表加 run_id 列 + 索引(前滚;顺序=建表→补列→建索引,否则既有库会 no such column)
- 3 个变异自检(删 catchUp / 去掉两道 status 闸 / truncated 恒 false)
⚠️ 生产库零动作:1191 行真跑与 77 条候选应用**未执行**(按红线由主人确认后主会话执行)
⚠️ 契约段在报告里(candidates 列表/apply/revert/tidy.run),Docs/ 待登记
|
2026-10-06 22:39:35 +08:00 |
|
AskaEth
|
57d0e8c543
|
feat(admin): 时间轴只读权威键 + 自排筛选轴 + browser 面板图标(Y2+Y3+Y4 前端一并落)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 时间轴:页首「已过保留期」的**唯一**判据改为 retentionBoundaryReached === true(truncated 只出
「未显示」);缺席(老后端)一句话都不说;删掉旧的 truncated+retentionEnabled+翻到底 启发式
- Backthink:筛选轴接上服务端 ?trigger=(客户端筛选保留作兜底)+ 自排闸用量行(三态文案)
- plugin/nav.ts 加 browser 图标
- 真机(隔离实例,46/46):翻页不重不漏、三种「必须 false」的权威键语义、**重启后 round 不重复**
⚠️ 未验:真机上没构造出权威键 true(需真有更早轮次被清扫)——别读成「真机验过 true」
|
2026-10-06 21:48:52 +08:00 |
|
AskaEth
|
e0a6bca501
|
feat(browser): B3 截图→上下文闭环 + B4 面板声明页(Y4)
- B3:截图落盘后**自动**并入本轮视图(能收图 ⇒ 真 image 部件挂 user 消息;收不了 ⇒ vision/OCR 文字;
大图先经 image-crop 裁切;取走即消费、每会话 ≤4 张、TTL 10min、跨会话隔离)
- B4:browser 面板**声明**(配置表单 + 运行状态/指纹自检/最近截图三张只读表 + 拍一张/重新自检)
+ 六个动作走 PLG-2 的通用动作端点(**不开新路由**)+ 保存事务(不可用则回滚 409)+ 热重载
- 读面**不启动浏览器**(计数型假 dialer 证明 0 次拨号);行数据不含路径/URL/内容
- 3 个变异(短路主路/行与列 key 脱钩/保存不回滚)
⚠️ **两行接线未落**(不在我文件面):wire_runtime.go 的 TransformContext + wire_frontend.go 的
wireBrowserPanel —— 落地前 B3/B4 只是「组件就绪」,跑着的后端里不存在(Y4 已写盯梢用例)
|
2026-10-06 21:48:50 +08:00 |
|
AskaEth
|
4213b268fe
|
feat(backthink): 自排的成本可见面(C2 读侧,Y3)
- GET /api/v1/admin/backthink 加可选 ?trigger=(闭集=内建触发标签 + 本服务订阅的事件主题;
未知/空 ⇒ 400 invalid_trigger,不静默忽略;合法值下推到 SQL ⇒ limit 是匹配条数)
- 新端点 GET /api/v1/admin/backthink/self-wake(writeAuth):闸的只读镜像
{available,running,reason?,configFile?,gate{pending,used,limit,windowSec,default*,*IsDefault}}
三态分开(未接线 / 有配置未运行 / 运行中),读它**不消耗额度**
- 零新 env 键;旋钮源 = $DATA_DIR/config/backthink.json(**写面未做**,前置=组合根接线,已登记)
- 键名沿用设计与现有代码的 selfWakePerWindow(派工书写错成 selfWakeLimit,未改键名以免废掉主人已写的配置)
- 5 个变异(未知值静默忽略/关掉下推/恒报运行中/白名单摘键/前端把看不到画成 0/0)
⚠️ 本片改了 4 个文件面外文件(admin_query.go/routes.go/api.ts/adminSystem.ts)——已逐条说明理由
|
2026-10-06 21:48:49 +08:00 |
|
AskaEth
|
215eedf8e9
|
feat(ws): tool_execution.end 带可选 resultSummary(Y1,答应安卓端的那条)
- 形状:data.resultSummary(可选)。与回看面 toolCalls[].resultSummary 代码级同源:同一个常量
(500 rune)与同一个截断函数,摘要由 **loop 侧**生成(trace.RedactText + renderToolResultSummary)
再随 StreamEvent 下发,gateway 只搬运不加工(与回看面同口径)
- 缺席语义:结果真拿到才带;失败/取消/超时/未落图 ⇒ 整键缺席(绝不用空串顶位)
- 线上帧证据:真 WS + 真 JWT + 真 loop ⇒ 客户端收到 resultSummary;同源钉用真图快照反读逐字比对
- 3 个变异(空摘要也写键/去掉脱敏/失败当结果)
⚠️ 待裁:失败路径现按派工书「整键缺席」,但回看面对失败给的是 error 摘要 ⇒ 实时无结果区、事后有;
若要实时也显示失败原因,一行可改(toolEndFrameSummary 的 return "")
|
2026-10-06 21:48:48 +08:00 |
|
AskaEth
|
a820d9968e
|
docs(contract): v0.81 —— 时间轴 retentionBoundaryReached + round 跨重启单调 + 自排可见面登记(X5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 新增响应键 retentionBoundaryReached(与 truncated 分意,判据三条 + 三个盲区如实登记)
- round 跨重启不再重复(行为修复,客户端可放心按 round 分组)
- 登记 trigger=self_wake 与 4 条 backthink.self_wake.* 审计动词,并标明入口工具 request_think
**未接线 ⇒ 生产里目前不会出现**
- §7.1 两行状态;计数不变(无新端点 ⇒ 14+18+40=72)
⚠️ 该键取自 X1 工作树实现 ⇒ 已在文档 4 处标明「提交后按 git show 逐字复核」(本提交即该实现)
|
2026-10-06 21:16:25 +08:00 |
|
AskaEth
|
2d211c2d16
|
feat(backthink,browser): 自排工具面 C1-3 + 浏览器两处偏离收口(X2+X3 同文件一并落)
X2 C1-3(自排工具面与提示词):
- request_think(thinktool.go,照 transcode/tool.go 范式)+ reason 必填/urgency 闭集 + 双守门
(安全闸 + owner 判定:群聊/陌生人一律拒)
- **成对**进两张唤醒态允许集(quiet + awake;CAPACITY_TOOL_SETS 是整体替换)
- 提示词抽共用段落 + 自排前缀优先(不枚举工具清单);面板加 self_wake 标签与筛选轴
- 4 个变异自检(拿掉 owner 判定/只加一张表/放宽 urgency/前端标签恒等)
X3 收口(与 X2 共用 wire_services.go ⇒ 一并落):
- 删掉第三份 SSRF 实现(guard.go),改由 io.ValidateRequestURL 唯一判定 + 薄门面(保两个错误身份)
- 浏览器会话挂进 a.close(真机:真 Chrome 被关、无残留、生产未扰动)
- a.wireBrowser() 归位到 wire_runtime.go,删掉 ioToolOptions 的临时挂点
- 5 个变异自检
顺带补提交 C1-2 漏掉的 internal/config/runtimeconfig/backthink_test.go(ed50053 漏 add)。
|
2026-10-06 21:16:23 +08:00 |
|
AskaEth
|
2bf3b536dd
|
fix(flow): 时间轴两处诚实性缺陷(X1)
① round 号跨重启被复用 ⇒ 时间轴把多个真实轮次并成一块(真机实测 web 的 r:1 挤了 4 路、
startedAt 跨 13:52→21:05)。修法:发号前从落盘播种(MaxRound,json_extract + json_valid 挡坏行),
每会话每进程只播一次、失败只 WARN 且不重试、并发首轮共享一次读;接口形状零变化
② truncated 一个字段兼两个意思 ⇒ 一页没拉完时前端会说「更早的轮次已过保留期」(假话)。
新增 retentionBoundaryReached(= 保留期启用 且 已翻到底 且 现存最早一轮 round>1 ⇒ 确有轮次被清扫),
truncated 语义一字未改;盲区如实登记(整段被清扫会说少、最早一轮落盘失败可能多说)
3 个变异自检(不落账/floor>0/去掉 hasMore 守卫)
|
2026-10-06 21:16:21 +08:00 |
|
AskaEth
|
b36798265a
|
docs(comments): proactive 判据键在 data 内、不在帧壳顶层(安卓核对指出)
4 处注释写「帧顶」而代码放在 data[proactive](+ 测试文件 6 处同源表述)。
安卓端两层都读所以不受影响,但注释与代码相反会误导下一个按注释写的人。
零行为改动(仅注释)。
|
2026-10-06 21:16:20 +08:00 |
|
AskaEth
|
f1fbc249fd
|
feat(browser): B1 接线 + B2 薄层工具面(V5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- browsercfg 接进组合根:配置读 $DATA_DIR/config/plugins/browser.json(零 env)→ 探测(本机
Chromium → 已在跑的 CDP)→ 不可用就不注册(原因码入日志)
- 自研最小出站 CDP 客户端(不 Enabled Runtime、隔离世界求值、不发 --enable-automation)+
导航 + 截图 0600 落盘 + 指纹自检;工具 browser(open/screenshot/info,owner-only)
- 3 个变异自检(启动恢复 --enable-automation/无视可用性/空闲回收失效)
- 真机对照(两站各 1 次,**如实结论**):两侧都 pass ⇒ 「headless 被拦 / headful 通过」**未被证明**;
但测到两处硬差异:无头 UA 带 HeadlessChrome、screen 800 vs 2632;两模式 webdriver 均 false
(把 L6 的推断变成实测)。跑完 0 残留 Chrome、无对外监听端口
⚠️ 未做:MCP 自动接入(口径委派未落地)/自动注入上下文/面板页/搜索降级/docker 配方/Windows 用
pipe(Go ExtraFiles 不支持);SSRF 落了第三份同策实现待收口;浏览器会话未挂进程关闭链
|
2026-10-06 20:25:16 +08:00 |
|
AskaEth
|
ea150ceea9
|
docs(contract): §2.0 端点计数回补(14+18+40=72)+ S6 未确证项收口(V4)
- writeAuth 17→18、总数 71→72(并写明自 v0.76 起 −1 的滞后已修正;历史版本条目保留原写法)
- S6 的未确证项逐条核对:能验的给代码级证据,验不了的写明「为什么验不了」(无凭据/禁重启)
- 契约版本仍 v0.80(同版内记账回补,零形状变更)
|
2026-10-06 20:25:14 +08:00 |
|
AskaEth
|
4a04ad43fe
|
feat(skills): 示例技能 + 端到端验收(V3)
- configs/skills/daily-brief/skill.yaml:真能跑的工作流(4 步依赖图 + 2 条 guardrail + allowed-tools)
- example_skill_e2e_test.go:真 Loader + 真 registry + 真 loop 三轮断言 —— 第1轮只有清单、
第2轮命中展开正文且工具面只剩 allowed(B 工具不可见)、第3轮正文保留而工具面恢复、BodyRenders==1
- 3 个变异自检(triggers 清空/关掉收缩/正文漏进清单)
⚠️ 生产未验:当前后端启动早于技能落盘 ⇒ 需一次 restart 才会出现 skills registered;
隔离实例(临时端口/临时 DATA_DIR)已 GREEN,含面板链四条真实回执
|
2026-10-06 20:25:14 +08:00 |
|
AskaEth
|
745365c7df
|
fix(admin): 时间轴分页与保留期文案按契约收紧(V2)
- 类型补上后端真在返回的 source/nextBefore/hasMore/retentionEnabled(此前类型里没有)
- 新增纯函数 pageHasOlder(hasMore 权威,老后端退满页启发式)/timelineCursorOf/retentionClaimable/
timelineHints;取数体抽成可执行导出函数 ⇒ effect 本体首次被真跑(真 fetch + 真状态机 + 真 SSR)
- 🔴 修一处成对契约缺陷:后端 truncated = hasMore || !storeWired,而契约/设计要求 truncated ⇒
「已过保留期」⇒ 旧前端会对主人说假话。现改为:只有「已翻到底 + 保留期启用 + 非 live-only」
才说「已过保留期」,其余说「更早的轮次未显示」
- 3 个变异自检(去掉 canLoadMore 短路/恒可翻页/忽略 hasMore)
⚠️ 未验(如实):鉴权后的真机 HTTP 往返拿不到 owner 凭据;前端产物未部署(面板仍是旧文案)
|
2026-10-06 20:25:06 +08:00 |
|
AskaEth
|
ed5005307a
|
feat(backthink): 自排 C1-2 本体 + TriggerUnknown 修 fail-open(V1)
- selfwake.go:WakeNow + 待跑槽容量 1(先探槽合并不过闸、槽空才过闸、同锁)+ 闸 4 轮/小时
($DATA_DIR/config/backthink.json,零新 env 键)+ 审计四条 what + 启动日志两键 + 组合根接线
- G-A(主人已批):TriggerUnknown 作零值、既有两值编号后移(语义不动),未知触发走**最紧分支**
- 5 个变异自检(拆闸/≤0 未回落/过闸挪到探槽前=G1 失效形态/槽无界/零值挪走)
⚠️ 服务侧已就绪但**她仍调不到**(自排工具面属 C1-3)⇒ 不得写「C 已完成」
|
2026-10-06 20:25:04 +08:00 |
|
AskaEth
|
301f8b4276
|
docs(comments): 清掉两处已过期的状态注释(ErrRunPending 早已不存在)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
S1/S3 大扫除点名的代码侧残留:
- cmd/cyrene/skills.go 注释仍称 Runner.Run 返回 ErrRunPending(哨兵已删,f75e7a9 起真执行)
- internal/services/gateway/admin_plugins_skill.go 注释仍称执行后置到 M12
零行为改动(仅注释)。
|
2026-10-06 18:46:58 +08:00 |
|
AskaEth
|
94da6bcd6d
|
feat(backthink): 静默期自排 C 的判定侧(C1-1,切片三之一)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 追加 TriggerSelfWake(不动既有编号)+ SelfWakeRequest(走既有 urgency 声明通道,不新造第二条)
- escalateImport 折算收成唯一 foldDeclared(事件轮与自排轮共用、只上抬取较高档)
- DecisionInput.Heartbeat bool → Kind TriggerKind;心跳仍免检、**自排不享心跳豁免**(要求声明 ≥ warning)
- push 与档位分离:push.go 零改动即自动正确(eventDriven 对 self_wake 为 false ⇒ 照旧过 PushGate)
- 4 个变异自检(折算空操作/删自排分支/常量传心跳/非事件一律豁免)
⚠️ 本片只是判定侧:WakeNow/worker/闸属 C1-2,工具面与提示词属 C1-3 ⇒ **不得写『C 已完成』**
⚠️ 遗留 G-A(建议登记进 design/12 C.4):TriggerKind 零值=TriggerHeartbeat ⇒ 漏填=最宽豁免(fail-open);
本片已在字段注释与单测里显式化,但规格侧应补一句或引入 TriggerUnknown
|
2026-10-06 18:46:27 +08:00 |
|
AskaEth
|
ea43f07bce
|
docs(contract): v0.80 —— 时间轴出口 + provider 热重载 + AMR 落副本
三批登记(S6 按代码逐条核对,30 条 文件:行 证据;我复核版本行与唯一当前版本):
① W1 GET /api/v1/admin/flow/timeline(时间正序/游标/retentionHours/truncated,计数 16→17、70→71)
② W3 热重载 + primary/fallback:新增错误码 primary_not_configured(400)/config_unstartable(409),
并改正 3 处已变假话的旧口径(『不热替换、需重启』『无主 provider 字段』)
③ W4 VoiceTranscoder 已注入 ⇒ AMR 落 wav 副本;6 处已变假话处改正
(S6 主动纠正一处口径:微信 iLink 语音刻意不下载字节,不算进『已修』)
⚠️ S6 发现既有记账错误:§2.0 条数自 v0.76(cb232e7) 起少 1(实测应为 14+18+40=72),
已在文档内如实标注请裁决,未擅自回补。
|
2026-10-06 18:46:26 +08:00 |
|
AskaEth
|
9c9a6d26c9
|
docs(env): .env 冻结最终口径 —— 基础键已定、以后不准改(不加/不改/不删)
主人 2026-10-06 二次收口:只求『统一不新增』且『基础键已定好、以后不准改』。
- AGENTS §10 与 design/13 §2.1 同步改写:新配置一律走 $DATA_DIR/config/** + 代码默认值;
既有基础键与 env: tag 保持现状(不迁移/不改默认值/不删键);E6 作废
- 新增一条判据:模板与代码若出现差异 ⇒ 那是**事故**(有人动了被冻结的东西),要回退而不是同步文档
|
2026-10-06 18:46:25 +08:00 |
|
AskaEth
|
ffe2518bf4
|
feat(memory): 留痕表正式迁移 0025 + 保留期清扫(W5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 迁移 0025_memory_ingest_reviews(幂等,既有部署重跑不炸/不丢行)
- GC:保留期 720h + 条数上限 10 万行,挂进审查服务生命周期(Start 起 / Stop 停 / 启动先扫)
- 过期即硬删(RevertMerged 只读 memory_items,删留痕不损失回滚能力)
- 偏离一处并给理由:表只留 created_at(先例 0017;append-only;加 updated_at 需三份 DDL 真源)
- 3 个变异自检(边界 <=/元组比较方向/Retention=0)
|
2026-10-06 15:59:16 +08:00 |
|
AskaEth
|
ddd3d00d09
|
fix: 修「假话但绿」的用例 + 补 VoiceTranscoder 注入(W4)
- history_toolcalls_gap_test.go 改成正向断言:单路回合经 StartRoundWithTurnID 带权威 turnId
⇒ 落库行按该标识查得到 ⇒ HTTP 出口 toolCalls 在场;并保留「无权威标识 ⇒ 整体缺席」边界用例
- 组合根补 VoiceTranscoder: a.transcoder ⇒ 微信/QQ 的 AMR 语音在落盘前先转 wav 再落副本
- 4 个变异自检(删注入/摘 ctx 标识/清 turn_id/调换 wiring 顺序,均红)
|
2026-10-06 15:59:14 +08:00 |
|
AskaEth
|
a30f4a49ff
|
feat(search): provider 热重载 + primary/fallback(W3)
- 保存被包成事务:快照 → 落盘 → 按新配置重建工具 → 原子替换注册表条目(registry.ReplaceTool
窄方法,保留运行期 Policy);下一轮即生效,不要求重启
- 若新配置会让后端下次起不来(主家未配齐 fail-fast)⇒ 文件按字节回滚 + 409 config_unstartable
- 声明面补 primary + fallback[](可配可生效);否决「另开 form」方案(会破坏既有 reauth 回填)
- 主 provider 解析口径核查结论:没被 PLG-4 改坏(顺手修了一处日志缺陷)
- 4 个变异自检(丢 Policy/不换条目/回滚 no-op/忽略 fallback)
|
2026-10-06 15:59:12 +08:00 |
|
AskaEth
|
5ee5f9126f
|
feat(admin): Flow 页时间轴视图(W2)
- 视图切换:最近一轮(既有 DOM 逐字节未变,7/7 SSR 对照 IDENTICAL)| 时间轴(新)
- 时间轴:竖轴=时间(默认最新在上)、每回合一个可折叠块、块内复用 LaneTracks 泳道、
块间不画任何连线(不臆造因果);before 游标分页;truncated ⇒ 显式「更早的轮次已过保留期」
- 降级诚实:只拿到最近一轮(老后端)⇒ 明确提示「当前只支持最近一轮」
- 4 个变异自检(排序方向/块间插线/truncated 静默/游标方向);老视图等价性实测
|
2026-10-06 15:59:11 +08:00 |
|
AskaEth
|
1efe3a6a79
|
feat(flow): 链路图时间轴出口(W1)—— 所有轮次按时间正序
- 新增 GET /api/v1/admin/flow/timeline?sessionId=&limit=&before=:时间正序多轮,
每轮 {turnId,round,startedAt,endedAt,lanes[]};limit 缺省 50/上限 200 + before 游标
- 数据源=落盘 flow_graphs + 内存最近一轮(同图 id 去重);保留期 168h 外的轮次不返回,
但响应给 retentionHours/retentionEnabled/truncated ⇒ 前端如实标注,不悄悄少一块
- 老出口 GET /admin/flow/graph 零 diff;角色门 writeAuth(owner|admin)
- 4 个变异自检(游标 <= 重复、分组降级、去重缺失、truncated 静默)
|
2026-10-06 15:59:09 +08:00 |
|
AskaEth
|
78e99d9f4c
|
docs(contract): v0.79 —— 面板动作真可用 + 记忆审查/语音可见面 + 回合标识全覆盖
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
四批一并登记(DOC-3 按代码逐条核对,含 文件:行 证据;我复核版本行/唯一当前版本/端点面):
① PLG-2 动作真可用:SetExecutor 已删、生产执行器接线(wire_frontend.go:205 →
wire_plugin_actions.go:61-89);错误码按代码重写 —— action_not_implemented(501) 只剩
「已声明未登记处理器」,兜底是 502 action_failed;并纠正一处输入说法:网关层非法 JSON
回 bad_request(非 invalid_payload),两个码都登记
② CODE-TC-2 回合标识全覆盖:StartRoundWithTurnID 是唯一生产建图点;graph.id 对单路也追加
#{回合标识} ⇒ 契约明写「一律用响应里的 id,不要自拼」
③ L45:新增 B16 GET /admin/memories/reviews + B17 POST /admin/memories/{id}/revert(writeAuth)
+ U19 GET /api/v1/media/{name} 与消息 DTO voice(覆盖率按实际填充面写,帧/握手回放标注不带)
④ L2/SK-5 零契约变更(已在报告说明)
计数:writeAuth 14→16、总数 68→70。
⚠️ DOC-3 指出的易误用点(已在文档区分):turns[].turnId(=graph.id) 与 lanes[].turnId(=权威 turn-N) 不同源。
未确证:documents.SaveAudio / UpdateMessageVoice / RevertMerged 本体未逐行核(标「转述自 L45」);真机未跑。
|
2026-10-06 14:20:08 +08:00 |
|
AskaEth
|
1420e8aaa6
|
feat(gateway): D3 审查读侧/revert 端点 + 语音用户可见面(L45)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
(a) 记忆入库前审查的两条 admin 端点(主人判「有必要就做」):
GET /api/v1/admin/memories/reviews?limit= (留痕读侧,不含记忆正文)
POST /api/v1/admin/memories/{id}/revert (回滚软合并,落审计 memory.review.revert)
两条 writeAuth(裁定 DECISIONS #10:留痕属私有面,不给 viewer)
(b) 语音用户可见面:DTO voice(含服务端派生 url)+ GET /api/v1/media/{name}
(200/206/304/416/HEAD、Accept-Ranges、非法名/穿越统一 404 media_not_found)
+ documents.Store.SaveAudio(内容寻址、$DATA_DIR 0600)+ history voice 列读写与引用计数差动
缺口(如实):message_persisted/握手回放仍不带 voice(文件面外,需给 history.Message 加字段);
AMR 需一行 VoiceTranscoder 注入(DECISIONS #11 已排)。4 个变异自检(含一条自认的等价变异)。
|
2026-10-06 14:12:27 +08:00 |
|
AskaEth
|
4a937bd269
|
feat(flow): toolCalls 覆盖率追到全部编排回合(L3)—— 图带权威回合标识
- 新增 StartRoundWithTurnID(ctx, owner, text):从 ctx 取权威回合标识(与 chat_messages.turn_id
同值);Run 的唯一生产建图入口切到它;原 StartRound 语义逐字不变(1 行包装)
- 归属桶(并行按路分图)保持不变;落盘查询键 lane_turn_id 对单路也有值 ⇒ 消息面按等值查得到图
- 无权威标识时**保持缺席**(不拿桶里的路标识顶上:不许臆造)
- 不需要迁移 0024(0023 的列+索引已在位;老单路行标识根本没存过,回填=编造)
形状变化(已登记契约):graph.id 对单路也追加 #{回合标识}(web-7 → web-7#turn-…),
顺带修掉「重启后单路轮次 upsert 掉旧行」的隐患。3 个变异自检(退回老入口/无视 ctx/臆造回退)。
|
2026-10-06 14:12:25 +08:00 |
|
AskaEth
|
6475eb875d
|
feat(skills): 渐进披露接线(L2)—— 清单投影 + 命中才展开 + 工具面只收不放
- 清单只给 name+description(+when-to-use),单条 ≤1536 字符 + 全局条数上限,超限如实标 truncated
- 命中才把正文作为一条消息注入:跨回合保留、按内容指纹缓存不重复读盘、注入 ≤16 KiB 标 .truncated
- invocation 三态与清单逐格对齐(model-only 清单不含 description;user-only 清单有但模型不可调)
- 激活轮的工具面 = allowed-tools ∩ 安全闸 ∩ 注册表 再减 disallowed-tools(owner 也看不见未允许的工具)
- 激活状态进程内按会话(不落库);注入量已界化(压缩器估值未含,超我文件面)
裁定(DECISIONS #3/#4/#5):删掉 paths 启发式(只认显式挂载);多技能并集;收缩作用域=激活的那个回合。
|
2026-10-06 14:12:24 +08:00 |
|
AskaEth
|
2e1c3ed3fd
|
feat(plugins): 面板动作执行器接线 —— 动作真可用,不再 501 兜底(L1)
- 插件管理器加生产动作处理器登记面(RegisterActions);未登记处理器/未声明动作仍 404
- web-search 四个动作真读写真源 + 真发 HTTP 探针:plugin.listProviders/saveProvider/
testProvider/reauthProvider(配置落 $DATA_DIR/config/plugins/web-search.json,0600)
- 失败回结构化错误码(不含上游原文)+ 审计带 code 字段;501 文案改为「未登记处理器」
- 拆出 panelactions.go(动作索引推导/OptionsActionName/三闸门分发),panels.go 519→347 行
缺口(如实):面板保存后运行中的工具**不热重载**(下一片做);声明面缺 primary/fallback
(按 DECISIONS-2026-10-06 #1/#2 补)。
|
2026-10-06 14:12:23 +08:00 |
|
AskaEth
|
a150211468
|
docs(contract): v0.78 登记 16 路已落地的六批形状(含 5 处如实的覆盖率/未实现标注)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
六批:① 消息读取面 toolCalls[] 已实现(迁移 0023 按路键;**覆盖率只到真并行路**)
② 管理面 panels + 动作端点 + panels_changed 帧(**动作执行器无生产实现 ⇒ 现全回 501**)
③ 搜索 provider 可变源(config/plugins/web-search.json;不新增 env 键)
④ 账号上下线落事件 + channel_event 帧(**只覆盖账号状态这一路**)
⑤ iLink 账号三态(-14=被拒非过期;**accounts[] 每账号三态未做**)
⑥ 语音:**只登记落库面已实现**,用户可见面(DTO voice + /media/{name})仍标未实现
我复核并改掉一处 overclaim:v0.78 总述原写「六条全部已实现」与 ⑥ 自述矛盾 ⇒
改为「①~⑤ 已实现;⑥ 只到落库面」。
值得记的一笔:DOC-2 拒绝照抄我任务书里「把语音改成已实现」的要求,回代码核到三条机器证据
(无 /media/ 路由、DTO 无 voice 字段、转码器无注入点)⇒ 没实现的没写成可用。这是对的。
未确证(文档内已标):六批都没在运行中的 :10942 上验过(本批禁换产物/重启);
lane_turn_id == chat_messages.turn_id 仍是代码级推论。
|
2026-10-06 09:11:48 +08:00 |
|
AskaEth
|
9e63af9063
|
feat(admin): 前端可接入点 —— 折叠分组 + 动态插件页 + schema 渲染(PLG-3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- sections.tsx 的分组升级为可折叠(CollapsibleGroup);新增 plugins 分组由
GET /api/v1/admin/panels 动态填充(navModel.ts 纯函数可单测)
- 插件页走动态段 /admin/plugin/<pluginId>/<panelId>,不进 router.ts 的静态白名单
- 把既有三段式(字段声明表 + 纯函数规则 + 渲染器,含「留空=不改」掩码语义)泛化成
schema → 控件(src/admin/schema/),复用它渲染插件表单/表格/事实面/日志
- 可测判据:新增插件页不得改动 sections.tsx / router.ts / AdminApp.tsx 的静态清单
- when 不满足 ⇒ 入口不出现;viewer 只看到只读区块
我的复跑:npx tsc --noEmit 通过;npx vite build 通过(3.67s);50 个前端脚本式测试全部
打印 checks passed(含本片新增 pluginData 90 / PluginPanel.render 55 / navModel 62 / schema values 40)。
|
2026-10-06 09:04:51 +08:00 |
|
AskaEth
|
ede00d9928
|
feat(voice): 语音消息落库 + 转码接入(切片 0020)
- 迁移 0020_chat_message_voice:按 design/24 §10 与契约 §7.2 已登记的 voice 形状建列/索引
- 语音回合进编排并落库;voice 引用与统计如实回填(转码/转写没成功就如实标未成功,不假装)
- 复用既有 internal/capability/transcode:非 wav(微信 AMR 等)先转码再送 ASR;失败可分类且不丢消息
⚠️ 部分完成(如实登记):生产端到端未成立(本片只到落库与转码接入),且**未经验真机**。
|
2026-10-06 09:04:06 +08:00 |
|
AskaEth
|
120d926cf8
|
feat(search): 搜索 provider 可变源 + 插件声明面板(PLG-4)
- 新增 $DATA_DIR/config/plugins/web-search.json(0600 + temp+rename,文件名 = 插件 id)
- 组合根改「先读可变源、缺失回落 .env 播种」;**不新增任何 env 键**(.env 已冻结)
- 多实例:每家 provider 独立 key/baseURL/启用态 + 「测试连通」探针
- 用 PLG-1 的声明库给出该面板的 adminPanels 声明;动作名交给 PLG-2 的通用动作端点分发
(本片自己不开路由)
⚠️ 本片自认越界:改了 cmd/cyrene/wire_web_search_test.go(既有测试,为适配 DataDir);
另有 design/28 的相关注册留给主会话。
|
2026-10-06 09:04:04 +08:00 |
|
AskaEth
|
2d21aa553e
|
feat(plugins): 插件管理器接线 —— 面板接入点快照 + 动作分发(PLG-2)
- GET /api/v1/admin/panels:按调用者角色过滤后的接入点快照(PLG-1 的 catalog 挂进插件管理器)
- POST /api/v1/admin/plugins/{id}/actions/{action}:**只接受该插件声明过的动作名**
(未声明 ⇒ 404);写动作 writeAuth + 落审计;插件只给动作名不给 URL ⇒ 不给插件开任意请求面
- 生命周期:install/uninstall/enable/disable 变化后**重算面板集合**并经进程内 bus 推 panels_changed
(前端可热更新;不新开端口)
契约 v0.78 登记由主会话随后统一落(本片报告给了可直接粘贴的契约段)。
|
2026-10-06 09:03:57 +08:00 |
|
AskaEth
|
c907a34510
|
feat(gateway): 面 1 补齐 —— 消息读取面 toolCalls[](工具回看)
前提由 FV2 提供(图自 v0.77 起带每路 turnId、每路各落一行)。本片:
- 迁移 0023:flow_graphs 加「按路查询键」(写入时回填图的 TurnID),老行按缺席处理
- GET /api/v1/sessions/{id}/messages 加**可选** toolCalls[],形状严格照 Docs/api-client.md
v0.76 ① 已登记的:{name, status(ok|error|unknown), durationMs, argsSummary, resultSummary}
- 🔴 图取不到 ⇒ **字段整体缺席**(绝不回空数组冒充「没调过工具」)
- 一条批量 SQL(按页内 turnId 集合),禁 N+1;单条 ≤20 个工具、resultSummary ≤500 字符(常量+依据)
- 只放图里已有的、已脱敏内容,不放宽脱敏通道
|
2026-10-06 09:03:55 +08:00 |
|
AskaEth
|
99a644d545
|
feat(channel): 微信 iLink 账号状态三态(诚实口径)
state ∈ 有效 / 被拒(需重扫)/ 未知;🔴 -14 是**被拒不是过期**,且**未知原因绝不写成「过期」**
(面板文案与错误码都如实)。三态判定为纯函数 + 三态各有用例。
⚠️ 未做(如实登记):每账号逐条三态(accounts[] 数组上的三态)本片未做。
|
2026-10-06 09:03:53 +08:00 |
|
AskaEth
|
7505c46b70
|
feat(channel): 微信 iLink 账号状态三态(诚实口径)
state ∈ 有效 / **被拒(需重扫)** / 未知;🔴 -14 是**被拒不是过期**,且**未知原因绝不
写成「过期」**(面板文案与错误码都如实)。三态判定为纯函数 + 三态各有用例。
⚠️ 未做(如实登记): 三态(每账号逐条三态)本片未做。
|
2026-10-06 09:03:44 +08:00 |
|
AskaEth
|
f75e7a9c9b
|
feat(skills): 技能加载/校验/真执行第一片(design/09 §6 定稿落地)
- 加载器/校验:目录式 configs/skills/<id>/skill.yaml(兼容旧单文件 <id>.yaml,先目录后文件);
新字段 version/when-to-use/invocation(三态)/triggers/paths/allowed-tools/disallowed-tools/
context(ine|fork)/agent/model/effort/metadata;**依赖环 ⇒ 加载期拒**(运行期绝不挂死);
depends 指向不存在步骤 ⇒ 拒;同一工具同时在 allowed 与 disallowed ⇒ 拒;错误可分类(哨兵 + %w)
- Runner.Run 真执行(替换 ErrRunPending):步骤拓扑(无依赖可并行、上限 2)+ guardrail 强制点
(enforced 违反 ⇒ 中止并如实报「哪条、哪一步」;否则 WARN 继续)+ 失败不静默跳过 + ctx 取消传播
- 🔴 能力收缩只收不放:该轮可用工具 = allowed-tools ∩ 安全闸 ∩ 注册表,再减 disallowed-tools
- 另有渐进披露相关的 listing/surface/activation(清单投影与激活判定)
⚠️ 未接线:本片是库与执行器,尚未把技能清单接进模型面(见 report 的分阶段)。
|
2026-10-06 09:03:35 +08:00 |
|
AskaEth
|
29181245ce
|
feat(browser): 浏览器配置与探测库(internal/capability/browsercfg,纯库零接线)
按 design/28 §2/§3 的「配置不进 .env」口径:读 $DATA_DIR/config/plugins/browser.json
(0600 + temp+rename 范式,**不读 env、不加新键**);有头/无头、CDP 端点、profile 目录、
视口/超时/并发(1)/截图上限全部类型化 + 常量默认值;**探测优先级**(本机 Chrome/Chromium
可执行文件 → 已在跑的容器)并返回可分类的「为什么不可用」(供上层如实降级,不假装可用);
profile 目录含凭据 ⇒ 0600 + 不进 git/不进上下文的注释与校验。
本片零既有文件改动(不注册工具、不碰 cmd/),接线留 B2。
|
2026-10-06 09:03:34 +08:00 |
|
AskaEth
|
b7fb4dd1c7
|
fix(gateway): 配置事实↔模板的守卫改为「在模板里 / 显式豁免带理由」
由来:主人 2026-10-06 红线把 `.env` 冻结为「只保证最小化功能可跑」的引导面 ⇒
调优旋钮不再进模板(我已把 MEMORY_TIDY_COMPARE_LIMIT 撤出),但既有测试仍要求
「每个事实键必须逐字出现在 .env.example」⇒ HEAD 上红一条。
改法(收窄不是放弃守卫):新增**封闭**的 envExampleExemptKeys(键→理由),
每个事实键满足「在模板里」或「在清单里且理由非空」;并加**反向断言**:
清单里不许有非事实键、也不许有其实已在模板里的键(防清单变垃圾桶)。
我的变异:往清单塞 NOT_A_FACT_KEY ⇒ TestAdminConfigFacts_KeysMatchEnvExample FAIL;
还原后复绿。
|
2026-10-06 09:03:32 +08:00 |
|